single-file-extension-background.js 193 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806
  1. (function () {
  2. 'use strict';
  3. /*
  4. * Copyright 2010-2020 Gildas Lormeau
  5. * contact : gildas.lormeau <at> gmail.com
  6. *
  7. * This file is part of SingleFile.
  8. *
  9. * The code in this file is free software: you can redistribute it and/or
  10. * modify it under the terms of the GNU Affero General Public License
  11. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  12. * of the License, or (at your option) any later version.
  13. *
  14. * The code in this file is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  17. * General Public License for more details.
  18. *
  19. * As additional permission under GNU AGPL version 3 section 7, you may
  20. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  21. * AGPL normally required by section 4, provided you include this license
  22. * notice and a URL through which recipients can access the Corresponding
  23. * Source.
  24. */
  25. /* global browser */
  26. const STATE_DOWNLOAD_COMPLETE = "complete";
  27. const STATE_DOWNLOAD_INTERRUPTED = "interrupted";
  28. const STATE_ERROR_CANCELED_CHROMIUM = "USER_CANCELED";
  29. const ERROR_DOWNLOAD_CANCELED_GECKO = "canceled";
  30. const ERROR_CONFLICT_ACTION_GECKO = "conflictaction prompt not yet implemented";
  31. const ERROR_INCOGNITO_GECKO = "'incognito'";
  32. const ERROR_INCOGNITO_GECKO_ALT = "\"incognito\"";
  33. const ERROR_INVALID_FILENAME_GECKO = "illegal characters";
  34. const ERROR_INVALID_FILENAME_CHROMIUM = "invalid filename";
  35. async function download(downloadInfo, replacementCharacter) {
  36. let downloadId;
  37. try {
  38. downloadId = await browser.downloads.download(downloadInfo);
  39. } catch (error) {
  40. if (error.message) {
  41. const errorMessage = error.message.toLowerCase();
  42. const invalidFilename = errorMessage.includes(ERROR_INVALID_FILENAME_GECKO) || errorMessage.includes(ERROR_INVALID_FILENAME_CHROMIUM);
  43. if (invalidFilename && downloadInfo.filename.startsWith(".")) {
  44. downloadInfo.filename = replacementCharacter + downloadInfo.filename;
  45. return download(downloadInfo, replacementCharacter);
  46. } else if (invalidFilename && downloadInfo.filename.includes(",")) {
  47. downloadInfo.filename = downloadInfo.filename.replace(/,/g, replacementCharacter);
  48. return download(downloadInfo, replacementCharacter);
  49. } else if (invalidFilename && downloadInfo.filename.match(/\u200C|\u200D|\u200E|\u200F/)) {
  50. downloadInfo.filename = downloadInfo.filename.replace(/\u200C|\u200D|\u200E|\u200F/g, replacementCharacter);
  51. return download(downloadInfo, replacementCharacter);
  52. } else if (invalidFilename && !downloadInfo.filename.match(/^[\x00-\x7F]+$/)) { // eslint-disable-line no-control-regex
  53. downloadInfo.filename = downloadInfo.filename.replace(/[^\x00-\x7F]+/g, replacementCharacter); // eslint-disable-line no-control-regex
  54. return download(downloadInfo, replacementCharacter);
  55. } else if ((errorMessage.includes(ERROR_INCOGNITO_GECKO) || errorMessage.includes(ERROR_INCOGNITO_GECKO_ALT)) && downloadInfo.incognito) {
  56. delete downloadInfo.incognito;
  57. return download(downloadInfo, replacementCharacter);
  58. } else if (errorMessage == ERROR_CONFLICT_ACTION_GECKO && downloadInfo.conflictAction) {
  59. delete downloadInfo.conflictAction;
  60. return download(downloadInfo, replacementCharacter);
  61. } else if (errorMessage.includes(ERROR_DOWNLOAD_CANCELED_GECKO)) {
  62. return {};
  63. } else {
  64. throw error;
  65. }
  66. } else {
  67. throw error;
  68. }
  69. }
  70. return new Promise((resolve, reject) => {
  71. browser.downloads.onChanged.addListener(onChanged);
  72. function onChanged(event) {
  73. if (event.id == downloadId && event.state) {
  74. if (event.state.current == STATE_DOWNLOAD_COMPLETE) {
  75. browser.downloads.search({ id: downloadId })
  76. .then(downloadItems => resolve({ filename: downloadItems[0] && downloadItems[0].filename }))
  77. .catch(() => resolve({}));
  78. browser.downloads.onChanged.removeListener(onChanged);
  79. }
  80. if (event.state.current == STATE_DOWNLOAD_INTERRUPTED) {
  81. if (event.error && event.error.current == STATE_ERROR_CANCELED_CHROMIUM) {
  82. resolve({});
  83. } else {
  84. reject(new Error(event.state.current));
  85. }
  86. browser.downloads.onChanged.removeListener(onChanged);
  87. }
  88. }
  89. }
  90. });
  91. }
  92. /*
  93. * Copyright 2010-2020 Gildas Lormeau
  94. * contact : gildas.lormeau <at> gmail.com
  95. *
  96. * This file is part of SingleFile.
  97. *
  98. * The code in this file is free software: you can redistribute it and/or
  99. * modify it under the terms of the GNU Affero General Public License
  100. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  101. * of the License, or (at your option) any later version.
  102. *
  103. * The code in this file is distributed in the hope that it will be useful,
  104. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  105. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  106. * General Public License for more details.
  107. *
  108. * As additional permission under GNU AGPL version 3 section 7, you may
  109. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  110. * AGPL normally required by section 4, provided you include this license
  111. * notice and a URL through which recipients can access the Corresponding
  112. * Source.
  113. */
  114. /* global browser, setTimeout */
  115. let persistentData, temporaryData, cleanedUp;
  116. setTimeout(() => getPersistent().then(tabsData => persistentData = tabsData), 0);
  117. function onMessage$e(message) {
  118. if (message.method.endsWith(".get")) {
  119. return getPersistent();
  120. }
  121. if (message.method.endsWith(".set")) {
  122. return setPersistent(message.tabsData);
  123. }
  124. }
  125. async function onTabReplaced$3(addedTabId, removedTabId) {
  126. let tabsData = await getPersistent();
  127. await updateTabsData(tabsData, addedTabId, removedTabId);
  128. setPersistent(tabsData);
  129. await updateTabsData(temporaryData, addedTabId, removedTabId);
  130. }
  131. async function updateTabsData(tabsData, addedTabId, removedTabId) {
  132. if (tabsData[removedTabId] && !tabsData[addedTabId]) {
  133. tabsData[addedTabId] = tabsData[removedTabId];
  134. delete tabsData[removedTabId];
  135. }
  136. }
  137. async function remove(tabId) {
  138. if (temporaryData) {
  139. delete temporaryData[tabId];
  140. }
  141. const tabsData = await getPersistent();
  142. if (tabsData[tabId]) {
  143. const autoSave = tabsData[tabId].autoSave;
  144. tabsData[tabId] = { autoSave };
  145. await setPersistent(tabsData);
  146. }
  147. }
  148. function getTemporary(desiredTabId) {
  149. if (!temporaryData) {
  150. temporaryData = {};
  151. }
  152. if (desiredTabId !== undefined && !temporaryData[desiredTabId]) {
  153. temporaryData[desiredTabId] = {};
  154. }
  155. return temporaryData;
  156. }
  157. async function getPersistent(desiredTabId) {
  158. if (!persistentData) {
  159. const config = await browser.storage.local.get();
  160. persistentData = config.tabsData || {};
  161. }
  162. cleanup();
  163. if (desiredTabId !== undefined && !persistentData[desiredTabId]) {
  164. persistentData[desiredTabId] = {};
  165. }
  166. return persistentData;
  167. }
  168. async function setPersistent(tabsData) {
  169. persistentData = tabsData;
  170. await browser.storage.local.set({ tabsData });
  171. }
  172. async function cleanup() {
  173. if (!cleanedUp) {
  174. cleanedUp = true;
  175. const tabs = await browser.tabs.query({ currentWindow: true, highlighted: true });
  176. Object.keys(persistentData).filter(key => {
  177. if (key != "autoSaveAll" && key != "autoSaveUnpinned" && key != "profileName") {
  178. return !tabs.find(tab => tab.id == key);
  179. }
  180. }).forEach(tabId => delete persistentData[tabId]);
  181. await browser.storage.local.set({ tabsData: persistentData });
  182. }
  183. }
  184. /*
  185. * Copyright 2010-2020 Gildas Lormeau
  186. * contact : gildas.lormeau <at> gmail.com
  187. *
  188. * This file is part of SingleFile.
  189. *
  190. * The code in this file is free software: you can redistribute it and/or
  191. * modify it under the terms of the GNU Affero General Public License
  192. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  193. * of the License, or (at your option) any later version.
  194. *
  195. * The code in this file is distributed in the hope that it will be useful,
  196. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  197. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  198. * General Public License for more details.
  199. *
  200. * As additional permission under GNU AGPL version 3 section 7, you may
  201. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  202. * AGPL normally required by section 4, provided you include this license
  203. * notice and a URL through which recipients can access the Corresponding
  204. * Source.
  205. */
  206. const CURRENT_PROFILE_NAME = "-";
  207. const DEFAULT_PROFILE_NAME = "__Default_Settings__";
  208. const DISABLED_PROFILE_NAME = "__Disabled_Settings__";
  209. const REGEXP_RULE_PREFIX = "regexp:";
  210. const PROFILE_NAME_PREFIX = "profile_";
  211. const IS_NOT_SAFARI = !/Safari/.test(navigator.userAgent) || /Chrome/.test(navigator.userAgent) || /Vivaldi/.test(navigator.userAgent) || /OPR/.test(navigator.userAgent);
  212. const BACKGROUND_SAVE_SUPPORTED = !(/Mobile.*Firefox/.test(navigator.userAgent) || /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent) && !/Vivaldi/.test(navigator.userAgent) && !/OPR/.test(navigator.userAgent));
  213. const BADGE_COLOR_SUPPORTED = IS_NOT_SAFARI;
  214. const AUTO_SAVE_SUPPORTED = IS_NOT_SAFARI;
  215. const SELECTABLE_TABS_SUPPORTED = IS_NOT_SAFARI;
  216. const AUTO_OPEN_EDITOR_SUPPORTED = IS_NOT_SAFARI;
  217. const OPEN_SAVED_PAGE_SUPPORTED = IS_NOT_SAFARI;
  218. const INFOBAR_SUPPORTED = IS_NOT_SAFARI;
  219. const BOOKMARKS_API_SUPPORTED = IS_NOT_SAFARI;
  220. const IDENTITY_API_SUPPORTED = IS_NOT_SAFARI;
  221. const CLIPBOARD_API_SUPPORTED = IS_NOT_SAFARI;
  222. const NATIVE_API_API_SUPPORTED = IS_NOT_SAFARI;
  223. const WEB_BLOCKING_API_SUPPORTED = IS_NOT_SAFARI;
  224. const DEFAULT_CONFIG = {
  225. removeHiddenElements: true,
  226. removeUnusedStyles: true,
  227. removeUnusedFonts: true,
  228. removeFrames: false,
  229. compressHTML: true,
  230. compressCSS: false,
  231. loadDeferredImages: true,
  232. loadDeferredImagesMaxIdleTime: 1500,
  233. loadDeferredImagesBlockCookies: false,
  234. loadDeferredImagesBlockStorage: false,
  235. loadDeferredImagesKeepZoomLevel: false,
  236. loadDeferredImagesDispatchScrollEvent: false,
  237. loadDeferredImagesBeforeFrames: false,
  238. filenameTemplate: "{page-title} ({date-locale} {time-locale}).html",
  239. infobarTemplate: "",
  240. includeInfobar: !IS_NOT_SAFARI,
  241. confirmInfobarContent: false,
  242. autoClose: false,
  243. confirmFilename: false,
  244. filenameConflictAction: "uniquify",
  245. filenameMaxLength: 192,
  246. filenameMaxLengthUnit: "bytes",
  247. filenameReplacedCharacters: ["~", "+", "\\\\", "?", "%", "*", ":", "|", "\"", "<", ">", "\x00-\x1f", "\x7F"],
  248. filenameReplacementCharacter: "_",
  249. replaceEmojisInFilename: false,
  250. contextMenuEnabled: true,
  251. tabMenuEnabled: true,
  252. browserActionMenuEnabled: true,
  253. shadowEnabled: true,
  254. logsEnabled: true,
  255. progressBarEnabled: true,
  256. maxResourceSizeEnabled: false,
  257. maxResourceSize: 10,
  258. displayInfobar: true,
  259. displayStats: false,
  260. backgroundSave: BACKGROUND_SAVE_SUPPORTED,
  261. defaultEditorMode: "normal",
  262. applySystemTheme: true,
  263. autoSaveDelay: 1,
  264. autoSaveLoad: false,
  265. autoSaveUnload: false,
  266. autoSaveLoadOrUnload: true,
  267. autoSaveDiscard: false,
  268. autoSaveRemove: false,
  269. autoSaveRepeat: false,
  270. autoSaveRepeatDelay: 10,
  271. removeAlternativeFonts: true,
  272. removeAlternativeMedias: true,
  273. removeAlternativeImages: true,
  274. groupDuplicateImages: true,
  275. maxSizeDuplicateImages: 512 * 1024,
  276. saveRawPage: false,
  277. saveToClipboard: false,
  278. addProof: false,
  279. saveToGDrive: false,
  280. saveWithWebDAV: false,
  281. webDAVURL: "",
  282. webDAVUser: "",
  283. webDAVPassword: "",
  284. saveToGitHub: false,
  285. githubToken: "",
  286. githubUser: "",
  287. githubRepository: "SingleFile-Archives",
  288. githubBranch: "main",
  289. saveWithCompanion: false,
  290. forceWebAuthFlow: false,
  291. resolveFragmentIdentifierURLs: false,
  292. userScriptEnabled: false,
  293. openEditor: false,
  294. openSavedPage: false,
  295. autoOpenEditor: false,
  296. saveCreatedBookmarks: false,
  297. allowedBookmarkFolders: [],
  298. ignoredBookmarkFolders: [],
  299. replaceBookmarkURL: true,
  300. saveFavicon: true,
  301. includeBOM: false,
  302. warnUnsavedPage: true,
  303. displayInfobarInEditor: false,
  304. compressContent: false,
  305. createRootDirectory: false,
  306. selfExtractingArchive: true,
  307. extractDataFromPage: true,
  308. insertTextBody: false,
  309. autoSaveExternalSave: false,
  310. insertMetaNoIndex: false,
  311. insertMetaCSP: true,
  312. passReferrerOnError: false,
  313. password: "",
  314. insertSingleFileComment: true,
  315. removeSavedDate: false,
  316. blockMixedContent: false,
  317. saveOriginalURLs: false,
  318. acceptHeaders: {
  319. font: "application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8",
  320. image: "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
  321. stylesheet: "text/css,*/*;q=0.1",
  322. script: "*/*",
  323. document: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  324. video: "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
  325. audio: "audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5"
  326. },
  327. moveStylesInHead: false,
  328. networkTimeout: 0,
  329. woleetKey: "",
  330. blockImages: false,
  331. blockStylesheets: false,
  332. blockFonts: false,
  333. blockScripts: true,
  334. blockVideos: true,
  335. blockAudios: true
  336. };
  337. const DEFAULT_RULES = [{
  338. "url": "file:",
  339. "profile": "__Default_Settings__",
  340. "autoSaveProfile": "__Disabled_Settings__"
  341. }];
  342. let configStorage;
  343. let pendingUpgradePromise = upgrade();
  344. async function upgrade() {
  345. const { sync } = await browser.storage.local.get();
  346. if (sync) {
  347. configStorage = browser.storage.sync;
  348. } else {
  349. configStorage = browser.storage.local;
  350. }
  351. const config = await configStorage.get();
  352. if (!config[PROFILE_NAME_PREFIX + DEFAULT_PROFILE_NAME]) {
  353. if (config.profiles) {
  354. const profileNames = Object.keys(config.profiles);
  355. for (const profileName of profileNames) {
  356. await setProfile(profileName, config.profiles[profileName]);
  357. }
  358. } else {
  359. await setProfile(DEFAULT_PROFILE_NAME, DEFAULT_CONFIG);
  360. }
  361. } else if (config.profiles) {
  362. await configStorage.remove(["profiles"]);
  363. }
  364. if (!config.rules) {
  365. await configStorage.set({ rules: DEFAULT_RULES });
  366. }
  367. if (!config.maxParallelWorkers) {
  368. await configStorage.set({ maxParallelWorkers: navigator.hardwareConcurrency || 4 });
  369. }
  370. if (!config.processInForeground) {
  371. await configStorage.set({ processInForeground: false });
  372. }
  373. const profileNames = await getProfileNames();
  374. profileNames.map(async profileName => {
  375. const profile = await getProfile(profileName);
  376. for (const key of Object.keys(DEFAULT_CONFIG)) {
  377. if (profile[key] === undefined) {
  378. profile[key] = DEFAULT_CONFIG[key];
  379. }
  380. }
  381. await setProfile(profileName, profile);
  382. });
  383. }
  384. async function getRule(url, ignoreWildcard) {
  385. const { rules } = await configStorage.get(["rules"]);
  386. const regExpRules = rules.filter(rule => testRegExpRule(rule));
  387. let rule = regExpRules.sort(sortRules).find(rule => url && url.match(new RegExp(rule.url.split(REGEXP_RULE_PREFIX)[1])));
  388. if (!rule) {
  389. const normalRules = rules.filter(rule => !testRegExpRule(rule));
  390. rule = normalRules.sort(sortRules).find(rule => (!ignoreWildcard && rule.url == "*") || (url && url.includes(rule.url)));
  391. }
  392. return rule;
  393. }
  394. async function getConfig() {
  395. await pendingUpgradePromise;
  396. const { maxParallelWorkers, processInForeground } = await configStorage.get(["maxParallelWorkers", "processInForeground"]);
  397. const rules = await getRules();
  398. const profiles = await getProfiles();
  399. return { profiles, rules, maxParallelWorkers, processInForeground };
  400. }
  401. function sortRules(ruleLeft, ruleRight) {
  402. return ruleRight.url.length - ruleLeft.url.length;
  403. }
  404. function testRegExpRule(rule) {
  405. return rule.url.toLowerCase().startsWith(REGEXP_RULE_PREFIX);
  406. }
  407. async function onMessage$d(message) {
  408. if (message.method.endsWith(".deleteRules")) {
  409. await deleteRules(message.profileName);
  410. }
  411. if (message.method.endsWith(".deleteRule")) {
  412. await deleteRule(message.url);
  413. }
  414. if (message.method.endsWith(".addRule")) {
  415. await addRule(message.url, message.profileName, message.autoSaveProfileName);
  416. }
  417. if (message.method.endsWith(".createProfile")) {
  418. await createProfile(message.profileName, message.fromProfileName || DEFAULT_PROFILE_NAME);
  419. }
  420. if (message.method.endsWith(".renameProfile")) {
  421. await renameProfile(message.profileName, message.newProfileName);
  422. }
  423. if (message.method.endsWith(".deleteProfile")) {
  424. await deleteProfile(message.profileName);
  425. }
  426. if (message.method.endsWith(".resetProfiles")) {
  427. await resetProfiles();
  428. }
  429. if (message.method.endsWith(".resetProfile")) {
  430. await resetProfile(message.profileName);
  431. }
  432. if (message.method.endsWith(".importConfig")) {
  433. await importConfig(message.config);
  434. }
  435. if (message.method.endsWith(".updateProfile")) {
  436. await updateProfile(message.profileName, message.profile);
  437. }
  438. if (message.method.endsWith(".updateRule")) {
  439. await updateRule(message.url, message.newUrl, message.profileName, message.autoSaveProfileName);
  440. }
  441. if (message.method.endsWith(".getConstants")) {
  442. return {
  443. DISABLED_PROFILE_NAME,
  444. DEFAULT_PROFILE_NAME,
  445. CURRENT_PROFILE_NAME,
  446. BACKGROUND_SAVE_SUPPORTED,
  447. BADGE_COLOR_SUPPORTED,
  448. AUTO_SAVE_SUPPORTED,
  449. SELECTABLE_TABS_SUPPORTED,
  450. OPEN_SAVED_PAGE_SUPPORTED,
  451. AUTO_OPEN_EDITOR_SUPPORTED,
  452. INFOBAR_SUPPORTED,
  453. BOOKMARKS_API_SUPPORTED,
  454. IDENTITY_API_SUPPORTED,
  455. CLIPBOARD_API_SUPPORTED,
  456. NATIVE_API_API_SUPPORTED,
  457. WEB_BLOCKING_API_SUPPORTED
  458. };
  459. }
  460. if (message.method.endsWith(".getRules")) {
  461. return getRules();
  462. }
  463. if (message.method.endsWith(".getProfiles")) {
  464. return getProfiles();
  465. }
  466. if (message.method.endsWith(".exportConfig")) {
  467. return exportConfig();
  468. }
  469. if (message.method.endsWith(".enableSync")) {
  470. await browser.storage.local.set({ sync: true });
  471. const syncConfig = await browser.storage.sync.get();
  472. if (!syncConfig || !syncConfig.rules) {
  473. const profileKeyNames = await getProfileKeyNames();
  474. const localConfig = await browser.storage.local.get(["rules", "maxParallelWorkers", "processInForeground", ...profileKeyNames]);
  475. await browser.storage.sync.set(localConfig);
  476. }
  477. configStorage = browser.storage.sync;
  478. await upgrade();
  479. return {};
  480. }
  481. if (message.method.endsWith(".disableSync")) {
  482. await browser.storage.local.set({ sync: false });
  483. const syncConfig = await browser.storage.sync.get();
  484. const localConfig = await browser.storage.local.get();
  485. if (syncConfig && syncConfig.rules && (!localConfig || !localConfig.rules)) {
  486. await browser.storage.local.set({ rules: syncConfig.rules, maxParallelWorkers: syncConfig.maxParallelWorkers, processInForeground: syncConfig.processInForeground });
  487. const profiles = {};
  488. // syncConfig.profileNames.forEach(profileKeyName => profiles[PROFILE_NAME_PREFIX + profileKeyName] = syncConfig[profileKeyName]);
  489. await browser.storage.local.set(profiles);
  490. }
  491. configStorage = browser.storage.local;
  492. await upgrade();
  493. return {};
  494. }
  495. if (message.method.endsWith(".isSync")) {
  496. return { sync: (await browser.storage.local.get()).sync };
  497. }
  498. return {};
  499. }
  500. async function createProfile(profileName, fromProfileName) {
  501. const profileNames = await getProfileNames();
  502. if (profileNames.includes(profileName)) {
  503. throw new Error("Duplicate profile name");
  504. }
  505. const profileFrom = await getProfile(fromProfileName);
  506. const profile = JSON.parse(JSON.stringify(profileFrom));
  507. await setProfile(profileName, profile);
  508. }
  509. async function getProfiles() {
  510. await pendingUpgradePromise;
  511. const profileKeyNames = await getProfileKeyNames();
  512. const profiles = await configStorage.get(profileKeyNames);
  513. const result = {};
  514. Object.keys(profiles).forEach(profileName => result[profileName.substring(PROFILE_NAME_PREFIX.length)] = profiles[profileName]);
  515. return result;
  516. }
  517. async function getOptions(url, autoSave) {
  518. await pendingUpgradePromise;
  519. const [rule, allTabsData] = await Promise.all([getRule(url), getPersistent()]);
  520. const tabProfileName = allTabsData.profileName || DEFAULT_PROFILE_NAME;
  521. let selectedProfileName;
  522. if (rule) {
  523. const profileName = rule[autoSave ? "autoSaveProfile" : "profile"];
  524. selectedProfileName = profileName == CURRENT_PROFILE_NAME ? tabProfileName : profileName;
  525. } else {
  526. selectedProfileName = tabProfileName;
  527. }
  528. const profile = await getProfile(selectedProfileName);
  529. return Object.assign({ profileName: selectedProfileName }, profile);
  530. }
  531. async function updateProfile(profileName, profile) {
  532. const profileNames = await getProfileNames();
  533. if (!profileNames.includes(profileName)) {
  534. throw new Error("Profile not found");
  535. }
  536. const previousProfile = await getProfile(profileName);
  537. Object.keys(previousProfile).forEach(key => {
  538. profile[key] = profile[key] === undefined ? previousProfile[key] : profile[key];
  539. });
  540. await setProfile(profileName, profile);
  541. }
  542. async function renameProfile(oldProfileName, profileName) {
  543. const profileNames = await getProfileNames();
  544. const allTabsData = await getPersistent();
  545. const rules = await getRules();
  546. if (!profileNames.includes(oldProfileName)) {
  547. throw new Error("Profile not found");
  548. }
  549. if (profileNames.includes(profileName)) {
  550. throw new Error("Duplicate profile name");
  551. }
  552. if (oldProfileName == DEFAULT_PROFILE_NAME) {
  553. throw new Error("Default settings cannot be renamed");
  554. }
  555. if (allTabsData.profileName == oldProfileName) {
  556. allTabsData.profileName = profileName;
  557. await setPersistent(allTabsData);
  558. }
  559. rules.forEach(rule => {
  560. if (rule.profile == oldProfileName) {
  561. rule.profile = profileName;
  562. }
  563. if (rule.autoSaveProfile == oldProfileName) {
  564. rule.autoSaveProfile = profileName;
  565. }
  566. });
  567. const profile = await getProfile(oldProfileName);
  568. await configStorage.remove([PROFILE_NAME_PREFIX + oldProfileName]);
  569. await configStorage.set({ [PROFILE_NAME_PREFIX + profileName]: profile, rules });
  570. }
  571. async function deleteProfile(profileName) {
  572. const profileNames = await getProfileNames();
  573. const allTabsData = await getPersistent();
  574. const rules = await getRules();
  575. if (!profileNames.includes(profileName)) {
  576. throw new Error("Profile not found");
  577. }
  578. if (profileName == DEFAULT_PROFILE_NAME) {
  579. throw new Error("Default settings cannot be deleted");
  580. }
  581. if (allTabsData.profileName == profileName) {
  582. delete allTabsData.profileName;
  583. await setPersistent(allTabsData);
  584. }
  585. rules.forEach(rule => {
  586. if (rule.profile == profileName) {
  587. rule.profile = DEFAULT_PROFILE_NAME;
  588. }
  589. if (rule.autoSaveProfile == profileName) {
  590. rule.autoSaveProfile = DEFAULT_PROFILE_NAME;
  591. }
  592. });
  593. configStorage.remove([PROFILE_NAME_PREFIX + profileName]);
  594. await configStorage.set({ rules });
  595. }
  596. async function getRules() {
  597. return (await configStorage.get(["rules"])).rules;
  598. }
  599. async function getProfileNames() {
  600. return Object.keys(await configStorage.get()).filter(key => key.startsWith(PROFILE_NAME_PREFIX)).map(key => key.substring(PROFILE_NAME_PREFIX.length));
  601. }
  602. async function getProfileKeyNames() {
  603. return Object.keys(await configStorage.get()).filter(key => key.startsWith(PROFILE_NAME_PREFIX));
  604. }
  605. async function getProfile(profileName) {
  606. const profileKey = PROFILE_NAME_PREFIX + profileName;
  607. const data = await configStorage.get([profileKey]);
  608. return data[profileKey];
  609. }
  610. async function setProfile(profileName, profileData) {
  611. const profileKey = PROFILE_NAME_PREFIX + profileName;
  612. await configStorage.set({ [profileKey]: profileData });
  613. }
  614. async function addRule(url, profile, autoSaveProfile) {
  615. if (!url) {
  616. throw new Error("URL is empty");
  617. }
  618. const rules = await getRules();
  619. if (rules.find(rule => rule.url == url)) {
  620. throw new Error("URL already exists");
  621. }
  622. rules.push({
  623. url,
  624. profile,
  625. autoSaveProfile
  626. });
  627. await configStorage.set({ rules });
  628. }
  629. async function deleteRule(url) {
  630. if (!url) {
  631. throw new Error("URL is empty");
  632. }
  633. const rules = await getRules();
  634. await configStorage.set({ rules: rules.filter(rule => rule.url != url) });
  635. }
  636. async function deleteRules(profileName) {
  637. const rules = await getRules();
  638. await configStorage.set({ rules: profileName ? rules.filter(rule => rule.autoSaveProfile != profileName && rule.profile != profileName) : [] });
  639. }
  640. async function updateRule(url, newURL, profile, autoSaveProfile) {
  641. if (!url || !newURL) {
  642. throw new Error("URL is empty");
  643. }
  644. const rules = await getRules();
  645. const urlConfig = rules.find(rule => rule.url == url);
  646. if (!urlConfig) {
  647. throw new Error("URL not found");
  648. }
  649. if (rules.find(rule => rule.url == newURL && rule.url != url)) {
  650. throw new Error("New URL already exists");
  651. }
  652. urlConfig.url = newURL;
  653. urlConfig.profile = profile;
  654. urlConfig.autoSaveProfile = autoSaveProfile;
  655. await configStorage.set({ rules });
  656. }
  657. async function getAuthInfo$1() {
  658. return (await configStorage.get()).authInfo;
  659. }
  660. async function setAuthInfo(authInfo) {
  661. await configStorage.set({ authInfo });
  662. }
  663. async function removeAuthInfo() {
  664. let authInfo = getAuthInfo$1();
  665. if (authInfo.revokableAccessToken) {
  666. setAuthInfo({ revokableAccessToken: authInfo.revokableAccessToken });
  667. } else {
  668. await configStorage.remove(["authInfo"]);
  669. }
  670. }
  671. async function resetProfiles() {
  672. await pendingUpgradePromise;
  673. const allTabsData = await getPersistent();
  674. delete allTabsData.profileName;
  675. await setPersistent(allTabsData);
  676. let profileKeyNames = await getProfileKeyNames();
  677. await configStorage.remove([...profileKeyNames, "rules", "maxParallelWorkers", "processInForeground"]);
  678. await upgrade();
  679. }
  680. async function resetProfile(profileName) {
  681. const profileNames = await getProfileNames();
  682. if (!profileNames.includes(profileName)) {
  683. throw new Error("Profile not found");
  684. }
  685. await setProfile(profileName, DEFAULT_CONFIG);
  686. }
  687. async function exportConfig() {
  688. const config = await getConfig();
  689. const textContent = JSON.stringify({ profiles: config.profiles, rules: config.rules, maxParallelWorkers: config.maxParallelWorkers, processInForeground: config.processInForeground }, null, 2);
  690. const filename = `singlefile-settings-${(new Date()).toISOString().replace(/:/g, "_")}.json`;
  691. if (IS_NOT_SAFARI) {
  692. const url = URL.createObjectURL(new Blob([textContent], { type: "text/json" }));
  693. try {
  694. await download({
  695. url,
  696. filename,
  697. saveAs: true
  698. }, "_");
  699. } finally {
  700. URL.revokeObjectURL(url);
  701. }
  702. return {};
  703. } else {
  704. return {
  705. filename,
  706. textContent
  707. };
  708. }
  709. }
  710. async function importConfig(config) {
  711. const profileNames = await getProfileNames();
  712. const profileKeyNames = await getProfileKeyNames();
  713. const allTabsData = await getPersistent();
  714. if (profileNames.includes(allTabsData.profileName)) {
  715. delete allTabsData.profileName;
  716. await setPersistent(allTabsData);
  717. }
  718. await configStorage.remove([...profileKeyNames, "rules", "maxParallelWorkers", "processInForeground"]);
  719. const newConfig = { rules: config.rules, maxParallelWorkers: config.maxParallelWorkers, processInForeground: config.processInForeground };
  720. Object.keys(config.profiles).forEach(profileName => newConfig[PROFILE_NAME_PREFIX + profileName] = config.profiles[profileName]);
  721. await configStorage.set(newConfig);
  722. await upgrade();
  723. }
  724. /*
  725. * Copyright 2010-2020 Gildas Lormeau
  726. * contact : gildas.lormeau <at> gmail.com
  727. *
  728. * This file is part of SingleFile.
  729. *
  730. * The code in this file is free software: you can redistribute it and/or
  731. * modify it under the terms of the GNU Affero General Public License
  732. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  733. * of the License, or (at your option) any later version.
  734. *
  735. * The code in this file is distributed in the hope that it will be useful,
  736. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  737. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  738. * General Public License for more details.
  739. *
  740. * As additional permission under GNU AGPL version 3 section 7, you may
  741. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  742. * AGPL normally required by section 4, provided you include this license
  743. * notice and a URL through which recipients can access the Corresponding
  744. * Source.
  745. */
  746. async function autoSaveIsEnabled(tab) {
  747. if (tab) {
  748. const [allTabsData, rule] = await Promise.all([getPersistent(), getRule(tab.url)]);
  749. return Boolean(allTabsData.autoSaveAll ||
  750. (allTabsData.autoSaveUnpinned && !tab.pinned) ||
  751. (allTabsData[tab.id] && allTabsData[tab.id].autoSave)) &&
  752. (!rule || rule.autoSaveProfile != DISABLED_PROFILE_NAME);
  753. }
  754. }
  755. async function refreshAutoSaveTabs() {
  756. const tabs = (await browser.tabs.query({}));
  757. return Promise.all(tabs.map(async tab => {
  758. const [options, autoSaveEnabled] = await Promise.all([getOptions(tab.url, true), autoSaveIsEnabled(tab)]);
  759. try {
  760. await browser.tabs.sendMessage(tab.id, { method: "content.init", autoSaveEnabled, options });
  761. } catch (error) {
  762. // ignored
  763. }
  764. }));
  765. }
  766. /*
  767. * Copyright 2010-2020 Gildas Lormeau
  768. * contact : gildas.lormeau <at> gmail.com
  769. *
  770. * This file is part of SingleFile.
  771. *
  772. * The code in this file is free software: you can redistribute it and/or
  773. * modify it under the terms of the GNU Affero General Public License
  774. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  775. * of the License, or (at your option) any later version.
  776. *
  777. * The code in this file is distributed in the hope that it will be useful,
  778. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  779. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  780. * General Public License for more details.
  781. *
  782. * As additional permission under GNU AGPL version 3 section 7, you may
  783. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  784. * AGPL normally required by section 4, provided you include this license
  785. * notice and a URL through which recipients can access the Corresponding
  786. * Source.
  787. */
  788. async function onMessage$c(message, sender) {
  789. if (message.method.endsWith(".init")) {
  790. const [optionsAutoSave, options, autoSaveEnabled] = await Promise.all([getOptions(sender.tab.url, true), getOptions(sender.tab.url), autoSaveIsEnabled(sender.tab)]);
  791. return { optionsAutoSave, options, autoSaveEnabled, tabId: sender.tab.id, tabIndex: sender.tab.index };
  792. }
  793. }
  794. /*
  795. * Copyright 2010-2020 Gildas Lormeau
  796. * contact : gildas.lormeau <at> gmail.com
  797. *
  798. * This file is part of SingleFile.
  799. *
  800. * The code in this file is free software: you can redistribute it and/or
  801. * modify it under the terms of the GNU Affero General Public License
  802. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  803. * of the License, or (at your option) any later version.
  804. *
  805. * The code in this file is distributed in the hope that it will be useful,
  806. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  807. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  808. * General Public License for more details.
  809. *
  810. * As additional permission under GNU AGPL version 3 section 7, you may
  811. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  812. * AGPL normally required by section 4, provided you include this license
  813. * notice and a URL through which recipients can access the Corresponding
  814. * Source.
  815. */
  816. const MAX_CONTENT_SIZE$1 = 32 * (1024 * 1024);
  817. const EDITOR_PAGE_URL = "/src/ui/pages/editor.html";
  818. const tabsData = new Map();
  819. const partialContents$1 = new Map();
  820. const EDITOR_URL = browser.runtime.getURL(EDITOR_PAGE_URL);
  821. async function open({ tabIndex, content, filename, compressContent }) {
  822. const createTabProperties = { active: true, url: EDITOR_PAGE_URL };
  823. if (tabIndex != null) {
  824. createTabProperties.index = tabIndex;
  825. }
  826. const tab = await browser.tabs.create(createTabProperties);
  827. tabsData.set(tab.id, { content, filename, compressContent });
  828. }
  829. function onTabRemoved$2(tabId) {
  830. tabsData.delete(tabId);
  831. }
  832. function isEditor(tab) {
  833. return tab.url == EDITOR_URL;
  834. }
  835. async function onMessage$b(message, sender) {
  836. if (message.method.endsWith(".getTabData")) {
  837. const tab = sender.tab;
  838. const tabData = tabsData.get(tab.id);
  839. if (tabData) {
  840. const options = await getOptions(tabData.url);
  841. const content = JSON.stringify(tabData);
  842. for (let blockIndex = 0; blockIndex * MAX_CONTENT_SIZE$1 < content.length; blockIndex++) {
  843. const message = {
  844. method: "editor.setTabData",
  845. compressContent: tabData.compressContent
  846. };
  847. message.truncated = content.length > MAX_CONTENT_SIZE$1;
  848. if (message.truncated) {
  849. message.finished = (blockIndex + 1) * MAX_CONTENT_SIZE$1 > content.length;
  850. message.content = content.substring(blockIndex * MAX_CONTENT_SIZE$1, (blockIndex + 1) * MAX_CONTENT_SIZE$1);
  851. if (message.finished) {
  852. message.options = options;
  853. }
  854. } else {
  855. message.content = content;
  856. message.options = options;
  857. }
  858. await browser.tabs.sendMessage(tab.id, message);
  859. }
  860. }
  861. return {};
  862. }
  863. if (message.method.endsWith(".open")) {
  864. let contents;
  865. const tab = sender.tab;
  866. if (message.truncated) {
  867. contents = partialContents$1.get(tab.id);
  868. if (!contents) {
  869. contents = [];
  870. partialContents$1.set(tab.id, contents);
  871. }
  872. contents.push(message.content);
  873. if (message.finished) {
  874. partialContents$1.delete(tab.id);
  875. }
  876. } else if (message.content) {
  877. contents = [message.content];
  878. }
  879. if (!message.truncated || message.finished) {
  880. const updateTabProperties = { url: EDITOR_PAGE_URL };
  881. await browser.tabs.update(tab.id, updateTabProperties);
  882. const content = message.compressContent ? contents.flat() : contents.join("");
  883. tabsData.set(tab.id, {
  884. url: tab.url,
  885. content,
  886. filename: message.filename,
  887. compressContent: message.compressContent,
  888. selfExtractingArchive: message.selfExtractingArchive,
  889. extractDataFromPageTags: message.extractDataFromPageTags,
  890. insertTextBody: message.insertTextBody
  891. });
  892. }
  893. return {};
  894. }
  895. if (message.method.endsWith(".ping")) {
  896. return {};
  897. }
  898. }
  899. /*
  900. * Copyright 2010-2020 Gildas Lormeau
  901. * contact : gildas.lormeau <at> gmail.com
  902. *
  903. * This file is part of SingleFile.
  904. *
  905. * The code in this file is free software: you can redistribute it and/or
  906. * modify it under the terms of the GNU Affero General Public License
  907. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  908. * of the License, or (at your option) any later version.
  909. *
  910. * The code in this file is distributed in the hope that it will be useful,
  911. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  912. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  913. * General Public License for more details.
  914. *
  915. * As additional permission under GNU AGPL version 3 section 7, you may
  916. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  917. * AGPL normally required by section 4, provided you include this license
  918. * notice and a URL through which recipients can access the Corresponding
  919. * Source.
  920. */
  921. /* global browser, XMLHttpRequest */
  922. const referrers = new Map();
  923. const REQUEST_ID_HEADER_NAME = "x-single-file-request-id";
  924. const MAX_CONTENT_SIZE = 8 * (1024 * 1024);
  925. browser.runtime.onMessage.addListener((message, sender) => {
  926. if (message.method && message.method.startsWith("singlefile.fetch")) {
  927. return new Promise(resolve => {
  928. onRequest(message, sender)
  929. .then(resolve)
  930. .catch(error => resolve({ error: error && error.toString() }));
  931. });
  932. }
  933. });
  934. async function onRequest(message, sender) {
  935. if (message.method == "singlefile.fetch") {
  936. try {
  937. const response = await fetchResource$1(message.url, { referrer: message.referrer, headers: message.headers });
  938. return sendResponse(sender.tab.id, message.requestId, response);
  939. } catch (error) {
  940. return sendResponse(sender.tab.id, message.requestId, { error: error.message, arrray: [] });
  941. }
  942. } else if (message.method == "singlefile.fetchFrame") {
  943. return browser.tabs.sendMessage(sender.tab.id, message);
  944. }
  945. }
  946. async function sendResponse(tabId, requestId, response) {
  947. for (let blockIndex = 0; blockIndex * MAX_CONTENT_SIZE <= response.array.length; blockIndex++) {
  948. const message = {
  949. method: "singlefile.fetchResponse",
  950. requestId,
  951. headers: response.headers,
  952. status: response.status,
  953. error: response.error
  954. };
  955. message.truncated = response.array.length > MAX_CONTENT_SIZE;
  956. if (message.truncated) {
  957. message.finished = (blockIndex + 1) * MAX_CONTENT_SIZE > response.array.length;
  958. message.array = response.array.slice(blockIndex * MAX_CONTENT_SIZE, (blockIndex + 1) * MAX_CONTENT_SIZE);
  959. } else {
  960. message.array = response.array;
  961. }
  962. await browser.tabs.sendMessage(tabId, message);
  963. }
  964. return {};
  965. }
  966. function fetchResource$1(url, options = {}, includeRequestId) {
  967. return new Promise((resolve, reject) => {
  968. const xhrRequest = new XMLHttpRequest();
  969. xhrRequest.withCredentials = true;
  970. xhrRequest.responseType = "arraybuffer";
  971. xhrRequest.onerror = event => reject(new Error(event.detail));
  972. xhrRequest.onreadystatechange = () => {
  973. if (xhrRequest.readyState == XMLHttpRequest.DONE) {
  974. if (xhrRequest.status || xhrRequest.response.byteLength) {
  975. if ((xhrRequest.status == 401 || xhrRequest.status == 403 || xhrRequest.status == 404) && !includeRequestId) {
  976. fetchResource$1(url, options, true)
  977. .then(resolve)
  978. .catch(reject);
  979. } else {
  980. resolve({
  981. arrayBuffer: xhrRequest.response,
  982. array: Array.from(new Uint8Array(xhrRequest.response)),
  983. headers: { "content-type": xhrRequest.getResponseHeader("Content-Type") },
  984. status: xhrRequest.status
  985. });
  986. }
  987. } else {
  988. reject(new Error("Empty response"));
  989. }
  990. }
  991. };
  992. xhrRequest.open("GET", url, true);
  993. if (options.headers) {
  994. for (const entry of Object.entries(options.headers)) {
  995. xhrRequest.setRequestHeader(entry[0], entry[1]);
  996. }
  997. }
  998. if (includeRequestId) {
  999. const randomId = String(Math.random()).substring(2);
  1000. setReferrer(randomId, options.referrer);
  1001. xhrRequest.setRequestHeader(REQUEST_ID_HEADER_NAME, randomId);
  1002. }
  1003. xhrRequest.send();
  1004. });
  1005. }
  1006. function setReferrer(requestId, referrer) {
  1007. referrers.set(requestId, referrer);
  1008. }
  1009. /*
  1010. * Copyright 2010-2020 Gildas Lormeau
  1011. * contact : gildas.lormeau <at> gmail.com
  1012. *
  1013. * This file is part of SingleFile.
  1014. *
  1015. * The code in this file is free software: you can redistribute it and/or
  1016. * modify it under the terms of the GNU Affero General Public License
  1017. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1018. * of the License, or (at your option) any later version.
  1019. *
  1020. * The code in this file is distributed in the hope that it will be useful,
  1021. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1022. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1023. * General Public License for more details.
  1024. *
  1025. * As additional permission under GNU AGPL version 3 section 7, you may
  1026. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1027. * AGPL normally required by section 4, provided you include this license
  1028. * notice and a URL through which recipients can access the Corresponding
  1029. * Source.
  1030. */
  1031. let referrerOnErrorEnabled = false;
  1032. function onMessage$a(message) {
  1033. if (message.method.endsWith(".enableReferrerOnError")) {
  1034. enableReferrerOnError();
  1035. return {};
  1036. }
  1037. if (message.method.endsWith(".disableReferrerOnError")) {
  1038. disableReferrerOnError();
  1039. return {};
  1040. }
  1041. }
  1042. function injectRefererHeader(details) {
  1043. if (referrerOnErrorEnabled) {
  1044. let requestIdHeader = details.requestHeaders.find(header => header.name === REQUEST_ID_HEADER_NAME);
  1045. if (requestIdHeader) {
  1046. details.requestHeaders = details.requestHeaders.filter(header => header.name !== REQUEST_ID_HEADER_NAME);
  1047. const referrer = referrers.get(requestIdHeader.value);
  1048. if (referrer) {
  1049. referrers.delete(requestIdHeader.value);
  1050. const header = details.requestHeaders.find(header => header.name.toLowerCase() === "referer");
  1051. if (!header) {
  1052. details.requestHeaders.push({ name: "Referer", value: referrer });
  1053. return { requestHeaders: details.requestHeaders };
  1054. }
  1055. }
  1056. }
  1057. }
  1058. }
  1059. function enableReferrerOnError() {
  1060. if (!referrerOnErrorEnabled) {
  1061. try {
  1062. browser.webRequest.onBeforeSendHeaders.addListener(injectRefererHeader, { urls: ["<all_urls>"] }, ["blocking", "requestHeaders", "extraHeaders"]);
  1063. } catch (error) {
  1064. browser.webRequest.onBeforeSendHeaders.addListener(injectRefererHeader, { urls: ["<all_urls>"] }, ["blocking", "requestHeaders"]);
  1065. }
  1066. referrerOnErrorEnabled = true;
  1067. }
  1068. }
  1069. function disableReferrerOnError() {
  1070. try {
  1071. browser.webRequest.onBeforeSendHeaders.removeListener(injectRefererHeader);
  1072. } catch (error) {
  1073. // ignored
  1074. }
  1075. referrerOnErrorEnabled = false;
  1076. }
  1077. /*
  1078. * Copyright 2010-2020 Gildas Lormeau
  1079. * contact : gildas.lormeau <at> gmail.com
  1080. *
  1081. * This file is part of SingleFile.
  1082. *
  1083. * The code in this file is free software: you can redistribute it and/or
  1084. * modify it under the terms of the GNU Affero General Public License
  1085. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1086. * of the License, or (at your option) any later version.
  1087. *
  1088. * The code in this file is distributed in the hope that it will be useful,
  1089. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1090. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1091. * General Public License for more details.
  1092. *
  1093. * As additional permission under GNU AGPL version 3 section 7, you may
  1094. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1095. * AGPL normally required by section 4, provided you include this license
  1096. * notice and a URL through which recipients can access the Corresponding
  1097. * Source.
  1098. */
  1099. async function queryTabs$1(options) {
  1100. const tabs = await browser.tabs.query(options);
  1101. return tabs.sort((tab1, tab2) => tab1.index - tab2.index);
  1102. }
  1103. function extractAuthCode(authURL) {
  1104. return new Promise((resolve, reject) => {
  1105. browser.tabs.onUpdated.addListener(onTabUpdated);
  1106. function onTabUpdated(tabId, changeInfo) {
  1107. if (changeInfo && changeInfo.url.startsWith(authURL)) {
  1108. browser.tabs.onUpdated.removeListener(onTabUpdated);
  1109. const code = new URLSearchParams(new URL(changeInfo.url).search).get("code");
  1110. if (code) {
  1111. browser.tabs.remove(tabId);
  1112. resolve(code);
  1113. } else {
  1114. reject();
  1115. }
  1116. }
  1117. }
  1118. });
  1119. }
  1120. async function launchWebAuthFlow(options) {
  1121. const tab = await browser.tabs.create({ url: options.url, active: true });
  1122. return new Promise((resolve, reject) => {
  1123. browser.tabs.onRemoved.addListener(onTabRemoved);
  1124. function onTabRemoved(tabId) {
  1125. if (tabId == tab.id) {
  1126. browser.tabs.onRemoved.removeListener(onTabRemoved);
  1127. reject(new Error("code_required"));
  1128. }
  1129. }
  1130. });
  1131. }
  1132. /*
  1133. * Copyright 2010-2020 Gildas Lormeau
  1134. * contact : gildas.lormeau <at> gmail.com
  1135. *
  1136. * This file is part of SingleFile.
  1137. *
  1138. * The code in this file is free software: you can redistribute it and/or
  1139. * modify it under the terms of the GNU Affero General Public License
  1140. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1141. * of the License, or (at your option) any later version.
  1142. *
  1143. * The code in this file is distributed in the hope that it will be useful,
  1144. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1145. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1146. * General Public License for more details.
  1147. *
  1148. * As additional permission under GNU AGPL version 3 section 7, you may
  1149. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1150. * AGPL normally required by section 4, provided you include this license
  1151. * notice and a URL through which recipients can access the Corresponding
  1152. * Source.
  1153. */
  1154. const DEFAULT_ICON_PATH = "/src/ui/resources/icon_128.png";
  1155. const WAIT_ICON_PATH_PREFIX = "/src/ui/resources/icon_128_wait";
  1156. const BUTTON_DEFAULT_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonDefaultTooltip");
  1157. const BUTTON_BLOCKED_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonBlockedTooltip");
  1158. const BUTTON_DEFAULT_BADGE_MESSAGE = "";
  1159. const BUTTON_INITIALIZING_BADGE_MESSAGE = browser.i18n.getMessage("buttonInitializingBadge");
  1160. const BUTTON_INITIALIZING_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonInitializingTooltip");
  1161. const BUTTON_ERROR_BADGE_MESSAGE = browser.i18n.getMessage("buttonErrorBadge");
  1162. const BUTTON_BLOCKED_BADGE_MESSAGE = browser.i18n.getMessage("buttonBlockedBadge");
  1163. const BUTTON_OK_BADGE_MESSAGE = browser.i18n.getMessage("buttonOKBadge");
  1164. const BUTTON_SAVE_PROGRESS_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonSaveProgressTooltip");
  1165. const BUTTON_UPLOAD_PROGRESS_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonUploadProgressTooltip");
  1166. const BUTTON_AUTOSAVE_ACTIVE_BADGE_MESSAGE = browser.i18n.getMessage("buttonAutoSaveActiveBadge");
  1167. const BUTTON_AUTOSAVE_ACTIVE_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonAutoSaveActiveTooltip");
  1168. const DEFAULT_COLOR = [2, 147, 20, 192];
  1169. const ACTIVE_COLOR = [4, 229, 36, 192];
  1170. const FORBIDDEN_COLOR = [255, 255, 255, 1];
  1171. const ERROR_COLOR = [229, 4, 12, 192];
  1172. const AUTOSAVE_DEFAULT_COLOR = [208, 208, 208, 192];
  1173. const AUTOSAVE_INITIALIZING_COLOR = [64, 64, 64, 192];
  1174. const INJECT_SCRIPTS_STEP$1 = 1;
  1175. const BUTTON_STATES = {
  1176. default: {
  1177. setBadgeBackgroundColor: { color: DEFAULT_COLOR },
  1178. setBadgeText: { text: BUTTON_DEFAULT_BADGE_MESSAGE },
  1179. setTitle: { title: BUTTON_DEFAULT_TOOLTIP_MESSAGE },
  1180. setIcon: { path: DEFAULT_ICON_PATH }
  1181. },
  1182. inject: {
  1183. setBadgeBackgroundColor: { color: DEFAULT_COLOR },
  1184. setBadgeText: { text: BUTTON_INITIALIZING_BADGE_MESSAGE },
  1185. setTitle: { title: BUTTON_INITIALIZING_TOOLTIP_MESSAGE },
  1186. },
  1187. execute: {
  1188. setBadgeBackgroundColor: { color: ACTIVE_COLOR },
  1189. setBadgeText: { text: BUTTON_INITIALIZING_BADGE_MESSAGE },
  1190. },
  1191. progress: {
  1192. setBadgeBackgroundColor: { color: ACTIVE_COLOR },
  1193. setBadgeText: { text: BUTTON_DEFAULT_BADGE_MESSAGE }
  1194. },
  1195. edit: {
  1196. setBadgeBackgroundColor: { color: DEFAULT_COLOR },
  1197. setBadgeText: { text: BUTTON_DEFAULT_BADGE_MESSAGE },
  1198. setTitle: { title: BUTTON_DEFAULT_TOOLTIP_MESSAGE },
  1199. setIcon: { path: DEFAULT_ICON_PATH }
  1200. },
  1201. end: {
  1202. setBadgeBackgroundColor: { color: ACTIVE_COLOR },
  1203. setBadgeText: { text: BUTTON_OK_BADGE_MESSAGE },
  1204. setTitle: { title: BUTTON_DEFAULT_TOOLTIP_MESSAGE },
  1205. setIcon: { path: DEFAULT_ICON_PATH }
  1206. },
  1207. error: {
  1208. setBadgeBackgroundColor: { color: ERROR_COLOR },
  1209. setBadgeText: { text: BUTTON_ERROR_BADGE_MESSAGE },
  1210. setTitle: { title: BUTTON_DEFAULT_BADGE_MESSAGE },
  1211. setIcon: { path: DEFAULT_ICON_PATH }
  1212. },
  1213. forbidden: {
  1214. setBadgeBackgroundColor: { color: FORBIDDEN_COLOR },
  1215. setBadgeText: { text: BUTTON_BLOCKED_BADGE_MESSAGE },
  1216. setTitle: { title: BUTTON_BLOCKED_TOOLTIP_MESSAGE },
  1217. setIcon: { path: DEFAULT_ICON_PATH }
  1218. },
  1219. autosave: {
  1220. inject: {
  1221. setBadgeBackgroundColor: { color: AUTOSAVE_INITIALIZING_COLOR },
  1222. setBadgeText: { text: BUTTON_AUTOSAVE_ACTIVE_BADGE_MESSAGE },
  1223. setTitle: { title: BUTTON_AUTOSAVE_ACTIVE_TOOLTIP_MESSAGE },
  1224. setIcon: { path: DEFAULT_ICON_PATH }
  1225. },
  1226. default: {
  1227. setBadgeBackgroundColor: { color: AUTOSAVE_DEFAULT_COLOR },
  1228. setBadgeText: { text: BUTTON_AUTOSAVE_ACTIVE_BADGE_MESSAGE },
  1229. setTitle: { title: BUTTON_AUTOSAVE_ACTIVE_TOOLTIP_MESSAGE },
  1230. setIcon: { path: DEFAULT_ICON_PATH }
  1231. }
  1232. }
  1233. };
  1234. let business$2;
  1235. browser.browserAction.onClicked.addListener(async tab => {
  1236. const highlightedTabs = await queryTabs$1({ currentWindow: true, highlighted: true });
  1237. if (highlightedTabs.length <= 1) {
  1238. toggleSaveTab(tab);
  1239. } else {
  1240. business$2.saveTabs(highlightedTabs);
  1241. }
  1242. function toggleSaveTab(tab) {
  1243. if (business$2.isSavingTab(tab)) {
  1244. business$2.cancelTab(tab.id);
  1245. } else {
  1246. business$2.saveTabs([tab]);
  1247. }
  1248. }
  1249. });
  1250. function init$3(businessApi) {
  1251. business$2 = businessApi;
  1252. }
  1253. function onMessage$9(message, sender) {
  1254. if (message.method.endsWith(".processInit")) {
  1255. const allTabsData = getTemporary(sender.tab.id);
  1256. delete allTabsData[sender.tab.id].button;
  1257. refreshTab$2(sender.tab);
  1258. }
  1259. if (message.method.endsWith(".processProgress")) {
  1260. if (message.maxIndex) {
  1261. onSaveProgress(sender.tab.id, message.index, message.maxIndex);
  1262. }
  1263. }
  1264. if (message.method.endsWith(".processEnd")) {
  1265. onEnd$1(sender.tab.id);
  1266. }
  1267. if (message.method.endsWith(".processError")) {
  1268. if (message.error) {
  1269. console.error("Initialization error", message.error); // eslint-disable-line no-console
  1270. }
  1271. onError$1(sender.tab.id);
  1272. }
  1273. if (message.method.endsWith(".processCancelled")) {
  1274. onCancelled$1(sender.tab);
  1275. }
  1276. return Promise.resolve({});
  1277. }
  1278. function onStart$1(tabId, step, autoSave) {
  1279. let state;
  1280. if (autoSave) {
  1281. state = getButtonState("inject", true);
  1282. } else {
  1283. state = step == INJECT_SCRIPTS_STEP$1 ? getButtonState("inject") : getButtonState("execute");
  1284. state.setTitle = { title: BUTTON_INITIALIZING_TOOLTIP_MESSAGE + " (" + step + "/2)" };
  1285. state.setIcon = { path: WAIT_ICON_PATH_PREFIX + "0.png" };
  1286. }
  1287. refresh(tabId, state);
  1288. }
  1289. function onError$1(tabId) {
  1290. refresh(tabId, getButtonState("error"));
  1291. }
  1292. function onEdit$1(tabId) {
  1293. refresh(tabId, getButtonState("edit"));
  1294. }
  1295. function onEnd$1(tabId, autoSave) {
  1296. refresh(tabId, autoSave ? getButtonState("default", true) : getButtonState("end"));
  1297. }
  1298. function onForbiddenDomain$1(tab) {
  1299. refresh(tab.id, getButtonState("forbidden"));
  1300. }
  1301. function onCancelled$1(tab) {
  1302. refreshTab$2(tab);
  1303. }
  1304. function onSaveProgress(tabId, index, maxIndex) {
  1305. onProgress(tabId, index, maxIndex, BUTTON_SAVE_PROGRESS_TOOLTIP_MESSAGE);
  1306. }
  1307. function onUploadProgress$1(tabId, index, maxIndex) {
  1308. onProgress(tabId, index, maxIndex, BUTTON_UPLOAD_PROGRESS_TOOLTIP_MESSAGE);
  1309. }
  1310. function onProgress(tabId, index, maxIndex, tooltipMessage) {
  1311. const progress = Math.max(Math.min(20, Math.floor((index / maxIndex) * 20)), 0);
  1312. const barProgress = Math.min(Math.floor((index / maxIndex) * 8), 8);
  1313. const path = WAIT_ICON_PATH_PREFIX + barProgress + ".png";
  1314. const state = getButtonState("progress");
  1315. state.setTitle = { title: tooltipMessage + (progress * 5) + "%" };
  1316. state.setIcon = { path };
  1317. refresh(tabId, state);
  1318. }
  1319. async function refreshTab$2(tab) {
  1320. const autoSave = await autoSaveIsEnabled(tab);
  1321. const state = getButtonState("default", autoSave);
  1322. await refresh(tab.id, state);
  1323. }
  1324. async function refresh(tabId, state) {
  1325. try {
  1326. const allTabsData = getTemporary(tabId);
  1327. if (state) {
  1328. if (!allTabsData[tabId].button) {
  1329. allTabsData[tabId].button = { lastState: null };
  1330. }
  1331. const lastState = allTabsData[tabId].button.lastState || {};
  1332. const newState = {};
  1333. Object.keys(state).forEach(property => {
  1334. if (state[property] !== undefined && (JSON.stringify(lastState[property]) != JSON.stringify(state[property]))) {
  1335. newState[property] = state[property];
  1336. }
  1337. });
  1338. if (Object.keys(newState).length) {
  1339. allTabsData[tabId].button.lastState = state;
  1340. await refreshAsync(tabId, newState);
  1341. }
  1342. }
  1343. } catch (error) {
  1344. // ignored
  1345. }
  1346. }
  1347. async function refreshAsync(tabId, state) {
  1348. for (const browserActionMethod of Object.keys(state)) {
  1349. await refreshProperty(tabId, browserActionMethod, state[browserActionMethod]);
  1350. }
  1351. }
  1352. async function refreshProperty(tabId, browserActionMethod, browserActionParameter) {
  1353. const actionMethodSupported = browserActionMethod != "setBadgeBackgroundColor" || BADGE_COLOR_SUPPORTED;
  1354. if (browser.browserAction[browserActionMethod] && actionMethodSupported) {
  1355. const parameter = JSON.parse(JSON.stringify(browserActionParameter));
  1356. parameter.tabId = tabId;
  1357. await browser.browserAction[browserActionMethod](parameter);
  1358. }
  1359. }
  1360. function getButtonState(name, autoSave) {
  1361. return JSON.parse(JSON.stringify(autoSave ? BUTTON_STATES.autosave[name] : BUTTON_STATES[name]));
  1362. }
  1363. /*
  1364. * Copyright 2010-2020 Gildas Lormeau
  1365. * contact : gildas.lormeau <at> gmail.com
  1366. *
  1367. * This file is part of SingleFile.
  1368. *
  1369. * The code in this file is free software: you can redistribute it and/or
  1370. * modify it under the terms of the GNU Affero General Public License
  1371. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1372. * of the License, or (at your option) any later version.
  1373. *
  1374. * The code in this file is distributed in the hope that it will be useful,
  1375. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1376. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1377. * General Public License for more details.
  1378. *
  1379. * As additional permission under GNU AGPL version 3 section 7, you may
  1380. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1381. * AGPL normally required by section 4, provided you include this license
  1382. * notice and a URL through which recipients can access the Corresponding
  1383. * Source.
  1384. */
  1385. const menus = browser.menus;
  1386. const BROWSER_MENUS_API_SUPPORTED = menus && menus.onClicked && menus.create && menus.update && menus.removeAll;
  1387. const MENU_ID_SAVE_PAGE = "save-page";
  1388. const MENU_ID_EDIT_AND_SAVE_PAGE = "edit-and-save-page";
  1389. const MENU_ID_SAVE_WITH_PROFILE = "save-with-profile";
  1390. const MENU_ID_SAVE_SELECTED_LINKS = "save-selected-links";
  1391. const MENU_ID_VIEW_PENDINGS = "view-pendings";
  1392. const MENU_ID_SELECT_PROFILE = "select-profile";
  1393. const MENU_ID_SAVE_WITH_PROFILE_PREFIX = "wasve-with-profile-";
  1394. const MENU_ID_SELECT_PROFILE_PREFIX = "select-profile-";
  1395. const MENU_ID_ASSOCIATE_WITH_PROFILE = "associate-with-profile";
  1396. const MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX = "associate-with-profile-";
  1397. const MENU_ID_SAVE_SELECTED = "save-selected";
  1398. const MENU_ID_SAVE_FRAME = "save-frame";
  1399. const MENU_ID_SAVE_TABS = "save-tabs";
  1400. const MENU_ID_SAVE_SELECTED_TABS = "save-selected-tabs";
  1401. const MENU_ID_SAVE_UNPINNED_TABS = "save-unpinned-tabs";
  1402. const MENU_ID_SAVE_ALL_TABS = "save-all-tabs";
  1403. const MENU_ID_BATCH_SAVE_URLS = "batch-save-urls";
  1404. const MENU_ID_BUTTON_SAVE_SELECTED_TABS = "button-" + MENU_ID_SAVE_SELECTED_TABS;
  1405. const MENU_ID_BUTTON_SAVE_UNPINNED_TABS = "button-" + MENU_ID_SAVE_UNPINNED_TABS;
  1406. const MENU_ID_BUTTON_SAVE_ALL_TABS = "button-" + MENU_ID_SAVE_ALL_TABS;
  1407. const MENU_ID_AUTO_SAVE = "auto-save";
  1408. const MENU_ID_AUTO_SAVE_DISABLED = "auto-save-disabled";
  1409. const MENU_ID_AUTO_SAVE_TAB = "auto-save-tab";
  1410. const MENU_ID_AUTO_SAVE_UNPINNED = "auto-save-unpinned";
  1411. const MENU_ID_AUTO_SAVE_ALL = "auto-save-all";
  1412. const MENU_CREATE_DOMAIN_RULE_MESSAGE = browser.i18n.getMessage("menuCreateDomainRule");
  1413. const MENU_UPDATE_RULE_MESSAGE = browser.i18n.getMessage("menuUpdateRule");
  1414. const MENU_SAVE_PAGE_MESSAGE = browser.i18n.getMessage("menuSavePage");
  1415. const MENU_SAVE_WITH_PROFILE = browser.i18n.getMessage("menuSaveWithProfile");
  1416. const MENU_SAVE_SELECTED_LINKS = browser.i18n.getMessage("menuSaveSelectedLinks");
  1417. const MENU_EDIT_PAGE_MESSAGE = browser.i18n.getMessage("menuEditPage");
  1418. const MENU_EDIT_AND_SAVE_PAGE_MESSAGE = browser.i18n.getMessage("menuEditAndSavePage");
  1419. const MENU_VIEW_PENDINGS_MESSAGE = browser.i18n.getMessage("menuViewPendingSaves");
  1420. const MENU_SAVE_SELECTION_MESSAGE = browser.i18n.getMessage("menuSaveSelection");
  1421. const MENU_SAVE_FRAME_MESSAGE = browser.i18n.getMessage("menuSaveFrame");
  1422. const MENU_SAVE_TABS_MESSAGE = browser.i18n.getMessage("menuSaveTabs");
  1423. const MENU_SAVE_SELECTED_TABS_MESSAGE = browser.i18n.getMessage("menuSaveSelectedTabs");
  1424. const MENU_SAVE_UNPINNED_TABS_MESSAGE = browser.i18n.getMessage("menuSaveUnpinnedTabs");
  1425. const MENU_SAVE_ALL_TABS_MESSAGE = browser.i18n.getMessage("menuSaveAllTabs");
  1426. const MENU_BATCH_SAVE_URLS_MESSAGE = browser.i18n.getMessage("menuBatchSaveUrls");
  1427. const MENU_SELECT_PROFILE_MESSAGE = browser.i18n.getMessage("menuSelectProfile");
  1428. const PROFILE_DEFAULT_SETTINGS_MESSAGE = browser.i18n.getMessage("profileDefaultSettings");
  1429. const MENU_AUTOSAVE_MESSAGE = browser.i18n.getMessage("menuAutoSave");
  1430. const MENU_AUTOSAVE_DISABLED_MESSAGE = browser.i18n.getMessage("menuAutoSaveDisabled");
  1431. const MENU_AUTOSAVE_TAB_MESSAGE = browser.i18n.getMessage("menuAutoSaveTab");
  1432. const MENU_AUTOSAVE_UNPINNED_TABS_MESSAGE = browser.i18n.getMessage("menuAutoSaveUnpinnedTabs");
  1433. const MENU_AUTOSAVE_ALL_TABS_MESSAGE = browser.i18n.getMessage("menuAutoSaveAllTabs");
  1434. const MENU_TOP_VISIBLE_ENTRIES = [
  1435. MENU_ID_EDIT_AND_SAVE_PAGE,
  1436. MENU_ID_SAVE_SELECTED_LINKS,
  1437. MENU_ID_SAVE_SELECTED,
  1438. MENU_ID_SAVE_FRAME,
  1439. MENU_ID_AUTO_SAVE,
  1440. MENU_ID_ASSOCIATE_WITH_PROFILE
  1441. ];
  1442. const menusCheckedState = new Map();
  1443. const menusTitleState = new Map();
  1444. let contextMenuVisibleState = true;
  1445. let allMenuVisibleState = true;
  1446. let profileIndexes = new Map();
  1447. let menusCreated, pendingRefresh, business$1;
  1448. Promise.resolve().then(initialize);
  1449. function init$2(businessApi) {
  1450. business$1 = businessApi;
  1451. }
  1452. function onMessage$8(message) {
  1453. if (message.method.endsWith("refreshMenu")) {
  1454. createMenus();
  1455. return Promise.resolve({});
  1456. }
  1457. }
  1458. async function createMenus(tab) {
  1459. const [profiles, allTabsData] = await Promise.all([getProfiles(), getPersistent()]);
  1460. const options = await getOptions(tab && tab.url);
  1461. if (BROWSER_MENUS_API_SUPPORTED && options) {
  1462. const pageContextsEnabled = ["page", "frame", "image", "link", "video", "audio", "selection"];
  1463. const defaultContextsDisabled = [];
  1464. if (options.browserActionMenuEnabled) {
  1465. defaultContextsDisabled.push("browser_action");
  1466. }
  1467. if (options.tabMenuEnabled) {
  1468. try {
  1469. await menus.create({
  1470. id: "temporary-id",
  1471. contexts: ["tab"],
  1472. title: "title"
  1473. });
  1474. defaultContextsDisabled.push("tab");
  1475. } catch (error) {
  1476. options.tabMenuEnabled = false;
  1477. }
  1478. }
  1479. await menus.removeAll();
  1480. const defaultContextsEnabled = defaultContextsDisabled.concat(...pageContextsEnabled);
  1481. const defaultContexts = options.contextMenuEnabled ? defaultContextsEnabled : defaultContextsDisabled;
  1482. menus.create({
  1483. id: MENU_ID_SAVE_PAGE,
  1484. contexts: defaultContexts,
  1485. title: MENU_SAVE_PAGE_MESSAGE
  1486. });
  1487. menus.create({
  1488. id: MENU_ID_EDIT_AND_SAVE_PAGE,
  1489. contexts: defaultContexts,
  1490. title: MENU_EDIT_AND_SAVE_PAGE_MESSAGE
  1491. });
  1492. menus.create({
  1493. id: MENU_ID_SAVE_SELECTED_LINKS,
  1494. contexts: options.contextMenuEnabled ? defaultContextsDisabled.concat(["selection"]) : defaultContextsDisabled,
  1495. title: MENU_SAVE_SELECTED_LINKS
  1496. });
  1497. if (Object.keys(profiles).length > 1) {
  1498. menus.create({
  1499. id: MENU_ID_SAVE_WITH_PROFILE,
  1500. contexts: defaultContexts,
  1501. title: MENU_SAVE_WITH_PROFILE
  1502. });
  1503. }
  1504. if (options.contextMenuEnabled) {
  1505. menus.create({
  1506. id: "separator-1",
  1507. contexts: pageContextsEnabled,
  1508. type: "separator"
  1509. });
  1510. }
  1511. menus.create({
  1512. id: MENU_ID_SAVE_SELECTED,
  1513. contexts: defaultContexts,
  1514. title: MENU_SAVE_SELECTION_MESSAGE
  1515. });
  1516. if (options.contextMenuEnabled) {
  1517. menus.create({
  1518. id: MENU_ID_SAVE_FRAME,
  1519. contexts: ["frame"],
  1520. title: MENU_SAVE_FRAME_MESSAGE
  1521. });
  1522. }
  1523. menus.create({
  1524. id: MENU_ID_SAVE_TABS,
  1525. contexts: defaultContextsDisabled,
  1526. title: MENU_SAVE_TABS_MESSAGE
  1527. });
  1528. menus.create({
  1529. id: MENU_ID_BUTTON_SAVE_SELECTED_TABS,
  1530. contexts: defaultContextsDisabled,
  1531. title: MENU_SAVE_SELECTED_TABS_MESSAGE,
  1532. parentId: MENU_ID_SAVE_TABS
  1533. });
  1534. menus.create({
  1535. id: MENU_ID_BUTTON_SAVE_UNPINNED_TABS,
  1536. contexts: defaultContextsDisabled,
  1537. title: MENU_SAVE_UNPINNED_TABS_MESSAGE,
  1538. parentId: MENU_ID_SAVE_TABS
  1539. });
  1540. menus.create({
  1541. id: MENU_ID_BUTTON_SAVE_ALL_TABS,
  1542. contexts: defaultContextsDisabled,
  1543. title: MENU_SAVE_ALL_TABS_MESSAGE,
  1544. parentId: MENU_ID_SAVE_TABS
  1545. });
  1546. if (options.contextMenuEnabled) {
  1547. if (SELECTABLE_TABS_SUPPORTED) {
  1548. menus.create({
  1549. id: MENU_ID_SAVE_SELECTED_TABS,
  1550. contexts: pageContextsEnabled,
  1551. title: MENU_SAVE_SELECTED_TABS_MESSAGE
  1552. });
  1553. }
  1554. menus.create({
  1555. id: MENU_ID_SAVE_UNPINNED_TABS,
  1556. contexts: pageContextsEnabled,
  1557. title: MENU_SAVE_UNPINNED_TABS_MESSAGE
  1558. });
  1559. menus.create({
  1560. id: MENU_ID_SAVE_ALL_TABS,
  1561. contexts: pageContextsEnabled,
  1562. title: MENU_SAVE_ALL_TABS_MESSAGE
  1563. });
  1564. menus.create({
  1565. id: "separator-2",
  1566. contexts: pageContextsEnabled,
  1567. type: "separator"
  1568. });
  1569. }
  1570. if (Object.keys(profiles).length > 1) {
  1571. menus.create({
  1572. id: MENU_ID_SELECT_PROFILE,
  1573. title: MENU_SELECT_PROFILE_MESSAGE,
  1574. contexts: defaultContexts,
  1575. });
  1576. menus.create({
  1577. id: MENU_ID_SAVE_WITH_PROFILE_PREFIX + "default",
  1578. contexts: defaultContexts,
  1579. title: PROFILE_DEFAULT_SETTINGS_MESSAGE,
  1580. parentId: MENU_ID_SAVE_WITH_PROFILE
  1581. });
  1582. const defaultProfileId = MENU_ID_SELECT_PROFILE_PREFIX + "default";
  1583. const defaultProfileChecked = !allTabsData.profileName || allTabsData.profileName == DEFAULT_PROFILE_NAME;
  1584. menus.create({
  1585. id: defaultProfileId,
  1586. type: "radio",
  1587. contexts: defaultContexts,
  1588. title: PROFILE_DEFAULT_SETTINGS_MESSAGE,
  1589. checked: defaultProfileChecked,
  1590. parentId: MENU_ID_SELECT_PROFILE
  1591. });
  1592. menusCheckedState.set(defaultProfileId, defaultProfileChecked);
  1593. menus.create({
  1594. id: MENU_ID_ASSOCIATE_WITH_PROFILE,
  1595. title: MENU_CREATE_DOMAIN_RULE_MESSAGE,
  1596. contexts: defaultContexts,
  1597. });
  1598. menusTitleState.set(MENU_ID_ASSOCIATE_WITH_PROFILE, MENU_CREATE_DOMAIN_RULE_MESSAGE);
  1599. let rule;
  1600. if (tab && tab.url) {
  1601. rule = await getRule(tab.url, true);
  1602. }
  1603. const currentProfileId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "current";
  1604. const currentProfileChecked = !rule || (rule.profile == CURRENT_PROFILE_NAME);
  1605. menus.create({
  1606. id: currentProfileId,
  1607. type: "radio",
  1608. contexts: defaultContexts,
  1609. title: CURRENT_PROFILE_NAME,
  1610. checked: currentProfileChecked,
  1611. parentId: MENU_ID_ASSOCIATE_WITH_PROFILE
  1612. });
  1613. menusCheckedState.set(currentProfileId, currentProfileChecked);
  1614. const associatedDefaultProfileId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default";
  1615. const associatedDefaultProfileChecked = Boolean(rule) && (rule.profile == DEFAULT_PROFILE_NAME);
  1616. menus.create({
  1617. id: associatedDefaultProfileId,
  1618. type: "radio",
  1619. contexts: defaultContexts,
  1620. title: PROFILE_DEFAULT_SETTINGS_MESSAGE,
  1621. checked: associatedDefaultProfileChecked,
  1622. parentId: MENU_ID_ASSOCIATE_WITH_PROFILE
  1623. });
  1624. menusCheckedState.set(associatedDefaultProfileId, associatedDefaultProfileChecked);
  1625. profileIndexes = new Map();
  1626. Object.keys(profiles).forEach((profileName, profileIndex) => {
  1627. if (profileName != DEFAULT_PROFILE_NAME) {
  1628. let profileId = MENU_ID_SAVE_WITH_PROFILE_PREFIX + profileIndex;
  1629. menus.create({
  1630. id: profileId,
  1631. contexts: defaultContexts,
  1632. title: profileName,
  1633. parentId: MENU_ID_SAVE_WITH_PROFILE
  1634. });
  1635. profileId = MENU_ID_SELECT_PROFILE_PREFIX + profileIndex;
  1636. let profileChecked = allTabsData.profileName == profileName;
  1637. menus.create({
  1638. id: profileId,
  1639. type: "radio",
  1640. contexts: defaultContexts,
  1641. title: profileName,
  1642. checked: profileChecked,
  1643. parentId: MENU_ID_SELECT_PROFILE
  1644. });
  1645. menusCheckedState.set(profileId, profileChecked);
  1646. profileId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + profileIndex;
  1647. profileChecked = Boolean(rule) && rule.profile == profileName;
  1648. menus.create({
  1649. id: profileId,
  1650. type: "radio",
  1651. contexts: defaultContexts,
  1652. title: profileName,
  1653. checked: profileChecked,
  1654. parentId: MENU_ID_ASSOCIATE_WITH_PROFILE
  1655. });
  1656. menusCheckedState.set(profileId, profileChecked);
  1657. profileIndexes.set(profileName, profileIndex);
  1658. }
  1659. });
  1660. if (options.contextMenuEnabled) {
  1661. menus.create({
  1662. id: "separator-3",
  1663. contexts: pageContextsEnabled,
  1664. type: "separator"
  1665. });
  1666. }
  1667. }
  1668. if (AUTO_SAVE_SUPPORTED) {
  1669. menus.create({
  1670. id: MENU_ID_AUTO_SAVE,
  1671. contexts: defaultContexts,
  1672. title: MENU_AUTOSAVE_MESSAGE
  1673. });
  1674. menus.create({
  1675. id: MENU_ID_AUTO_SAVE_DISABLED,
  1676. type: "radio",
  1677. title: MENU_AUTOSAVE_DISABLED_MESSAGE,
  1678. contexts: defaultContexts,
  1679. checked: true,
  1680. parentId: MENU_ID_AUTO_SAVE
  1681. });
  1682. menusCheckedState.set(MENU_ID_AUTO_SAVE_DISABLED, true);
  1683. menus.create({
  1684. id: MENU_ID_AUTO_SAVE_TAB,
  1685. type: "radio",
  1686. title: MENU_AUTOSAVE_TAB_MESSAGE,
  1687. contexts: defaultContexts,
  1688. checked: false,
  1689. parentId: MENU_ID_AUTO_SAVE
  1690. });
  1691. menusCheckedState.set(MENU_ID_AUTO_SAVE_TAB, false);
  1692. menus.create({
  1693. id: MENU_ID_AUTO_SAVE_UNPINNED,
  1694. type: "radio",
  1695. title: MENU_AUTOSAVE_UNPINNED_TABS_MESSAGE,
  1696. contexts: defaultContexts,
  1697. checked: false,
  1698. parentId: MENU_ID_AUTO_SAVE
  1699. });
  1700. menusCheckedState.set(MENU_ID_AUTO_SAVE_UNPINNED, false);
  1701. menus.create({
  1702. id: MENU_ID_AUTO_SAVE_ALL,
  1703. type: "radio",
  1704. title: MENU_AUTOSAVE_ALL_TABS_MESSAGE,
  1705. contexts: defaultContexts,
  1706. checked: false,
  1707. parentId: MENU_ID_AUTO_SAVE
  1708. });
  1709. menusCheckedState.set(MENU_ID_AUTO_SAVE_ALL, false);
  1710. menus.create({
  1711. id: "separator-4",
  1712. contexts: defaultContexts,
  1713. type: "separator"
  1714. });
  1715. }
  1716. menus.create({
  1717. id: MENU_ID_BATCH_SAVE_URLS,
  1718. contexts: defaultContexts,
  1719. title: MENU_BATCH_SAVE_URLS_MESSAGE
  1720. });
  1721. menus.create({
  1722. id: MENU_ID_VIEW_PENDINGS,
  1723. contexts: defaultContexts,
  1724. title: MENU_VIEW_PENDINGS_MESSAGE
  1725. });
  1726. }
  1727. menusCreated = true;
  1728. if (pendingRefresh) {
  1729. pendingRefresh = false;
  1730. (await browser.tabs.query({})).forEach(async tab => await refreshTab$1(tab));
  1731. }
  1732. }
  1733. async function initialize() {
  1734. if (BROWSER_MENUS_API_SUPPORTED) {
  1735. createMenus();
  1736. menus.onClicked.addListener(async (event, tab) => {
  1737. if (event.menuItemId == MENU_ID_SAVE_PAGE) {
  1738. if (event.linkUrl) {
  1739. business$1.saveUrls([event.linkUrl]);
  1740. } else {
  1741. business$1.saveTabs([tab]);
  1742. }
  1743. }
  1744. if (event.menuItemId == MENU_ID_EDIT_AND_SAVE_PAGE) {
  1745. const allTabsData = await getPersistent(tab.id);
  1746. if (allTabsData[tab.id].savedPageDetected) {
  1747. business$1.openEditor(tab);
  1748. } else {
  1749. if (event.linkUrl) {
  1750. business$1.saveUrls([event.linkUrl], { openEditor: true });
  1751. } else {
  1752. business$1.saveTabs([tab], { openEditor: true });
  1753. }
  1754. }
  1755. }
  1756. if (event.menuItemId == MENU_ID_SAVE_SELECTED_LINKS) {
  1757. business$1.saveSelectedLinks(tab);
  1758. }
  1759. if (event.menuItemId == MENU_ID_VIEW_PENDINGS) {
  1760. await browser.tabs.create({ active: true, url: "/src/ui/pages/pendings.html" });
  1761. }
  1762. if (event.menuItemId == MENU_ID_SAVE_SELECTED) {
  1763. business$1.saveTabs([tab], { selected: true });
  1764. }
  1765. if (event.menuItemId == MENU_ID_SAVE_FRAME) {
  1766. business$1.saveTabs([tab], { frameId: event.frameId });
  1767. }
  1768. if (event.menuItemId == MENU_ID_SAVE_SELECTED_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_SELECTED_TABS) {
  1769. const tabs = await queryTabs$1({ currentWindow: true, highlighted: true });
  1770. business$1.saveTabs(tabs);
  1771. }
  1772. if (event.menuItemId == MENU_ID_SAVE_UNPINNED_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_UNPINNED_TABS) {
  1773. const tabs = await queryTabs$1({ currentWindow: true, pinned: false });
  1774. business$1.saveTabs(tabs);
  1775. }
  1776. if (event.menuItemId == MENU_ID_SAVE_ALL_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_ALL_TABS) {
  1777. const tabs = await queryTabs$1({ currentWindow: true });
  1778. business$1.saveTabs(tabs);
  1779. }
  1780. if (event.menuItemId == MENU_ID_BATCH_SAVE_URLS) {
  1781. business$1.batchSaveUrls();
  1782. }
  1783. if (event.menuItemId == MENU_ID_AUTO_SAVE_TAB) {
  1784. const allTabsData = await getPersistent(tab.id);
  1785. allTabsData[tab.id].autoSave = true;
  1786. await setPersistent(allTabsData);
  1787. refreshExternalComponents(tab);
  1788. }
  1789. if (event.menuItemId == MENU_ID_AUTO_SAVE_DISABLED) {
  1790. const allTabsData = await getPersistent();
  1791. Object.keys(allTabsData).forEach(tabId => {
  1792. if (typeof allTabsData[tabId] == "object" && allTabsData[tabId].autoSave) {
  1793. allTabsData[tabId].autoSave = false;
  1794. }
  1795. });
  1796. allTabsData.autoSaveUnpinned = allTabsData.autoSaveAll = false;
  1797. await setPersistent(allTabsData);
  1798. refreshExternalComponents(tab);
  1799. }
  1800. if (event.menuItemId == MENU_ID_AUTO_SAVE_ALL) {
  1801. const allTabsData = await getPersistent();
  1802. allTabsData.autoSaveAll = event.checked;
  1803. await setPersistent(allTabsData);
  1804. refreshExternalComponents(tab);
  1805. }
  1806. if (event.menuItemId == MENU_ID_AUTO_SAVE_UNPINNED) {
  1807. const allTabsData = await getPersistent();
  1808. allTabsData.autoSaveUnpinned = event.checked;
  1809. await setPersistent(allTabsData);
  1810. refreshExternalComponents(tab);
  1811. }
  1812. if (event.menuItemId.startsWith(MENU_ID_SAVE_WITH_PROFILE_PREFIX)) {
  1813. const profiles = await getProfiles();
  1814. const profileId = event.menuItemId.split(MENU_ID_SAVE_WITH_PROFILE_PREFIX)[1];
  1815. let profileName;
  1816. if (profileId == "default") {
  1817. profileName = DEFAULT_PROFILE_NAME;
  1818. } else {
  1819. const profileIndex = Number(profileId);
  1820. profileName = Object.keys(profiles)[profileIndex];
  1821. }
  1822. profiles[profileName].profileName = profileName;
  1823. business$1.saveTabs([tab], profiles[profileName]);
  1824. }
  1825. if (event.menuItemId.startsWith(MENU_ID_SELECT_PROFILE_PREFIX)) {
  1826. const [profiles, allTabsData] = await Promise.all([getProfiles(), getPersistent()]);
  1827. const profileId = event.menuItemId.split(MENU_ID_SELECT_PROFILE_PREFIX)[1];
  1828. if (profileId == "default") {
  1829. allTabsData.profileName = DEFAULT_PROFILE_NAME;
  1830. } else {
  1831. const profileIndex = Number(profileId);
  1832. allTabsData.profileName = Object.keys(profiles)[profileIndex];
  1833. }
  1834. await setPersistent(allTabsData);
  1835. refreshExternalComponents(tab);
  1836. }
  1837. if (event.menuItemId.startsWith(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX)) {
  1838. const [profiles, rule] = await Promise.all([getProfiles(), getRule(tab.url, true)]);
  1839. const profileId = event.menuItemId.split(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX)[1];
  1840. let profileName;
  1841. if (profileId == "default") {
  1842. profileName = DEFAULT_PROFILE_NAME;
  1843. } else if (profileId == "current") {
  1844. profileName = CURRENT_PROFILE_NAME;
  1845. } else {
  1846. const profileIndex = Number(profileId);
  1847. profileName = Object.keys(profiles)[profileIndex];
  1848. }
  1849. if (rule) {
  1850. await updateRule(rule.url, rule.url, profileName, profileName);
  1851. } else {
  1852. await updateTitleValue(MENU_ID_ASSOCIATE_WITH_PROFILE, MENU_UPDATE_RULE_MESSAGE);
  1853. await addRule(new URL(tab.url).hostname, profileName, profileName);
  1854. }
  1855. }
  1856. });
  1857. if (menusCreated) {
  1858. pendingRefresh = true;
  1859. } else {
  1860. (await browser.tabs.query({})).forEach(async tab => await refreshTab$1(tab));
  1861. }
  1862. }
  1863. }
  1864. async function refreshExternalComponents(tab) {
  1865. const allTabsData = await getPersistent(tab.id);
  1866. await refreshAutoSaveTabs();
  1867. await refreshTab$2(tab);
  1868. try {
  1869. await browser.runtime.sendMessage({ method: "options.refresh", profileName: allTabsData.profileName });
  1870. } catch (error) {
  1871. // ignored
  1872. }
  1873. }
  1874. async function refreshTab$1(tab) {
  1875. if (BROWSER_MENUS_API_SUPPORTED && menusCreated) {
  1876. const promises = [];
  1877. const allTabsData = await getPersistent(tab.id);
  1878. if (allTabsData[tab.id].editorDetected) {
  1879. updateAllVisibleValues(false);
  1880. } else {
  1881. updateAllVisibleValues(true);
  1882. if (AUTO_SAVE_SUPPORTED) {
  1883. promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_DISABLED, !allTabsData[tab.id].autoSave));
  1884. promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_TAB, allTabsData[tab.id].autoSave));
  1885. promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_UNPINNED, Boolean(allTabsData.autoSaveUnpinned)));
  1886. promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_ALL, Boolean(allTabsData.autoSaveAll)));
  1887. }
  1888. if (tab && tab.url) {
  1889. const options = await getOptions(tab.url);
  1890. promises.push(updateVisibleValue(tab, options.contextMenuEnabled));
  1891. promises.push(updateTitleValue(MENU_ID_EDIT_AND_SAVE_PAGE, allTabsData[tab.id].savedPageDetected ? MENU_EDIT_PAGE_MESSAGE : MENU_EDIT_AND_SAVE_PAGE_MESSAGE));
  1892. if (SELECTABLE_TABS_SUPPORTED) {
  1893. promises.push(menus.update(MENU_ID_SAVE_SELECTED, { visible: !options.saveRawPage }));
  1894. }
  1895. promises.push(menus.update(MENU_ID_EDIT_AND_SAVE_PAGE, { visible: !options.openEditor || allTabsData[tab.id].savedPageDetected }));
  1896. let selectedEntryId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default";
  1897. let title = MENU_CREATE_DOMAIN_RULE_MESSAGE;
  1898. const [profiles, rule] = await Promise.all([getProfiles(), getRule(tab.url)]);
  1899. if (rule) {
  1900. const profileIndex = profileIndexes.get(rule.profile);
  1901. if (profileIndex) {
  1902. selectedEntryId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + profileIndex;
  1903. title = MENU_UPDATE_RULE_MESSAGE;
  1904. }
  1905. }
  1906. if (Object.keys(profiles).length > 1) {
  1907. Object.keys(profiles).forEach((profileName, profileIndex) => {
  1908. if (profileName == DEFAULT_PROFILE_NAME) {
  1909. promises.push(updateCheckedValue(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default", selectedEntryId == MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default"));
  1910. } else {
  1911. promises.push(updateCheckedValue(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + profileIndex, selectedEntryId == MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + profileIndex));
  1912. }
  1913. });
  1914. promises.push(updateTitleValue(MENU_ID_ASSOCIATE_WITH_PROFILE, title));
  1915. }
  1916. }
  1917. }
  1918. await Promise.all(promises);
  1919. }
  1920. }
  1921. async function updateAllVisibleValues(visible) {
  1922. const lastVisibleState = allMenuVisibleState;
  1923. allMenuVisibleState = visible;
  1924. if (lastVisibleState === undefined || lastVisibleState != visible) {
  1925. const promises = [];
  1926. try {
  1927. MENU_TOP_VISIBLE_ENTRIES.forEach(id => promises.push(menus.update(id, { visible })));
  1928. await Promise.all(promises);
  1929. } catch (error) {
  1930. // ignored
  1931. }
  1932. }
  1933. }
  1934. async function updateVisibleValue(tab, visible) {
  1935. const lastVisibleState = contextMenuVisibleState;
  1936. contextMenuVisibleState = visible;
  1937. if (lastVisibleState === undefined || lastVisibleState != visible) {
  1938. await createMenus(tab);
  1939. }
  1940. }
  1941. function updateTitleValue(id, title) {
  1942. const lastTitleValue = menusTitleState.get(id);
  1943. menusTitleState.set(id, title);
  1944. if (lastTitleValue === undefined) {
  1945. return menus.update(id, { title });
  1946. } else if (lastTitleValue != title) {
  1947. return menus.update(id, { title });
  1948. }
  1949. }
  1950. async function updateCheckedValue(id, checked) {
  1951. checked = Boolean(checked);
  1952. menusCheckedState.set(id, checked);
  1953. await menus.update(id, { checked });
  1954. }
  1955. /*
  1956. * Copyright 2010-2020 Gildas Lormeau
  1957. * contact : gildas.lormeau <at> gmail.com
  1958. *
  1959. * This file is part of SingleFile.
  1960. *
  1961. * The code in this file is free software: you can redistribute it and/or
  1962. * modify it under the terms of the GNU Affero General Public License
  1963. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1964. * of the License, or (at your option) any later version.
  1965. *
  1966. * The code in this file is distributed in the hope that it will be useful,
  1967. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1968. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1969. * General Public License for more details.
  1970. *
  1971. * As additional permission under GNU AGPL version 3 section 7, you may
  1972. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1973. * AGPL normally required by section 4, provided you include this license
  1974. * notice and a URL through which recipients can access the Corresponding
  1975. * Source.
  1976. */
  1977. const commands = browser.commands;
  1978. const BROWSER_COMMANDS_API_SUPPORTED = commands && commands.onCommand && commands.onCommand.addListener;
  1979. let business;
  1980. function init$1(businessApi) {
  1981. business = businessApi;
  1982. }
  1983. if (BROWSER_COMMANDS_API_SUPPORTED) {
  1984. commands.onCommand.addListener(async command => {
  1985. if (command == "save-selected-tabs") {
  1986. const highlightedTabs = await queryTabs$1({ currentWindow: true, highlighted: true });
  1987. business.saveTabs(highlightedTabs, { optionallySelected: true });
  1988. }
  1989. if (command == "save-all-tabs") {
  1990. const tabs = await queryTabs$1({ currentWindow: true });
  1991. business.saveTabs(tabs);
  1992. }
  1993. });
  1994. }
  1995. /*
  1996. * Copyright 2010-2020 Gildas Lormeau
  1997. * contact : gildas.lormeau <at> gmail.com
  1998. *
  1999. * This file is part of SingleFile.
  2000. *
  2001. * The code in this file is free software: you can redistribute it and/or
  2002. * modify it under the terms of the GNU Affero General Public License
  2003. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2004. * of the License, or (at your option) any later version.
  2005. *
  2006. * The code in this file is distributed in the hope that it will be useful,
  2007. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2008. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2009. * General Public License for more details.
  2010. *
  2011. * As additional permission under GNU AGPL version 3 section 7, you may
  2012. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2013. * AGPL normally required by section 4, provided you include this license
  2014. * notice and a URL through which recipients can access the Corresponding
  2015. * Source.
  2016. */
  2017. function init(businessApi) {
  2018. init$2(businessApi);
  2019. init$3(businessApi);
  2020. init$1(businessApi);
  2021. }
  2022. function onMessage$7(message, sender) {
  2023. if (message.method.endsWith(".refreshMenu")) {
  2024. return onMessage$8(message);
  2025. } else {
  2026. return onMessage$9(message, sender);
  2027. }
  2028. }
  2029. async function refreshTab(tab) {
  2030. return Promise.all([createMenus(tab), refreshTab$2(tab)]);
  2031. }
  2032. function onForbiddenDomain(tab) {
  2033. onForbiddenDomain$1(tab);
  2034. }
  2035. function onStart(tabId, step, autoSave) {
  2036. onStart$1(tabId, step, autoSave);
  2037. }
  2038. async function onError(tabId, message, link) {
  2039. onError$1(tabId);
  2040. try {
  2041. if (message) {
  2042. await browser.tabs.sendMessage(tabId, { method: "content.error", error: message.toString(), link });
  2043. }
  2044. } catch (error) {
  2045. // ignored
  2046. }
  2047. }
  2048. function onEdit(tabId) {
  2049. onEdit$1(tabId);
  2050. }
  2051. function onEnd(tabId, autoSave) {
  2052. onEnd$1(tabId, autoSave);
  2053. }
  2054. function onCancelled(tabId) {
  2055. onCancelled$1(tabId);
  2056. }
  2057. function onUploadProgress(tabId, index, maxIndex) {
  2058. onUploadProgress$1(tabId, index, maxIndex);
  2059. }
  2060. function onTabCreated$1(tab) {
  2061. refreshTab$1(tab);
  2062. }
  2063. function onTabActivated$1(tab) {
  2064. refreshTab$1(tab);
  2065. }
  2066. function onInit$3(tab) {
  2067. refreshTab$1(tab);
  2068. }
  2069. /*
  2070. * Copyright 2010-2020 Gildas Lormeau
  2071. * contact : gildas.lormeau <at> gmail.com
  2072. *
  2073. * This file is part of SingleFile.
  2074. *
  2075. * The code in this file is free software: you can redistribute it and/or
  2076. * modify it under the terms of the GNU Affero General Public License
  2077. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2078. * of the License, or (at your option) any later version.
  2079. *
  2080. * The code in this file is distributed in the hope that it will be useful,
  2081. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2082. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2083. * General Public License for more details.
  2084. *
  2085. * As additional permission under GNU AGPL version 3 section 7, you may
  2086. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2087. * AGPL normally required by section 4, provided you include this license
  2088. * notice and a URL through which recipients can access the Corresponding
  2089. * Source.
  2090. */
  2091. /* global browser, fetch, TextDecoder */
  2092. let contentScript, frameScript;
  2093. const contentScriptFiles = [
  2094. "lib/chrome-browser-polyfill.js",
  2095. "lib/single-file.js"
  2096. ];
  2097. const frameScriptFiles = [
  2098. "lib/chrome-browser-polyfill.js",
  2099. "lib/single-file-frames.js"
  2100. ];
  2101. const basePath = "../../../";
  2102. async function inject(tabId, options) {
  2103. await initScripts(options);
  2104. let scriptsInjected;
  2105. if (!options.removeFrames) {
  2106. try {
  2107. await browser.tabs.executeScript(tabId, { code: frameScript, allFrames: true, matchAboutBlank: true, runAt: "document_start" });
  2108. } catch (error) {
  2109. // ignored
  2110. }
  2111. }
  2112. try {
  2113. await browser.tabs.executeScript(tabId, { code: contentScript, allFrames: false, runAt: "document_idle" });
  2114. scriptsInjected = true;
  2115. } catch (error) {
  2116. // ignored
  2117. }
  2118. if (scriptsInjected) {
  2119. if (options.frameId) {
  2120. await browser.tabs.executeScript(tabId, { code: "document.documentElement.dataset.requestedFrameId = true", frameId: options.frameId, matchAboutBlank: true, runAt: "document_start" });
  2121. }
  2122. }
  2123. return scriptsInjected;
  2124. }
  2125. async function initScripts(options) {
  2126. const extensionScriptFiles = options.extensionScriptFiles || [];
  2127. if (!contentScript && !frameScript) {
  2128. [contentScript, frameScript] = await Promise.all([
  2129. getScript(contentScriptFiles.concat(extensionScriptFiles)),
  2130. getScript(frameScriptFiles)
  2131. ]);
  2132. }
  2133. }
  2134. async function getScript(scriptFiles) {
  2135. const scriptsPromises = scriptFiles.map(async scriptFile => {
  2136. if (typeof scriptFile == "function") {
  2137. return "(" + scriptFile.toString() + ")();";
  2138. } else {
  2139. const scriptResource = await fetch(browser.runtime.getURL(basePath + scriptFile));
  2140. return new TextDecoder().decode(await scriptResource.arrayBuffer());
  2141. }
  2142. });
  2143. let content = "";
  2144. for (const scriptPromise of scriptsPromises) {
  2145. content += await scriptPromise;
  2146. }
  2147. return content;
  2148. }
  2149. /*
  2150. * Copyright 2010-2020 Gildas Lormeau
  2151. * contact : gildas.lormeau <at> gmail.com
  2152. *
  2153. * This file is part of SingleFile.
  2154. *
  2155. * The code in this file is free software: you can redistribute it and/or
  2156. * modify it under the terms of the GNU Affero General Public License
  2157. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2158. * of the License, or (at your option) any later version.
  2159. *
  2160. * The code in this file is distributed in the hope that it will be useful,
  2161. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2162. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2163. * General Public License for more details.
  2164. *
  2165. * As additional permission under GNU AGPL version 3 section 7, you may
  2166. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2167. * AGPL normally required by section 4, provided you include this license
  2168. * notice and a URL through which recipients can access the Corresponding
  2169. * Source.
  2170. */
  2171. /* global browser, window, CustomEvent, setTimeout, clearTimeout */
  2172. const FETCH_REQUEST_EVENT = "single-file-request-fetch";
  2173. const FETCH_ACK_EVENT = "single-file-ack-fetch";
  2174. const FETCH_RESPONSE_EVENT = "single-file-response-fetch";
  2175. const ERR_HOST_FETCH = "Host fetch error (SingleFile)";
  2176. const HOST_FETCH_MAX_DELAY = 2500;
  2177. const USE_HOST_FETCH = Boolean(window.wrappedJSObject);
  2178. const addEventListener = (type, listener, options) => window.addEventListener(type, listener, options);
  2179. const dispatchEvent = event => window.dispatchEvent(event);
  2180. const removeEventListener = (type, listener, options) => window.removeEventListener(type, listener, options);
  2181. const fetch$2 = (url, options) => window.fetch(url, options);
  2182. let requestId = 0, pendingResponses = new Map();
  2183. browser.runtime.onMessage.addListener(message => {
  2184. if (message.method == "singlefile.fetchFrame" && window.frameId && window.frameId == message.frameId) {
  2185. return onFetchFrame(message);
  2186. }
  2187. if (message.method == "singlefile.fetchResponse") {
  2188. return onFetchResponse(message);
  2189. }
  2190. });
  2191. async function onFetchFrame(message) {
  2192. try {
  2193. const response = await fetch$2(message.url, { cache: "force-cache", headers: message.headers });
  2194. return {
  2195. status: response.status,
  2196. headers: [...response.headers],
  2197. array: Array.from(new Uint8Array(await response.arrayBuffer()))
  2198. };
  2199. } catch (error) {
  2200. return {
  2201. error: error && error.toString()
  2202. };
  2203. }
  2204. }
  2205. async function onFetchResponse(message) {
  2206. const pendingResponse = pendingResponses.get(message.requestId);
  2207. if (pendingResponse) {
  2208. if (message.error) {
  2209. pendingResponse.reject(new Error(message.error));
  2210. pendingResponses.delete(message.requestId);
  2211. } else {
  2212. if (message.truncated) {
  2213. if (pendingResponse.array) {
  2214. pendingResponse.array = pendingResponse.array.concat(message.array);
  2215. } else {
  2216. pendingResponse.array = message.array;
  2217. pendingResponses.set(message.requestId, pendingResponse);
  2218. }
  2219. if (message.finished) {
  2220. message.array = pendingResponse.array;
  2221. }
  2222. }
  2223. if (!message.truncated || message.finished) {
  2224. pendingResponse.resolve({
  2225. status: message.status,
  2226. headers: { get: headerName => message.headers && message.headers[headerName] },
  2227. arrayBuffer: async () => new Uint8Array(message.array).buffer
  2228. });
  2229. pendingResponses.delete(message.requestId);
  2230. }
  2231. }
  2232. }
  2233. return {};
  2234. }
  2235. async function hostFetch(url, options) {
  2236. const result = new Promise((resolve, reject) => {
  2237. dispatchEvent(new CustomEvent(FETCH_REQUEST_EVENT, { detail: JSON.stringify({ url, options }) }));
  2238. addEventListener(FETCH_ACK_EVENT, onAckFetch, false);
  2239. addEventListener(FETCH_RESPONSE_EVENT, onResponseFetch, false);
  2240. const timeout = setTimeout(() => {
  2241. removeListeners();
  2242. reject(new Error(ERR_HOST_FETCH));
  2243. }, HOST_FETCH_MAX_DELAY);
  2244. function onResponseFetch(event) {
  2245. if (event.detail) {
  2246. if (event.detail.url == url) {
  2247. removeListeners();
  2248. if (event.detail.response) {
  2249. resolve({
  2250. status: event.detail.status,
  2251. headers: new Map(event.detail.headers),
  2252. arrayBuffer: async () => event.detail.response
  2253. });
  2254. } else {
  2255. reject(event.detail.error);
  2256. }
  2257. }
  2258. } else {
  2259. reject();
  2260. }
  2261. }
  2262. function onAckFetch() {
  2263. clearTimeout(timeout);
  2264. }
  2265. function removeListeners() {
  2266. removeEventListener(FETCH_RESPONSE_EVENT, onResponseFetch, false);
  2267. removeEventListener(FETCH_ACK_EVENT, onAckFetch, false);
  2268. }
  2269. });
  2270. try {
  2271. return await result;
  2272. } catch (error) {
  2273. if (error && error.message == ERR_HOST_FETCH) {
  2274. return fetch$2(url, options);
  2275. } else {
  2276. throw error;
  2277. }
  2278. }
  2279. }
  2280. async function fetchResource(url, options = {}) {
  2281. try {
  2282. const fetchOptions = { cache: "force-cache", headers: options.headers };
  2283. return await (options.referrer && USE_HOST_FETCH ? hostFetch(url, fetchOptions) : fetch$2(url, fetchOptions));
  2284. }
  2285. catch (error) {
  2286. requestId++;
  2287. const promise = new Promise((resolve, reject) => pendingResponses.set(requestId, { resolve, reject }));
  2288. await sendMessage({ method: "singlefile.fetch", url, requestId, referrer: options.referrer, headers: options.headers });
  2289. return promise;
  2290. }
  2291. }
  2292. async function frameFetch(url, options) {
  2293. const response = await sendMessage({ method: "singlefile.fetchFrame", url, frameId: options.frameId, referrer: options.referrer, headers: options.headers });
  2294. return {
  2295. status: response.status,
  2296. headers: new Map(response.headers),
  2297. arrayBuffer: async () => new Uint8Array(response.array).buffer
  2298. };
  2299. }
  2300. async function sendMessage(message) {
  2301. const response = await browser.runtime.sendMessage(message);
  2302. if (!response || response.error) {
  2303. throw new Error(response && response.error && response.error.toString());
  2304. } else {
  2305. return response;
  2306. }
  2307. }
  2308. /*
  2309. * Copyright 2010-2020 Gildas Lormeau
  2310. * contact : gildas.lormeau <at> gmail.com
  2311. *
  2312. * This file is part of SingleFile.
  2313. *
  2314. * The code in this file is free software: you can redistribute it and/or
  2315. * modify it under the terms of the GNU Affero General Public License
  2316. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2317. * of the License, or (at your option) any later version.
  2318. *
  2319. * The code in this file is distributed in the hope that it will be useful,
  2320. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2321. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2322. * General Public License for more details.
  2323. *
  2324. * As additional permission under GNU AGPL version 3 section 7, you may
  2325. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2326. * AGPL normally required by section 4, provided you include this license
  2327. * notice and a URL through which recipients can access the Corresponding
  2328. * Source.
  2329. */
  2330. function injectScript(tabId, options) {
  2331. return inject(tabId, options);
  2332. }
  2333. function getPageData(options, doc, win, initOptions = { fetch: fetchResource, frameFetch }) {
  2334. return globalThis.singlefile.getPageData(options, initOptions, doc, win);
  2335. }
  2336. /*
  2337. * Copyright 2010-2020 Gildas Lormeau
  2338. * contact : gildas.lormeau <at> gmail.com
  2339. *
  2340. * This file is part of SingleFile.
  2341. *
  2342. * The code in this file is free software: you can redistribute it and/or
  2343. * modify it under the terms of the GNU Affero General Public License
  2344. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2345. * of the License, or (at your option) any later version.
  2346. *
  2347. * The code in this file is distributed in the hope that it will be useful,
  2348. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2349. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2350. * General Public License for more details.
  2351. *
  2352. * As additional permission under GNU AGPL version 3 section 7, you may
  2353. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2354. * AGPL normally required by section 4, provided you include this license
  2355. * notice and a URL through which recipients can access the Corresponding
  2356. * Source.
  2357. */
  2358. const ERROR_CONNECTION_ERROR_CHROMIUM = "Could not establish connection. Receiving end does not exist.";
  2359. const ERROR_CONNECTION_LOST_CHROMIUM = "The message port closed before a response was received.";
  2360. const ERROR_CONNECTION_LOST_GECKO = "Message manager disconnected";
  2361. const ERROR_EDITOR_PAGE_CHROMIUM = "Cannot access contents of url ";
  2362. const INJECT_SCRIPTS_STEP = 1;
  2363. const EXECUTE_SCRIPTS_STEP = 2;
  2364. const TASK_PENDING_STATE = "pending";
  2365. const TASK_PROCESSING_STATE = "processing";
  2366. const extensionScriptFiles = [
  2367. "lib/single-file-extension.js"
  2368. ];
  2369. const tasks = [];
  2370. let currentTaskId = 0, maxParallelWorkers, processInForeground;
  2371. init({ isSavingTab, saveTabs, saveUrls, cancelTab, openEditor, saveSelectedLinks, batchSaveUrls });
  2372. async function saveSelectedLinks(tab) {
  2373. const tabOptions = { extensionScriptFiles, tabId: tab.id, tabIndex: tab.index };
  2374. const scriptsInjected = await injectScript(tab.id, tabOptions);
  2375. if (scriptsInjected) {
  2376. const response = await browser.tabs.sendMessage(tab.id, { method: "content.getSelectedLinks" });
  2377. if (response.urls && response.urls.length) {
  2378. const tab = await batchSaveUrls();
  2379. const onTabUpdated = (tabId, changeInfo) => {
  2380. if (changeInfo.status == "complete" && tabId == tab.id) {
  2381. browser.tabs.onUpdated.removeListener(onTabUpdated);
  2382. browser.tabs.sendMessage(tab.id, { method: "newUrls.addURLs", urls: response.urls });
  2383. }
  2384. };
  2385. browser.tabs.onUpdated.addListener(onTabUpdated);
  2386. }
  2387. } else {
  2388. onForbiddenDomain(tab);
  2389. }
  2390. }
  2391. async function batchSaveUrls() {
  2392. return browser.tabs.create({ active: true, url: "/src/ui/pages/batch-save-urls.html" });
  2393. }
  2394. async function saveUrls(urls, options = {}) {
  2395. await initMaxParallelWorkers();
  2396. await Promise.all(urls.map(async url => {
  2397. const tabOptions = await getOptions(url);
  2398. Object.keys(options).forEach(key => tabOptions[key] = options[key]);
  2399. tabOptions.autoClose = true;
  2400. tabOptions.extensionScriptFiles = extensionScriptFiles;
  2401. if (tabOptions.passReferrerOnError) {
  2402. enableReferrerOnError();
  2403. }
  2404. addTask({
  2405. tab: { url },
  2406. status: TASK_PENDING_STATE,
  2407. options: tabOptions,
  2408. method: "content.save"
  2409. });
  2410. }));
  2411. runTasks();
  2412. }
  2413. async function saveTabs(tabs, options = {}) {
  2414. await initMaxParallelWorkers();
  2415. await Promise.all(tabs.map(async tab => {
  2416. const tabId = tab.id;
  2417. const tabOptions = await getOptions(tab.url);
  2418. Object.keys(options).forEach(key => tabOptions[key] = options[key]);
  2419. tabOptions.tabId = tabId;
  2420. tabOptions.tabIndex = tab.index;
  2421. tabOptions.extensionScriptFiles = extensionScriptFiles;
  2422. if (tabOptions.passReferrerOnError) {
  2423. enableReferrerOnError();
  2424. }
  2425. const tabData = {
  2426. id: tab.id,
  2427. index: tab.index,
  2428. url: tab.url,
  2429. title: tab.title
  2430. };
  2431. if (options.autoSave) {
  2432. if (autoSaveIsEnabled(tab)) {
  2433. const taskInfo = addTask({
  2434. status: TASK_PROCESSING_STATE,
  2435. tab: tabData,
  2436. options: tabOptions,
  2437. method: "content.autosave"
  2438. });
  2439. runTask(taskInfo);
  2440. }
  2441. } else {
  2442. onStart(tabId, INJECT_SCRIPTS_STEP);
  2443. const scriptsInjected = await injectScript(tabId, tabOptions);
  2444. if (scriptsInjected || isEditor(tab)) {
  2445. onStart(tabId, EXECUTE_SCRIPTS_STEP);
  2446. addTask({
  2447. status: TASK_PENDING_STATE,
  2448. tab: tabData,
  2449. options: tabOptions,
  2450. method: "content.save"
  2451. });
  2452. } else {
  2453. onForbiddenDomain(tab);
  2454. }
  2455. }
  2456. }));
  2457. runTasks();
  2458. }
  2459. function addTask(info) {
  2460. const taskInfo = {
  2461. id: currentTaskId,
  2462. status: info.status,
  2463. tab: info.tab,
  2464. options: info.options,
  2465. method: info.method,
  2466. done: function () {
  2467. tasks.splice(tasks.findIndex(taskInfo => taskInfo.id == this.id), 1);
  2468. runTasks();
  2469. }
  2470. };
  2471. tasks.push(taskInfo);
  2472. currentTaskId++;
  2473. return taskInfo;
  2474. }
  2475. function openEditor(tab) {
  2476. browser.tabs.sendMessage(tab.id, { method: "content.openEditor" });
  2477. }
  2478. async function initMaxParallelWorkers() {
  2479. if (!maxParallelWorkers) {
  2480. const configData = await getConfig();
  2481. processInForeground = configData.processInForeground;
  2482. maxParallelWorkers = processInForeground ? 1 : configData.maxParallelWorkers;
  2483. }
  2484. }
  2485. function runTasks() {
  2486. const processingCount = tasks.filter(taskInfo => taskInfo.status == TASK_PROCESSING_STATE).length;
  2487. for (let index = 0; index < Math.min(tasks.length - processingCount, (maxParallelWorkers - processingCount)); index++) {
  2488. const taskInfo = tasks.find(taskInfo => taskInfo.status == TASK_PENDING_STATE);
  2489. if (taskInfo) {
  2490. runTask(taskInfo);
  2491. }
  2492. }
  2493. }
  2494. async function runTask(taskInfo) {
  2495. const taskId = taskInfo.id;
  2496. taskInfo.status = TASK_PROCESSING_STATE;
  2497. if (!taskInfo.tab.id) {
  2498. let scriptsInjected;
  2499. try {
  2500. const tab = await createTabAndWaitUntilComplete({ url: taskInfo.tab.url, active: false });
  2501. taskInfo.tab.id = taskInfo.options.tabId = tab.id;
  2502. taskInfo.tab.index = taskInfo.options.tabIndex = tab.index;
  2503. onStart(taskInfo.tab.id, INJECT_SCRIPTS_STEP);
  2504. scriptsInjected = await injectScript(taskInfo.tab.id, taskInfo.options);
  2505. } catch (tabId) {
  2506. taskInfo.tab.id = tabId;
  2507. }
  2508. if (scriptsInjected) {
  2509. onStart(taskInfo.tab.id, EXECUTE_SCRIPTS_STEP);
  2510. } else {
  2511. taskInfo.done();
  2512. return;
  2513. }
  2514. }
  2515. taskInfo.options.taskId = taskId;
  2516. try {
  2517. if (processInForeground) {
  2518. await browser.tabs.update(taskInfo.tab.id, { active: true });
  2519. }
  2520. await browser.tabs.sendMessage(taskInfo.tab.id, { method: taskInfo.method, options: taskInfo.options });
  2521. } catch (error) {
  2522. if (error && (!error.message || !isIgnoredError(error))) {
  2523. console.log(error.message ? error.message : error); // eslint-disable-line no-console
  2524. onError(taskInfo.tab.id, error.message, error.link);
  2525. taskInfo.done();
  2526. }
  2527. }
  2528. }
  2529. function isIgnoredError(error) {
  2530. return error.message == ERROR_CONNECTION_LOST_CHROMIUM ||
  2531. error.message == ERROR_CONNECTION_ERROR_CHROMIUM ||
  2532. error.message == ERROR_CONNECTION_LOST_GECKO ||
  2533. error.message.startsWith(ERROR_EDITOR_PAGE_CHROMIUM + JSON.stringify(EDITOR_URL));
  2534. }
  2535. function isSavingTab(tab) {
  2536. return Boolean(tasks.find(taskInfo => taskInfo.tab.id == tab.id));
  2537. }
  2538. function onInit$2(tab) {
  2539. cancelTab(tab.id);
  2540. }
  2541. function onTabReplaced$2(addedTabId, removedTabId) {
  2542. tasks.forEach(taskInfo => {
  2543. if (taskInfo.tab.id == removedTabId) {
  2544. taskInfo.tab.id = addedTabId;
  2545. }
  2546. });
  2547. }
  2548. function onSaveEnd(taskId) {
  2549. const taskInfo = tasks.find(taskInfo => taskInfo.id == taskId);
  2550. if (taskInfo) {
  2551. if (taskInfo.options.autoClose && !taskInfo.cancelled) {
  2552. browser.tabs.remove(taskInfo.tab.id);
  2553. }
  2554. taskInfo.done();
  2555. }
  2556. }
  2557. async function createTabAndWaitUntilComplete(createProperties) {
  2558. const tab = await browser.tabs.create(createProperties);
  2559. return new Promise((resolve, reject) => {
  2560. browser.tabs.onUpdated.addListener(onTabUpdated);
  2561. browser.tabs.onRemoved.addListener(onTabRemoved);
  2562. function onTabUpdated(tabId, changeInfo) {
  2563. if (tabId == tab.id && changeInfo.status == "complete") {
  2564. resolve(tab);
  2565. browser.tabs.onUpdated.removeListener(onTabUpdated);
  2566. browser.tabs.onRemoved.removeListener(onTabRemoved);
  2567. }
  2568. }
  2569. function onTabRemoved(tabId) {
  2570. if (tabId == tab.id) {
  2571. reject(tabId);
  2572. browser.tabs.onRemoved.removeListener(onTabRemoved);
  2573. }
  2574. }
  2575. });
  2576. }
  2577. function setCancelCallback(taskId, cancelCallback) {
  2578. const taskInfo = tasks.find(taskInfo => taskInfo.id == taskId);
  2579. if (taskInfo) {
  2580. taskInfo.cancel = cancelCallback;
  2581. }
  2582. }
  2583. function cancelTab(tabId) {
  2584. Array.from(tasks).filter(taskInfo => taskInfo.tab.id == tabId && !taskInfo.options.autoSave).forEach(cancel);
  2585. }
  2586. function cancelTask(taskId) {
  2587. cancel(tasks.find(taskInfo => taskInfo.id == taskId));
  2588. }
  2589. function cancelAllTasks() {
  2590. Array.from(tasks).forEach(cancel);
  2591. }
  2592. function getTasksInfo() {
  2593. return tasks.map(mapTaskInfo);
  2594. }
  2595. function getTaskInfo(taskId) {
  2596. return tasks.find(taskInfo => taskInfo.id == taskId);
  2597. }
  2598. function cancel(taskInfo) {
  2599. const tabId = taskInfo.tab.id;
  2600. taskInfo.cancelled = true;
  2601. browser.tabs.sendMessage(tabId, {
  2602. method: "content.cancelSave",
  2603. options: {
  2604. loadDeferredImages: taskInfo.options.loadDeferredImages,
  2605. loadDeferredImagesKeepZoomLevel: taskInfo.options.loadDeferredImagesKeepZoomLevel
  2606. }
  2607. });
  2608. if (taskInfo.cancel) {
  2609. taskInfo.cancel();
  2610. }
  2611. if (taskInfo.method == "content.autosave") {
  2612. onEnd(tabId, true);
  2613. }
  2614. onCancelled(taskInfo.tab);
  2615. taskInfo.done();
  2616. }
  2617. function mapTaskInfo(taskInfo) {
  2618. return { id: taskInfo.id, tabId: taskInfo.tab.id, index: taskInfo.tab.index, url: taskInfo.tab.url, title: taskInfo.tab.title, cancelled: taskInfo.cancelled, status: taskInfo.status };
  2619. }
  2620. /*
  2621. * Copyright 2010-2020 Gildas Lormeau
  2622. * contact : gildas.lormeau <at> gmail.com
  2623. *
  2624. * This file is part of SingleFile.
  2625. *
  2626. * The code in this file is free software: you can redistribute it and/or
  2627. * modify it under the terms of the GNU Affero General Public License
  2628. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2629. * of the License, or (at your option) any later version.
  2630. *
  2631. * The code in this file is distributed in the hope that it will be useful,
  2632. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2633. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2634. * General Public License for more details.
  2635. *
  2636. * As additional permission under GNU AGPL version 3 section 7, you may
  2637. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2638. * AGPL normally required by section 4, provided you include this license
  2639. * notice and a URL through which recipients can access the Corresponding
  2640. * Source.
  2641. */
  2642. /* global browser */
  2643. let enabled = true;
  2644. async function onMessage$6(message) {
  2645. if (message.method.endsWith(".state")) {
  2646. return { enabled };
  2647. }
  2648. }
  2649. async function externalSave(pageData) {
  2650. pageData.autoSaveExternalSave = false;
  2651. let response;
  2652. try {
  2653. response = await browser.runtime.sendNativeMessage("singlefile_companion", {
  2654. method: "externalSave",
  2655. pageData
  2656. });
  2657. } catch (error) {
  2658. if (!error.message || !error.message.includes("Native host has exited")) {
  2659. throw error;
  2660. }
  2661. }
  2662. if (response && response.error) {
  2663. throw new Error(response.error + " (Companion)");
  2664. }
  2665. }
  2666. async function save(pageData) {
  2667. let response;
  2668. try {
  2669. response = await browser.runtime.sendNativeMessage("singlefile_companion", {
  2670. method: "save",
  2671. pageData
  2672. });
  2673. } catch (error) {
  2674. if (!error.message || !error.message.includes("Native host has exited")) {
  2675. throw error;
  2676. }
  2677. }
  2678. if (response && response.error) {
  2679. throw new Error(response.error + " (Companion)");
  2680. }
  2681. }
  2682. /*
  2683. * Copyright 2010-2020 Gildas Lormeau
  2684. * contact : gildas.lormeau <at> gmail.com
  2685. *
  2686. * This file is part of SingleFile.
  2687. *
  2688. * The code in this file is free software: you can redistribute it and/or
  2689. * modify it under the terms of the GNU Affero General Public License
  2690. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2691. * of the License, or (at your option) any later version.
  2692. *
  2693. * The code in this file is distributed in the hope that it will be useful,
  2694. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2695. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2696. * General Public License for more details.
  2697. *
  2698. * As additional permission under GNU AGPL version 3 section 7, you may
  2699. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2700. * AGPL normally required by section 4, provided you include this license
  2701. * notice and a URL through which recipients can access the Corresponding
  2702. * Source.
  2703. */
  2704. const pendingSaves = new Set();
  2705. Promise.resolve().then(enable);
  2706. async function onMessage$5(message) {
  2707. if (message.method.endsWith(".saveCreatedBookmarks")) {
  2708. enable();
  2709. return {};
  2710. }
  2711. if (message.method.endsWith(".disable")) {
  2712. disable();
  2713. return {};
  2714. }
  2715. }
  2716. async function enable() {
  2717. try {
  2718. browser.bookmarks.onCreated.removeListener(onCreated);
  2719. browser.bookmarks.onMoved.removeListener(onMoved);
  2720. } catch (error) {
  2721. // ignored
  2722. }
  2723. let enabled;
  2724. const profiles = await getProfiles();
  2725. Object.keys(profiles).forEach(profileName => {
  2726. if (profiles[profileName].saveCreatedBookmarks) {
  2727. enabled = true;
  2728. }
  2729. });
  2730. if (enabled) {
  2731. browser.bookmarks.onCreated.addListener(onCreated);
  2732. browser.bookmarks.onMoved.addListener(onMoved);
  2733. }
  2734. }
  2735. async function disable() {
  2736. let disabled;
  2737. const profiles = await getProfiles();
  2738. Object.keys(profiles).forEach(profileName => disabled = disabled || !profiles[profileName].saveCreatedBookmarks);
  2739. if (disabled) {
  2740. browser.bookmarks.onCreated.removeListener(onCreated);
  2741. browser.bookmarks.onMoved.removeListener(onMoved);
  2742. }
  2743. }
  2744. async function update(id, changes) {
  2745. try {
  2746. await browser.bookmarks.update(id, changes);
  2747. } catch (error) {
  2748. // ignored
  2749. }
  2750. }
  2751. async function onCreated(bookmarkId, bookmarkInfo) {
  2752. pendingSaves.add(bookmarkId);
  2753. await saveBookmark(bookmarkId, bookmarkInfo.url, bookmarkInfo);
  2754. }
  2755. async function onMoved(bookmarkId, bookmarkInfo) {
  2756. if (pendingSaves.has(bookmarkId)) {
  2757. const bookmarks = await browser.bookmarks.get(bookmarkId);
  2758. if (bookmarks[0]) {
  2759. await saveBookmark(bookmarkId, bookmarks[0].url, bookmarkInfo);
  2760. }
  2761. }
  2762. }
  2763. async function saveBookmark(bookmarkId, url, bookmarkInfo) {
  2764. const activeTabs = await browser.tabs.query({ lastFocusedWindow: true, active: true });
  2765. const options = await getOptions(url);
  2766. if (options.saveCreatedBookmarks) {
  2767. const bookmarkFolders = await getParentFolders(bookmarkInfo.parentId);
  2768. const allowedBookmarkSet = options.allowedBookmarkFolders.toString();
  2769. const allowedBookmark = bookmarkFolders.find(folder => options.allowedBookmarkFolders.includes(folder));
  2770. const ignoredBookmarkSet = options.ignoredBookmarkFolders.toString();
  2771. const ignoredBookmark = bookmarkFolders.find(folder => options.ignoredBookmarkFolders.includes(folder));
  2772. if (
  2773. ((allowedBookmarkSet && allowedBookmark) || !allowedBookmarkSet) &&
  2774. ((ignoredBookmarkSet && !ignoredBookmark) || !ignoredBookmarkSet)
  2775. ) {
  2776. if (activeTabs.length && activeTabs[0].url == url) {
  2777. pendingSaves.delete(bookmarkId);
  2778. saveTabs(activeTabs, { bookmarkId, bookmarkFolders });
  2779. } else {
  2780. const tabs = await browser.tabs.query({});
  2781. if (tabs.length) {
  2782. const tab = tabs.find(tab => tab.url == url);
  2783. if (tab) {
  2784. pendingSaves.delete(bookmarkId);
  2785. saveTabs([tab], { bookmarkId, bookmarkFolders });
  2786. } else {
  2787. if (url) {
  2788. if (url == "about:blank") {
  2789. browser.bookmarks.onChanged.addListener(onChanged);
  2790. } else {
  2791. saveUrl(url);
  2792. }
  2793. }
  2794. }
  2795. }
  2796. }
  2797. }
  2798. }
  2799. async function getParentFolders(id, folderNames = []) {
  2800. if (id) {
  2801. const bookmarkNode = (await browser.bookmarks.get(id))[0];
  2802. if (bookmarkNode && bookmarkNode.title) {
  2803. folderNames.unshift(bookmarkNode.title);
  2804. await getParentFolders(bookmarkNode.parentId, folderNames);
  2805. }
  2806. }
  2807. return folderNames;
  2808. }
  2809. function onChanged(id, changeInfo) {
  2810. if (id == bookmarkId && changeInfo.url) {
  2811. browser.bookmarks.onChanged.removeListener(onChanged);
  2812. saveUrl(changeInfo.url);
  2813. }
  2814. }
  2815. function saveUrl(url) {
  2816. pendingSaves.delete(bookmarkId);
  2817. saveUrls([url], { bookmarkId });
  2818. }
  2819. }
  2820. /*
  2821. * Copyright 2010-2020 Gildas Lormeau
  2822. * contact : gildas.lormeau <at> gmail.com
  2823. *
  2824. * This file is part of SingleFile.
  2825. *
  2826. * The code in this file is free software: you can redistribute it and/or
  2827. * modify it under the terms of the GNU Affero General Public License
  2828. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2829. * of the License, or (at your option) any later version.
  2830. *
  2831. * The code in this file is distributed in the hope that it will be useful,
  2832. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2833. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2834. * General Public License for more details.
  2835. *
  2836. * As additional permission under GNU AGPL version 3 section 7, you may
  2837. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2838. * AGPL normally required by section 4, provided you include this license
  2839. * notice and a URL through which recipients can access the Corresponding
  2840. * Source.
  2841. */
  2842. /* global fetch */
  2843. const urlService = "https://api.woleet.io/v1/anchor";
  2844. const apiKey = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhYzZmZTMzMi0wODNjLTRjZmMtYmYxNC0xNWU5MTJmMWY4OWIiLCJpYXQiOjE1NzYxNzQzNDV9.n31j9ctJj7R1Vjwyc5yd1d6Cmg0NDnpwSaLWsqtZJQA";
  2845. async function anchor(hash, userKey) {
  2846. let bearer = userKey || apiKey;
  2847. const response = await fetch(urlService, {
  2848. method: "POST",
  2849. headers: {
  2850. "Accept": "application/json",
  2851. "Content-Type": "application/json",
  2852. "Authorization": "Bearer " + bearer
  2853. },
  2854. body: JSON.stringify({
  2855. "name": hash,
  2856. "hash": hash,
  2857. "public": true
  2858. })
  2859. });
  2860. if (response.status == 401) {
  2861. const error = new Error("Your access token on Woleet is invalid. Go to __DOC_LINK__ to create your account.");
  2862. error.link = "https://app.woleet.io/";
  2863. throw error;
  2864. } else if (response.status == 402) {
  2865. const error = new Error("You have no more credits on Woleet. Go to __DOC_LINK__ to recharge them.");
  2866. error.link = "https://app.woleet.io/";
  2867. throw error;
  2868. } else if (response.status >= 400) {
  2869. throw new Error((response.statusText || ("Error " + response.status)) + " (Woleet)");
  2870. }
  2871. return response.json();
  2872. }
  2873. /*
  2874. * Copyright 2010-2020 Gildas Lormeau
  2875. * contact : gildas.lormeau <at> gmail.com
  2876. *
  2877. * This file is part of SingleFile.
  2878. *
  2879. * The code in this file is free software: you can redistribute it and/or
  2880. * modify it under the terms of the GNU Affero General Public License
  2881. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2882. * of the License, or (at your option) any later version.
  2883. *
  2884. * The code in this file is distributed in the hope that it will be useful,
  2885. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2886. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2887. * General Public License for more details.
  2888. *
  2889. * As additional permission under GNU AGPL version 3 section 7, you may
  2890. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2891. * AGPL normally required by section 4, provided you include this license
  2892. * notice and a URL through which recipients can access the Corresponding
  2893. * Source.
  2894. */
  2895. /* global browser, fetch, setInterval, URLSearchParams, URL */
  2896. const TOKEN_URL = "https://oauth2.googleapis.com/token";
  2897. const AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
  2898. const REVOKE_ACCESS_URL = "https://accounts.google.com/o/oauth2/revoke";
  2899. const GDRIVE_URL = "https://www.googleapis.com/drive/v3/files";
  2900. const GDRIVE_UPLOAD_URL = "https://www.googleapis.com/upload/drive/v3/files";
  2901. const CONFLICT_ACTION_UNIQUIFY$3 = "uniquify";
  2902. const CONFLICT_ACTION_OVERWRITE$2 = "overwrite";
  2903. const CONFLICT_ACTION_SKIP$3 = "skip";
  2904. const CONFLICT_ACTION_PROMPT$2 = "prompt";
  2905. class GDrive {
  2906. constructor(clientId, clientKey, scopes) {
  2907. this.clientId = clientId;
  2908. this.clientKey = clientKey;
  2909. this.scopes = scopes;
  2910. this.folderIds = new Map();
  2911. setInterval(() => this.folderIds.clear(), 60 * 1000);
  2912. }
  2913. async auth(options = { interactive: true }) {
  2914. if (nativeAuth(options)) {
  2915. this.accessToken = await browser.identity.getAuthToken({ interactive: options.interactive });
  2916. return { revokableAccessToken: this.accessToken };
  2917. } else {
  2918. this.authURL = AUTH_URL +
  2919. "?client_id=" + this.clientId +
  2920. "&response_type=code" +
  2921. "&access_type=offline" +
  2922. "&redirect_uri=" + browser.identity.getRedirectURL() +
  2923. "&scope=" + this.scopes.join(" ");
  2924. return options.code ? authFromCode(this, options) : initAuth(this, options);
  2925. }
  2926. }
  2927. setAuthInfo(authInfo, options) {
  2928. if (!nativeAuth(options)) {
  2929. if (authInfo) {
  2930. this.accessToken = authInfo.accessToken;
  2931. this.refreshToken = authInfo.refreshToken;
  2932. this.expirationDate = authInfo.expirationDate;
  2933. } else {
  2934. delete this.accessToken;
  2935. delete this.refreshToken;
  2936. delete this.expirationDate;
  2937. }
  2938. }
  2939. }
  2940. async refreshAuthToken() {
  2941. if (this.refreshToken) {
  2942. const httpResponse = await fetch(TOKEN_URL, {
  2943. method: "POST",
  2944. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  2945. body: "client_id=" + this.clientId +
  2946. "&refresh_token=" + this.refreshToken +
  2947. "&grant_type=refresh_token" +
  2948. "&client_secret=" + this.clientKey
  2949. });
  2950. if (httpResponse.status == 400) {
  2951. throw new Error("unknown_token");
  2952. }
  2953. const response = await getJSON(httpResponse);
  2954. this.accessToken = response.access_token;
  2955. if (response.refresh_token) {
  2956. this.refreshToken = response.refresh_token;
  2957. }
  2958. if (response.expires_in) {
  2959. this.expirationDate = Date.now() + (response.expires_in * 1000);
  2960. }
  2961. return { accessToken: this.accessToken, refreshToken: this.refreshToken, expirationDate: this.expirationDate };
  2962. } else {
  2963. try {
  2964. if (browser.identity && browser.identity.removeCachedAuthToken && this.accessToken) {
  2965. await browser.identity.removeCachedAuthToken({ token: this.accessToken });
  2966. }
  2967. this.accessToken = await browser.identity.getAuthToken({ interactive: false });
  2968. return { revokableAccessToken: this.accessToken };
  2969. } catch (error) {
  2970. delete this.accessToken;
  2971. }
  2972. }
  2973. }
  2974. async revokeAuthToken(accessToken) {
  2975. if (accessToken) {
  2976. if (browser.identity && browser.identity.removeCachedAuthToken) {
  2977. try {
  2978. await browser.identity.removeCachedAuthToken({ token: accessToken });
  2979. } catch (error) {
  2980. // ignored
  2981. }
  2982. }
  2983. const httpResponse = await fetch(REVOKE_ACCESS_URL, {
  2984. method: "POST",
  2985. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  2986. body: "token=" + accessToken
  2987. });
  2988. try {
  2989. await getJSON(httpResponse);
  2990. }
  2991. catch (error) {
  2992. if (error.message != "invalid_token") {
  2993. throw error;
  2994. }
  2995. }
  2996. finally {
  2997. delete this.accessToken;
  2998. delete this.refreshToken;
  2999. delete this.expirationDate;
  3000. }
  3001. }
  3002. }
  3003. async upload(fullFilename, blob, options, setCancelCallback, retry = true) {
  3004. const parentFolderId = await getParentFolderId(this, fullFilename);
  3005. const fileParts = fullFilename.split("/");
  3006. const filename = fileParts.pop();
  3007. const uploader = new MediaUploader({
  3008. token: this.accessToken,
  3009. file: blob,
  3010. parents: [parentFolderId],
  3011. filename,
  3012. onProgress: options.onProgress,
  3013. filenameConflictAction: options.filenameConflictAction,
  3014. prompt: options.prompt
  3015. });
  3016. try {
  3017. if (setCancelCallback) {
  3018. setCancelCallback(() => uploader.cancelled = true);
  3019. }
  3020. await uploader.upload();
  3021. }
  3022. catch (error) {
  3023. if (error.message == "path_not_found" && retry) {
  3024. this.folderIds.clear();
  3025. return this.upload(fullFilename, blob, options, setCancelCallback);
  3026. } else {
  3027. throw error;
  3028. }
  3029. }
  3030. }
  3031. }
  3032. class MediaUploader {
  3033. constructor(options) {
  3034. this.file = options.file;
  3035. this.onProgress = options.onProgress;
  3036. this.contentType = this.file.type || "application/octet-stream";
  3037. this.metadata = {
  3038. name: options.filename,
  3039. mimeType: this.contentType,
  3040. parents: options.parents || ["root"]
  3041. };
  3042. this.token = options.token;
  3043. this.offset = 0;
  3044. this.chunkSize = options.chunkSize || 512 * 1024;
  3045. this.filenameConflictAction = options.filenameConflictAction;
  3046. this.prompt = options.prompt;
  3047. }
  3048. async upload(indexFilename = 1) {
  3049. let method = "POST";
  3050. let fileId;
  3051. const httpListResponse = getResponse(await fetch(GDRIVE_URL + `?q=name = '${this.metadata.name}' and trashed != true and '${this.metadata.parents[0]}' in parents`, {
  3052. headers: {
  3053. "Authorization": "Bearer " + this.token,
  3054. "Content-Type": "application/json"
  3055. }
  3056. }));
  3057. const response = await httpListResponse.json();
  3058. if (response.files.length) {
  3059. if (this.filenameConflictAction == CONFLICT_ACTION_OVERWRITE$2) {
  3060. method = "PATCH";
  3061. fileId = response.files[0].id;
  3062. this.metadata.parents = null;
  3063. } else if (this.filenameConflictAction == CONFLICT_ACTION_UNIQUIFY$3) {
  3064. let nameWithoutExtension = this.metadata.name;
  3065. let extension = "";
  3066. const dotIndex = this.metadata.name.lastIndexOf(".");
  3067. if (dotIndex > -1) {
  3068. nameWithoutExtension = this.metadata.name.substring(0, dotIndex);
  3069. extension = this.metadata.name.substring(dotIndex + 1);
  3070. }
  3071. const name = nameWithoutExtension + " (" + indexFilename + ")." + extension;
  3072. const httpResponse = getResponse(await fetch(GDRIVE_URL + `?q=name = '${name}' and trashed != true and '${this.metadata.parents[0]}' in parents`, {
  3073. headers: {
  3074. "Authorization": "Bearer " + this.token,
  3075. "Content-Type": "application/json"
  3076. }
  3077. }));
  3078. const response = await httpResponse.json();
  3079. if (response.files.length) {
  3080. return this.upload(indexFilename + 1);
  3081. } else {
  3082. this.metadata.name = name;
  3083. }
  3084. } else if (this.filenameConflictAction == CONFLICT_ACTION_PROMPT$2) {
  3085. if (this.prompt) {
  3086. const name = await this.prompt(this.metadata.name);
  3087. if (name) {
  3088. this.metadata.name = name;
  3089. return this.upload(indexFilename);
  3090. } else {
  3091. return response;
  3092. }
  3093. } else {
  3094. this.filenameConflictAction = CONFLICT_ACTION_UNIQUIFY$3;
  3095. return this.upload(indexFilename);
  3096. }
  3097. } else if (this.filenameConflictAction == CONFLICT_ACTION_SKIP$3) {
  3098. return response;
  3099. }
  3100. }
  3101. const httpResponse = getResponse(await fetch(GDRIVE_UPLOAD_URL + (fileId ? "/" + fileId : "") + "?uploadType=resumable", {
  3102. method,
  3103. headers: {
  3104. "Authorization": "Bearer " + this.token,
  3105. "Content-Type": "application/json",
  3106. "X-Upload-Content-Length": this.file.size,
  3107. "X-Upload-Content-Type": this.contentType
  3108. },
  3109. body: JSON.stringify(this.metadata)
  3110. }));
  3111. const location = httpResponse.headers.get("Location");
  3112. this.url = location;
  3113. if (!this.cancelled) {
  3114. if (this.onProgress) {
  3115. this.onProgress(0, this.file.size);
  3116. }
  3117. return sendFile(this);
  3118. }
  3119. }
  3120. }
  3121. async function authFromCode(gdrive, options) {
  3122. const httpResponse = await fetch(TOKEN_URL, {
  3123. method: "POST",
  3124. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  3125. body: "client_id=" + gdrive.clientId +
  3126. "&client_secret=" + gdrive.clientKey +
  3127. "&grant_type=authorization_code" +
  3128. "&code=" + options.code +
  3129. "&redirect_uri=" + browser.identity.getRedirectURL()
  3130. });
  3131. const response = await getJSON(httpResponse);
  3132. gdrive.accessToken = response.access_token;
  3133. gdrive.refreshToken = response.refresh_token;
  3134. gdrive.expirationDate = Date.now() + (response.expires_in * 1000);
  3135. return { accessToken: gdrive.accessToken, refreshToken: gdrive.refreshToken, expirationDate: gdrive.expirationDate };
  3136. }
  3137. async function initAuth(gdrive, options) {
  3138. let code;
  3139. try {
  3140. if (browser.identity && browser.identity.launchWebAuthFlow && !options.forceWebAuthFlow) {
  3141. const authURL = await browser.identity.launchWebAuthFlow({
  3142. interactive: options.interactive,
  3143. url: gdrive.authURL
  3144. });
  3145. options.code = new URLSearchParams(new URL(authURL).search).get("code");
  3146. return await authFromCode(gdrive, options);
  3147. } else if (options.launchWebAuthFlow) {
  3148. options.extractAuthCode(browser.identity.getRedirectURL())
  3149. .then(authCode => code = authCode)
  3150. .catch(() => { /* ignored */ });
  3151. return await options.launchWebAuthFlow({ url: gdrive.authURL });
  3152. } else {
  3153. throw new Error("auth_not_supported");
  3154. }
  3155. }
  3156. catch (error) {
  3157. if (error.message && (error.message == "code_required" || error.message.includes("access"))) {
  3158. if (code) {
  3159. options.code = code;
  3160. return await authFromCode(gdrive, options);
  3161. } else {
  3162. throw new Error("code_required");
  3163. }
  3164. } else {
  3165. throw error;
  3166. }
  3167. }
  3168. }
  3169. function nativeAuth(options = {}) {
  3170. return Boolean(browser.identity && browser.identity.getAuthToken) && !options.forceWebAuthFlow;
  3171. }
  3172. async function getParentFolderId(gdrive, filename, retry = true) {
  3173. const fileParts = filename.split("/");
  3174. fileParts.pop();
  3175. const folderId = gdrive.folderIds.get(fileParts.join("/"));
  3176. if (folderId) {
  3177. return folderId;
  3178. }
  3179. let parentFolderId = "root";
  3180. if (fileParts.length) {
  3181. let fullFolderName = "";
  3182. for (const folderName of fileParts) {
  3183. if (fullFolderName) {
  3184. fullFolderName += "/";
  3185. }
  3186. fullFolderName += folderName;
  3187. const folderId = gdrive.folderIds.get(fullFolderName);
  3188. if (folderId) {
  3189. parentFolderId = folderId;
  3190. } else {
  3191. try {
  3192. parentFolderId = await getOrCreateFolder(gdrive, folderName, parentFolderId);
  3193. gdrive.folderIds.set(fullFolderName, parentFolderId);
  3194. } catch (error) {
  3195. if (error.message == "path_not_found" && retry) {
  3196. gdrive.folderIds.clear();
  3197. return getParentFolderId(gdrive, filename, false);
  3198. } else {
  3199. throw error;
  3200. }
  3201. }
  3202. }
  3203. }
  3204. }
  3205. return parentFolderId;
  3206. }
  3207. async function getOrCreateFolder(gdrive, folderName, parentFolderId) {
  3208. const response = await getFolder(gdrive, folderName, parentFolderId);
  3209. if (response.files.length) {
  3210. return response.files[0].id;
  3211. } else {
  3212. const response = await createFolder(gdrive, folderName, parentFolderId);
  3213. return response.id;
  3214. }
  3215. }
  3216. async function getFolder(gdrive, folderName, parentFolderId) {
  3217. const httpResponse = await fetch(GDRIVE_URL + "?q=mimeType = 'application/vnd.google-apps.folder' and name = '" + folderName + "' and trashed != true and '" + parentFolderId + "' in parents", {
  3218. headers: {
  3219. "Authorization": "Bearer " + gdrive.accessToken
  3220. }
  3221. });
  3222. return getJSON(httpResponse);
  3223. }
  3224. async function createFolder(gdrive, folderName, parentFolderId) {
  3225. const httpResponse = await fetch(GDRIVE_URL, {
  3226. method: "POST",
  3227. headers: {
  3228. "Authorization": "Bearer " + gdrive.accessToken,
  3229. "Content-Type": "application/json"
  3230. },
  3231. body: JSON.stringify({
  3232. name: folderName,
  3233. parents: [parentFolderId],
  3234. mimeType: "application/vnd.google-apps.folder"
  3235. })
  3236. });
  3237. return getJSON(httpResponse);
  3238. }
  3239. async function sendFile(mediaUploader) {
  3240. let content = mediaUploader.file, end = mediaUploader.file.size;
  3241. if (mediaUploader.offset || mediaUploader.chunkSize) {
  3242. if (mediaUploader.chunkSize) {
  3243. end = Math.min(mediaUploader.offset + mediaUploader.chunkSize, mediaUploader.file.size);
  3244. }
  3245. content = content.slice(mediaUploader.offset, end);
  3246. }
  3247. const httpResponse = await fetch(mediaUploader.url, {
  3248. method: "PUT",
  3249. headers: {
  3250. "Authorization": "Bearer " + mediaUploader.token,
  3251. "Content-Type": mediaUploader.contentType,
  3252. "Content-Range": "bytes " + mediaUploader.offset + "-" + (end - 1) + "/" + mediaUploader.file.size,
  3253. "X-Upload-Content-Type": mediaUploader.contentType
  3254. },
  3255. body: content
  3256. });
  3257. if (mediaUploader.onProgress && !mediaUploader.cancelled) {
  3258. mediaUploader.onProgress(mediaUploader.offset + mediaUploader.chunkSize, mediaUploader.file.size);
  3259. }
  3260. if (httpResponse.status == 200 || httpResponse.status == 201) {
  3261. return httpResponse.json();
  3262. } else if (httpResponse.status == 308) {
  3263. const range = httpResponse.headers.get("Range");
  3264. if (range) {
  3265. mediaUploader.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1;
  3266. }
  3267. if (mediaUploader.cancelled) {
  3268. throw new Error("upload_cancelled");
  3269. } else {
  3270. return sendFile(mediaUploader);
  3271. }
  3272. } else {
  3273. getResponse(httpResponse);
  3274. }
  3275. }
  3276. async function getJSON(httpResponse) {
  3277. httpResponse = getResponse(httpResponse);
  3278. const response = await httpResponse.json();
  3279. if (response.error) {
  3280. throw new Error(response.error);
  3281. } else {
  3282. return response;
  3283. }
  3284. }
  3285. function getResponse(httpResponse) {
  3286. if (httpResponse.status == 200) {
  3287. return httpResponse;
  3288. } else if (httpResponse.status == 404) {
  3289. throw new Error("path_not_found");
  3290. } else if (httpResponse.status == 401) {
  3291. throw new Error("invalid_token");
  3292. } else {
  3293. throw new Error("unknown_error (" + httpResponse.status + ")");
  3294. }
  3295. }
  3296. /*
  3297. * Copyright 2010-2020 Gildas Lormeau
  3298. * contact : gildas.lormeau <at> gmail.com
  3299. *
  3300. * This file is part of SingleFile.
  3301. *
  3302. * The code in this file is free software: you can redistribute it and/or
  3303. * modify it under the terms of the GNU Affero General Public License
  3304. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  3305. * of the License, or (at your option) any later version.
  3306. *
  3307. * The code in this file is distributed in the hope that it will be useful,
  3308. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  3309. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  3310. * General Public License for more details.
  3311. *
  3312. * As additional permission under GNU AGPL version 3 section 7, you may
  3313. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  3314. * AGPL normally required by section 4, provided you include this license
  3315. * notice and a URL through which recipients can access the Corresponding
  3316. * Source.
  3317. */
  3318. /* global fetch, btoa, AbortController */
  3319. const EMPTY_STRING$1 = "";
  3320. const CONFLICT_ACTION_SKIP$2 = "skip";
  3321. const CONFLICT_ACTION_UNIQUIFY$2 = "uniquify";
  3322. const CONFLICT_ACTION_OVERWRITE$1 = "overwrite";
  3323. const CONFLICT_ACTION_PROMPT$1 = "prompt";
  3324. const BASIC_PREFIX_AUTHORIZATION = "Basic ";
  3325. const AUTHORIZATION_HEADER$1 = "Authorization";
  3326. const AUTHORIZATION_SEPARATOR = ":";
  3327. const DIRECTORY_SEPARATOR = "/";
  3328. const EXTENSION_SEPARATOR$1 = ".";
  3329. const ERROR_PREFIX_MESSAGE = "Error ";
  3330. const INDEX_FILENAME_PREFIX$1 = " (";
  3331. const INDEX_FILENAME_SUFFIX$1 = ")";
  3332. const INDEX_FILENAME_REGEXP$1 = /\s\((\d+)\)$/;
  3333. const ABORT_ERROR_NAME$1 = "AbortError";
  3334. const HEAD_METHOD = "HEAD";
  3335. const PUT_METHOD$1 = "PUT";
  3336. const DELETE_METHOD = "DELETE";
  3337. const PROPFIND_METHOD = "PROPFIND";
  3338. const MKCOL_METHOD = "MKCOL";
  3339. const CONTENT_TYPE_HEADER = "Content-Type";
  3340. const HTML_CONTENT_TYPE = "text/html";
  3341. const CREDENTIALS_PARAMETER = "omit";
  3342. const FOUND_STATUS = 200;
  3343. const CREATED_STATUS = 201;
  3344. const NOT_FOUND_STATUS = 404;
  3345. const MIN_ERROR_STATUS = 400;
  3346. class WebDAV {
  3347. constructor(url, username, password) {
  3348. if (!url.endsWith(DIRECTORY_SEPARATOR)) {
  3349. url += DIRECTORY_SEPARATOR;
  3350. }
  3351. this.url = url;
  3352. this.authorization = BASIC_PREFIX_AUTHORIZATION + btoa(username + AUTHORIZATION_SEPARATOR + password);
  3353. }
  3354. upload(filename, content, options) {
  3355. this.controller = new AbortController();
  3356. options.signal = this.controller.signal;
  3357. options.authorization = this.authorization;
  3358. options.url = this.url;
  3359. return upload$1(filename, content, options);
  3360. }
  3361. abort() {
  3362. if (this.controller) {
  3363. this.controller.abort();
  3364. }
  3365. }
  3366. }
  3367. async function upload$1(filename, content, options) {
  3368. const { authorization, filenameConflictAction, prompt, signal, preventRetry } = options;
  3369. let { url } = options;
  3370. try {
  3371. if (filenameConflictAction == CONFLICT_ACTION_OVERWRITE$1) {
  3372. let response = await sendRequest(filename, PUT_METHOD$1, content);
  3373. if (response.status == CREATED_STATUS) {
  3374. return response;
  3375. } else if (response.status >= MIN_ERROR_STATUS) {
  3376. response = await sendRequest(filename, DELETE_METHOD);
  3377. if (response.status >= MIN_ERROR_STATUS) {
  3378. throw new Error(ERROR_PREFIX_MESSAGE + response.status);
  3379. }
  3380. return await upload$1(filename, content, options);
  3381. }
  3382. } else {
  3383. let response = await sendRequest(filename, HEAD_METHOD);
  3384. if (response.status == FOUND_STATUS) {
  3385. if (filenameConflictAction == CONFLICT_ACTION_UNIQUIFY$2 || (filenameConflictAction == CONFLICT_ACTION_PROMPT$1 && !prompt)) {
  3386. const { filenameWithoutExtension, extension, indexFilename } = splitFilename(filename);
  3387. options.indexFilename = indexFilename + 1;
  3388. return await upload$1(getFilename(filenameWithoutExtension, extension), content, options);
  3389. } else if (filenameConflictAction == CONFLICT_ACTION_PROMPT$1) {
  3390. filename = await prompt(filename);
  3391. return filename ? upload$1(filename, content, options) : response;
  3392. } else if (filenameConflictAction == CONFLICT_ACTION_SKIP$2) {
  3393. return response;
  3394. }
  3395. } else if (response.status == NOT_FOUND_STATUS) {
  3396. response = await sendRequest(filename, PUT_METHOD$1, content);
  3397. if (response.status >= MIN_ERROR_STATUS && !preventRetry) {
  3398. if (filename.includes(DIRECTORY_SEPARATOR)) {
  3399. await createDirectories();
  3400. options.preventRetry = true;
  3401. return await upload$1(filename, content, options);
  3402. } else {
  3403. throw new Error(ERROR_PREFIX_MESSAGE + response.status);
  3404. }
  3405. } else {
  3406. return response;
  3407. }
  3408. } else if (response.status >= MIN_ERROR_STATUS) {
  3409. throw new Error(ERROR_PREFIX_MESSAGE + response.status);
  3410. }
  3411. }
  3412. } catch (error) {
  3413. if (error.name != ABORT_ERROR_NAME$1) {
  3414. throw error;
  3415. }
  3416. }
  3417. function sendRequest(path, method, body) {
  3418. const headers = {
  3419. [AUTHORIZATION_HEADER$1]: authorization
  3420. };
  3421. if (body) {
  3422. headers[CONTENT_TYPE_HEADER] = HTML_CONTENT_TYPE;
  3423. }
  3424. return fetch(url + path, { method, headers, signal, body, credentials: CREDENTIALS_PARAMETER });
  3425. }
  3426. function splitFilename(filename) {
  3427. let filenameWithoutExtension = filename;
  3428. let extension = EMPTY_STRING$1;
  3429. const indexExtensionSeparator = filename.lastIndexOf(EXTENSION_SEPARATOR$1);
  3430. if (indexExtensionSeparator > -1) {
  3431. filenameWithoutExtension = filename.substring(0, indexExtensionSeparator);
  3432. extension = filename.substring(indexExtensionSeparator + 1);
  3433. }
  3434. let indexFilename;
  3435. ({ filenameWithoutExtension, indexFilename } = extractIndexFilename(filenameWithoutExtension));
  3436. return { filenameWithoutExtension, extension, indexFilename };
  3437. }
  3438. function extractIndexFilename(filenameWithoutExtension) {
  3439. const indexFilenameMatch = filenameWithoutExtension.match(INDEX_FILENAME_REGEXP$1);
  3440. let indexFilename = 0;
  3441. if (indexFilenameMatch && indexFilenameMatch.length > 1) {
  3442. const parsedIndexFilename = Number(indexFilenameMatch[indexFilenameMatch.length - 1]);
  3443. if (!Number.isNaN(parsedIndexFilename)) {
  3444. indexFilename = parsedIndexFilename;
  3445. filenameWithoutExtension = filenameWithoutExtension.replace(INDEX_FILENAME_REGEXP$1, EMPTY_STRING$1);
  3446. }
  3447. }
  3448. return { filenameWithoutExtension, indexFilename };
  3449. }
  3450. function getFilename(filenameWithoutExtension, extension) {
  3451. return filenameWithoutExtension +
  3452. INDEX_FILENAME_PREFIX$1 + options.indexFilename + INDEX_FILENAME_SUFFIX$1 +
  3453. (extension ? EXTENSION_SEPARATOR$1 + extension : EMPTY_STRING$1);
  3454. }
  3455. async function createDirectories() {
  3456. const filenameParts = filename.split(DIRECTORY_SEPARATOR);
  3457. filenameParts.pop();
  3458. let path = EMPTY_STRING$1;
  3459. for (const filenamePart of filenameParts) {
  3460. if (filenamePart) {
  3461. path += filenamePart;
  3462. const response = await sendRequest(path, PROPFIND_METHOD);
  3463. if (response.status == NOT_FOUND_STATUS) {
  3464. const response = await sendRequest(path, MKCOL_METHOD);
  3465. if (response.status >= MIN_ERROR_STATUS) {
  3466. throw new Error(ERROR_PREFIX_MESSAGE + response.status);
  3467. }
  3468. }
  3469. path += DIRECTORY_SEPARATOR;
  3470. }
  3471. }
  3472. }
  3473. }
  3474. /*
  3475. * Copyright 2010-2020 Gildas Lormeau
  3476. * contact : gildas.lormeau <at> gmail.com
  3477. *
  3478. * This file is part of SingleFile.
  3479. *
  3480. * The code in this file is free software: you can redistribute it and/or
  3481. * modify it under the terms of the GNU Affero General Public License
  3482. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  3483. * of the License, or (at your option) any later version.
  3484. *
  3485. * The code in this file is distributed in the hope that it will be useful,
  3486. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  3487. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  3488. * General Public License for more details.
  3489. *
  3490. * As additional permission under GNU AGPL version 3 section 7, you may
  3491. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  3492. * AGPL normally required by section 4, provided you include this license
  3493. * notice and a URL through which recipients can access the Corresponding
  3494. * Source.
  3495. */
  3496. /* global fetch, btoa, Blob, FileReader, AbortController */
  3497. const EMPTY_STRING = "";
  3498. const CONFLICT_ACTION_SKIP$1 = "skip";
  3499. const CONFLICT_ACTION_UNIQUIFY$1 = "uniquify";
  3500. const CONFLICT_ACTION_OVERWRITE = "overwrite";
  3501. const CONFLICT_ACTION_PROMPT = "prompt";
  3502. const AUTHORIZATION_HEADER = "Authorization";
  3503. const BEARER_PREFIX_AUTHORIZATION = "Bearer ";
  3504. const ACCEPT_HEADER = "Accept";
  3505. const GITHUB_API_CONTENT_TYPE = "application/vnd.github+json";
  3506. const GITHUB_API_VERSION_HEADER = "X-GitHub-Api-Version";
  3507. const GITHUB_API_VERSION = "2022-11-28";
  3508. const EXTENSION_SEPARATOR = ".";
  3509. const INDEX_FILENAME_PREFIX = " (";
  3510. const INDEX_FILENAME_SUFFIX = ")";
  3511. const INDEX_FILENAME_REGEXP = /\s\((\d+)\)$/;
  3512. const ABORT_ERROR_NAME = "AbortError";
  3513. const GET_METHOD = "GET";
  3514. const PUT_METHOD = "PUT";
  3515. const GITHUB_URL = "https://github.com";
  3516. const GITHUB_API_URL = "https://api.github.com";
  3517. const BLOB_PATH = "blob";
  3518. const REPOS_PATH = "repos";
  3519. const CONTENTS_PATH = "contents";
  3520. let pendingPush;
  3521. class GitHub {
  3522. constructor(token, userName, repositoryName, branch) {
  3523. this.headers = new Map([
  3524. [AUTHORIZATION_HEADER, BEARER_PREFIX_AUTHORIZATION + token],
  3525. [ACCEPT_HEADER, GITHUB_API_CONTENT_TYPE],
  3526. [GITHUB_API_VERSION_HEADER, GITHUB_API_VERSION]
  3527. ]);
  3528. this.userName = userName;
  3529. this.repositoryName = repositoryName;
  3530. this.branch = branch;
  3531. }
  3532. async upload(path, content, options) {
  3533. this.controller = new AbortController();
  3534. options.signal = this.controller.signal;
  3535. options.headers = this.headers;
  3536. const base64Content = content instanceof Blob ? await blobToBase64(content) : btoa(unescape(encodeURIComponent(content)));
  3537. return upload(this.userName, this.repositoryName, this.branch, path, base64Content, options);
  3538. }
  3539. abort() {
  3540. if (this.controller) {
  3541. this.controller.abort();
  3542. }
  3543. }
  3544. }
  3545. async function upload(userName, repositoryName, branch, path, content, options) {
  3546. const { filenameConflictAction, prompt, signal, headers } = options;
  3547. while (pendingPush) {
  3548. await pendingPush;
  3549. }
  3550. try {
  3551. pendingPush = await createContent({ path, content });
  3552. } finally {
  3553. pendingPush = null;
  3554. }
  3555. return {
  3556. url: `${GITHUB_URL}/${userName}/${repositoryName}/${BLOB_PATH}/${branch}/${path}`
  3557. };
  3558. async function createContent({ path, content, message = EMPTY_STRING, sha }) {
  3559. try {
  3560. const response = await fetchContentData(PUT_METHOD, JSON.stringify({
  3561. content,
  3562. message,
  3563. branch,
  3564. sha
  3565. }));
  3566. const responseData = await response.json();
  3567. if (response.status == 422) {
  3568. if (filenameConflictAction == CONFLICT_ACTION_OVERWRITE) {
  3569. const response = await fetchContentData(GET_METHOD);
  3570. const responseData = await response.json();
  3571. const sha = responseData.sha;
  3572. return await createContent({ path, content, message, sha });
  3573. } else if (filenameConflictAction == CONFLICT_ACTION_UNIQUIFY$1) {
  3574. const { filenameWithoutExtension, extension, indexFilename } = splitFilename(path);
  3575. options.indexFilename = indexFilename + 1;
  3576. path = getFilename(filenameWithoutExtension, extension);
  3577. return await createContent({ path, content, message });
  3578. } else if (filenameConflictAction == CONFLICT_ACTION_SKIP$1) {
  3579. return responseData;
  3580. } else if (filenameConflictAction == CONFLICT_ACTION_PROMPT) {
  3581. if (prompt) {
  3582. path = await prompt(path);
  3583. if (path) {
  3584. return await createContent({ path, content, message });
  3585. } else {
  3586. return responseData;
  3587. }
  3588. } else {
  3589. options.filenameConflictAction = CONFLICT_ACTION_UNIQUIFY$1;
  3590. return await createContent({ path, content, message });
  3591. }
  3592. }
  3593. }
  3594. if (response.status < 400) {
  3595. return responseData;
  3596. } else {
  3597. throw new Error(responseData.message);
  3598. }
  3599. } catch (error) {
  3600. if (error.name != ABORT_ERROR_NAME) {
  3601. throw error;
  3602. }
  3603. }
  3604. function fetchContentData(method, body) {
  3605. return fetch(`${GITHUB_API_URL}/${REPOS_PATH}/${userName}/${repositoryName}/${CONTENTS_PATH}/${path}`, {
  3606. method,
  3607. headers,
  3608. body,
  3609. signal
  3610. });
  3611. }
  3612. }
  3613. function splitFilename(filename) {
  3614. let filenameWithoutExtension = filename;
  3615. let extension = EMPTY_STRING;
  3616. const indexExtensionSeparator = filename.lastIndexOf(EXTENSION_SEPARATOR);
  3617. if (indexExtensionSeparator > -1) {
  3618. filenameWithoutExtension = filename.substring(0, indexExtensionSeparator);
  3619. extension = filename.substring(indexExtensionSeparator + 1);
  3620. }
  3621. let indexFilename;
  3622. ({ filenameWithoutExtension, indexFilename } = extractIndexFilename(filenameWithoutExtension));
  3623. return { filenameWithoutExtension, extension, indexFilename };
  3624. }
  3625. function extractIndexFilename(filenameWithoutExtension) {
  3626. const indexFilenameMatch = filenameWithoutExtension.match(INDEX_FILENAME_REGEXP);
  3627. let indexFilename = 0;
  3628. if (indexFilenameMatch && indexFilenameMatch.length > 1) {
  3629. const parsedIndexFilename = Number(indexFilenameMatch[indexFilenameMatch.length - 1]);
  3630. if (!Number.isNaN(parsedIndexFilename)) {
  3631. indexFilename = parsedIndexFilename;
  3632. filenameWithoutExtension = filenameWithoutExtension.replace(INDEX_FILENAME_REGEXP, EMPTY_STRING);
  3633. }
  3634. }
  3635. return { filenameWithoutExtension, indexFilename };
  3636. }
  3637. function getFilename(filenameWithoutExtension, extension) {
  3638. return filenameWithoutExtension +
  3639. INDEX_FILENAME_PREFIX + options.indexFilename + INDEX_FILENAME_SUFFIX +
  3640. (extension ? EXTENSION_SEPARATOR + extension : EMPTY_STRING);
  3641. }
  3642. }
  3643. function blobToBase64(blob) {
  3644. return new Promise((resolve, reject) => {
  3645. const reader = new FileReader();
  3646. reader.onloadend = () => resolve(reader.result.match(/^data:[^,]+,(.*)$/)[1]);
  3647. reader.onerror = event => reject(event.detail);
  3648. reader.readAsDataURL(blob);
  3649. });
  3650. }
  3651. /* global TextEncoder, TextDecoder */
  3652. const DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024;
  3653. const TYPE_REFERENCE = 0;
  3654. const SPECIAL_TYPES = [TYPE_REFERENCE];
  3655. const EMPTY_SLOT_VALUE = Symbol();
  3656. const textEncoder = new TextEncoder();
  3657. const textDecoder = new TextDecoder();
  3658. const types = new Array(256);
  3659. let typeIndex = 0;
  3660. registerType(serializeCircularReference, parseCircularReference, testCircularReference, TYPE_REFERENCE);
  3661. registerType(null, parseObject, testObject);
  3662. registerType(serializeArray, parseArray, testArray);
  3663. registerType(serializeString, parseString, testString);
  3664. registerType(serializeTypedArray, parseFloat64Array, testFloat64Array);
  3665. registerType(serializeTypedArray, parseFloat32Array, testFloat32Array);
  3666. registerType(serializeTypedArray, parseUint32Array, testUint32Array);
  3667. registerType(serializeTypedArray, parseInt32Array, testInt32Array);
  3668. registerType(serializeTypedArray, parseUint16Array, testUint16Array);
  3669. registerType(serializeTypedArray, parseInt16Array, testInt16Array);
  3670. registerType(serializeTypedArray, parseUint8ClampedArray, testUint8ClampedArray);
  3671. registerType(serializeTypedArray, parseUint8Array, testUint8Array);
  3672. registerType(serializeTypedArray, parseInt8Array, testInt8Array);
  3673. registerType(serializeArrayBuffer, parseArrayBuffer, testArrayBuffer);
  3674. registerType(serializeNumber, parseNumber, testNumber);
  3675. registerType(serializeUint32, parseUint32, testUint32);
  3676. registerType(serializeInt32, parseInt32, testInt32);
  3677. registerType(serializeUint16, parseUint16, testUint16);
  3678. registerType(serializeInt16, parseInt16, testInt16);
  3679. registerType(serializeUint8, parseUint8, testUint8);
  3680. registerType(serializeInt8, parseInt8, testInt8);
  3681. registerType(null, parseUndefined, testUndefined);
  3682. registerType(null, parseNull, testNull);
  3683. registerType(null, parseNaN, testNaN);
  3684. registerType(serializeBoolean, parseBoolean, testBoolean);
  3685. registerType(serializeSymbol, parseSymbol, testSymbol);
  3686. registerType(null, parseEmptySlot, testEmptySlot);
  3687. registerType(serializeMap, parseMap, testMap);
  3688. registerType(serializeSet, parseSet, testSet);
  3689. registerType(serializeDate, parseDate, testDate);
  3690. registerType(serializeError, parseError, testError);
  3691. registerType(serializeRegExp, parseRegExp, testRegExp);
  3692. registerType(serializeStringObject, parseStringObject, testStringObject);
  3693. registerType(serializeNumberObject, parseNumberObject, testNumberObject);
  3694. registerType(serializeBooleanObject, parseBooleanObject, testBooleanObject);
  3695. function registerType(serialize, parse, test, type) {
  3696. if (type === undefined) {
  3697. typeIndex++;
  3698. if (types.length - typeIndex >= SPECIAL_TYPES.length) {
  3699. types[types.length - typeIndex] = { serialize, parse, test };
  3700. } else {
  3701. throw new Error("Reached maximum number of custom types");
  3702. }
  3703. } else {
  3704. types[type] = { serialize, parse, test };
  3705. }
  3706. }
  3707. async function parse(array) {
  3708. const parser = getParser();
  3709. await parser.next(array);
  3710. const result = await parser.next();
  3711. return result.value;
  3712. }
  3713. class SerializerData {
  3714. constructor(appendData, chunkSize) {
  3715. this.stream = new WriteStream(appendData, chunkSize);
  3716. this.objects = [];
  3717. }
  3718. append(array) {
  3719. return this.stream.append(array);
  3720. }
  3721. flush() {
  3722. return this.stream.flush();
  3723. }
  3724. addObject(value) {
  3725. this.objects.push(testReferenceable(value) && !testCircularReference(value, this) ? value : undefined);
  3726. }
  3727. }
  3728. class WriteStream {
  3729. constructor(appendData, chunkSize) {
  3730. this.offset = 0;
  3731. this.appendData = appendData;
  3732. this.value = new Uint8Array(chunkSize);
  3733. }
  3734. async append(array) {
  3735. if (this.offset + array.length > this.value.length) {
  3736. const offset = this.value.length - this.offset;
  3737. await this.append(array.subarray(0, offset));
  3738. await this.appendData({ value: this.value });
  3739. this.offset = 0;
  3740. await this.append(array.subarray(offset));
  3741. } else {
  3742. this.value.set(array, this.offset);
  3743. this.offset += array.length;
  3744. }
  3745. }
  3746. async flush() {
  3747. if (this.offset) {
  3748. await this.appendData({ value: this.value.subarray(0, this.offset), done: true });
  3749. }
  3750. }
  3751. }
  3752. function getSerializer(value, { chunkSize = DEFAULT_CHUNK_SIZE } = {}) {
  3753. let serializerData, result, setResult, iterationDone, previousResult, resolvePreviousResult;
  3754. return {
  3755. [Symbol.asyncIterator]() {
  3756. return {
  3757. next() {
  3758. return iterationDone ? { done: iterationDone } : getResult();
  3759. },
  3760. return() {
  3761. return { done: true };
  3762. }
  3763. };
  3764. }
  3765. };
  3766. async function getResult() {
  3767. if (resolvePreviousResult) {
  3768. resolvePreviousResult();
  3769. } else {
  3770. initSerializerData().catch(() => { /* ignored */ });
  3771. }
  3772. initPreviousData();
  3773. const value = await getValue();
  3774. return { value };
  3775. }
  3776. async function initSerializerData() {
  3777. initResult();
  3778. serializerData = new SerializerData(appendData, chunkSize);
  3779. await serializeValue(serializerData, value);
  3780. await serializerData.flush();
  3781. }
  3782. function initResult() {
  3783. result = new Promise(resolve => setResult = resolve);
  3784. }
  3785. function initPreviousData() {
  3786. previousResult = new Promise(resolve => resolvePreviousResult = resolve);
  3787. }
  3788. async function appendData(result) {
  3789. setResult(result);
  3790. await previousResult;
  3791. }
  3792. async function getValue() {
  3793. const { value, done } = await result;
  3794. iterationDone = done;
  3795. if (!done) {
  3796. initResult();
  3797. }
  3798. return value;
  3799. }
  3800. }
  3801. async function serializeValue(data, value) {
  3802. const type = types.findIndex(({ test } = {}) => test && test(value, data));
  3803. data.addObject(value);
  3804. await data.append(new Uint8Array([type]));
  3805. const serialize = types[type].serialize;
  3806. if (serialize) {
  3807. await serialize(data, value);
  3808. }
  3809. if (type != TYPE_REFERENCE && testObject(value)) {
  3810. await serializeSymbols(data, value);
  3811. await serializeOwnProperties(data, value);
  3812. }
  3813. }
  3814. async function serializeSymbols(data, value) {
  3815. const ownPropertySymbols = Object.getOwnPropertySymbols(value);
  3816. const symbols = ownPropertySymbols.map(propertySymbol => [propertySymbol, value[propertySymbol]]);
  3817. await serializeArray(data, symbols);
  3818. }
  3819. async function serializeOwnProperties(data, value) {
  3820. let entries = Object.entries(value);
  3821. if (testArray(value)) {
  3822. entries = entries.filter(([key]) => !testInteger(Number(key)));
  3823. }
  3824. await serializeValue(data, entries.length);
  3825. for (const [key, value] of entries) {
  3826. await serializeString(data, key);
  3827. await serializeValue(data, value);
  3828. }
  3829. }
  3830. async function serializeCircularReference(data, value) {
  3831. const index = data.objects.indexOf(value);
  3832. await serializeValue(data, index);
  3833. }
  3834. async function serializeArray(data, array) {
  3835. await serializeValue(data, array.length);
  3836. const notEmptyIndexes = Object.keys(array).filter(key => testInteger(Number(key))).map(key => Number(key));
  3837. let indexNotEmptyIndexes = 0, currentNotEmptyIndex = notEmptyIndexes[indexNotEmptyIndexes];
  3838. for (const [indexArray, value] of array.entries()) {
  3839. if (currentNotEmptyIndex == indexArray) {
  3840. currentNotEmptyIndex = notEmptyIndexes[++indexNotEmptyIndexes];
  3841. await serializeValue(data, value);
  3842. } else {
  3843. await serializeValue(data, EMPTY_SLOT_VALUE);
  3844. }
  3845. }
  3846. }
  3847. async function serializeString(data, string) {
  3848. const encodedString = textEncoder.encode(string);
  3849. await serializeValue(data, encodedString.length);
  3850. await data.append(encodedString);
  3851. }
  3852. async function serializeTypedArray(data, array) {
  3853. await serializeValue(data, array.length);
  3854. await data.append(new Uint8Array(array.buffer));
  3855. }
  3856. async function serializeArrayBuffer(data, arrayBuffer) {
  3857. await serializeValue(data, arrayBuffer.byteLength);
  3858. await data.append(new Uint8Array(arrayBuffer));
  3859. }
  3860. async function serializeNumber(data, number) {
  3861. const serializedNumber = new Uint8Array(new Float64Array([number]).buffer);
  3862. await data.append(serializedNumber);
  3863. }
  3864. async function serializeUint32(data, number) {
  3865. const serializedNumber = new Uint8Array(new Uint32Array([number]).buffer);
  3866. await data.append(serializedNumber);
  3867. }
  3868. async function serializeInt32(data, number) {
  3869. const serializedNumber = new Uint8Array(new Int32Array([number]).buffer);
  3870. await data.append(serializedNumber);
  3871. }
  3872. async function serializeUint16(data, number) {
  3873. const serializedNumber = new Uint8Array(new Uint16Array([number]).buffer);
  3874. await data.append(serializedNumber);
  3875. }
  3876. async function serializeInt16(data, number) {
  3877. const serializedNumber = new Uint8Array(new Int16Array([number]).buffer);
  3878. await data.append(serializedNumber);
  3879. }
  3880. async function serializeUint8(data, number) {
  3881. const serializedNumber = new Uint8Array([number]);
  3882. await data.append(serializedNumber);
  3883. }
  3884. async function serializeInt8(data, number) {
  3885. const serializedNumber = new Uint8Array(new Int8Array([number]).buffer);
  3886. await data.append(serializedNumber);
  3887. }
  3888. async function serializeBoolean(data, boolean) {
  3889. const serializedBoolean = new Uint8Array([Number(boolean)]);
  3890. await data.append(serializedBoolean);
  3891. }
  3892. async function serializeMap(data, map) {
  3893. const entries = map.entries();
  3894. await serializeValue(data, map.size);
  3895. for (const [key, value] of entries) {
  3896. await serializeValue(data, key);
  3897. await serializeValue(data, value);
  3898. }
  3899. }
  3900. async function serializeSet(data, set) {
  3901. await serializeValue(data, set.size);
  3902. for (const value of set) {
  3903. await serializeValue(data, value);
  3904. }
  3905. }
  3906. async function serializeDate(data, date) {
  3907. await serializeNumber(data, date.getTime());
  3908. }
  3909. async function serializeError(data, error) {
  3910. await serializeString(data, error.message);
  3911. await serializeString(data, error.stack);
  3912. }
  3913. async function serializeRegExp(data, regExp) {
  3914. await serializeString(data, regExp.source);
  3915. await serializeString(data, regExp.flags);
  3916. }
  3917. async function serializeStringObject(data, string) {
  3918. await serializeString(data, string.valueOf());
  3919. }
  3920. async function serializeNumberObject(data, number) {
  3921. await serializeNumber(data, number.valueOf());
  3922. }
  3923. async function serializeBooleanObject(data, boolean) {
  3924. await serializeBoolean(data, boolean.valueOf());
  3925. }
  3926. async function serializeSymbol(data, symbol) {
  3927. await serializeString(data, symbol.description);
  3928. }
  3929. class Reference {
  3930. constructor(index, data) {
  3931. this.index = index;
  3932. this.data = data;
  3933. }
  3934. getObject() {
  3935. return this.data.objects[this.index];
  3936. }
  3937. }
  3938. class ParserData {
  3939. constructor(consumeData) {
  3940. this.stream = new ReadStream(consumeData);
  3941. this.objects = [];
  3942. this.setters = [];
  3943. }
  3944. consume(size) {
  3945. return this.stream.consume(size);
  3946. }
  3947. getObjectId() {
  3948. const objectIndex = this.objects.length;
  3949. this.objects.push(undefined);
  3950. return objectIndex;
  3951. }
  3952. resolveObject(objectId, value) {
  3953. if (testReferenceable(value) && !testReference(value)) {
  3954. this.objects[objectId] = value;
  3955. }
  3956. }
  3957. setObject(functionArguments, setterFunction) {
  3958. this.setters.push({ functionArguments, setterFunction });
  3959. }
  3960. executeSetters() {
  3961. this.setters.forEach(({ functionArguments, setterFunction }) => {
  3962. const resolvedArguments = functionArguments.map(argument => testReference(argument) ? argument.getObject() : argument);
  3963. setterFunction(...resolvedArguments);
  3964. });
  3965. }
  3966. }
  3967. class ReadStream {
  3968. constructor(consumeData) {
  3969. this.offset = 0;
  3970. this.value = new Uint8Array(0);
  3971. this.consumeData = consumeData;
  3972. }
  3973. async consume(size) {
  3974. if (this.offset + size > this.value.length) {
  3975. const pending = this.value.subarray(this.offset, this.value.length);
  3976. const value = await this.consumeData();
  3977. if (pending.length + value.length != this.value.length) {
  3978. this.value = new Uint8Array(pending.length + value.length);
  3979. }
  3980. this.value.set(pending);
  3981. this.value.set(value, pending.length);
  3982. this.offset = 0;
  3983. return this.consume(size);
  3984. } else {
  3985. const result = this.value.slice(this.offset, this.offset + size);
  3986. this.offset += result.length;
  3987. return result;
  3988. }
  3989. }
  3990. }
  3991. function getParser() {
  3992. let parserData, input, setInput, value, previousData, resolvePreviousData;
  3993. return {
  3994. async next(input) {
  3995. return input ? getResult(input) : { value: await value, done: true };
  3996. },
  3997. return() {
  3998. return { done: true };
  3999. }
  4000. };
  4001. async function getResult(input) {
  4002. if (previousData) {
  4003. await previousData;
  4004. } else {
  4005. initParserData().catch(() => { /* ignored */ });
  4006. }
  4007. initPreviousData();
  4008. setInput(input);
  4009. return { done: false };
  4010. }
  4011. async function initParserData() {
  4012. let setValue;
  4013. value = new Promise(resolve => setValue = resolve);
  4014. parserData = new ParserData(consumeData);
  4015. initChunk();
  4016. const data = await parseValue(parserData);
  4017. parserData.executeSetters();
  4018. setValue(data);
  4019. }
  4020. function initChunk() {
  4021. input = new Promise(resolve => setInput = resolve);
  4022. }
  4023. function initPreviousData() {
  4024. previousData = new Promise(resolve => resolvePreviousData = resolve);
  4025. }
  4026. async function consumeData() {
  4027. const data = await input;
  4028. initChunk();
  4029. if (resolvePreviousData) {
  4030. resolvePreviousData();
  4031. }
  4032. return data;
  4033. }
  4034. }
  4035. async function parseValue(data) {
  4036. const array = await data.consume(1);
  4037. const parserType = array[0];
  4038. const parse = types[parserType].parse;
  4039. const valueId = data.getObjectId();
  4040. const result = await parse(data);
  4041. if (parserType != TYPE_REFERENCE && testObject(result)) {
  4042. await parseSymbols(data, result);
  4043. await parseOwnProperties(data, result);
  4044. }
  4045. data.resolveObject(valueId, result);
  4046. return result;
  4047. }
  4048. async function parseSymbols(data, value) {
  4049. const symbols = await parseArray(data);
  4050. data.setObject([symbols], symbols => symbols.forEach(([symbol, propertyValue]) => value[symbol] = propertyValue));
  4051. }
  4052. async function parseOwnProperties(data, object) {
  4053. const size = await parseValue(data);
  4054. if (size) {
  4055. await parseNextProperty();
  4056. }
  4057. async function parseNextProperty(indexKey = 0) {
  4058. const key = await parseString(data);
  4059. const value = await parseValue(data);
  4060. data.setObject([value], value => object[key] = value);
  4061. if (indexKey < size - 1) {
  4062. await parseNextProperty(indexKey + 1);
  4063. }
  4064. }
  4065. }
  4066. async function parseCircularReference(data) {
  4067. const index = await parseValue(data);
  4068. const result = new Reference(index, data);
  4069. return result;
  4070. }
  4071. function parseObject() {
  4072. return {};
  4073. }
  4074. async function parseArray(data) {
  4075. const length = await parseValue(data);
  4076. const array = new Array(length);
  4077. if (length) {
  4078. await parseNextSlot();
  4079. }
  4080. return array;
  4081. async function parseNextSlot(indexArray = 0) {
  4082. const value = await parseValue(data);
  4083. if (!testEmptySlot(value)) {
  4084. data.setObject([value], value => array[indexArray] = value);
  4085. }
  4086. if (indexArray < length - 1) {
  4087. await parseNextSlot(indexArray + 1);
  4088. }
  4089. }
  4090. }
  4091. function parseEmptySlot() {
  4092. return EMPTY_SLOT_VALUE;
  4093. }
  4094. async function parseString(data) {
  4095. const size = await parseValue(data);
  4096. const array = await data.consume(size);
  4097. return textDecoder.decode(array);
  4098. }
  4099. async function parseFloat64Array(data) {
  4100. const length = await parseValue(data);
  4101. const array = await data.consume(length * 8);
  4102. return new Float64Array(array.buffer);
  4103. }
  4104. async function parseFloat32Array(data) {
  4105. const length = await parseValue(data);
  4106. const array = await data.consume(length * 4);
  4107. return new Float32Array(array.buffer);
  4108. }
  4109. async function parseUint32Array(data) {
  4110. const length = await parseValue(data);
  4111. const array = await data.consume(length * 4);
  4112. return new Uint32Array(array.buffer);
  4113. }
  4114. async function parseInt32Array(data) {
  4115. const length = await parseValue(data);
  4116. const array = await data.consume(length * 4);
  4117. return new Int32Array(array.buffer);
  4118. }
  4119. async function parseUint16Array(data) {
  4120. const length = await parseValue(data);
  4121. const array = await data.consume(length * 2);
  4122. return new Uint16Array(array.buffer);
  4123. }
  4124. async function parseInt16Array(data) {
  4125. const length = await parseValue(data);
  4126. const array = await data.consume(length * 2);
  4127. return new Int16Array(array.buffer);
  4128. }
  4129. async function parseUint8ClampedArray(data) {
  4130. const length = await parseValue(data);
  4131. const array = await data.consume(length);
  4132. return new Uint8ClampedArray(array.buffer);
  4133. }
  4134. async function parseUint8Array(data) {
  4135. const length = await parseValue(data);
  4136. const array = await data.consume(length);
  4137. return array;
  4138. }
  4139. async function parseInt8Array(data) {
  4140. const length = await parseValue(data);
  4141. const array = await data.consume(length);
  4142. return new Int8Array(array.buffer);
  4143. }
  4144. async function parseArrayBuffer(data) {
  4145. const length = await parseValue(data);
  4146. const array = await data.consume(length);
  4147. return array.buffer;
  4148. }
  4149. async function parseNumber(data) {
  4150. const array = await data.consume(8);
  4151. return new Float64Array(array.buffer)[0];
  4152. }
  4153. async function parseUint32(data) {
  4154. const array = await data.consume(4);
  4155. return new Uint32Array(array.buffer)[0];
  4156. }
  4157. async function parseInt32(data) {
  4158. const array = await data.consume(4);
  4159. return new Int32Array(array.buffer)[0];
  4160. }
  4161. async function parseUint16(data) {
  4162. const array = await data.consume(2);
  4163. return new Uint16Array(array.buffer)[0];
  4164. }
  4165. async function parseInt16(data) {
  4166. const array = await data.consume(2);
  4167. return new Int16Array(array.buffer)[0];
  4168. }
  4169. async function parseUint8(data) {
  4170. const array = await data.consume(1);
  4171. return new Uint8Array(array.buffer)[0];
  4172. }
  4173. async function parseInt8(data) {
  4174. const array = await data.consume(1);
  4175. return new Int8Array(array.buffer)[0];
  4176. }
  4177. function parseUndefined() {
  4178. return undefined;
  4179. }
  4180. function parseNull() {
  4181. return null;
  4182. }
  4183. function parseNaN() {
  4184. return NaN;
  4185. }
  4186. async function parseBoolean(data) {
  4187. const array = await data.consume(1);
  4188. return Boolean(array[0]);
  4189. }
  4190. async function parseMap(data) {
  4191. const size = await parseValue(data);
  4192. const map = new Map();
  4193. if (size) {
  4194. await parseNextEntry();
  4195. }
  4196. return map;
  4197. async function parseNextEntry(indexKey = 0) {
  4198. const key = await parseValue(data);
  4199. const value = await parseValue(data);
  4200. data.setObject([key, value], (key, value) => map.set(key, value));
  4201. if (indexKey < size - 1) {
  4202. await parseNextEntry(indexKey + 1);
  4203. }
  4204. }
  4205. }
  4206. async function parseSet(data) {
  4207. const size = await parseValue(data);
  4208. const set = new Set();
  4209. if (size) {
  4210. await parseNextEntry();
  4211. }
  4212. return set;
  4213. async function parseNextEntry(indexKey = 0) {
  4214. const value = await parseValue(data);
  4215. data.setObject([value], value => set.add(value));
  4216. if (indexKey < size - 1) {
  4217. await parseNextEntry(indexKey + 1);
  4218. }
  4219. }
  4220. }
  4221. async function parseDate(data) {
  4222. const milliseconds = await parseNumber(data);
  4223. return new Date(milliseconds);
  4224. }
  4225. async function parseError(data) {
  4226. const message = await parseString(data);
  4227. const stack = await parseString(data);
  4228. const error = new Error(message);
  4229. error.stack = stack;
  4230. return error;
  4231. }
  4232. async function parseRegExp(data) {
  4233. const source = await parseString(data);
  4234. const flags = await parseString(data);
  4235. return new RegExp(source, flags);
  4236. }
  4237. async function parseStringObject(data) {
  4238. return new String(await parseString(data));
  4239. }
  4240. async function parseNumberObject(data) {
  4241. return new Number(await parseNumber(data));
  4242. }
  4243. async function parseBooleanObject(data) {
  4244. return new Boolean(await parseBoolean(data));
  4245. }
  4246. async function parseSymbol(data) {
  4247. const description = await parseString(data);
  4248. return Symbol(description);
  4249. }
  4250. function testCircularReference(value, data) {
  4251. return testObject(value) && data.objects.includes(value);
  4252. }
  4253. function testReference(value) {
  4254. return value instanceof Reference;
  4255. }
  4256. function testObject(value) {
  4257. return value === Object(value);
  4258. }
  4259. function testArray(value) {
  4260. return typeof value.length == "number";
  4261. }
  4262. function testEmptySlot(value) {
  4263. return value === EMPTY_SLOT_VALUE;
  4264. }
  4265. function testString(value) {
  4266. return typeof value == "string";
  4267. }
  4268. function testFloat64Array(value) {
  4269. return value instanceof Float64Array;
  4270. }
  4271. function testUint32Array(value) {
  4272. return value instanceof Uint32Array;
  4273. }
  4274. function testInt32Array(value) {
  4275. return value instanceof Int32Array;
  4276. }
  4277. function testUint16Array(value) {
  4278. return value instanceof Uint16Array;
  4279. }
  4280. function testFloat32Array(value) {
  4281. return value instanceof Float32Array;
  4282. }
  4283. function testInt16Array(value) {
  4284. return value instanceof Int16Array;
  4285. }
  4286. function testUint8ClampedArray(value) {
  4287. return value instanceof Uint8ClampedArray;
  4288. }
  4289. function testUint8Array(value) {
  4290. return value instanceof Uint8Array;
  4291. }
  4292. function testInt8Array(value) {
  4293. return value instanceof Int8Array;
  4294. }
  4295. function testArrayBuffer(value) {
  4296. return value instanceof ArrayBuffer;
  4297. }
  4298. function testNumber(value) {
  4299. return typeof value == "number";
  4300. }
  4301. function testUint32(value) {
  4302. return testInteger(value) && value >= 0 && value <= 4294967295;
  4303. }
  4304. function testInt32(value) {
  4305. return testInteger(value) && value >= -2147483648 && value <= 2147483647;
  4306. }
  4307. function testUint16(value) {
  4308. return testInteger(value) && value >= 0 && value <= 65535;
  4309. }
  4310. function testInt16(value) {
  4311. return testInteger(value) && value >= -32768 && value <= 32767;
  4312. }
  4313. function testUint8(value) {
  4314. return testInteger(value) && value >= 0 && value <= 255;
  4315. }
  4316. function testInt8(value) {
  4317. return testInteger(value) && value >= -128 && value <= 127;
  4318. }
  4319. function testInteger(value) {
  4320. return testNumber(value) && Number.isInteger(value);
  4321. }
  4322. function testUndefined(value) {
  4323. return value === undefined;
  4324. }
  4325. function testNull(value) {
  4326. return value === null;
  4327. }
  4328. function testNaN(value) {
  4329. return Number.isNaN(value);
  4330. }
  4331. function testBoolean(value) {
  4332. return typeof value == "boolean";
  4333. }
  4334. function testMap(value) {
  4335. return value instanceof Map;
  4336. }
  4337. function testSet(value) {
  4338. return value instanceof Set;
  4339. }
  4340. function testDate(value) {
  4341. return value instanceof Date;
  4342. }
  4343. function testError(value) {
  4344. return value instanceof Error;
  4345. }
  4346. function testRegExp(value) {
  4347. return value instanceof RegExp;
  4348. }
  4349. function testStringObject(value) {
  4350. return value instanceof String;
  4351. }
  4352. function testNumberObject(value) {
  4353. return value instanceof Number;
  4354. }
  4355. function testBooleanObject(value) {
  4356. return value instanceof Boolean;
  4357. }
  4358. function testSymbol(value) {
  4359. return typeof value == "symbol";
  4360. }
  4361. function testReferenceable(value) {
  4362. return testObject(value) || testSymbol(value);
  4363. }
  4364. /*
  4365. * Copyright 2010-2020 Gildas Lormeau
  4366. * contact : gildas.lormeau <at> gmail.com
  4367. *
  4368. * This file is part of SingleFile.
  4369. *
  4370. * The code in this file is free software: you can redistribute it and/or
  4371. * modify it under the terms of the GNU Affero General Public License
  4372. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  4373. * of the License, or (at your option) any later version.
  4374. *
  4375. * The code in this file is distributed in the hope that it will be useful,
  4376. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  4377. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  4378. * General Public License for more details.
  4379. *
  4380. * As additional permission under GNU AGPL version 3 section 7, you may
  4381. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  4382. * AGPL normally required by section 4, provided you include this license
  4383. * notice and a URL through which recipients can access the Corresponding
  4384. * Source.
  4385. */
  4386. const partialContents = new Map();
  4387. const parsers = new Map();
  4388. const MIMETYPE_HTML = "text/html";
  4389. const GDRIVE_CLIENT_ID = "207618107333-7tjs1im1pighftpoepea2kvkubnfjj44.apps.googleusercontent.com";
  4390. const GDRIVE_CLIENT_KEY = "VQJ8Gq8Vxx72QyxPyeLtWvUt";
  4391. const SCOPES = ["https://www.googleapis.com/auth/drive.file"];
  4392. const CONFLICT_ACTION_SKIP = "skip";
  4393. const CONFLICT_ACTION_UNIQUIFY = "uniquify";
  4394. const REGEXP_ESCAPE = /([{}()^$&.*?/+|[\\\\]|\]|-)/g;
  4395. const gDrive = new GDrive(GDRIVE_CLIENT_ID, GDRIVE_CLIENT_KEY, SCOPES);
  4396. async function onMessage$4(message, sender) {
  4397. if (message.method.endsWith(".download")) {
  4398. return downloadTabPage(message, sender.tab);
  4399. }
  4400. if (message.method.endsWith(".disableGDrive")) {
  4401. const authInfo = await getAuthInfo$1();
  4402. removeAuthInfo();
  4403. await gDrive.revokeAuthToken(authInfo && (authInfo.accessToken || authInfo.revokableAccessToken));
  4404. return {};
  4405. }
  4406. if (message.method.endsWith(".end")) {
  4407. if (message.hash) {
  4408. try {
  4409. await anchor(message.hash, message.woleetKey);
  4410. } catch (error) {
  4411. onError(sender.tab.id, error.message, error.link);
  4412. }
  4413. }
  4414. onSaveEnd(message.taskId);
  4415. return {};
  4416. }
  4417. if (message.method.endsWith(".getInfo")) {
  4418. return getTasksInfo();
  4419. }
  4420. if (message.method.endsWith(".cancel")) {
  4421. cancelTask(message.taskId);
  4422. return {};
  4423. }
  4424. if (message.method.endsWith(".cancelAll")) {
  4425. cancelAllTasks();
  4426. return {};
  4427. }
  4428. if (message.method.endsWith(".saveUrls")) {
  4429. saveUrls(message.urls);
  4430. return {};
  4431. }
  4432. }
  4433. async function downloadTabPage(message, tab) {
  4434. const tabId = tab.id;
  4435. let contents;
  4436. if (message.blobURL) {
  4437. try {
  4438. if (message.compressContent) {
  4439. message.pageData = await parse(new Uint8Array(await (await fetch(message.blobURL)).arrayBuffer()));
  4440. await downloadCompressedContent(message, tab);
  4441. } else {
  4442. message.content = await (await fetch(message.blobURL)).text();
  4443. await downloadContent([message.content], tab, tab.incognito, message);
  4444. }
  4445. } catch (error) {
  4446. return { error: true };
  4447. }
  4448. } else if (message.compressContent) {
  4449. let parser = parsers.get(tabId);
  4450. if (!parser) {
  4451. parser = getParser();
  4452. parsers.set(tabId, parser);
  4453. }
  4454. let result = await parser.next(message.data);
  4455. if (result.done) {
  4456. const message = result.value;
  4457. parsers.delete(tabId);
  4458. await downloadCompressedContent(message, tab);
  4459. }
  4460. } else {
  4461. if (message.truncated) {
  4462. contents = partialContents.get(tabId);
  4463. if (!contents) {
  4464. contents = [];
  4465. partialContents.set(tabId, contents);
  4466. }
  4467. contents.push(message.content);
  4468. if (message.finished) {
  4469. partialContents.delete(tabId);
  4470. }
  4471. } else if (message.content) {
  4472. contents = [message.content];
  4473. }
  4474. if (!message.truncated || message.finished) {
  4475. await downloadContent(contents, tab, tab.incognito, message);
  4476. }
  4477. }
  4478. return {};
  4479. }
  4480. async function downloadContent(contents, tab, incognito, message) {
  4481. const tabId = tab.id;
  4482. let skipped;
  4483. if (message.backgroundSave && !message.saveToGDrive && !message.saveWithWebDAV && !message.saveToGitHub) {
  4484. const testSkip = await testSkipSave(message.filename, message);
  4485. message.filenameConflictAction = testSkip.filenameConflictAction;
  4486. skipped = testSkip.skipped;
  4487. }
  4488. if (skipped) {
  4489. onEnd(tabId);
  4490. } else {
  4491. if (message.openEditor) {
  4492. onEdit(tabId);
  4493. await open({ tabIndex: tab.index + 1, filename: message.filename, content: contents.join("") });
  4494. } else {
  4495. if (message.saveToClipboard) {
  4496. message.content = contents.join("");
  4497. saveToClipboard(message);
  4498. onEnd(tabId);
  4499. } else {
  4500. try {
  4501. const prompt = filename => promptFilename(tabId, filename);
  4502. let response;
  4503. if (message.saveWithWebDAV) {
  4504. response = await saveWithWebDAV(message.taskId, encodeSharpCharacter(message.filename), contents.join(""), message.webDAVURL, message.webDAVUser, message.webDAVPassword, { filenameConflictAction: message.filenameConflictAction, prompt });
  4505. } else if (message.saveToGDrive) {
  4506. await saveToGDrive(message.taskId, encodeSharpCharacter(message.filename), new Blob(contents, { type: MIMETYPE_HTML }), {
  4507. forceWebAuthFlow: message.forceWebAuthFlow
  4508. }, {
  4509. onProgress: (offset, size) => onUploadProgress(tabId, offset, size),
  4510. filenameConflictAction: message.filenameConflictAction,
  4511. prompt
  4512. });
  4513. } else if (message.saveToGitHub) {
  4514. response = await saveToGitHub(message.taskId, encodeSharpCharacter(message.filename), contents.join(""), message.githubToken, message.githubUser, message.githubRepository, message.githubBranch, {
  4515. filenameConflictAction: message.filenameConflictAction,
  4516. prompt
  4517. });
  4518. await response.pushPromise;
  4519. } else if (message.saveWithCompanion) {
  4520. await save({
  4521. filename: message.filename,
  4522. content: message.content,
  4523. filenameConflictAction: message.filenameConflictAction
  4524. });
  4525. } else {
  4526. message.url = URL.createObjectURL(new Blob(contents, { type: MIMETYPE_HTML }));
  4527. response = await downloadPage(message, {
  4528. confirmFilename: message.confirmFilename,
  4529. incognito,
  4530. filenameConflictAction: message.filenameConflictAction,
  4531. filenameReplacementCharacter: message.filenameReplacementCharacter,
  4532. includeInfobar: message.includeInfobar
  4533. });
  4534. }
  4535. if (message.replaceBookmarkURL && response && response.url) {
  4536. await update(message.bookmarkId, { url: response.url });
  4537. }
  4538. onEnd(tabId);
  4539. if (message.openSavedPage) {
  4540. const createTabProperties = { active: true, url: URL.createObjectURL(new Blob(contents, { type: MIMETYPE_HTML })) };
  4541. if (tab.index != null) {
  4542. createTabProperties.index = tab.index + 1;
  4543. }
  4544. browser.tabs.create(createTabProperties);
  4545. }
  4546. } catch (error) {
  4547. if (!error.message || error.message != "upload_cancelled") {
  4548. console.error(error); // eslint-disable-line no-console
  4549. onError(tabId, error.message, error.link);
  4550. }
  4551. } finally {
  4552. if (message.url) {
  4553. URL.revokeObjectURL(message.url);
  4554. }
  4555. }
  4556. }
  4557. }
  4558. }
  4559. }
  4560. async function downloadCompressedContent(message, tab) {
  4561. const tabId = tab.id;
  4562. let skipped;
  4563. if (message.backgroundSave && !message.saveToGDrive && !message.saveWithWebDAV && !message.saveToGitHub) {
  4564. const testSkip = await testSkipSave(message.filename, message);
  4565. message.filenameConflictAction = testSkip.filenameConflictAction;
  4566. skipped = testSkip.skipped;
  4567. }
  4568. if (skipped) {
  4569. onEnd(tabId);
  4570. } else {
  4571. const pageData = message.pageData;
  4572. const blob = await singlefile.processors.compression.process(pageData, {
  4573. insertTextBody: message.insertTextBody,
  4574. url: pageData.url || tab.url,
  4575. createRootDirectory: message.createRootDirectory,
  4576. tabId,
  4577. selfExtractingArchive: message.selfExtractingArchive,
  4578. extractDataFromPage: message.extractDataFromPage,
  4579. insertCanonicalLink: message.insertCanonicalLink,
  4580. insertMetaNoIndex: message.insertMetaNoIndex,
  4581. password: message.password
  4582. });
  4583. if (message.openEditor) {
  4584. onEdit(tabId);
  4585. await open({ tabIndex: tab.index + 1, filename: message.filename, content: Array.from(new Uint8Array(await blob.arrayBuffer())), compressContent: true });
  4586. } else {
  4587. try {
  4588. const prompt = filename => promptFilename(tabId, filename);
  4589. let response;
  4590. if (message.saveWithWebDAV) {
  4591. response = await saveWithWebDAV(message.taskId, encodeSharpCharacter(message.filename), blob, message.webDAVURL, message.webDAVUser, message.webDAVPassword, { filenameConflictAction: message.filenameConflictAction, prompt });
  4592. } else if (message.saveToGDrive) {
  4593. await saveToGDrive(message.taskId, encodeSharpCharacter(message.filename), blob, {
  4594. forceWebAuthFlow: message.forceWebAuthFlow
  4595. }, {
  4596. onProgress: (offset, size) => onUploadProgress(tabId, offset, size),
  4597. filenameConflictAction: message.filenameConflictAction,
  4598. prompt
  4599. });
  4600. } else if (message.saveToGitHub) {
  4601. response = await saveToGitHub(message.taskId, encodeSharpCharacter(message.filename), blob, message.githubToken, message.githubUser, message.githubRepository, message.githubBranch, {
  4602. filenameConflictAction: message.filenameConflictAction,
  4603. prompt
  4604. });
  4605. await response.pushPromise;
  4606. } else {
  4607. if (message.backgroundSave) {
  4608. message.url = URL.createObjectURL(blob);
  4609. response = await downloadPage(message, {
  4610. confirmFilename: message.confirmFilename,
  4611. incognito: tab.incognito,
  4612. filenameConflictAction: message.filenameConflictAction,
  4613. filenameReplacementCharacter: message.filenameReplacementCharacter,
  4614. bookmarkId: message.bookmarkId,
  4615. replaceBookmarkURL: message.replaceBookmarkURL
  4616. });
  4617. } else {
  4618. await downloadPageForeground(message.taskId, message.filename, blob, tabId);
  4619. }
  4620. }
  4621. if (message.bookmarkId && message.replaceBookmarkURL && response && response.url) {
  4622. await update(message.bookmarkId, { url: response.url });
  4623. }
  4624. onEnd(tabId);
  4625. } catch (error) {
  4626. if (!error.message || error.message != "upload_cancelled") {
  4627. console.error(error); // eslint-disable-line no-console
  4628. onError(tabId, error.message);
  4629. }
  4630. } finally {
  4631. if (message.url) {
  4632. URL.revokeObjectURL(message.url);
  4633. }
  4634. }
  4635. }
  4636. }
  4637. }
  4638. function encodeSharpCharacter(path) {
  4639. return path.replace(/#/g, "%23");
  4640. }
  4641. function getRegExp(string) {
  4642. return string.replace(REGEXP_ESCAPE, "\\$1");
  4643. }
  4644. async function getAuthInfo(authOptions, force) {
  4645. let authInfo = await getAuthInfo$1();
  4646. const options = {
  4647. interactive: true,
  4648. forceWebAuthFlow: authOptions.forceWebAuthFlow,
  4649. launchWebAuthFlow: options => launchWebAuthFlow(options),
  4650. extractAuthCode: authURL => extractAuthCode(authURL)
  4651. };
  4652. gDrive.setAuthInfo(authInfo, options);
  4653. if (!authInfo || !authInfo.accessToken || force) {
  4654. authInfo = await gDrive.auth(options);
  4655. if (authInfo) {
  4656. await setAuthInfo(authInfo);
  4657. } else {
  4658. await removeAuthInfo();
  4659. }
  4660. }
  4661. return authInfo;
  4662. }
  4663. async function saveToGitHub(taskId, filename, content, githubToken, githubUser, githubRepository, githubBranch, { filenameConflictAction, prompt }) {
  4664. try {
  4665. const taskInfo = getTaskInfo(taskId);
  4666. if (!taskInfo || !taskInfo.cancelled) {
  4667. const client = new GitHub(githubToken, githubUser, githubRepository, githubBranch);
  4668. setCancelCallback(taskId, () => client.abort());
  4669. return await client.upload(filename, content, { filenameConflictAction, prompt });
  4670. }
  4671. } catch (error) {
  4672. throw new Error(error.message + " (GitHub)");
  4673. }
  4674. }
  4675. async function saveWithWebDAV(taskId, filename, content, url, username, password, { filenameConflictAction, prompt }) {
  4676. try {
  4677. const taskInfo = getTaskInfo(taskId);
  4678. if (!taskInfo || !taskInfo.cancelled) {
  4679. const client = new WebDAV(url, username, password);
  4680. setCancelCallback(taskId, () => client.abort());
  4681. return await client.upload(filename, content, { filenameConflictAction, prompt });
  4682. }
  4683. } catch (error) {
  4684. throw new Error(error.message + " (WebDAV)");
  4685. }
  4686. }
  4687. async function saveToGDrive(taskId, filename, blob, authOptions, uploadOptions) {
  4688. try {
  4689. await getAuthInfo(authOptions);
  4690. const taskInfo = getTaskInfo(taskId);
  4691. if (!taskInfo || !taskInfo.cancelled) {
  4692. return await gDrive.upload(filename, blob, uploadOptions, callback => setCancelCallback(taskId, callback));
  4693. }
  4694. }
  4695. catch (error) {
  4696. if (error.message == "invalid_token") {
  4697. let authInfo;
  4698. try {
  4699. authInfo = await gDrive.refreshAuthToken();
  4700. } catch (error) {
  4701. if (error.message == "unknown_token") {
  4702. authInfo = await getAuthInfo(authOptions, true);
  4703. } else {
  4704. throw new Error(error.message + " (Google Drive)");
  4705. }
  4706. }
  4707. if (authInfo) {
  4708. await setAuthInfo(authInfo);
  4709. } else {
  4710. await removeAuthInfo();
  4711. }
  4712. return await saveToGDrive(taskId, filename, blob, authOptions, uploadOptions);
  4713. } else {
  4714. throw new Error(error.message + " (Google Drive)");
  4715. }
  4716. }
  4717. }
  4718. async function testSkipSave(filename, options) {
  4719. let skipped, filenameConflictAction = options.filenameConflictAction;
  4720. if (filenameConflictAction == CONFLICT_ACTION_SKIP) {
  4721. const downloadItems = await browser.downloads.search({
  4722. filenameRegex: "(\\\\|/)" + getRegExp(filename) + "$",
  4723. exists: true
  4724. });
  4725. if (downloadItems.length) {
  4726. skipped = true;
  4727. } else {
  4728. filenameConflictAction = CONFLICT_ACTION_UNIQUIFY;
  4729. }
  4730. }
  4731. return { skipped, filenameConflictAction };
  4732. }
  4733. function promptFilename(tabId, filename) {
  4734. return browser.tabs.sendMessage(tabId, { method: "content.prompt", message: "Filename conflict, please enter a new filename", value: filename });
  4735. }
  4736. async function downloadPage(pageData, options) {
  4737. const downloadInfo = {
  4738. url: pageData.url,
  4739. saveAs: options.confirmFilename,
  4740. filename: pageData.filename,
  4741. conflictAction: options.filenameConflictAction
  4742. };
  4743. if (options.incognito) {
  4744. downloadInfo.incognito = true;
  4745. }
  4746. const downloadData = await download(downloadInfo, options.filenameReplacementCharacter);
  4747. if (downloadData.filename) {
  4748. let url = downloadData.filename;
  4749. if (!url.startsWith("file:")) {
  4750. if (url.startsWith("/")) {
  4751. url = url.substring(1);
  4752. }
  4753. url = "file:///" + encodeSharpCharacter(url);
  4754. }
  4755. return { url };
  4756. }
  4757. }
  4758. function saveToClipboard(pageData) {
  4759. const command = "copy";
  4760. document.addEventListener(command, listener);
  4761. document.execCommand(command);
  4762. document.removeEventListener(command, listener);
  4763. function listener(event) {
  4764. event.clipboardData.setData(MIMETYPE_HTML, pageData.content);
  4765. event.clipboardData.setData("text/plain", pageData.content);
  4766. event.preventDefault();
  4767. }
  4768. }
  4769. async function downloadPageForeground(taskId, filename, content, tabId) {
  4770. const serializer = getSerializer({ filename, taskId, content: await content.arrayBuffer() });
  4771. for await (const data of serializer) {
  4772. await browser.tabs.sendMessage(tabId, {
  4773. method: "content.download",
  4774. data: Array.from(data)
  4775. });
  4776. }
  4777. await browser.tabs.sendMessage(tabId, { method: "content.download" });
  4778. }
  4779. /*
  4780. * Copyright 2010-2020 Gildas Lormeau
  4781. * contact : gildas.lormeau <at> gmail.com
  4782. *
  4783. * This file is part of SingleFile.
  4784. *
  4785. * The code in this file is free software: you can redistribute it and/or
  4786. * modify it under the terms of the GNU Affero General Public License
  4787. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  4788. * of the License, or (at your option) any later version.
  4789. *
  4790. * The code in this file is distributed in the hope that it will be useful,
  4791. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  4792. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  4793. * General Public License for more details.
  4794. *
  4795. * As additional permission under GNU AGPL version 3 section 7, you may
  4796. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  4797. * AGPL normally required by section 4, provided you include this license
  4798. * notice and a URL through which recipients can access the Corresponding
  4799. * Source.
  4800. */
  4801. const pendingMessages = {};
  4802. const replacedTabIds = {};
  4803. async function onMessage$3(message, sender) {
  4804. if (message.method.endsWith(".save")) {
  4805. if (message.autoSaveDiscard || message.autoSaveRemove) {
  4806. if (sender.tab) {
  4807. message.tab = sender.tab;
  4808. pendingMessages[sender.tab.id] = message;
  4809. } else if (pendingMessages[message.tabId] &&
  4810. ((pendingMessages[message.tabId].removed && message.autoSaveRemove) ||
  4811. (pendingMessages[message.tabId].discarded && message.autoSaveDiscard))
  4812. ) {
  4813. delete pendingMessages[message.tabId];
  4814. await saveContent(message, { id: message.tabId, index: message.tabIndex, url: sender.url });
  4815. }
  4816. if (message.autoSaveUnload) {
  4817. delete pendingMessages[message.tabId];
  4818. await saveContent(message, sender.tab);
  4819. }
  4820. } else {
  4821. delete pendingMessages[message.tabId];
  4822. await saveContent(message, sender.tab);
  4823. }
  4824. return {};
  4825. }
  4826. }
  4827. function onTabUpdated$1(tabId) {
  4828. delete pendingMessages[tabId];
  4829. }
  4830. async function onTabRemoved$1(tabId) {
  4831. const message = pendingMessages[tabId];
  4832. if (message) {
  4833. if (message.autoSaveRemove) {
  4834. delete pendingMessages[tabId];
  4835. await saveContent(message, message.tab);
  4836. }
  4837. } else {
  4838. pendingMessages[tabId] = { removed: true };
  4839. }
  4840. }
  4841. async function onTabDiscarded(tabId) {
  4842. const message = pendingMessages[tabId];
  4843. if (message) {
  4844. delete pendingMessages[tabId];
  4845. await saveContent(message, message.tab);
  4846. } else {
  4847. pendingMessages[tabId] = { discarded: true };
  4848. }
  4849. }
  4850. async function onTabReplaced$1(addedTabId, removedTabId) {
  4851. if (pendingMessages[removedTabId] && !pendingMessages[addedTabId]) {
  4852. pendingMessages[addedTabId] = pendingMessages[removedTabId];
  4853. delete pendingMessages[removedTabId];
  4854. replacedTabIds[removedTabId] = addedTabId;
  4855. }
  4856. }
  4857. async function onMessageExternal(message, currentTab) {
  4858. if (message.method == "enableAutoSave") {
  4859. const allTabsData = await getPersistent(currentTab.id);
  4860. allTabsData[currentTab.id].autoSave = message.enabled;
  4861. await setPersistent(allTabsData);
  4862. refreshTab(currentTab);
  4863. }
  4864. if (message.method == "isAutoSaveEnabled") {
  4865. return autoSaveIsEnabled(currentTab);
  4866. }
  4867. }
  4868. async function onInit$1(tab) {
  4869. const [options, autoSaveEnabled] = await Promise.all([getOptions(tab.url, true), autoSaveIsEnabled(tab)]);
  4870. if (options && ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && autoSaveEnabled)) {
  4871. saveTabs([tab], { autoSave: true });
  4872. }
  4873. }
  4874. async function saveContent(message, tab) {
  4875. const tabId = tab.id;
  4876. const options = await getOptions(tab.url, true);
  4877. if (options) {
  4878. onStart(tabId, 1, true);
  4879. options.content = message.content;
  4880. options.url = message.url;
  4881. options.frames = message.frames;
  4882. options.canvases = message.canvases;
  4883. options.fonts = message.fonts;
  4884. options.stylesheets = message.stylesheets;
  4885. options.images = message.images;
  4886. options.posters = message.posters;
  4887. options.videos = message.videos;
  4888. options.usedFonts = message.usedFonts;
  4889. options.shadowRoots = message.shadowRoots;
  4890. options.referrer = message.referrer;
  4891. options.updatedResources = message.updatedResources;
  4892. options.adoptedStyleSheets = message.adoptedStyleSheets;
  4893. options.visitDate = new Date(message.visitDate);
  4894. options.backgroundTab = true;
  4895. options.autoSave = true;
  4896. options.incognito = tab.incognito;
  4897. options.tabId = tabId;
  4898. options.tabIndex = tab.index;
  4899. options.keepFilename = options.saveToGDrive || options.saveToGitHub || options.saveWithWebDAV;
  4900. let pageData;
  4901. try {
  4902. if (options.autoSaveExternalSave) {
  4903. await externalSave(options);
  4904. } else {
  4905. if (options.passReferrerOnError) {
  4906. enableReferrerOnError();
  4907. }
  4908. options.tabId = tabId;
  4909. pageData = await getPageData(options, null, null, { fetch: fetch$1 });
  4910. let skipped;
  4911. if (!options.saveToGDrive && !options.saveWithWebDAV && !options.saveToGitHub && !options.saveWithCompanion) {
  4912. const testSkip = await testSkipSave(pageData.filename, options);
  4913. skipped = testSkip.skipped;
  4914. options.filenameConflictAction = testSkip.filenameConflictAction;
  4915. }
  4916. if (!skipped) {
  4917. let { content } = pageData;
  4918. if (options.compressContent) {
  4919. content = new Blob([new Uint8Array(content)], { type: "text/html" });
  4920. }
  4921. if (options.saveToGDrive) {
  4922. if (!(content instanceof Blob)) {
  4923. content = new Blob([content], { type: "text/html" });
  4924. }
  4925. await saveToGDrive(message.taskId, encodeSharpCharacter(pageData.filename), content, options, {
  4926. forceWebAuthFlow: options.forceWebAuthFlow
  4927. }, {
  4928. filenameConflictAction: options.filenameConflictAction
  4929. });
  4930. } else if (options.saveWithWebDAV) {
  4931. await saveWithWebDAV(message.taskId, encodeSharpCharacter(pageData.filename), content, options.webDAVURL, options.webDAVUser, options.webDAVPassword, {
  4932. filenameConflictAction: options.filenameConflictAction
  4933. });
  4934. } else if (options.saveToGitHub) {
  4935. await (await saveToGitHub(message.taskId, encodeSharpCharacter(pageData.filename), content, options.githubToken, options.githubUser, options.githubRepository, options.githubBranch, {
  4936. filenameConflictAction: options.filenameConflictAction
  4937. })).pushPromise;
  4938. } else if (options.saveWithCompanion && !options.compressContent) {
  4939. await save({
  4940. filename: pageData.filename,
  4941. content: pageData.content,
  4942. filenameConflictAction: options.filenameConflictAction
  4943. });
  4944. } else {
  4945. if (!(content instanceof Blob)) {
  4946. content = new Blob([content], { type: "text/html" });
  4947. }
  4948. pageData.url = URL.createObjectURL(content);
  4949. await downloadPage(pageData, options);
  4950. if (options.openSavedPage && !options.compressContent) {
  4951. const createTabProperties = { active: true, url: URL.createObjectURL(content), windowId: tab.windowId };
  4952. const index = tab.index;
  4953. try {
  4954. await browser.tabs.get(tabId);
  4955. createTabProperties.index = index + 1;
  4956. } catch (error) {
  4957. createTabProperties.index = index;
  4958. }
  4959. browser.tabs.create(createTabProperties);
  4960. }
  4961. }
  4962. if (pageData.hash) {
  4963. await anchor(pageData.hash, options.woleetKey);
  4964. }
  4965. }
  4966. }
  4967. } finally {
  4968. if (message.taskId) {
  4969. onSaveEnd(message.taskId);
  4970. } else if (options.autoClose) {
  4971. browser.tabs.remove(replacedTabIds[tabId] || tabId);
  4972. delete replacedTabIds[tabId];
  4973. }
  4974. if (pageData && pageData.url) {
  4975. URL.revokeObjectURL(pageData.url);
  4976. }
  4977. onEnd(tabId, true);
  4978. }
  4979. }
  4980. }
  4981. async function fetch$1(url, options = {}) {
  4982. const response = await fetchResource$1(url, options);
  4983. return {
  4984. status: response.status,
  4985. headers: {
  4986. get: name => response.headers.get(name)
  4987. },
  4988. arrayBuffer: () => response.arrayBuffer
  4989. };
  4990. }
  4991. /*
  4992. * Copyright 2010-2020 Gildas Lormeau
  4993. * contact : gildas.lormeau <at> gmail.com
  4994. *
  4995. * This file is part of SingleFile.
  4996. *
  4997. * The code in this file is free software: you can redistribute it and/or
  4998. * modify it under the terms of the GNU Affero General Public License
  4999. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5000. * of the License, or (at your option) any later version.
  5001. *
  5002. * The code in this file is distributed in the hope that it will be useful,
  5003. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5004. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5005. * General Public License for more details.
  5006. *
  5007. * As additional permission under GNU AGPL version 3 section 7, you may
  5008. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5009. * AGPL normally required by section 4, provided you include this license
  5010. * notice and a URL through which recipients can access the Corresponding
  5011. * Source.
  5012. */
  5013. async function onMessage$2(message) {
  5014. if (message.method.endsWith(".resourceCommitted")) {
  5015. if (message.tabId && message.url && (message.type == "stylesheet" || message.type == "script")) {
  5016. await browser.tabs.sendMessage(message.tabId, message);
  5017. }
  5018. }
  5019. }
  5020. /*
  5021. * Copyright 2010-2020 Gildas Lormeau
  5022. * contact : gildas.lormeau <at> gmail.com
  5023. *
  5024. * This file is part of SingleFile.
  5025. *
  5026. * The code in this file is free software: you can redistribute it and/or
  5027. * modify it under the terms of the GNU Affero General Public License
  5028. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5029. * of the License, or (at your option) any later version.
  5030. *
  5031. * The code in this file is distributed in the hope that it will be useful,
  5032. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5033. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5034. * General Public License for more details.
  5035. *
  5036. * As additional permission under GNU AGPL version 3 section 7, you may
  5037. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5038. * AGPL normally required by section 4, provided you include this license
  5039. * notice and a URL through which recipients can access the Corresponding
  5040. * Source.
  5041. */
  5042. const DELAY_MAYBE_INIT = 1500;
  5043. browser.tabs.onCreated.addListener(tab => onTabCreated(tab));
  5044. browser.tabs.onActivated.addListener(activeInfo => onTabActivated(activeInfo));
  5045. browser.tabs.onRemoved.addListener(tabId => onTabRemoved(tabId));
  5046. browser.tabs.onUpdated.addListener((tabId, changeInfo) => onTabUpdated(tabId, changeInfo));
  5047. browser.tabs.onReplaced.addListener((addedTabId, removedTabId) => onTabReplaced(addedTabId, removedTabId));
  5048. async function onMessage$1(message, sender) {
  5049. if (message.method.endsWith(".init")) {
  5050. await onInit(sender.tab, message);
  5051. onInit$3(sender.tab);
  5052. onInit$2(sender.tab);
  5053. onInit$1(sender.tab);
  5054. }
  5055. if (message.method.endsWith(".getOptions")) {
  5056. return getOptions(message.url);
  5057. }
  5058. if (message.method.endsWith(".activate")) {
  5059. await browser.tabs.update(message.tabId, { active: true });
  5060. }
  5061. }
  5062. async function onInit(tab, options) {
  5063. await remove(tab.id);
  5064. const allTabsData = await getPersistent(tab.id);
  5065. allTabsData[tab.id].savedPageDetected = options.savedPageDetected;
  5066. await setPersistent(allTabsData);
  5067. }
  5068. async function onTabUpdated(tabId, changeInfo) {
  5069. if (changeInfo.status == "complete") {
  5070. setTimeout(async () => {
  5071. try {
  5072. await browser.tabs.sendMessage(tabId, { method: "content.maybeInit" });
  5073. }
  5074. catch (error) {
  5075. // ignored
  5076. }
  5077. }, DELAY_MAYBE_INIT);
  5078. onTabUpdated$1(tabId);
  5079. const tab = await browser.tabs.get(tabId);
  5080. if (isEditor(tab)) {
  5081. const allTabsData = await getPersistent(tab.id);
  5082. allTabsData[tab.id].editorDetected = true;
  5083. await setPersistent(allTabsData);
  5084. onTabActivated$1(tab);
  5085. }
  5086. }
  5087. if (changeInfo.discarded) {
  5088. onTabDiscarded(tabId);
  5089. }
  5090. }
  5091. function onTabReplaced(addedTabId, removedTabId) {
  5092. onTabReplaced$3(addedTabId, removedTabId);
  5093. onTabReplaced$1(addedTabId, removedTabId);
  5094. onTabReplaced$2(addedTabId, removedTabId);
  5095. }
  5096. function onTabCreated(tab) {
  5097. onTabCreated$1(tab);
  5098. }
  5099. async function onTabActivated(activeInfo) {
  5100. const tab = await browser.tabs.get(activeInfo.tabId);
  5101. onTabActivated$1(tab);
  5102. }
  5103. function onTabRemoved(tabId) {
  5104. remove(tabId);
  5105. onTabRemoved$2(tabId);
  5106. cancelTab(tabId);
  5107. onTabRemoved$1(tabId);
  5108. }
  5109. /*
  5110. * Copyright 2010-2020 Gildas Lormeau
  5111. * contact : gildas.lormeau <at> gmail.com
  5112. *
  5113. * This file is part of SingleFile.
  5114. *
  5115. * The code in this file is free software: you can redistribute it and/or
  5116. * modify it under the terms of the GNU Affero General Public License
  5117. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5118. * of the License, or (at your option) any later version.
  5119. *
  5120. * The code in this file is distributed in the hope that it will be useful,
  5121. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5122. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5123. * General Public License for more details.
  5124. *
  5125. * As additional permission under GNU AGPL version 3 section 7, you may
  5126. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5127. * AGPL normally required by section 4, provided you include this license
  5128. * notice and a URL through which recipients can access the Corresponding
  5129. * Source.
  5130. */
  5131. /* global browser */
  5132. browser.runtime.onMessage.addListener((message, sender) => {
  5133. if (message.method == "singlefile.frameTree.initResponse" || message.method == "singlefile.frameTree.ackInitRequest") {
  5134. browser.tabs.sendMessage(sender.tab.id, message, { frameId: 0 });
  5135. return Promise.resolve({});
  5136. }
  5137. });
  5138. /*
  5139. * Copyright 2010-2020 Gildas Lormeau
  5140. * contact : gildas.lormeau <at> gmail.com
  5141. *
  5142. * This file is part of SingleFile.
  5143. *
  5144. * The code in this file is free software: you can redistribute it and/or
  5145. * modify it under the terms of the GNU Affero General Public License
  5146. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5147. * of the License, or (at your option) any later version.
  5148. *
  5149. * The code in this file is distributed in the hope that it will be useful,
  5150. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5151. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5152. * General Public License for more details.
  5153. *
  5154. * As additional permission under GNU AGPL version 3 section 7, you may
  5155. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5156. * AGPL normally required by section 4, provided you include this license
  5157. * notice and a URL through which recipients can access the Corresponding
  5158. * Source.
  5159. */
  5160. /* global browser, setTimeout, clearTimeout */
  5161. const timeouts = new Map();
  5162. browser.runtime.onMessage.addListener((message, sender) => {
  5163. if (message.method == "singlefile.lazyTimeout.setTimeout") {
  5164. let tabTimeouts = timeouts.get(sender.tab.id);
  5165. let frameTimeouts;
  5166. if (tabTimeouts) {
  5167. frameTimeouts = tabTimeouts.get(sender.frameId);
  5168. if (frameTimeouts) {
  5169. const previousTimeoutId = frameTimeouts.get(message.type);
  5170. if (previousTimeoutId) {
  5171. clearTimeout(previousTimeoutId);
  5172. }
  5173. } else {
  5174. frameTimeouts = new Map();
  5175. }
  5176. }
  5177. const timeoutId = setTimeout(async () => {
  5178. try {
  5179. const tabTimeouts = timeouts.get(sender.tab.id);
  5180. const frameTimeouts = tabTimeouts.get(sender.frameId);
  5181. if (tabTimeouts && frameTimeouts) {
  5182. deleteTimeout(frameTimeouts, message.type);
  5183. }
  5184. await browser.tabs.sendMessage(sender.tab.id, { method: "singlefile.lazyTimeout.onTimeout", type: message.type });
  5185. } catch (error) {
  5186. // ignored
  5187. }
  5188. }, message.delay);
  5189. if (!tabTimeouts) {
  5190. tabTimeouts = new Map();
  5191. frameTimeouts = new Map();
  5192. tabTimeouts.set(sender.frameId, frameTimeouts);
  5193. timeouts.set(sender.tab.id, tabTimeouts);
  5194. }
  5195. frameTimeouts.set(message.type, timeoutId);
  5196. return Promise.resolve({});
  5197. }
  5198. if (message.method == "singlefile.lazyTimeout.clearTimeout") {
  5199. let tabTimeouts = timeouts.get(sender.tab.id);
  5200. if (tabTimeouts) {
  5201. const frameTimeouts = tabTimeouts.get(sender.frameId);
  5202. if (frameTimeouts) {
  5203. const timeoutId = frameTimeouts.get(message.type);
  5204. if (timeoutId) {
  5205. clearTimeout(timeoutId);
  5206. }
  5207. deleteTimeout(frameTimeouts, message.type);
  5208. }
  5209. }
  5210. return Promise.resolve({});
  5211. }
  5212. });
  5213. browser.tabs.onRemoved.addListener(tabId => timeouts.delete(tabId));
  5214. function deleteTimeout(framesTimeouts, type) {
  5215. framesTimeouts.delete(type);
  5216. }
  5217. /*
  5218. * Copyright 2010-2020 Gildas Lormeau
  5219. * contact : gildas.lormeau <at> gmail.com
  5220. *
  5221. * This file is part of SingleFile.
  5222. *
  5223. * The code in this file is free software: you can redistribute it and/or
  5224. * modify it under the terms of the GNU Affero General Public License
  5225. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5226. * of the License, or (at your option) any later version.
  5227. *
  5228. * The code in this file is distributed in the hope that it will be useful,
  5229. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5230. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5231. * General Public License for more details.
  5232. *
  5233. * As additional permission under GNU AGPL version 3 section 7, you may
  5234. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5235. * AGPL normally required by section 4, provided you include this license
  5236. * notice and a URL through which recipients can access the Corresponding
  5237. * Source.
  5238. */
  5239. const ACTION_SAVE_PAGE = "save-page";
  5240. const ACTION_EDIT_AND_SAVE_PAGE = "edit-and-save-page";
  5241. const ACTION_SAVE_SELECTED_LINKS = "save-selected-links";
  5242. const ACTION_SAVE_SELECTED = "save-selected-content";
  5243. const ACTION_SAVE_SELECTED_TABS = "save-selected-tabs";
  5244. const ACTION_SAVE_UNPINNED_TABS = "save-unpinned-tabs";
  5245. const ACTION_SAVE_ALL_TABS = "save-all-tabs";
  5246. async function onMessage(message, sender) {
  5247. if (message == ACTION_SAVE_PAGE) {
  5248. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  5249. tabs.length = 1;
  5250. await saveTabs(tabs);
  5251. } else if (message == ACTION_EDIT_AND_SAVE_PAGE) {
  5252. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  5253. tabs.length = 1;
  5254. await saveTabs(tabs, { openEditor: true });
  5255. } else if (message == ACTION_SAVE_SELECTED_LINKS) {
  5256. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  5257. await saveSelectedLinks(tabs[0]);
  5258. } else if (message == ACTION_SAVE_SELECTED) {
  5259. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  5260. await saveTabs(tabs, { selected: true });
  5261. } else if (message == ACTION_SAVE_SELECTED_TABS) {
  5262. const tabs = await queryTabs({ currentWindow: true, highlighted: true });
  5263. await saveTabs(tabs);
  5264. } else if (message == ACTION_SAVE_UNPINNED_TABS) {
  5265. const tabs = await queryTabs({ currentWindow: true, pinned: false });
  5266. await saveTabs(tabs);
  5267. } else if (message == ACTION_SAVE_ALL_TABS) {
  5268. const tabs = await queryTabs({ currentWindow: true });
  5269. await saveTabs(tabs);
  5270. } else if (message.method) {
  5271. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  5272. const currentTab = tabs[0];
  5273. if (currentTab) {
  5274. return onMessageExternal(message, currentTab);
  5275. } else {
  5276. return false;
  5277. }
  5278. }
  5279. }
  5280. async function queryTabs(options) {
  5281. const tabs = await browser.tabs.query(options);
  5282. return tabs.sort((tab1, tab2) => tab1.index - tab2.index);
  5283. }
  5284. /*
  5285. * Copyright 2010-2020 Gildas Lormeau
  5286. * contact : gildas.lormeau <at> gmail.com
  5287. *
  5288. * This file is part of SingleFile.
  5289. *
  5290. * The code in this file is free software: you can redistribute it and/or
  5291. * modify it under the terms of the GNU Affero General Public License
  5292. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5293. * of the License, or (at your option) any later version.
  5294. *
  5295. * The code in this file is distributed in the hope that it will be useful,
  5296. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5297. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5298. * General Public License for more details.
  5299. *
  5300. * As additional permission under GNU AGPL version 3 section 7, you may
  5301. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5302. * AGPL normally required by section 4, provided you include this license
  5303. * notice and a URL through which recipients can access the Corresponding
  5304. * Source.
  5305. */
  5306. browser.runtime.onMessage.addListener((message, sender) => {
  5307. if (message.method.startsWith("tabs.")) {
  5308. return onMessage$1(message, sender);
  5309. }
  5310. if (message.method.startsWith("downloads.")) {
  5311. return onMessage$4(message, sender);
  5312. }
  5313. if (message.method.startsWith("autosave.")) {
  5314. return onMessage$3(message, sender);
  5315. }
  5316. if (message.method.startsWith("ui.")) {
  5317. return onMessage$7(message, sender);
  5318. }
  5319. if (message.method.startsWith("config.")) {
  5320. return onMessage$d(message);
  5321. }
  5322. if (message.method.startsWith("tabsData.")) {
  5323. return onMessage$e(message);
  5324. }
  5325. if (message.method.startsWith("devtools.")) {
  5326. return onMessage$2(message);
  5327. }
  5328. if (message.method.startsWith("editor.")) {
  5329. return onMessage$b(message, sender);
  5330. }
  5331. if (message.method.startsWith("bookmarks.")) {
  5332. return onMessage$5(message);
  5333. }
  5334. if (message.method.startsWith("companion.")) {
  5335. return onMessage$6(message);
  5336. }
  5337. if (message.method.startsWith("requests.")) {
  5338. return onMessage$a(message);
  5339. }
  5340. if (message.method.startsWith("bootstrap.")) {
  5341. return onMessage$c(message, sender);
  5342. }
  5343. });
  5344. if (browser.runtime.onMessageExternal) {
  5345. browser.runtime.onMessageExternal.addListener(onMessage);
  5346. }
  5347. browser.runtime.onInstalled.addListener((details) => {
  5348. if (details.reason == "update" && details.previousVersion.startsWith("1.21")) {
  5349. browser.tabs.create({
  5350. url: "https://gildas-lormeau.github.io/singlefile-updates/version-1-22.html"
  5351. });
  5352. }
  5353. });
  5354. })();