From 7ad42b0c929de22b5007d596359552e6d1a982fd Mon Sep 17 00:00:00 2001 From: Gildas Date: Sun, 26 Sep 2021 01:24:03 +0200 Subject: [PATCH] build background scripts --- dist/extension-background.js | 1 + dist/single-file-background.js | 1 + extension/core/bg/autosave-util.js | 54 +++++++++++ extension/core/bg/autosave.js | 36 ++----- extension/core/bg/background.html | 9 +- extension/core/bg/bookmarks.js | 9 +- extension/core/bg/business.js | 39 ++++++-- extension/core/bg/config.js | 4 +- extension/core/bg/devtools.js | 4 +- extension/core/bg/download-util.js | 94 ++++++++++++++++++ extension/core/bg/downloads.js | 77 ++------------- extension/core/bg/messages.js | 4 +- extension/core/bg/tabs-data.js | 6 +- extension/core/bg/tabs-util.js | 105 ++++++++++++++++++++ extension/core/bg/tabs.js | 121 +----------------------- extension/lib/single-file/background.js | 26 +++++ extension/ui/bg/index.js | 15 ++- extension/ui/bg/ui-button.js | 18 ++-- extension/ui/bg/ui-commands.js | 19 +++- extension/ui/bg/ui-menus.js | 34 ++++--- rollup.config.dev.js | 14 +++ rollup.config.js | 14 +++ 22 files changed, 428 insertions(+), 276 deletions(-) create mode 100644 dist/extension-background.js create mode 100644 dist/single-file-background.js create mode 100644 extension/core/bg/autosave-util.js create mode 100644 extension/core/bg/download-util.js create mode 100644 extension/core/bg/tabs-util.js create mode 100644 extension/lib/single-file/background.js diff --git a/dist/extension-background.js b/dist/extension-background.js new file mode 100644 index 00000000..a9f241e3 --- /dev/null +++ b/dist/extension-background.js @@ -0,0 +1 @@ +!function(){"use strict";async function e(t,a){let r;try{r=await browser.downloads.download(t)}catch(r){if(r.message){const o=r.message.toLowerCase(),n=o.includes("illegal characters")||o.includes("invalid filename");if(n&&t.filename.startsWith("."))return t.filename=a+t.filename,e(t,a);if(n&&t.filename.includes(","))return t.filename=t.filename.replace(/,/g,a),e(t,a);if(n&&!t.filename.match(/^[\x00-\x7F]+$/))return t.filename=t.filename.replace(/[^\x00-\x7F]+/g,a),e(t,a);if((o.includes("'incognito'")||o.includes('"incognito"'))&&t.incognito)return delete t.incognito,e(t,a);if("conflictaction prompt not yet implemented"==o&&t.conflictAction)return delete t.conflictAction,e(t,a);if(o.includes("canceled"))return{};throw r}throw r}return new Promise(((e,t)=>{browser.downloads.onChanged.addListener((function a(o){o.id==r&&o.state&&("complete"==o.state.current&&(browser.downloads.search({id:r}).then((t=>e({filename:t[0]&&t[0].filename}))).catch((()=>e({}))),browser.downloads.onChanged.removeListener(a)),"interrupted"==o.state.current&&(o.error&&"USER_CANCELED"==o.error.current?e({}):t(new Error(o.state.current)),browser.downloads.onChanged.removeListener(a)))}))}))}let t,a,r;function o(e){return a||(a={}),void 0===e||a[e]||(a[e]={}),a}async function n(e){if(!t){const e=await browser.storage.local.get();t=e.tabsData||{}}return async function(){if(!r){r=!0;const e=await browser.tabs.query({currentWindow:!0,highlighted:!0});Object.keys(t).filter((t=>{if("autoSaveAll"!=t&&"autoSaveUnpinned"!=t&&"profileName"!=t)return!e.find((e=>e.id==t))})).forEach((e=>delete t[e])),await browser.storage.local.set({tabsData:t})}}(),void 0===e||t[e]||(t[e]={}),t}async function s(e){t=e,await browser.storage.local.set({tabsData:e})}setTimeout((()=>n().then((e=>t=e))),0);const i="-",c="__Default_Settings__",l="__Disabled_Settings__",d="regexp:",u={removeHiddenElements:!0,removeUnusedStyles:!0,removeUnusedFonts:!0,removeFrames:!1,removeImports:!0,removeScripts:!0,compressHTML:!0,compressCSS:!1,loadDeferredImages:!0,loadDeferredImagesMaxIdleTime:1500,loadDeferredImagesBlockCookies:!1,loadDeferredImagesBlockStorage:!1,loadDeferredImagesKeepZoomLevel:!1,filenameTemplate:"{page-title} ({date-locale} {time-locale}).html",infobarTemplate:"",includeInfobar:!1,confirmInfobarContent:!1,autoClose:!1,confirmFilename:!1,filenameConflictAction:"uniquify",filenameMaxLength:192,filenameReplacedCharacters:["~","+","\\\\","?","%","*",":","|",'"',"<",">","\0-",""],filenameReplacementCharacter:"_",contextMenuEnabled:!0,tabMenuEnabled:!0,browserActionMenuEnabled:!0,shadowEnabled:!0,logsEnabled:!0,progressBarEnabled:!0,maxResourceSizeEnabled:!1,maxResourceSize:10,removeAudioSrc:!0,removeVideoSrc:!0,displayInfobar:!0,displayStats:!1,backgroundSave:!/Mobile.*Firefox/.test(navigator.userAgent),autoSaveDelay:1,autoSaveLoad:!1,autoSaveUnload:!1,autoSaveLoadOrUnload:!0,autoSaveDiscard:!1,autoSaveRemove:!1,autoSaveRepeat:!1,autoSaveRepeatDelay:10,removeAlternativeFonts:!0,removeAlternativeMedias:!0,removeAlternativeImages:!0,saveRawPage:!1,saveToGDrive:!1,saveToGitHub:!1,githubToken:"",githubUser:"",githubRepository:"SingleFileZ-Archives",githubBranch:"main",forceWebAuthFlow:!1,extractAuthCode:!0,insertTextBody:!0,resolveFragmentIdentifierURLs:!1,userScriptEnabled:!1,saveCreatedBookmarks:!1,allowedBookmarkFolders:[],ignoredBookmarkFolders:[],replaceBookmarkURL:!0,saveFavicon:!0,includeBOM:!1,createRootDirectory:!1,selfExtractingArchive:!0,insertMetaNoIndex:!1,insertMetaCSP:!0,passReferrerOnError:!1,password:"",insertSingleFileComment:!0};let f,w=h();async function h(){const{sync:e}=await browser.storage.local.get();f=e?browser.storage.sync:browser.storage.local;const t=await f.get();if(t.profiles)t.rules||(t.rules=[]),Object.keys(t.profiles).forEach((e=>m(t.profiles[e]))),await f.remove(["profiles","defaultProfile","rules"]),await f.set({profiles:t.profiles,rules:t.rules});else{const e=t;delete e.tabsData,m(e);const a={profiles:{},rules:[]};a.profiles.__Default_Settings__=e,f.remove(Object.keys(u)),await f.set(a)}t.maxParallelWorkers||await f.set({maxParallelWorkers:navigator.hardwareConcurrency||4})}function m(e){Object.keys(u).forEach((t=>function(e,t){void 0===e[t]&&(e[t]=u[t])}(e,t)))}async function p(e,t){const a=await b();let r=a.rules.filter((e=>y(e))).sort(g).find((t=>e&&e.match(new RegExp(t.url.split(d)[1]))));if(!r){r=a.rules.filter((e=>!y(e))).sort(g).find((a=>!t&&"*"==a.url||e&&e.includes(a.url)))}return r}async function b(){return await w,f.get(["profiles","rules","maxParallelWorkers"])}function g(e,t){return t.url.length-e.url.length}function y(e){return e.url.toLowerCase().startsWith(d)}async function v(t){if(t.method.endsWith(".deleteRules")&&await async function(e){const t=await b();t.rules=t.rules=e?t.rules.filter((t=>t.autoSaveProfile!=e&&t.profile!=e)):[],await f.set({rules:t.rules})}(t.profileName),t.method.endsWith(".deleteRule")&&await async function(e){if(!e)throw new Error("URL is empty");const t=await b();t.rules=t.rules.filter((t=>t.url!=e)),await f.set({rules:t.rules})}(t.url),t.method.endsWith(".addRule")&&await x(t.url,t.profileName,t.autoSaveProfileName),t.method.endsWith(".createProfile")&&await async function(e,t){const a=await b();if(Object.keys(a.profiles).includes(e))throw new Error("Duplicate profile name");a.profiles[e]=JSON.parse(JSON.stringify(a.profiles[t])),await f.set({profiles:a.profiles})}(t.profileName,t.fromProfileName||c),t.method.endsWith(".renameProfile")&&await async function(e,t){const[a,r]=await Promise.all([b(),n()]);if(!Object.keys(a.profiles).includes(e))throw new Error("Profile not found");if(Object.keys(a.profiles).includes(t))throw new Error("Duplicate profile name");if(e==c)throw new Error("Default settings cannot be renamed");r.profileName==e&&(r.profileName=t,await s(r));a.profiles[t]=a.profiles[e],a.rules.forEach((a=>{a.profile==e&&(a.profile=t),a.autoSaveProfile==e&&(a.autoSaveProfile=t)})),delete a.profiles[e],await f.set({profiles:a.profiles,rules:a.rules})}(t.profileName,t.newProfileName),t.method.endsWith(".deleteProfile")&&await async function(e){const[t,a]=await Promise.all([b(),n()]);if(!Object.keys(t.profiles).includes(e))throw new Error("Profile not found");if(e==c)throw new Error("Default settings cannot be deleted");a.profileName==e&&(delete a.profileName,await s(a));t.rules.forEach((t=>{t.profile==e&&(t.profile=c),t.autoSaveProfile==e&&(t.autoSaveProfile=c)})),delete t.profiles[e],await f.set({profiles:t.profiles,rules:t.rules})}(t.profileName),t.method.endsWith(".resetProfiles")&&await async function(){await w;const e=await n();delete e.profileName,await s(e),await f.remove(["profiles","rules","maxParallelWorkers"]),await browser.storage.local.set({sync:!1}),f=browser.storage.local,await h()}(),t.method.endsWith(".resetProfile")&&await async function(e){const t=await b();if(!Object.keys(t.profiles).includes(e))throw new Error("Profile not found");t.profiles[e]=u,await f.set({profiles:t.profiles})}(t.profileName),t.method.endsWith(".importConfig")&&await async function(e){await f.remove(["profiles","rules","maxParallelWorkers"]),await f.set({profiles:e.profiles,rules:e.rules,maxParallelWorkers:e.maxParallelWorkers}),await h()}(t.config),t.method.endsWith(".updateProfile")&&await async function(e,t){const a=await b();if(!Object.keys(a.profiles).includes(e))throw new Error("Profile not found");Object.keys(t).forEach((r=>a.profiles[e][r]=t[r])),await f.set({profiles:a.profiles})}(t.profileName,t.profile),t.method.endsWith(".updateRule")&&await S(t.url,t.newUrl,t.profileName,t.autoSaveProfileName),t.method.endsWith(".getConstants"))return{DISABLED_PROFILE_NAME:l,DEFAULT_PROFILE_NAME:c,CURRENT_PROFILE_NAME:i};if(t.method.endsWith(".getRules"))return async function(){return(await b()).rules}();if(t.method.endsWith(".getProfiles"))return k();if(t.method.endsWith(".exportConfig"))return async function(){const t=await b(),a=URL.createObjectURL(new Blob([JSON.stringify({profiles:t.profiles,rules:t.rules,maxParallelWorkers:t.maxParallelWorkers},null,2)],{type:"text/json"})),r={url:a,filename:`singlefilez-settings-${(new Date).toISOString().replace(/:/g,"_")}.json`,saveAs:!0};try{await e(r,"_")}finally{URL.revokeObjectURL(a)}}();if(t.method.endsWith(".enableSync")){await browser.storage.local.set({sync:!0});const e=await browser.storage.sync.get();if(!e||!e.profiles){const e=await browser.storage.local.get();await browser.storage.sync.set({profiles:e.profiles,rules:e.rules,maxParallelWorkers:e.maxParallelWorkers})}return f=browser.storage.sync,{}}if(t.method.endsWith(".disableSync")){await browser.storage.local.set({sync:!1});const e=await browser.storage.sync.get();e&&e.profiles&&await browser.storage.local.set({profiles:e.profiles,rules:e.rules,maxParallelWorkers:e.maxParallelWorkers}),f=browser.storage.local}return t.method.endsWith(".isSync")?{sync:(await browser.storage.local.get()).sync}:{}}async function k(){return(await b()).profiles}async function I(e,t){const[a,r,o]=await Promise.all([b(),p(e),n()]),s=o.profileName||c;let l;if(r){const e=r[t?"autoSaveProfile":"profile"];l=e==i?s:e}else l=s;return Object.assign({profileName:l},a.profiles[l])}async function x(e,t,a){if(!e)throw new Error("URL is empty");const r=await b();if(r.rules.find((t=>t.url==e)))throw new Error("URL already exists");r.rules.push({url:e,profile:t,autoSaveProfile:a}),await f.set({rules:r.rules})}async function S(e,t,a,r){if(!e||!t)throw new Error("URL is empty");const o=await b(),n=o.rules.find((t=>t.url==e));if(!n)throw new Error("URL not found");if(o.rules.find((a=>a.url==t&&a.url!=e)))throw new Error("New URL already exists");n.url=t,n.profile=a,n.autoSaveProfile=r,await f.set({rules:o.rules})}async function T(){return(await f.get()).authInfo}async function A(e){await f.set({authInfo:e})}async function P(){let e=T();e.revokableAccessToken?A({revokableAccessToken:e.revokableAccessToken}):await f.remove(["authInfo"])}async function R(e){if(e){const[t,a]=await Promise.all([n(),p(e.url)]);return Boolean(t.autoSaveAll||t.autoSaveUnpinned&&!e.pinned||t[e.id]&&t[e.id].autoSave)&&(!a||a.autoSaveProfile!=l)}}const E=new Map,M="x-single-file-request-id",L=8388608;function C(e,t,a){return new Promise(((r,o)=>{const n=new XMLHttpRequest;if(n.withCredentials=!0,n.responseType="arraybuffer",n.onerror=e=>o(new Error(e.detail)),n.onreadystatechange=()=>{n.readyState==XMLHttpRequest.DONE&&(n.status||n.response.byteLength?401!=n.status&&403!=n.status&&404!=n.status||a?r({array:Array.from(new Uint8Array(n.response)),headers:{"content-type":n.getResponseHeader("Content-Type")},status:n.status}):C(e,t,!0).then(r).catch(o):o())},n.open("GET",e,!0),a){const e=String(Math.random()).substring(2);s=e,i=t.referrer,E.set(s,i),n.setRequestHeader(M,e)}var s,i;n.send()}))}browser.runtime.onMessage.addListener(((e,t)=>{if(e.method&&e.method.startsWith("singlefile.fetch"))return new Promise((a=>{(function(e,t){if("singlefile.fetch"==e.method)return C(e.url,{referrer:e.referrer});if("singlefile.fetchFrame"==e.method)return browser.tabs.sendMessage(t.tab.id,e)})(e,t).then(a).catch((e=>a({error:e&&e.toString()})))}));"singlefile.multipartFetch"==e.method&&async function(e,t){try{const a=await C(e.url),r=e.id;for(let e=0;e*LL,o.truncated?(o.finished=(e+1)*L>a.array.length,o.array=a.array.slice(e*L,(e+1)*L)):o.array=a.array,o.truncated&&!o.finished||(o.headers=a.headers,o.status=a.status),browser.tabs.sendMessage(t.tab.id,o)}}catch(a){await browser.tabs.sendMessage(t.tab.id,{method:"singlefile.multipartResponse",id:e.id,error:a&&a.toString()})}}(e,t)}));let W=!1;function B(e){return e.method.endsWith(".enableReferrerOnError")?(_(),{}):e.method.endsWith(".disableReferrerOnError")?(function(){try{browser.webRequest.onBeforeSendHeaders.removeListener(U)}catch(e){}W=!1}(),{}):void 0}function U(e){if(W){let t=e.requestHeaders.find((e=>e.name===M));if(t){e.requestHeaders=e.requestHeaders.filter((e=>e.name!==M));const a=E.get(t.value);if(a){E.delete(t.value);if(!e.requestHeaders.find((e=>"referer"===e.name.toLowerCase())))return e.requestHeaders.push({name:"Referer",value:a}),{requestHeaders:e.requestHeaders}}}}}function _(){if(!W){try{browser.webRequest.onBeforeSendHeaders.addListener(U,{urls:[""]},["blocking","requestHeaders","extraHeaders"])}catch(e){browser.webRequest.onBeforeSendHeaders.addListener(U,{urls:[""]},["blocking","requestHeaders"])}W=!0}}const D=new Map;async function O(e){return(await browser.tabs.query(e)).sort(((e,t)=>e.index-t.index))}const j="/extension/ui/resources/icon_128.png",F="/extension/ui/resources/icon_128_wait",N=browser.i18n.getMessage("buttonDefaultTooltip"),q=browser.i18n.getMessage("buttonBlockedTooltip"),z=browser.i18n.getMessage("buttonInitializingBadge"),H=browser.i18n.getMessage("buttonInitializingTooltip"),G=browser.i18n.getMessage("buttonErrorBadge"),J=browser.i18n.getMessage("buttonBlockedBadge"),$=browser.i18n.getMessage("buttonOKBadge"),X=browser.i18n.getMessage("buttonSaveProgressTooltip"),K=browser.i18n.getMessage("buttonUploadProgressTooltip"),V=browser.i18n.getMessage("buttonAutoSaveActiveBadge"),Z=browser.i18n.getMessage("buttonAutoSaveActiveTooltip"),Q=[2,147,20,192],Y=[4,229,36,192],ee={default:{setBadgeBackgroundColor:{color:Q},setBadgeText:{text:""},setTitle:{title:N},setIcon:{path:j}},inject:{setBadgeBackgroundColor:{color:Q},setBadgeText:{text:z},setTitle:{title:H}},execute:{setBadgeBackgroundColor:{color:Y},setBadgeText:{text:z}},progress:{setBadgeBackgroundColor:{color:Y},setBadgeText:{text:""}},end:{setBadgeBackgroundColor:{color:Y},setBadgeText:{text:$},setTitle:{title:N},setIcon:{path:j}},error:{setBadgeBackgroundColor:{color:[229,4,12,192]},setBadgeText:{text:G},setTitle:{title:""},setIcon:{path:j}},forbidden:{setBadgeBackgroundColor:{color:[255,255,255,1]},setBadgeText:{text:J},setTitle:{title:q},setIcon:{path:j}},autosave:{inject:{setBadgeBackgroundColor:{color:[64,64,64,192]},setBadgeText:{text:V},setTitle:{title:Z},setIcon:{path:j}},default:{setBadgeBackgroundColor:{color:[208,208,208,192]},setBadgeText:{text:V},setTitle:{title:Z},setIcon:{path:j}}}};let te;function ae(e,t){if(e.method.endsWith(".processInit")){delete o(t.tab.id)[t.tab.id].button,se(t.tab)}var a,r,n;return e.method.endsWith(".processProgress")&&e.maxIndex&&(a=t.tab.id,r=e.index,n=e.maxIndex,ne(a,r,n,X)),e.method.endsWith(".processError")&&(e.error&&console.error("Initialization error",e.error),re(t.tab.id)),e.method.endsWith(".processCancelled")&&oe(t.tab),Promise.resolve({})}function re(e){ie(e,le("error"))}function oe(e){se(e)}function ne(e,t,a,r){const o=Math.max(Math.min(20,Math.floor(t/a*20)),0),n=Math.min(Math.floor(t/a*8),8),s=F+n+".png",i=le("progress");i.setTitle={title:r+5*o+"%"},i.setIcon={path:s},ie(e,i)}async function se(e){const t=le("default",await R(e));await ie(e.id,t)}async function ie(e,t){const a=o(e);if(t){a[e].button||(a[e].button={lastState:null});const r=a[e].button.lastState||{},o={};Object.keys(t).forEach((e=>{void 0!==t[e]&&JSON.stringify(r[e])!=JSON.stringify(t[e])&&(o[e]=t[e])})),Object.keys(o).length&&(a[e].button.lastState=t,await async function(e,t){for(const a of Object.keys(t))await ce(e,a,t[a])}(e,o))}}async function ce(e,t,a){if(browser.browserAction[t]){const r=JSON.parse(JSON.stringify(a));r.tabId=e,await browser.browserAction[t](r)}}function le(e,t){return JSON.parse(JSON.stringify(t?ee.autosave[e]:ee[e]))}browser.browserAction.onClicked.addListener((async e=>{const t=await O({currentWindow:!0,highlighted:!0});t.length<=1?function(e){te.isSavingTab(e)?te.cancelTab(e.id):te.saveTabs([e])}(e):te.saveTabs(t)}));const de=browser.menus||browser.contextMenus,ue=de&&de.onClicked&&de.create&&de.update&&de.removeAll,fe="save-page",we="save-with-profile",he="save-selected-links",me="view-pendings",pe="select-profile",be="wasve-with-profile-",ge="select-profile-",ye="associate-with-profile",ve="associate-with-profile-",ke="save-selected",Ie="save-frame",xe="save-tabs",Se="save-selected-tabs",Te="save-unpinned-tabs",Ae="save-all-tabs",Pe="button-save-selected-tabs",Re="button-save-unpinned-tabs",Ee="button-save-all-tabs",Me="auto-save",Le="auto-save-disabled",Ce="auto-save-tab",We="auto-save-unpinned",Be="auto-save-all",Ue=browser.i18n.getMessage("menuCreateDomainRule"),_e=browser.i18n.getMessage("menuUpdateRule"),De=browser.i18n.getMessage("menuSavePage"),Oe=browser.i18n.getMessage("menuSaveWithProfile"),je=browser.i18n.getMessage("menuSaveSelectedLinks"),Fe=browser.i18n.getMessage("menuViewPendingSaves"),Ne=browser.i18n.getMessage("menuSaveSelection"),qe=browser.i18n.getMessage("menuSaveFrame"),ze=browser.i18n.getMessage("menuSaveTabs"),He=browser.i18n.getMessage("menuSaveSelectedTabs"),Ge=browser.i18n.getMessage("menuSaveUnpinnedTabs"),Je=browser.i18n.getMessage("menuSaveAllTabs"),$e=browser.i18n.getMessage("menuSelectProfile"),Xe=browser.i18n.getMessage("profileDefaultSettings"),Ke=browser.i18n.getMessage("menuAutoSave"),Ve=browser.i18n.getMessage("menuAutoSaveDisabled"),Ze=browser.i18n.getMessage("menuAutoSaveTab"),Qe=browser.i18n.getMessage("menuAutoSaveUnpinnedTabs"),Ye=browser.i18n.getMessage("menuAutoSaveAllTabs"),et=new Map,tt=new Map;let at,rt,ot,nt,st=new Map;async function it(e){const[t,a]=await Promise.all([k(),n()]),r=await I(e&&e.url);if(ue&&r){const o=["page","frame","image","link","video","audio","selection"],n=[];if(r.browserActionMenuEnabled&&n.push("browser_action"),r.tabMenuEnabled)try{de.create({id:"temporary-id",contexts:["tab"],title:"title"}),n.push("tab")}catch(e){r.tabMenuEnabled=!1}await de.removeAll();const s=n.concat(...o),l=r.contextMenuEnabled?s:n;if(de.create({id:fe,contexts:l,title:De}),de.create({id:he,contexts:r.contextMenuEnabled?n.concat(["selection"]):n,title:je}),Object.keys(t).length>1&&de.create({id:we,contexts:l,title:Oe}),r.contextMenuEnabled&&de.create({id:"separator-1",contexts:o,type:"separator"}),de.create({id:ke,contexts:l,title:Ne}),r.contextMenuEnabled&&de.create({id:Ie,contexts:["frame"],title:qe}),de.create({id:xe,contexts:n,title:ze}),de.create({id:Pe,contexts:n,title:He,parentId:xe}),de.create({id:Re,contexts:n,title:Ge,parentId:xe}),de.create({id:Ee,contexts:n,title:Je,parentId:xe}),r.contextMenuEnabled&&(de.create({id:Se,contexts:o,title:He}),de.create({id:Te,contexts:o,title:Ge}),de.create({id:Ae,contexts:o,title:Je}),de.create({id:"separator-2",contexts:o,type:"separator"})),Object.keys(t).length>1){de.create({id:pe,title:$e,contexts:l}),de.create({id:"wasve-with-profile-default",contexts:l,title:Xe,parentId:we});const n="select-profile-default",s=!a.profileName||a.profileName==c;let d;de.create({id:n,type:"radio",contexts:l,title:Xe,checked:s,parentId:pe}),et.set(n,s),de.create({id:ye,title:Ue,contexts:l}),tt.set(ye,Ue),e&&e.url&&(d=await p(e.url,!0));const u="associate-with-profile-current",f=!d||d.profile==i;de.create({id:u,type:"radio",contexts:l,title:i,checked:f,parentId:ye}),et.set(u,f);const w="associate-with-profile-default",h=Boolean(d)&&d.profile==c;de.create({id:w,type:"radio",contexts:l,title:Xe,checked:h,parentId:ye}),et.set(w,h),st=new Map,Object.keys(t).forEach(((e,t)=>{if(e!=c){let r=be+t;de.create({id:r,contexts:l,title:e,parentId:we}),r=ge+t;let o=a.profileName==e;de.create({id:r,type:"radio",contexts:l,title:e,checked:o,parentId:pe}),et.set(r,o),r=ve+t,o=Boolean(d)&&d.profile==e,de.create({id:r,type:"radio",contexts:l,title:e,checked:o,parentId:ye}),et.set(r,o),st.set(e,t)}})),r.contextMenuEnabled&&de.create({id:"separator-3",contexts:o,type:"separator"})}de.create({id:Me,contexts:l,title:Ke}),de.create({id:Le,type:"radio",title:Ve,contexts:l,checked:!0,parentId:Me}),et.set(Le,!0),de.create({id:Ce,type:"radio",title:Ze,contexts:l,checked:!1,parentId:Me}),et.set(Ce,!1),de.create({id:We,type:"radio",title:Qe,contexts:l,checked:!1,parentId:Me}),et.set(We,!1),de.create({id:Be,type:"radio",title:Ye,contexts:l,checked:!1,parentId:Me}),et.set(Be,!1),de.create({id:"separator-4",contexts:l,type:"separator"}),de.create({id:me,contexts:l,title:Fe})}rt=!0,ot&&(ot=!1,(await browser.tabs.query({})).forEach((async e=>await lt(e))))}async function ct(e){const t=await n(e.id);await async function(){const e=await browser.tabs.query({});return Promise.all(e.map((async e=>{const[t,a]=await Promise.all([I(e.url,!0),R(e)]);try{await browser.tabs.sendMessage(e.id,{method:"content.init",autoSaveEnabled:a,options:t})}catch(e){}})))}(),await se(e);try{await browser.runtime.sendMessage({method:"options.refresh",profileName:t.profileName})}catch(e){}}async function lt(e){if(ue&&rt){const t=await n(e.id),a=[];if(a.push(ut(Le,!t[e.id].autoSave)),a.push(ut(Ce,t[e.id].autoSave)),a.push(ut(We,Boolean(t.autoSaveUnpinned))),a.push(ut(Be,Boolean(t.autoSaveAll))),e&&e.url){const t=await I(e.url);a.push(async function(e,t){const a=at;at=t,(void 0===a||a!=t)&&await it(e)}(e,t.contextMenuEnabled)),a.push(de.update(ke,{visible:!t.saveRawPage}));let r="associate-with-profile-default",o=Ue;const[n,s]=await Promise.all([k(),p(e.url)]);if(s){const e=st.get(s.profile);e&&(r=ve+e,o=_e)}Object.keys(n).length>1&&(Object.keys(n).forEach(((e,t)=>{e==c?a.push(ut("associate-with-profile-default","associate-with-profile-default"==r)):a.push(ut(ve+t,r==ve+t))})),a.push(dt(ye,o)))}await Promise.all(a)}}function dt(e,t){const a=tt.get(e);return tt.set(e,t),void 0===a||a!=t?de.update(e,{title:t}):void 0}async function ut(e,t){t=Boolean(t),et.set(e,t),await de.update(e,{checked:t})}Promise.resolve().then((async function(){ue&&(it(),de.onClicked.addListener((async(e,t)=>{if(e.menuItemId==fe&&(e.linkUrl?nt.saveUrls([e.linkUrl]):nt.saveTabs([t])),e.menuItemId==he&&nt.saveSelectedLinks(t),e.menuItemId==me&&await browser.tabs.create({active:!0,url:"/extension/ui/pages/pendings.html"}),e.menuItemId==ke&&nt.saveTabs([t],{selected:!0}),e.menuItemId==Ie&&nt.saveTabs([t],{frameId:e.frameId}),e.menuItemId==Se||e.menuItemId==Pe){const e=await O({currentWindow:!0,highlighted:!0});nt.saveTabs(e)}if(e.menuItemId==Te||e.menuItemId==Re){const e=await O({currentWindow:!0,pinned:!1});nt.saveTabs(e)}if(e.menuItemId==Ae||e.menuItemId==Ee){const e=await O({currentWindow:!0});nt.saveTabs(e)}if(e.menuItemId==Ce){const e=await n(t.id);e[t.id].autoSave=!0,await s(e),ct(t)}if(e.menuItemId==Le){const e=await n();Object.keys(e).forEach((t=>{"object"==typeof e[t]&&e[t].autoSave&&(e[t].autoSave=!1)})),e.autoSaveUnpinned=e.autoSaveAll=!1,await s(e),ct(t)}if(e.menuItemId==Be){const a=await n();a.autoSaveAll=e.checked,await s(a),ct(t)}if(e.menuItemId==We){const a=await n();a.autoSaveUnpinned=e.checked,await s(a),ct(t)}if(e.menuItemId.startsWith(be)){const a=await k(),r=e.menuItemId.split(be)[1];let o;if("default"==r)o=c;else{const e=Number(r);o=Object.keys(a)[e]}a[o].profileName=o,nt.saveTabs([t],a[o])}if(e.menuItemId.startsWith(ge)){const[a,r]=await Promise.all([k(),n()]),o=e.menuItemId.split(ge)[1];if("default"==o)r.profileName=c;else{const e=Number(o);r.profileName=Object.keys(a)[e]}await s(r),ct(t)}if(e.menuItemId.startsWith(ve)){const[a,r]=await Promise.all([k(),p(t.url,!0)]),o=e.menuItemId.split(ve)[1];let n;if("default"==o)n=c;else if("current"==o)n=i;else{const e=Number(o);n=Object.keys(a)[e]}r?await S(r.url,r.url,n,n):(await dt(ye,_e),await x(new URL(t.url).hostname,n,n))}})),rt?ot=!0:(await browser.tabs.query({})).forEach((async e=>await lt(e))))}));const ft=browser.commands;let wt,ht,mt;function pt(e,t){return e.method.endsWith(".refreshMenu")?function(e){if(e.method.endsWith("refreshMenu"))return it(),Promise.resolve({})}(e):ae(e,t)}function bt(e){!function(e){ie(e.id,le("forbidden"))}(e)}function gt(e,t,a){!function(e,t,a){let r;a?r=le("inject",!0):(r=le(1==t?"inject":"execute"),r.setTitle={title:H+" ("+t+"/2)"},r.setIcon={path:F+"0.png"}),ie(e,r)}(e,t,a)}async function yt(e,t){re(e),t&&await browser.tabs.sendMessage(e,{method:"content.error",error:t.toString()})}function vt(e,t){!function(e,t){ie(e,t?le("default",!0):le("end"))}(e,t)}function kt(e,t,a){!function(e,t,a){ne(e,t,a,K)}(e,t,a)}ft&&ft.onCommand&&ft.onCommand.addListener&&ft.onCommand.addListener((async e=>{if("save-selected-tabs"==e){const e=await O({currentWindow:!0,highlighted:!0});wt.saveTabs(e,{optionallySelected:!0})}if("save-all-tabs"==e){const e=await O({currentWindow:!0});wt.saveTabs(e)}}));const It=["dist/chrome-browser-polyfill.js","dist/single-file.js"],xt=["dist/chrome-browser-polyfill.js","dist/single-file-frames.js"];async function St(e,t){let a;if(await async function(e){const t=e.extensionScriptFiles||[];ht||mt||([ht,mt]=await Promise.all([Tt(It.concat(t)),Tt(xt)]))}(t),!t.removeFrames)try{await browser.tabs.executeScript(e,{code:mt,allFrames:!0,matchAboutBlank:!0,runAt:"document_start"})}catch(e){}try{await browser.tabs.executeScript(e,{code:ht,allFrames:!1,runAt:"document_idle"}),a=!0}catch(e){}return a&&t.frameId&&await browser.tabs.executeScript(e,{code:"document.documentElement.dataset.requestedFrameId = true",frameId:t.frameId,matchAboutBlank:!0,runAt:"document_start"}),a}async function Tt(e){const t=e.map((async e=>{if("function"==typeof e)return"("+e.toString()+")();";{const t=await fetch(browser.runtime.getURL("../../../"+e));return(new TextDecoder).decode(await t.arrayBuffer())}}));let a="";for(const e of t)a+=await e;return a}const At="single-filez-response-fetch",Pt=window.fetch;async function Rt(e,t={}){try{let t=await Pt(e,{cache:"force-cache"});return 401!=t.status&&403!=t.status&&404!=t.status||(t=await Lt(e)),t}catch(a){const r=await Mt({method:"singlefile.fetch",url:e,referrer:t.referrer});return{status:r.status,headers:{get:e=>r.headers&&r.headers[e]},arrayBuffer:async()=>new Uint8Array(r.array).buffer}}}async function Et(e,t){const a=await Mt({method:"singlefile.fetchFrame",url:e,frameId:t.frameId,referrer:t.referrer});return{status:a.status,headers:new Map(a.headers),arrayBuffer:async()=>new Uint8Array(a.array).buffer}}async function Mt(e){const t=await browser.runtime.sendMessage(e);if(!t||t.error)throw new Error(t&&t.error&&t.error.toString());return t}function Lt(e){return new Promise(((t,a)=>{var r,o,n,s;r=new CustomEvent("single-filez-request-fetch",{detail:e}),window.dispatchEvent(r),o=At,n=function r(o){var n,s,i;o.detail?o.detail.url==e&&(n=At,s=r,i=!1,window.removeEventListener(n,s,i),o.detail.response?t({status:o.detail.status,headers:new Map(o.detail.headers),arrayBuffer:async()=>o.detail.response}):a(o.detail.error)):a()},s=!1,window.addEventListener(o,n,s)}))}function Ct(e,t){return St(e,t)}browser.runtime.onMessage.addListener((e=>{if("singlefile.fetchFrame"==e.method&&window.frameId&&window.frameId==e.frameId)return async function(e){try{let t=await Pt(e.url,{cache:"force-cache"});return 401!=t.status&&403!=t.status&&404!=t.status||(t=await Promise.race([Lt(e.url),new Promise(((e,t)=>setTimeout((()=>t()),5e3)))])),{status:t.status,headers:[...t.headers],array:Array.from(new Uint8Array(await t.arrayBuffer()))}}catch(e){return{error:e&&e.toString()}}}(e)}));const Wt="Could not establish connection. Receiving end does not exist.",Bt="The message port closed before a response was received.",Ut="Message manager disconnected",_t="pending",Dt="processing",Ot=["dist/protobuf.js","dist/infobar.js","dist/extension.js","/extension/core/common/page-proto.js"],jt=[];let Ft,Nt=0;var qt;async function zt(e,t={}){await Jt(),await Promise.all(e.map((async e=>{const a=await I(e);Object.keys(t).forEach((e=>a[e]=t[e])),a.autoClose=!0,a.extensionScriptFiles=Ot,a.passReferrerOnError&&await _(),Gt({tab:{url:e},status:_t,options:a,method:"content.save"})}))),$t()}async function Ht(e,t={}){await Jt(),await Promise.all(e.map((async e=>{const a=e.id,r=await I(e.url);if(Object.keys(t).forEach((e=>r[e]=t[e])),r.tabId=a,r.tabIndex=e.index,r.extensionScriptFiles=Ot,r.passReferrerOnError&&await _(),t.autoSave){if(R(e)){Xt(Gt({status:Dt,tab:e,options:r,method:"content.autosave"}))}}else{gt(a,1);await Ct(a,r)?(gt(a,2),Gt({status:_t,tab:e,options:r,method:"content.save"})):bt(e)}}))),$t()}function Gt(e){const t={id:Nt,status:e.status,tab:e.tab,options:e.options,method:e.method,done:function(){jt.splice(jt.findIndex((e=>e.id==this.id)),1),$t()}};return jt.push(t),Nt++,t}async function Jt(){Ft||(Ft=(await b()).maxParallelWorkers)}function $t(){const e=jt.filter((e=>e.status==Dt)).length;for(let t=0;te.status==_t));e&&Xt(e)}}async function Xt(e){const t=e.id;if(e.status=Dt,!e.tab.id){let t;try{const a=await async function(e){const t=await browser.tabs.create(e);return new Promise(((e,a)=>{function r(a,n){a==t.id&&"complete"==n.status&&(e(t),browser.tabs.onUpdated.removeListener(r),browser.tabs.onRemoved.removeListener(o))}function o(e){e==t.id&&(a(e),browser.tabs.onRemoved.removeListener(o))}browser.tabs.onUpdated.addListener(r),browser.tabs.onRemoved.addListener(o)}))}({url:e.tab.url,active:!1});e.tab.id=e.options.tabId=a.id,e.tab.index=e.options.tabIndex=a.index,gt(e.tab.id,1),t=await Ct(e.tab.id,e.options)}catch(t){e.tab.id=t}if(!t)return void e.done();gt(e.tab.id,2)}e.options.taskId=t;try{await browser.tabs.sendMessage(e.tab.id,{method:e.method,options:e.options})}catch(t){!t||t.message&&function(e){return e.message==Bt||e.message==Wt||e.message==Ut}(t)||(console.log(t.message?t.message:t),yt(e.tab.id,t.message),e.done())}}function Kt(e){const t=jt.find((t=>t.id==e));t&&(t.options.autoClose&&!t.cancelled&&browser.tabs.remove(t.tab.id),t.done())}function Vt(e,t){const a=jt.find((t=>t.id==e));a&&(a.cancel=t)}function Zt(e){Array.from(jt).filter((t=>t.tab.id==e&&!t.options.autoSave)).forEach(Yt)}function Qt(e){return jt.find((t=>t.id==e))}function Yt(e){const t=e.tab.id;e.cancelled=!0,browser.tabs.sendMessage(t,{method:"content.cancelSave",options:{loadDeferredImages:e.options.loadDeferredImages,loadDeferredImagesKeepZoomLevel:e.options.loadDeferredImagesKeepZoomLevel}}),e.cancel&&e.cancel(),"content.autosave"==e.method&&vt(t,!0),function(e){oe(e)}(e.tab),e.done()}function ea(e){return{id:e.id,tabId:e.tab.id,index:e.tab.index,url:e.tab.url,title:e.tab.title,cancelled:e.cancelled,status:e.status}}async function ta(e){return e.method.endsWith(".saveCreatedBookmarks")?(aa(),{}):e.method.endsWith(".disable")?(async function(){let e;const t=await k();Object.keys(t).forEach((a=>e=e||!t[a].saveCreatedBookmarks)),e&&browser.bookmarks.onCreated.removeListener(ra)}(),{}):void 0}async function aa(){try{browser.bookmarks.onCreated.removeListener(ra)}catch(e){}let e;const t=await k();Object.keys(t).forEach((a=>{t[a].saveCreatedBookmarks&&(e=!0)})),e&&browser.bookmarks.onCreated.addListener(ra)}async function ra(e,t){const a=await browser.tabs.query({lastFocusedWindow:!0,active:!0}),r=await I(t.url);if(r.saveCreatedBookmarks){const n=await async function e(t,a=[]){if(t){const r=(await browser.bookmarks.get(t))[0];r&&r.title&&(a.unshift(r.title),await e(r.parentId,a))}return a}(t.parentId),s=r.allowedBookmarkFolders.toString(),i=n.find((e=>r.allowedBookmarkFolders.includes(e))),c=r.ignoredBookmarkFolders.toString(),l=n.find((e=>r.ignoredBookmarkFolders.includes(e)));if((s&&i||!s)&&(c&&!l||!c))if(a.length&&a[0].url==t.url)Ht(a,{bookmarkId:e,bookmarkFolders:n});else{const a=await browser.tabs.query({});if(a.length){const r=a.find((e=>e.url==t.url));r?Ht([r],{bookmarkId:e,bookmarkFolders:n}):t.url&&("about:blank"==t.url?browser.bookmarks.onChanged.addListener((function t(a,r){a==e&&r.url&&(browser.bookmarks.onChanged.removeListener(t),o(r.url))})):o(t.url))}}}function o(t){zt([t],{bookmarkId:e})}}(function(e){nt=e})(qt={isSavingTab:function(e){return Boolean(jt.find((t=>t.tab.id==e.id)))},saveTabs:Ht,saveUrls:zt,cancelTab:Zt,saveSelectedLinks:async function(e){const t={extensionScriptFiles:Ot,tabId:e.id,tabIndex:e.index};if(await Ct(e.id,t)){const t=await browser.tabs.sendMessage(e.id,{method:"content.getSelectedLinks"});t.urls&&t.urls.length&&await zt(t.urls)}else bt(e)}}),function(e){te=e}(qt),function(e){wt=e}(qt),Promise.resolve().then(aa);const oa="https://oauth2.googleapis.com/token",na="https://www.googleapis.com/drive/v3/files";let sa,ia=!0;class ca{constructor(e){this.file=e.file,this.onProgress=e.onProgress,this.contentType=this.file.type||"application/octet-stream",this.metadata={name:e.filename,mimeType:this.contentType,parents:e.parents||["root"]},this.token=e.token,this.offset=0,this.chunkSize=e.chunkSize||524288}async upload(){const e=pa(await fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable",{method:"POST",headers:{Authorization:"Bearer "+this.token,"Content-Type":"application/json","X-Upload-Content-Length":this.file.size,"X-Upload-Content-Type":this.contentType},body:JSON.stringify(this.metadata)})).headers.get("Location");if(this.url=e,!this.cancelled)return this.onProgress&&this.onProgress(0,this.file.size),ha(this)}}async function la(e,t){const a=await fetch(oa,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"client_id="+e.clientId+"&grant_type=authorization_code&code="+t.code+"&redirect_uri="+e.redirectURI}),r=await ma(a);return e.accessToken=r.access_token,e.refreshToken=r.refresh_token,e.expirationDate=Date.now()+1e3*r.expires_in,{accessToken:e.accessToken,refreshToken:e.refreshToken,expirationDate:e.expirationDate}}function da(e,t={}){return e.redirectURI=encodeURIComponent("urn:ietf:wg:oauth:2.0:oob"+(t.auto?":auto":"")),e.authURL="https://accounts.google.com/o/oauth2/v2/auth?client_id="+e.clientId+"&response_type=code&access_type=offline&redirect_uri="+e.redirectURI+"&scope="+e.scopes.join(" "),e.authURL}function ua(e={}){return Boolean(browser.identity&&browser.identity.getAuthToken)&&!e.forceWebAuthFlow}async function fa(e,t,a=!0){const r=t.split("/");r.pop();const o=e.folderIds.get(r.join("/"));if(o)return o;let n="root";if(r.length){let o="";for(const s of r){o&&(o+="/"),o+=s;const r=e.folderIds.get(o);if(r)n=r;else try{n=await wa(e,s,n),e.folderIds.set(o,n)}catch(r){if("path_not_found"==r.message&&a)return e.folderIds.clear(),fa(e,t,!1);throw r}}}return n}async function wa(e,t,a){const r=await async function(e,t,a){return ma(await fetch(na+"?q=mimeType = 'application/vnd.google-apps.folder' and name = '"+t+"' and trashed != true and '"+a+"' in parents",{headers:{Authorization:"Bearer "+e.accessToken}}))}(e,t,a);if(r.files.length)return r.files[0].id;return(await async function(e,t,a){return ma(await fetch(na,{method:"POST",headers:{Authorization:"Bearer "+e.accessToken,"Content-Type":"application/json"},body:JSON.stringify({name:t,parents:[a],mimeType:"application/vnd.google-apps.folder"})}))}(e,t,a)).id}async function ha(e){let t=e.file,a=e.file.size;(e.offset||e.chunkSize)&&(e.chunkSize&&(a=Math.min(e.offset+e.chunkSize,e.file.size)),t=t.slice(e.offset,a));const r=await fetch(e.url,{method:"PUT",headers:{Authorization:"Bearer "+e.token,"Content-Type":e.contentType,"Content-Range":"bytes "+e.offset+"-"+(a-1)+"/"+e.file.size,"X-Upload-Content-Type":e.contentType},body:t});if(e.onProgress&&!e.cancelled&&e.onProgress(e.offset+e.chunkSize,e.file.size),200==r.status||201==r.status)return r.json();if(308==r.status){const t=r.headers.get("Range");if(t&&(e.offset=parseInt(t.match(/\d+/g).pop(),10)+1),e.cancelled)throw new Error("upload_cancelled");return ha(e)}pa(r)}async function ma(e){e=pa(e);const t=await e.json();if(t.error)throw new Error(t.error);return t}function pa(e){if(200==e.status)return e;throw 404==e.status?new Error("path_not_found"):401==e.status?new Error("invalid_token"):new Error("unknown_error ("+e.status+")")}async function ba(e,t,a,r,o,n){for(;sa;)await sa;const s=new AbortController;return sa=(async()=>{try{await async function({path:o,blob:n,message:s=""},i){try{const c=await function(e){return new Promise(((t,a)=>{const r=new FileReader;r.onloadend=()=>t(r.result.match(/^data:[^,]+,(.*)$/)[1]),r.onerror=e=>a(e.detail),r.readAsDataURL(e)}))}(n),l=await fetch(`https://api.github.com/repos/${t}/${a}/contents/${o.replace(/#/g,"%23")}`,{method:"PUT",headers:new Map([["Authorization",`token ${e}`],["Accept","application/vnd.github.v3+json"]]),body:JSON.stringify({content:c,message:s,branch:r}),signal:i}),d=await l.json();if(l.status<400)return d;throw new Error(d.message)}catch(e){if("AbortError"!=e.name)throw e}}({path:o,blob:n},s.signal)}finally{sa=null}})(),{cancelPush:()=>s.abort(),pushPromise:sa}}const ga=new Map,ya=33554432,va=/([{}()^$&.*?/+|[\\\\]|\]|-)/g,ka=browser.runtime.getManifest(),Ia=ka.optional_permissions&&ka.optional_permissions.includes("identity"),xa=new class{constructor(e,t){this.clientId=e,this.scopes=t,this.folderIds=new Map,setInterval((()=>this.folderIds.clear()),6e4)}async auth(e={interactive:!0,auto:!0}){if(e.requestPermissionIdentity&&ia)try{await browser.permissions.request({permissions:["identity"]}),ia=!1}catch(e){}return ua(e)?(this.accessToken=await browser.identity.getAuthToken({interactive:e.interactive}),{revokableAccessToken:this.accessToken}):(da(this,e),e.code?la(this,e):async function(e,t){let a;t.extractAuthCode&&t.extractAuthCode(da(e,t)).then((e=>a=e)).catch((()=>{}));try{if(browser.identity&&browser.identity.launchWebAuthFlow&&!t.forceWebAuthFlow)return await browser.identity.launchWebAuthFlow({interactive:t.interactive,url:e.authURL});if(t.launchWebAuthFlow)return await t.launchWebAuthFlow({url:e.authURL});throw new Error("auth_not_supported")}catch(r){if(r.message&&("code_required"==r.message||r.message.includes("access"))){if(t.auto||a||!t.promptAuthCode||(a=await t.promptAuthCode()),a)return t.code=a,await la(e,t);throw new Error("code_required")}throw r}}(this,e))}setAuthInfo(e,t){ua(t)||(e?(this.accessToken=e.accessToken,this.refreshToken=e.refreshToken,this.expirationDate=e.expirationDate):(delete this.accessToken,delete this.refreshToken,delete this.expirationDate))}async refreshAuthToken(){if(this.refreshToken){const e=await fetch(oa,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"client_id="+this.clientId+"&refresh_token="+this.refreshToken+"&grant_type=refresh_token"});if(400==e.status)throw new Error("unknown_token");const t=await ma(e);return this.accessToken=t.access_token,t.refresh_token&&(this.refreshToken=t.refresh_token),t.expires_in&&(this.expirationDate=Date.now()+1e3*t.expires_in),{accessToken:this.accessToken,refreshToken:this.refreshToken,expirationDate:this.expirationDate}}}async revokeAuthToken(e){if(e){if(browser.identity&&browser.identity.removeCachedAuthToken)try{await browser.identity.removeCachedAuthToken({token:e})}catch(e){}const t=await fetch("https://accounts.google.com/o/oauth2/revoke",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"token="+e});try{await ma(t)}catch(e){if("invalid_token"!=e.message)throw e}finally{delete this.accessToken,delete this.refreshToken,delete this.expirationDate}}}async upload(e,t,a,r=!0){const o=await fa(this,e),n=e.split("/").pop(),s=new ca({token:this.accessToken,file:t,parents:[o],filename:n,onProgress:a.onProgress});try{return{cancelUpload:()=>s.cancelled=!0,uploadPromise:s.upload()}}catch(o){if("path_not_found"==o.message&&r)return this.folderIds.clear(),this.upload(e,t,a,!1);throw o}}}("7544745492-ig6uqhua0ads4jei52lervm1pqsi6hot.apps.googleusercontent.com",["https://www.googleapis.com/auth/drive.file"]);async function Sa(e,t){if(e.method.endsWith(".download"))return async function(e,t){const a=t.id;let r;e.truncated?(r=ga.get(a),r||(r=[],ga.set(a,r)),r.push(e.content),e.finished&&ga.delete(a)):e.content&&(r=[e.content]);if(!e.truncated||e.finished){let o;if(e.backgroundSave&&!e.saveToGDrive){const t=await Ra(e.filename,e);e.filenameConflictAction=t.filenameConflictAction,o=t.skipped}if(o)vt(a);else{const o=protobuf.roots.default.Page.decode(singlefile.helper.flatten(r)),n=await singlefile.processors.compression.process(o,{insertTextBody:e.insertTextBody,url:t.url,createRootDirectory:e.createRootDirectory,tabId:a,selfExtractingArchive:e.selfExtractingArchive,insertCanonicalLink:e.insertCanonicalLink,insertMetaNoIndex:e.insertMetaNoIndex,password:e.password});await async function(e,t,a,r){try{r.saveToGDrive?await(await Pa(r.taskId,r.filename,e,{forceWebAuthFlow:r.forceWebAuthFlow,extractAuthCode:r.extractAuthCode},{onProgress:(e,a)=>kt(t,e,a)})).uploadPromise:r.saveToGitHub?await(await Aa(r.taskId,r.filename,e,r.githubToken,r.githubUser,r.githubRepository,r.githubBranch)).pushPromise:r.backgroundSave?(r.url=URL.createObjectURL(e),await Ea(r,{confirmFilename:r.confirmFilename,incognito:a,filenameConflictAction:r.filenameConflictAction,filenameReplacementCharacter:r.filenameReplacementCharacter,bookmarkId:r.bookmarkId,replaceBookmarkURL:r.replaceBookmarkURL})):await async function(e,t,a,r){for(let o=0;o*yaya,n.truncated){n.finished=(o+1)*ya>a.size;const e=a.slice(o*ya,(o+1)*ya);n.content=Array.from(new Uint8Array(await new Response(e).arrayBuffer()))}else n.content=Array.from(new Uint8Array(await new Response(a).arrayBuffer()));await browser.tabs.sendMessage(r,n)}}(r.taskId,r.filename,e,t),vt(t)}catch(e){e.message&&"upload_cancelled"==e.message||(console.error(e),yt(t,e.message))}finally{r.url&&URL.revokeObjectURL(r.url)}}(n,a,t.incognito,e)}}return{}}(e,t.tab);if(e.method.endsWith(".disableGDrive")){const e=await T();return P(),await xa.revokeAuthToken(e&&(e.accessToken||e.revokableAccessToken)),{}}return e.method.endsWith(".end")?(Kt(e.taskId),{}):e.method.endsWith(".getInfo")?jt.map(ea):e.method.endsWith(".cancel")?(a=e.taskId,Yt(jt.find((e=>e.id==a))),{}):e.method.endsWith(".cancelAll")?(Array.from(jt).forEach(Yt),{}):e.method.endsWith(".saveUrls")?(zt(e.urls),{}):void 0;var a}async function Ta(e,t){let a=await T();const r={interactive:!0,auto:e.extractAuthCode,forceWebAuthFlow:e.forceWebAuthFlow,requestPermissionIdentity:Ia,launchWebAuthFlow:e=>async function(e){const t=await browser.tabs.create({url:e.url,active:!0});return new Promise(((e,a)=>{browser.tabs.onRemoved.addListener((function e(r){r==t.id&&(browser.tabs.onRemoved.removeListener(e),a(new Error("code_required")))}))}))}(e),extractAuthCode:e=>function(e){return new Promise(((t,a)=>{let r;function o(a,s){s&&s.url==e&&(r=a),r==a&&s&&s.title&&s.title.startsWith("Success code=")&&(browser.tabs.onUpdated.removeListener(o),browser.tabs.onUpdated.removeListener(n),t(s.title.substring(13,s.title.length-49)))}function n(e){e==r&&(browser.tabs.onUpdated.removeListener(o),browser.tabs.onUpdated.removeListener(n),a())}browser.tabs.onUpdated.addListener(o),browser.tabs.onRemoved.addListener(n)}))}(e),promptAuthCode:()=>async function(e){const t=await browser.tabs.query({currentWindow:!0,active:!0});return new Promise(((a,r)=>{const o=t[0].id;browser.tabs.onRemoved.addListener((function e(t){t==o&&(D.delete(t),browser.tabs.onUpdated.removeListener(e),r())})),D.set(o,{resolve:a,reject:r}),browser.tabs.sendMessage(o,{method:"common.promptValueRequest",promptMessage:e})}))}("Please enter the access code for Google Drive")};return xa.setAuthInfo(a,r),a&&a.accessToken&&!t||(a=await xa.auth(r),a?await A(a):await P()),a}async function Aa(e,t,a,r,o,n,s){const i=Qt(e);if(!i||!i.cancelled){const i=ba(r,o,n,s,t,a);Vt(e,i.cancelPush);try{return await(await i).pushPromise,i}catch(e){throw new Error(e.message+" (GitHub)")}}}async function Pa(e,t,a,r,o){try{await Ta(r);const n=Qt(e);if(!n||!n.cancelled){const r=await xa.upload(t,a,o);return Vt(e,r.cancelUpload),await r.uploadPromise}}catch(n){if("invalid_token"!=n.message)throw new Error(n.message+" (Google Drive)");{let n;try{n=await xa.refreshAuthToken()}catch(e){if("unknown_token"!=e.message)throw new Error(e.message+" (Google Drive)");n=await Ta(r,!0)}n?await A(n):await P(),await Pa(e,t,a,r,o)}}}async function Ra(e,t){let a,r=t.filenameConflictAction;if("skip"==r){(await browser.downloads.search({filenameRegex:"(\\\\|/)"+(o=e,o.replace(va,"\\$1")+"$"),exists:!0})).length?a=!0:r="uniquify"}var o;return{skipped:a,filenameConflictAction:r}}async function Ea(t,a){const r={url:t.url,saveAs:a.confirmFilename,filename:t.filename,conflictAction:a.filenameConflictAction};a.incognito&&(r.incognito=!0);const o=await e(r,a.filenameReplacementCharacter);o.filename&&t.bookmarkId&&t.replaceBookmarkURL&&(o.filename.startsWith("file:")||(o.filename.startsWith("/")&&(o.filename=o.filename.substring(1)),o.filename="file:///"+o.filename.replace(/#/g,"%23")),await async function(e,t){try{await browser.bookmarks.update(e,t)}catch(e){}}(t.bookmarkId,{url:o.filename}))}const Ma={},La={};async function Ca(e,t){if("enableAutoSave"==e.method){const a=await n(t.id);a[t.id].autoSave=e.enabled,await s(a),async function(e){Promise.all([it(e),se(e)])}(t)}if("isAutoSaveEnabled"==e.method)return R(t)}async function Wa(e,t){const a=t.id,r=await I(t.url,!0);if(r){let o;gt(a,1,!0),r.content=e.content,r.url=e.url,r.frames=e.frames,r.canvases=e.canvases,r.fonts=e.fonts,r.stylesheets=e.stylesheets,r.images=e.images,r.posters=e.posters,r.usedFonts=e.usedFonts,r.shadowRoots=e.shadowRoots,r.imports=e.imports,r.referrer=e.referrer,r.updatedResources=e.updatedResources,r.visitDate=new Date(e.visitDate),r.backgroundTab=!0,r.autoSave=!0,r.incognito=t.incognito,r.tabId=a,r.tabIndex=t.index;try{let n;if(o=await function(e,t,a,r={fetch:Rt,frameFetch:Et}){return globalThis.singlefile.getPageData(e,r,t,a)}(r,null,null,{fetch:Ba}),!r.saveToGDrive){const e=await Ra(o.filename,r);n=e.skipped,r.filenameConflictAction=e.filenameConflictAction}if(!n){r.includeInfobar&&await infobar.includeScript(o);const n=await singlefile.processors.compression.process(o,{insertTextBody:r.insertTextBody,url:t.url,createRootDirectory:r.createRootDirectory,tabId:a,selfExtractingArchive:r.selfExtractingArchive,insertCanonicalLink:r.insertCanonicalLink,insertMetaNoIndex:r.insertMetaNoIndex,password:r.password});r.saveToGDrive?await(await Pa(e.taskId,o.filename,n,r,{})).uploadPromise:r.saveToGitHub?await(await Aa(e.taskId,o.filename,n,r.githubToken,r.githubUser,r.githubRepository,r.githubBranch)).pushPromise:(o.url=URL.createObjectURL(n),await Ea(o,r))}}finally{e.taskId?Kt(e.taskId):r.autoClose&&(browser.tabs.remove(La[a]||a),delete La[a]),o&&o.url&&URL.revokeObjectURL(o.url),vt(a,!0)}}}function Ba(e){return new Promise(((t,a)=>{const r=new XMLHttpRequest;r.withCredentials=!0,r.responseType="arraybuffer",r.onerror=e=>a(new Error(e.detail)),r.onreadystatechange=()=>{r.readyState==XMLHttpRequest.DONE&&t({status:r.status,headers:{get:e=>r.getResponseHeader(e)},arrayBuffer:async()=>r.response})},r.open("GET",e,!0),r.send()}))}async function Ua(e,t){if(e.method.endsWith(".init")&&(lt(t.tab),function(e){Zt(e.id)}(t.tab),async function(e){const[t,a]=await Promise.all([I(e.url,!0),R(e)]);t&&(t.autoSaveLoad||t.autoSaveLoadOrUnload)&&a&&Ht([e],{autoSave:!0})}(t.tab)),e.method.endsWith(".promptValueResponse")&&async function(e,t){const a=D.get(t.tab.id);a&&(a.resolve(e.value),D.delete(t.tab.id))}(e,t),e.method.endsWith(".getOptions"))return I(e.url);e.method.endsWith(".activate")&&await browser.tabs.update(e.tabId,{active:!0})}browser.tabs.onCreated.addListener((e=>function(e){!function(e){lt(e)}(e)}(e))),browser.tabs.onActivated.addListener((e=>async function(e){!function(e){lt(e)}(await browser.tabs.get(e.tabId))}(e))),browser.tabs.onRemoved.addListener((e=>function(e){(async function(e){a&&delete a[e];const t=await n();if(t[e]){const a=t[e].autoSave;t[e]={autoSave:a},await s(t)}})(e),Zt(e),async function(e){const t=Ma[e];t?t.autoSaveRemove&&(delete Ma[e],await Wa(t,t.tab)):Ma[e]={removed:!0}}(e)}(e))),browser.tabs.onUpdated.addListener(((e,t)=>function(e,t){"complete"==t.status&&(setTimeout((async()=>{try{await browser.tabs.sendMessage(e,{method:"content.maybeInit"})}catch(e){}}),1500),function(e){delete Ma[e]}(e));t.discarded&&async function(e){const t=Ma[e];t?(delete Ma[e],await Wa(t,t.tab)):Ma[e]={discarded:!0}}(e)}(e,t))),browser.tabs.onReplaced.addListener(((e,t)=>function(e,t){!async function(e,t){Ma[t]&&!Ma[e]&&(Ma[e]=Ma[t],delete Ma[t],La[t]=e);const a=await n();a[t]&&!a[e]&&(a[e]=a[t],delete a[t],await s(a))}(e,t)}(e,t))),browser.runtime.onMessage.addListener(((e,t)=>e.method.startsWith("tabs.")?Ua(e,t):e.method.startsWith("downloads.")?Sa(e,t):e.method.startsWith("autosave.")?async function(e,t){if(e.method.endsWith(".init")){const[e,a]=await Promise.all([I(t.tab.url,!0),R(t.tab)]);return{options:e,autoSaveEnabled:a,tabId:t.tab.id,tabIndex:t.tab.index}}if(e.method.endsWith(".save"))return e.autoSaveDiscard||e.autoSaveRemove?(t.tab?(e.tab=t.tab,Ma[t.tab.id]=e):Ma[e.tabId]&&(Ma[e.tabId].removed&&e.autoSaveRemove||Ma[e.tabId].discarded&&e.autoSaveDiscard)&&(delete Ma[e.tabId],await Wa(e,{id:e.tabId,index:e.tabIndex,url:t.url})),e.autoSaveUnload&&(delete Ma[e.tabId],await Wa(e,t.tab))):(delete Ma[e.tabId],await Wa(e,t.tab)),{}}(e,t):e.method.startsWith("ui.")?pt(e,t):e.method.startsWith("config.")?v(e):e.method.startsWith("tabsData.")?function(e){return e.method.endsWith(".get")?n():e.method.endsWith(".set")?s(e.tabsData):void 0}(e):e.method.startsWith("devtools.")?async function(e){e.method.endsWith(".resourceCommitted")&&e.tabId&&e.url&&("stylesheet"==e.type||"script"==e.type)&&await browser.tabs.sendMessage(e.tabId,e)}(e):e.method.startsWith("bookmarks.")?ta(e):e.method.startsWith("requests.")?B(e):void 0)),browser.runtime.onMessageExternal&&browser.runtime.onMessageExternal.addListener((async(e,t)=>{const a=(await browser.tabs.query({currentWindow:!0,active:!0}))[0];return!!a&&Ca(e,a)}))}(); diff --git a/dist/single-file-background.js b/dist/single-file-background.js new file mode 100644 index 00000000..511917f3 --- /dev/null +++ b/dist/single-file-background.js @@ -0,0 +1 @@ +!function(){"use strict";const e=new Map,t=8388608;function r(t,s,a){return new Promise(((n,i)=>{const o=new XMLHttpRequest;if(o.withCredentials=!0,o.responseType="arraybuffer",o.onerror=e=>i(new Error(e.detail)),o.onreadystatechange=()=>{o.readyState==XMLHttpRequest.DONE&&(o.status||o.response.byteLength?401!=o.status&&403!=o.status&&404!=o.status||a?n({array:Array.from(new Uint8Array(o.response)),headers:{"content-type":o.getResponseHeader("Content-Type")},status:o.status}):r(t,s,!0).then(n).catch(i):i())},o.open("GET",t,!0),a){const t=String(Math.random()).substring(2);d=t,l=s.referrer,e.set(d,l),o.setRequestHeader("x-single-file-request-id",t)}var d,l;o.send()}))}browser.runtime.onMessage.addListener(((e,s)=>{if(e.method&&e.method.startsWith("singlefile.fetch"))return new Promise((t=>{(function(e,t){if("singlefile.fetch"==e.method)return r(e.url,{referrer:e.referrer});if("singlefile.fetchFrame"==e.method)return browser.tabs.sendMessage(t.tab.id,e)})(e,s).then(t).catch((e=>t({error:e&&e.toString()})))}));"singlefile.multipartFetch"==e.method&&async function(e,s){try{const a=await r(e.url),n=e.id;for(let e=0;e*tt,r.truncated?(r.finished=(e+1)*t>a.array.length,r.array=a.array.slice(e*t,(e+1)*t)):r.array=a.array,r.truncated&&!r.finished||(r.headers=a.headers,r.status=a.status),browser.tabs.sendMessage(s.tab.id,r)}}catch(t){await browser.tabs.sendMessage(s.tab.id,{method:"singlefile.multipartResponse",id:e.id,error:t&&t.toString()})}}(e,s)})),browser.runtime.onMessage.addListener(((e,t)=>{if("singlefile.frameTree.initResponse"==e.method||"singlefile.frameTree.ackInitRequest"==e.method)return browser.tabs.sendMessage(t.tab.id,e,{frameId:0}),Promise.resolve({})}));const s=new Map;function a(e,t){e.delete(t)}browser.runtime.onMessage.addListener(((e,t)=>{if("singlefile.lazyTimeout.setTimeout"==e.method){let r,n=s.get(t.tab.id);if(n)if(r=n.get(t.frameId),r){const t=r.get(e.type);t&&clearTimeout(t)}else r=new Map;const i=setTimeout((async()=>{try{const r=s.get(t.tab.id),n=r.get(t.frameId);r&&n&&a(n,e.type),await browser.tabs.sendMessage(t.tab.id,{method:"singlefile.lazyTimeout.onTimeout",type:e.type})}catch(e){}}),e.delay);return n||(n=new Map,r=new Map,n.set(t.frameId,r),s.set(t.tab.id,n)),r.set(e.type,i),Promise.resolve({})}if("singlefile.lazyTimeout.clearTimeout"==e.method){let r=s.get(t.tab.id);if(r){const s=r.get(t.frameId);if(s){const t=s.get(e.type);t&&clearTimeout(t),a(s,e.type)}}return Promise.resolve({})}})),browser.tabs.onRemoved.addListener((e=>s.delete(e)))}(); diff --git a/extension/core/bg/autosave-util.js b/extension/core/bg/autosave-util.js new file mode 100644 index 00000000..ebab2b87 --- /dev/null +++ b/extension/core/bg/autosave-util.js @@ -0,0 +1,54 @@ +/* + * Copyright 2010-2020 Gildas Lormeau + * contact : gildas.lormeau gmail.com + * + * This file is part of SingleFile. + * + * The code in this file is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * (GNU AGPL) as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * The code in this file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * As additional permission under GNU AGPL version 3 section 7, you may + * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU + * AGPL normally required by section 4, provided you include this license + * notice and a URL through which recipients can access the Corresponding + * Source. + */ + +/* global browser */ + +import * as config from "./config.js"; +import * as tabsData from "./tabs-data.js"; + +export { + autoSaveIsEnabled, + refreshAutoSaveTabs +}; + +async function autoSaveIsEnabled(tab) { + if (tab) { + const [allTabsData, rule] = await Promise.all([tabsData.get(), config.getRule(tab.url)]); + return Boolean(allTabsData.autoSaveAll || + (allTabsData.autoSaveUnpinned && !tab.pinned) || + (allTabsData[tab.id] && allTabsData[tab.id].autoSave)) && + (!rule || rule.autoSaveProfile != config.DISABLED_PROFILE_NAME); + } +} + +async function refreshAutoSaveTabs() { + const tabs = (await browser.tabs.query({})); + return Promise.all(tabs.map(async tab => { + const [options, autoSaveEnabled] = await Promise.all([config.getOptions(tab.url, true), autoSaveIsEnabled(tab)]); + try { + await browser.tabs.sendMessage(tab.id, { method: "content.init", autoSaveEnabled, options }); + } catch (error) { + // ignored + } + })); +} \ No newline at end of file diff --git a/extension/core/bg/autosave.js b/extension/core/bg/autosave.js index 77daa93d..391343a7 100644 --- a/extension/core/bg/autosave.js +++ b/extension/core/bg/autosave.js @@ -21,15 +21,15 @@ * Source. */ -/* global infobar, URL, XMLHttpRequest, singlefile */ +/* global browser, infobar, URL, XMLHttpRequest, singlefile */ import * as config from "./config.js"; import * as business from "./business.js"; import * as downloads from "./downloads.js"; import * as tabsData from "./tabs-data.js"; -import * as tabs from "./tabs.js"; import * as ui from "./../../ui/bg/index.js"; import { getPageData } from "./../../index.js"; +import { autoSaveIsEnabled } from "./autosave-util.js"; const pendingMessages = {}; const replacedTabIds = {}; @@ -38,8 +38,6 @@ export { onMessage, onMessageExternal, onInit, - isEnabled, - refreshTabs, onTabUpdated, onTabRemoved, onTabDiscarded, @@ -48,7 +46,7 @@ export { async function onMessage(message, sender) { if (message.method.endsWith(".init")) { - const [options, autoSaveEnabled] = await Promise.all([config.getOptions(sender.tab.url, true), isEnabled(sender.tab)]); + const [options, autoSaveEnabled] = await Promise.all([config.getOptions(sender.tab.url, true), autoSaveIsEnabled(sender.tab)]); return { options, autoSaveEnabled, tabId: sender.tab.id, tabIndex: sender.tab.index }; } if (message.method.endsWith(".save")) { @@ -123,39 +121,17 @@ async function onMessageExternal(message, currentTab) { ui.refreshTab(currentTab); } if (message.method == "isAutoSaveEnabled") { - return isEnabled(currentTab); + return autoSaveIsEnabled(currentTab); } } async function onInit(tab) { - const [options, autoSaveEnabled] = await Promise.all([config.getOptions(tab.url, true), isEnabled(tab)]); + const [options, autoSaveEnabled] = await Promise.all([config.getOptions(tab.url, true), autoSaveIsEnabled(tab)]); if (options && ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && autoSaveEnabled)) { business.saveTabs([tab], { autoSave: true }); } } -async function isEnabled(tab) { - if (tab) { - const [allTabsData, rule] = await Promise.all([tabsData.get(), config.getRule(tab.url)]); - return Boolean(allTabsData.autoSaveAll || - (allTabsData.autoSaveUnpinned && !tab.pinned) || - (allTabsData[tab.id] && allTabsData[tab.id].autoSave)) && - (!rule || rule.autoSaveProfile != config.DISABLED_PROFILE_NAME); - } -} - -async function refreshTabs() { - const allTabs = (await tabs.get({})); - return Promise.all(allTabs.map(async tab => { - const [options, autoSaveEnabled] = await Promise.all([config.getOptions(tab.url, true), isEnabled(tab)]); - try { - await tabs.sendMessage(tab.id, { method: "content.init", autoSaveEnabled, options }); - } catch (error) { - // ignored - } - })); -} - async function saveContent(message, tab) { const tabId = tab.id; const options = await config.getOptions(tab.url, true); @@ -216,7 +192,7 @@ async function saveContent(message, tab) { if (message.taskId) { business.onSaveEnd(message.taskId); } else if (options.autoClose) { - tabs.remove(replacedTabIds[tabId] || tabId); + browser.tabs.remove(replacedTabIds[tabId] || tabId); delete replacedTabIds[tabId]; } if (pageData && pageData.url) { diff --git a/extension/core/bg/background.html b/extension/core/bg/background.html index 8aa47049..14755fbe 100644 --- a/extension/core/bg/background.html +++ b/extension/core/bg/background.html @@ -8,15 +8,12 @@ + + - - - - - - + \ No newline at end of file diff --git a/extension/core/bg/bookmarks.js b/extension/core/bg/bookmarks.js index bb76e26e..b535bdd1 100644 --- a/extension/core/bg/bookmarks.js +++ b/extension/core/bg/bookmarks.js @@ -25,7 +25,6 @@ import * as config from "./config.js"; import * as business from "./business.js"; -import * as tabs from "./tabs.js"; Promise.resolve().then(enable); @@ -83,7 +82,7 @@ async function update(id, changes) { } async function onCreated(bookmarkId, bookmarkInfo) { - const activeTabs = await tabs.get({ lastFocusedWindow: true, active: true }); + const activeTabs = await browser.tabs.query({ lastFocusedWindow: true, active: true }); const options = await config.getOptions(bookmarkInfo.url); if (options.saveCreatedBookmarks) { const bookmarkFolders = await getParentFolders(bookmarkInfo.parentId); @@ -98,9 +97,9 @@ async function onCreated(bookmarkId, bookmarkInfo) { if (activeTabs.length && activeTabs[0].url == bookmarkInfo.url) { business.saveTabs(activeTabs, { bookmarkId, bookmarkFolders }); } else { - const allTabs = await tabs.get({}); - if (allTabs.length) { - const tab = allTabs.find(tab => tab.url == bookmarkInfo.url); + const tabs = await browser.tabs.query({}); + if (tabs.length) { + const tab = tabs.find(tab => tab.url == bookmarkInfo.url); if (tab) { business.saveTabs([tab], { bookmarkId, bookmarkFolders }); } else { diff --git a/extension/core/bg/business.js b/extension/core/bg/business.js index 1874a8c5..8320ec40 100644 --- a/extension/core/bg/business.js +++ b/extension/core/bg/business.js @@ -21,10 +21,11 @@ * Source. */ +/* global browser */ + import * as config from "./config.js"; -import * as autosave from "./autosave.js"; +import { autoSaveIsEnabled } from "./autosave-util.js"; import * as requests from "./requests.js"; -import * as tabs from "./tabs.js"; import * as ui from "./../../ui/bg/index.js"; import { injectScript } from "./../../index.js"; @@ -45,6 +46,7 @@ const extensionScriptFiles = [ const tasks = []; let currentTaskId = 0, maxParallelWorkers; +ui.setBusiness({ isSavingTab, saveTabs, saveUrls, cancelTab, saveSelectedLinks }); export { isSavingTab, @@ -66,7 +68,7 @@ async function saveSelectedLinks(tab) { const tabOptions = { extensionScriptFiles, tabId: tab.id, tabIndex: tab.index }; const scriptsInjected = await injectScript(tab.id, tabOptions); if (scriptsInjected) { - const response = await tabs.sendMessage(tab.id, { method: "content.getSelectedLinks" }); + const response = await browser.tabs.sendMessage(tab.id, { method: "content.getSelectedLinks" }); if (response.urls && response.urls.length) { await saveUrls(response.urls); } @@ -108,7 +110,7 @@ async function saveTabs(tabs, options = {}) { await requests.enableReferrerOnError(); } if (options.autoSave) { - if (autosave.isEnabled(tab)) { + if (autoSaveIsEnabled(tab)) { const taskInfo = addTask({ status: TASK_PROCESSING_STATE, tab, @@ -175,7 +177,7 @@ async function runTask(taskInfo) { if (!taskInfo.tab.id) { let scriptsInjected; try { - const tab = await tabs.createAndWait({ url: taskInfo.tab.url, active: false }); + const tab = await createTabAndWaitUntilComplete({ url: taskInfo.tab.url, active: false }); taskInfo.tab.id = taskInfo.options.tabId = tab.id; taskInfo.tab.index = taskInfo.options.tabIndex = tab.index; ui.onStart(taskInfo.tab.id, INJECT_SCRIPTS_STEP); @@ -192,7 +194,7 @@ async function runTask(taskInfo) { } taskInfo.options.taskId = taskId; try { - await tabs.sendMessage(taskInfo.tab.id, { method: taskInfo.method, options: taskInfo.options }); + await browser.tabs.sendMessage(taskInfo.tab.id, { method: taskInfo.method, options: taskInfo.options }); } catch (error) { if (error && (!error.message || !isIgnoredError(error))) { console.log(error.message ? error.message : error); // eslint-disable-line no-console @@ -220,12 +222,33 @@ function onSaveEnd(taskId) { const taskInfo = tasks.find(taskInfo => taskInfo.id == taskId); if (taskInfo) { if (taskInfo.options.autoClose && !taskInfo.cancelled) { - tabs.remove(taskInfo.tab.id); + browser.tabs.remove(taskInfo.tab.id); } taskInfo.done(); } } +async function createTabAndWaitUntilComplete(createProperties) { + const tab = await browser.tabs.create(createProperties); + return new Promise((resolve, reject) => { + browser.tabs.onUpdated.addListener(onTabUpdated); + browser.tabs.onRemoved.addListener(onTabRemoved); + function onTabUpdated(tabId, changeInfo) { + if (tabId == tab.id && changeInfo.status == "complete") { + resolve(tab); + browser.tabs.onUpdated.removeListener(onTabUpdated); + browser.tabs.onRemoved.removeListener(onTabRemoved); + } + } + function onTabRemoved(tabId) { + if (tabId == tab.id) { + reject(tabId); + browser.tabs.onRemoved.removeListener(onTabRemoved); + } + } + }); +} + function setCancelCallback(taskId, cancelCallback) { const taskInfo = tasks.find(taskInfo => taskInfo.id == taskId); if (taskInfo) { @@ -256,7 +279,7 @@ function getTaskInfo(taskId) { function cancel(taskInfo) { const tabId = taskInfo.tab.id; taskInfo.cancelled = true; - tabs.sendMessage(tabId, { + browser.tabs.sendMessage(tabId, { method: "content.cancelSave", options: { loadDeferredImages: taskInfo.options.loadDeferredImages, diff --git a/extension/core/bg/config.js b/extension/core/bg/config.js index 7acc547e..86b4bd23 100644 --- a/extension/core/bg/config.js +++ b/extension/core/bg/config.js @@ -23,7 +23,7 @@ /* global browser, navigator, URL, Blob */ -import * as downloads from "./downloads.js"; +import { download } from "./download-util.js"; import * as tabsData from "./tabs-data.js"; const CURRENT_PROFILE_NAME = "-"; @@ -457,7 +457,7 @@ async function exportConfig() { saveAs: true }; try { - await downloads.download(downloadInfo, "_"); + await download(downloadInfo, "_"); } finally { URL.revokeObjectURL(url); } diff --git a/extension/core/bg/devtools.js b/extension/core/bg/devtools.js index 252dae09..0ac3b162 100644 --- a/extension/core/bg/devtools.js +++ b/extension/core/bg/devtools.js @@ -21,7 +21,7 @@ * Source. */ -import * as tabs from "./tabs.js"; +/* global browser */ export { onMessage @@ -30,7 +30,7 @@ export { async function onMessage(message) { if (message.method.endsWith(".resourceCommitted")) { if (message.tabId && message.url && (message.type == "stylesheet" || message.type == "script")) { - await tabs.sendMessage(message.tabId, message); + await browser.tabs.sendMessage(message.tabId, message); } } } \ No newline at end of file diff --git a/extension/core/bg/download-util.js b/extension/core/bg/download-util.js new file mode 100644 index 00000000..b657b9f5 --- /dev/null +++ b/extension/core/bg/download-util.js @@ -0,0 +1,94 @@ +/* + * Copyright 2010-2020 Gildas Lormeau + * contact : gildas.lormeau gmail.com + * + * This file is part of SingleFile. + * + * The code in this file is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * (GNU AGPL) as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * The code in this file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * As additional permission under GNU AGPL version 3 section 7, you may + * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU + * AGPL normally required by section 4, provided you include this license + * notice and a URL through which recipients can access the Corresponding + * Source. + */ + +/* global browser */ + +const STATE_DOWNLOAD_COMPLETE = "complete"; +const STATE_DOWNLOAD_INTERRUPTED = "interrupted"; +const STATE_ERROR_CANCELED_CHROMIUM = "USER_CANCELED"; +const ERROR_DOWNLOAD_CANCELED_GECKO = "canceled"; +const ERROR_CONFLICT_ACTION_GECKO = "conflictaction prompt not yet implemented"; +const ERROR_INCOGNITO_GECKO = "'incognito'"; +const ERROR_INCOGNITO_GECKO_ALT = "\"incognito\""; +const ERROR_INVALID_FILENAME_GECKO = "illegal characters"; +const ERROR_INVALID_FILENAME_CHROMIUM = "invalid filename"; + +export { + download +}; + +async function download(downloadInfo, replacementCharacter) { + let downloadId; + try { + downloadId = await browser.downloads.download(downloadInfo); + } catch (error) { + if (error.message) { + const errorMessage = error.message.toLowerCase(); + const invalidFilename = errorMessage.includes(ERROR_INVALID_FILENAME_GECKO) || errorMessage.includes(ERROR_INVALID_FILENAME_CHROMIUM); + if (invalidFilename && downloadInfo.filename.startsWith(".")) { + downloadInfo.filename = replacementCharacter + downloadInfo.filename; + return download(downloadInfo, replacementCharacter); + } else if (invalidFilename && downloadInfo.filename.includes(",")) { + downloadInfo.filename = downloadInfo.filename.replace(/,/g, replacementCharacter); + return download(downloadInfo, replacementCharacter); + } else if (invalidFilename && !downloadInfo.filename.match(/^[\x00-\x7F]+$/)) { // eslint-disable-line no-control-regex + downloadInfo.filename = downloadInfo.filename.replace(/[^\x00-\x7F]+/g, replacementCharacter); // eslint-disable-line no-control-regex + return download(downloadInfo, replacementCharacter); + } else if ((errorMessage.includes(ERROR_INCOGNITO_GECKO) || errorMessage.includes(ERROR_INCOGNITO_GECKO_ALT)) && downloadInfo.incognito) { + delete downloadInfo.incognito; + return download(downloadInfo, replacementCharacter); + } else if (errorMessage == ERROR_CONFLICT_ACTION_GECKO && downloadInfo.conflictAction) { + delete downloadInfo.conflictAction; + return download(downloadInfo, replacementCharacter); + } else if (errorMessage.includes(ERROR_DOWNLOAD_CANCELED_GECKO)) { + return {}; + } else { + throw error; + } + } else { + throw error; + } + } + return new Promise((resolve, reject) => { + browser.downloads.onChanged.addListener(onChanged); + + function onChanged(event) { + if (event.id == downloadId && event.state) { + if (event.state.current == STATE_DOWNLOAD_COMPLETE) { + browser.downloads.search({ id: downloadId }) + .then(downloadItems => resolve({ filename: downloadItems[0] && downloadItems[0].filename })) + .catch(() => resolve({})); + browser.downloads.onChanged.removeListener(onChanged); + } + if (event.state.current == STATE_DOWNLOAD_INTERRUPTED) { + if (event.error && event.error.current == STATE_ERROR_CANCELED_CHROMIUM) { + resolve({}); + } else { + reject(new Error(event.state.current)); + } + browser.downloads.onChanged.removeListener(onChanged); + } + } + } + }); +} \ No newline at end of file diff --git a/extension/core/bg/downloads.js b/extension/core/bg/downloads.js index b5bc3181..b08b843b 100644 --- a/extension/core/bg/downloads.js +++ b/extension/core/bg/downloads.js @@ -26,22 +26,14 @@ import * as config from "./config.js"; import * as bookmarks from "./bookmarks.js"; import * as business from "./business.js"; -import * as tabs from "./tabs.js"; +import { launchWebAuthFlow, extractAuthCode, promptValue } from "./tabs-util.js"; import * as ui from "./../../ui/bg/index.js"; import { GDrive } from "./../../lib/gdrive/gdrive.js"; import { pushGitHub } from "./../../lib/github/github.js"; +import { download } from "./download-util.js"; const partialContents = new Map(); const MAX_CONTENT_SIZE = 32 * (1024 * 1024); -const STATE_DOWNLOAD_COMPLETE = "complete"; -const STATE_DOWNLOAD_INTERRUPTED = "interrupted"; -const STATE_ERROR_CANCELED_CHROMIUM = "USER_CANCELED"; -const ERROR_DOWNLOAD_CANCELED_GECKO = "canceled"; -const ERROR_CONFLICT_ACTION_GECKO = "conflictaction prompt not yet implemented"; -const ERROR_INCOGNITO_GECKO = "'incognito'"; -const ERROR_INCOGNITO_GECKO_ALT = "\"incognito\""; -const ERROR_INVALID_FILENAME_GECKO = "illegal characters"; -const ERROR_INVALID_FILENAME_CHROMIUM = "invalid filename"; const CLIENT_ID = "7544745492-ig6uqhua0ads4jei52lervm1pqsi6hot.apps.googleusercontent.com"; const SCOPES = ["https://www.googleapis.com/auth/drive.file"]; const CONFLICT_ACTION_SKIP = "skip"; @@ -53,7 +45,6 @@ const requestPermissionIdentity = manifest.optional_permissions && manifest.opti const gDrive = new GDrive(CLIENT_ID, SCOPES); export { onMessage, - download, downloadPage, testSkipSave, saveToGDrive, @@ -184,9 +175,9 @@ async function getAuthInfo(authOptions, force) { auto: authOptions.extractAuthCode, forceWebAuthFlow: authOptions.forceWebAuthFlow, requestPermissionIdentity, - launchWebAuthFlow: options => tabs.launchWebAuthFlow(options), - extractAuthCode: authURL => tabs.extractAuthCode(authURL), - promptAuthCode: () => tabs.promptValue("Please enter the access code for Google Drive") + launchWebAuthFlow: options => launchWebAuthFlow(options), + extractAuthCode: authURL => extractAuthCode(authURL), + promptAuthCode: () => promptValue("Please enter the access code for Google Drive") }; gDrive.setAuthInfo(authInfo, options); if (!authInfo || !authInfo.accessToken || force) { @@ -286,62 +277,6 @@ async function downloadPage(pageData, options) { } } -async function download(downloadInfo, replacementCharacter) { - let downloadId; - try { - downloadId = await browser.downloads.download(downloadInfo); - } catch (error) { - if (error.message) { - const errorMessage = error.message.toLowerCase(); - const invalidFilename = errorMessage.includes(ERROR_INVALID_FILENAME_GECKO) || errorMessage.includes(ERROR_INVALID_FILENAME_CHROMIUM); - if (invalidFilename && downloadInfo.filename.startsWith(".")) { - downloadInfo.filename = replacementCharacter + downloadInfo.filename; - return download(downloadInfo, replacementCharacter); - } else if (invalidFilename && downloadInfo.filename.includes(",")) { - downloadInfo.filename = downloadInfo.filename.replace(/,/g, replacementCharacter); - return download(downloadInfo, replacementCharacter); - } else if (invalidFilename && !downloadInfo.filename.match(/^[\x00-\x7F]+$/)) { // eslint-disable-line no-control-regex - downloadInfo.filename = downloadInfo.filename.replace(/[^\x00-\x7F]+/g, replacementCharacter); // eslint-disable-line no-control-regex - return download(downloadInfo, replacementCharacter); - } else if ((errorMessage.includes(ERROR_INCOGNITO_GECKO) || errorMessage.includes(ERROR_INCOGNITO_GECKO_ALT)) && downloadInfo.incognito) { - delete downloadInfo.incognito; - return download(downloadInfo, replacementCharacter); - } else if (errorMessage == ERROR_CONFLICT_ACTION_GECKO && downloadInfo.conflictAction) { - delete downloadInfo.conflictAction; - return download(downloadInfo, replacementCharacter); - } else if (errorMessage.includes(ERROR_DOWNLOAD_CANCELED_GECKO)) { - return {}; - } else { - throw error; - } - } else { - throw error; - } - } - return new Promise((resolve, reject) => { - browser.downloads.onChanged.addListener(onChanged); - - function onChanged(event) { - if (event.id == downloadId && event.state) { - if (event.state.current == STATE_DOWNLOAD_COMPLETE) { - browser.downloads.search({ id: downloadId }) - .then(downloadItems => resolve({ filename: downloadItems[0] && downloadItems[0].filename })) - .catch(() => resolve({})); - browser.downloads.onChanged.removeListener(onChanged); - } - if (event.state.current == STATE_DOWNLOAD_INTERRUPTED) { - if (event.error && event.error.current == STATE_ERROR_CANCELED_CHROMIUM) { - resolve({}); - } else { - reject(new Error(event.state.current)); - } - browser.downloads.onChanged.removeListener(onChanged); - } - } - } - }); -} - async function downloadPageForeground(taskId, filename, content, tabId) { for (let blockIndex = 0; blockIndex * MAX_CONTENT_SIZE < content.size; blockIndex++) { const message = { @@ -357,6 +292,6 @@ async function downloadPageForeground(taskId, filename, content, tabId) { } else { message.content = Array.from(new Uint8Array(await new Response(content).arrayBuffer())); } - await tabs.sendMessage(tabId, message); + await browser.tabs.sendMessage(tabId, message); } } \ No newline at end of file diff --git a/extension/core/bg/messages.js b/extension/core/bg/messages.js index d0b6afd4..4c7b76d8 100644 --- a/extension/core/bg/messages.js +++ b/extension/core/bg/messages.js @@ -64,8 +64,8 @@ browser.runtime.onMessage.addListener((message, sender) => { }); if (browser.runtime.onMessageExternal) { browser.runtime.onMessageExternal.addListener(async (message, sender) => { - const allTabs = await tabs.get({ currentWindow: true, active: true }); - const currentTab = allTabs[0]; + const tabs = await browser.tabs.query({ currentWindow: true, active: true }); + const currentTab = tabs[0]; if (currentTab) { return autosave.onMessageExternal(message, currentTab, sender); } else { diff --git a/extension/core/bg/tabs-data.js b/extension/core/bg/tabs-data.js index 88fa440d..b481dd09 100644 --- a/extension/core/bg/tabs-data.js +++ b/extension/core/bg/tabs-data.js @@ -23,8 +23,6 @@ /* global browser, setTimeout */ -import * as tabs from "./tabs.js"; - let persistentData, temporaryData, cleanedUp; setTimeout(() => getPersistent().then(tabsData => persistentData = tabsData), 0); export { @@ -86,10 +84,10 @@ async function setPersistent(tabsData) { async function cleanup() { if (!cleanedUp) { cleanedUp = true; - const allTabs = await tabs.get({ currentWindow: true, highlighted: true }); + const tabs = await browser.tabs.query({ currentWindow: true, highlighted: true }); Object.keys(persistentData).filter(key => { if (key != "autoSaveAll" && key != "autoSaveUnpinned" && key != "profileName") { - return !allTabs.find(tab => tab.id == key); + return !tabs.find(tab => tab.id == key); } }).forEach(tabId => delete persistentData[tabId]); await browser.storage.local.set({ tabsData: persistentData }); diff --git a/extension/core/bg/tabs-util.js b/extension/core/bg/tabs-util.js new file mode 100644 index 00000000..74839ea1 --- /dev/null +++ b/extension/core/bg/tabs-util.js @@ -0,0 +1,105 @@ +/* + * Copyright 2010-2020 Gildas Lormeau + * contact : gildas.lormeau gmail.com + * + * This file is part of SingleFile. + * + * The code in this file is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * (GNU AGPL) as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * The code in this file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * As additional permission under GNU AGPL version 3 section 7, you may + * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU + * AGPL normally required by section 4, provided you include this license + * notice and a URL through which recipients can access the Corresponding + * Source. + */ + +/* global browser */ + +const pendingPrompts = new Map(); + +export { + onPromptValueResponse, + queryTabs, + promptValue, + extractAuthCode, + launchWebAuthFlow +}; + +async function onPromptValueResponse(message, sender) { + const promptPromise = pendingPrompts.get(sender.tab.id); + if (promptPromise) { + promptPromise.resolve(message.value); + pendingPrompts.delete(sender.tab.id); + } +} + +async function queryTabs(options) { + const tabs = await browser.tabs.query(options); + return tabs.sort((tab1, tab2) => tab1.index - tab2.index); +} + +async function promptValue(promptMessage) { + const tabs = await browser.tabs.query({ currentWindow: true, active: true }); + return new Promise((resolve, reject) => { + const selectedTabId = tabs[0].id; + browser.tabs.onRemoved.addListener(onTabRemoved); + pendingPrompts.set(selectedTabId, { resolve, reject }); + browser.tabs.sendMessage(selectedTabId, { method: "common.promptValueRequest", promptMessage }); + + function onTabRemoved(tabId) { + if (tabId == selectedTabId) { + pendingPrompts.delete(tabId); + browser.tabs.onUpdated.removeListener(onTabRemoved); + reject(); + } + } + }); +} + +function extractAuthCode(authURL) { + return new Promise((resolve, reject) => { + let authTabId; + browser.tabs.onUpdated.addListener(onTabUpdated); + browser.tabs.onRemoved.addListener(onTabRemoved); + + function onTabUpdated(tabId, changeInfo) { + if (changeInfo && changeInfo.url == authURL) { + authTabId = tabId; + } + if (authTabId == tabId && changeInfo && changeInfo.title && changeInfo.title.startsWith("Success code=")) { + browser.tabs.onUpdated.removeListener(onTabUpdated); + browser.tabs.onUpdated.removeListener(onTabRemoved); + resolve(changeInfo.title.substring(13, changeInfo.title.length - 49)); + } + } + + function onTabRemoved(tabId) { + if (tabId == authTabId) { + browser.tabs.onUpdated.removeListener(onTabUpdated); + browser.tabs.onUpdated.removeListener(onTabRemoved); + reject(); + } + } + }); +} + +async function launchWebAuthFlow(options) { + const tab = await browser.tabs.create({ url: options.url, active: true }); + return new Promise((resolve, reject) => { + browser.tabs.onRemoved.addListener(onTabRemoved); + function onTabRemoved(tabId) { + if (tabId == tab.id) { + browser.tabs.onRemoved.removeListener(onTabRemoved); + reject(new Error("code_required")); + } + } + }); +} \ No newline at end of file diff --git a/extension/core/bg/tabs.js b/extension/core/bg/tabs.js index 3ceb5028..6d681682 100644 --- a/extension/core/bg/tabs.js +++ b/extension/core/bg/tabs.js @@ -28,9 +28,9 @@ import * as autosave from "./autosave.js"; import * as business from "./business.js"; import * as tabsData from "./tabs-data.js"; import * as ui from "./../../ui/bg/index.js"; +import { onPromptValueResponse } from "./tabs-util.js"; const DELAY_MAYBE_INIT = 1500; -const pendingPrompts = new Map(); browser.tabs.onCreated.addListener(tab => onTabCreated(tab)); browser.tabs.onActivated.addListener(activeInfo => onTabActivated(activeInfo)); @@ -38,16 +38,7 @@ browser.tabs.onRemoved.addListener(tabId => onTabRemoved(tabId)); browser.tabs.onUpdated.addListener((tabId, changeInfo) => onTabUpdated(tabId, changeInfo)); browser.tabs.onReplaced.addListener((addedTabId, removedTabId) => onTabReplaced(addedTabId, removedTabId)); export { - onMessage, - get, - create, - createAndWait, - sendMessage, - update, - remove, - promptValue, - extractAuthCode, - launchWebAuthFlow + onMessage }; async function onMessage(message, sender) { @@ -57,11 +48,7 @@ async function onMessage(message, sender) { autosave.onInit(sender.tab); } if (message.method.endsWith(".promptValueResponse")) { - const promptPromise = pendingPrompts.get(sender.tab.id); - if (promptPromise) { - promptPromise.resolve(message.value); - pendingPrompts.delete(sender.tab.id); - } + onPromptValueResponse(message, sender); } if (message.method.endsWith(".getOptions")) { return config.getOptions(message.url); @@ -71,106 +58,6 @@ async function onMessage(message, sender) { } } -function sendMessage(tabId, message, options) { - return browser.tabs.sendMessage(tabId, message, options); -} - -function update(tabId, updateProperties) { - return browser.tabs.update(tabId, updateProperties); -} - -function remove(tabId) { - return browser.tabs.remove(tabId); -} - -function create(createProperties) { - return browser.tabs.create(createProperties); -} - -async function createAndWait(createProperties) { - const tab = await browser.tabs.create(createProperties); - return new Promise((resolve, reject) => { - browser.tabs.onUpdated.addListener(onTabUpdated); - browser.tabs.onRemoved.addListener(onTabRemoved); - function onTabUpdated(tabId, changeInfo) { - if (tabId == tab.id && changeInfo.status == "complete") { - resolve(tab); - browser.tabs.onUpdated.removeListener(onTabUpdated); - browser.tabs.onRemoved.removeListener(onTabRemoved); - } - } - function onTabRemoved(tabId) { - if (tabId == tab.id) { - reject(tabId); - browser.tabs.onRemoved.removeListener(onTabRemoved); - } - } - }); -} - -async function get(options) { - const tabs = await browser.tabs.query(options); - return tabs.sort((tab1, tab2) => tab1.index - tab2.index); -} - -async function promptValue(promptMessage) { - const tabs = await browser.tabs.query({ currentWindow: true, active: true }); - return new Promise((resolve, reject) => { - const selectedTabId = tabs[0].id; - browser.tabs.onRemoved.addListener(onTabRemoved); - pendingPrompts.set(selectedTabId, { resolve, reject }); - browser.tabs.sendMessage(selectedTabId, { method: "common.promptValueRequest", promptMessage }); - - function onTabRemoved(tabId) { - if (tabId == selectedTabId) { - pendingPrompts.delete(tabId); - browser.tabs.onUpdated.removeListener(onTabRemoved); - reject(); - } - } - }); -} - -function extractAuthCode(authURL) { - return new Promise((resolve, reject) => { - let authTabId; - browser.tabs.onUpdated.addListener(onTabUpdated); - browser.tabs.onRemoved.addListener(onTabRemoved); - - function onTabUpdated(tabId, changeInfo) { - if (changeInfo && changeInfo.url == authURL) { - authTabId = tabId; - } - if (authTabId == tabId && changeInfo && changeInfo.title && changeInfo.title.startsWith("Success code=")) { - browser.tabs.onUpdated.removeListener(onTabUpdated); - browser.tabs.onUpdated.removeListener(onTabRemoved); - resolve(changeInfo.title.substring(13, changeInfo.title.length - 49)); - } - } - - function onTabRemoved(tabId) { - if (tabId == authTabId) { - browser.tabs.onUpdated.removeListener(onTabUpdated); - browser.tabs.onUpdated.removeListener(onTabRemoved); - reject(); - } - } - }); -} - -async function launchWebAuthFlow(options) { - const tab = await browser.tabs.create({ url: options.url, active: true }); - return new Promise((resolve, reject) => { - browser.tabs.onRemoved.addListener(onTabRemoved); - function onTabRemoved(tabId) { - if (tabId == tab.id) { - browser.tabs.onRemoved.removeListener(onTabRemoved); - reject(new Error("code_required")); - } - } - }); -} - function onTabUpdated(tabId, changeInfo) { if (changeInfo.status == "complete") { setTimeout(async () => { @@ -181,7 +68,7 @@ function onTabUpdated(tabId, changeInfo) { // ignored } }, DELAY_MAYBE_INIT); - autosave.onTabUpdated(tabId); + autosave.onTabUpdated(tabId); } if (changeInfo.discarded) { autosave.onTabDiscarded(tabId); diff --git a/extension/lib/single-file/background.js b/extension/lib/single-file/background.js new file mode 100644 index 00000000..71867d10 --- /dev/null +++ b/extension/lib/single-file/background.js @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2020 Gildas Lormeau + * contact : gildas.lormeau gmail.com + * + * This file is part of SingleFile. + * + * The code in this file is free software: you can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License + * (GNU AGPL) as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * The code in this file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero + * General Public License for more details. + * + * As additional permission under GNU AGPL version 3 section 7, you may + * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU + * AGPL normally required by section 4, provided you include this license + * notice and a URL through which recipients can access the Corresponding + * Source. + */ + +import "./fetch/bg/fetch.js"; +import "./frame-tree/bg/frame-tree.js"; +import "./lazy/bg/lazy-timeout.js"; \ No newline at end of file diff --git a/extension/ui/bg/index.js b/extension/ui/bg/index.js index 9aaa22d7..0bdcc3f7 100644 --- a/extension/ui/bg/index.js +++ b/extension/ui/bg/index.js @@ -21,9 +21,11 @@ * Source. */ +/* global browser */ + import * as button from "./ui-button.js"; import * as menus from "./ui-menus.js"; -import * as tabs from "./../../core/bg/tabs.js"; +import * as command from "./ui-commands.js"; export { onMessage, @@ -36,9 +38,16 @@ export { onUploadProgress, onTabCreated, onTabActivated, - onInit + onInit, + setBusiness }; +function setBusiness(businessApi) { + menus.setBusiness(businessApi); + button.setBusiness(businessApi); + command.setBusiness(businessApi); +} + function onMessage(message, sender) { if (message.method.endsWith(".refreshMenu")) { return menus.onMessage(message, sender); @@ -62,7 +71,7 @@ function onStart(tabId, step, autoSave) { async function onError(tabId, message) { button.onError(tabId); if (message) { - await tabs.sendMessage(tabId, { method: "content.error", error: message.toString() }); + await browser.tabs.sendMessage(tabId, { method: "content.error", error: message.toString() }); } } diff --git a/extension/ui/bg/ui-button.js b/extension/ui/bg/ui-button.js index f1adc051..b5ed54f7 100644 --- a/extension/ui/bg/ui-button.js +++ b/extension/ui/bg/ui-button.js @@ -23,10 +23,9 @@ /* global browser */ -import * as business from "./../../core/bg/business.js"; -import * as tabs from "./../../core/bg/tabs.js"; +import { queryTabs } from "./../../core/bg/tabs-util.js"; import * as tabsData from "./../../core/bg/tabs-data.js"; -import * as autosave from "./../../core/bg/autosave.js"; +import { autoSaveIsEnabled } from "../../core/bg/autosave-util.js"; const DEFAULT_ICON_PATH = "/extension/ui/resources/icon_128.png"; const WAIT_ICON_PATH_PREFIX = "/extension/ui/resources/icon_128_wait"; @@ -104,8 +103,10 @@ const BUTTON_STATES = { } }; +let business; + browser.browserAction.onClicked.addListener(async tab => { - const highlightedTabs = await tabs.get({ currentWindow: true, highlighted: true }); + const highlightedTabs = await queryTabs({ currentWindow: true, highlighted: true }); if (highlightedTabs.length <= 1) { toggleSaveTab(tab); } else { @@ -129,9 +130,14 @@ export { onError, onEnd, onCancelled, - refreshTab + refreshTab, + setBusiness }; +function setBusiness(businessApi) { + business = businessApi; +} + function onMessage(message, sender) { if (message.method.endsWith(".processInit")) { const allTabsData = tabsData.getTemporary(sender.tab.id); @@ -202,7 +208,7 @@ function onProgress(tabId, index, maxIndex, tooltipMessage) { } async function refreshTab(tab) { - const autoSave = await autosave.isEnabled(tab); + const autoSave = await autoSaveIsEnabled(tab); const state = getButtonState("default", autoSave); await refresh(tab.id, state); } diff --git a/extension/ui/bg/ui-commands.js b/extension/ui/bg/ui-commands.js index 4f51c7cb..9ae65ee6 100644 --- a/extension/ui/bg/ui-commands.js +++ b/extension/ui/bg/ui-commands.js @@ -23,21 +23,30 @@ /* global browser */ -import * as tabs from "./../../core/bg/tabs.js"; -import * as business from "./../../core/bg/business.js"; +import { queryTabs } from "./../../core/bg/tabs-util.js"; const commands = browser.commands; const BROWSER_COMMANDS_API_SUPPORTED = commands && commands.onCommand && commands.onCommand.addListener; +let business; + +export { + setBusiness +}; + +function setBusiness(businessApi) { + business = businessApi; +} + if (BROWSER_COMMANDS_API_SUPPORTED) { commands.onCommand.addListener(async command => { if (command == "save-selected-tabs") { - const highlightedTabs = await tabs.get({ currentWindow: true, highlighted: true }); + const highlightedTabs = await queryTabs({ currentWindow: true, highlighted: true }); business.saveTabs(highlightedTabs, { optionallySelected: true }); } if (command == "save-all-tabs") { - const allTabs = await tabs.get({ currentWindow: true }); - business.saveTabs(allTabs); + const tabs = await queryTabs({ currentWindow: true }); + business.saveTabs(tabs); } }); } \ No newline at end of file diff --git a/extension/ui/bg/ui-menus.js b/extension/ui/bg/ui-menus.js index b201d81d..6d140ac8 100644 --- a/extension/ui/bg/ui-menus.js +++ b/extension/ui/bg/ui-menus.js @@ -24,10 +24,9 @@ /* global browser, URL */ import * as config from "./../../core/bg/config.js"; -import * as tabs from "./../../core/bg/tabs.js"; +import { queryTabs } from "./../../core/bg/tabs-util.js"; import * as tabsData from "./../../core/bg/tabs-data.js"; -import * as business from "./../../core/bg/business.js"; -import * as autosave from "./../../core/bg/autosave.js"; +import { refreshAutoSaveTabs } from "./../../core/bg/autosave-util.js"; import * as button from "./ui-button.js"; const menus = browser.menus || browser.contextMenus; @@ -79,16 +78,21 @@ const menusCheckedState = new Map(); const menusTitleState = new Map(); let menusVisibleState; let profileIndexes = new Map(); -let menusCreated, pendingRefresh; +let menusCreated, pendingRefresh, business; Promise.resolve().then(initialize); export { onMessage, refreshTab as onTabCreated, refreshTab as onTabActivated, refreshTab as onInit, - createMenus as refreshTab + createMenus as refreshTab, + setBusiness }; +function setBusiness(businessApi) { + business = businessApi; +} + function onMessage(message) { if (message.method.endsWith("refreshMenu")) { createMenus(); @@ -355,7 +359,7 @@ async function createMenus(tab) { menusCreated = true; if (pendingRefresh) { pendingRefresh = false; - (await tabs.get({})).forEach(async tab => await refreshTab(tab)); + (await browser.tabs.query({})).forEach(async tab => await refreshTab(tab)); } } @@ -374,7 +378,7 @@ async function initialize() { business.saveSelectedLinks(tab); } if (event.menuItemId == MENU_ID_VIEW_PENDINGS) { - await tabs.create({ active: true, url: "/extension/ui/pages/pendings.html" }); + await browser.tabs.create({ active: true, url: "/extension/ui/pages/pendings.html" }); } if (event.menuItemId == MENU_ID_SAVE_SELECTED) { business.saveTabs([tab], { selected: true }); @@ -383,16 +387,16 @@ async function initialize() { business.saveTabs([tab], { frameId: event.frameId }); } if (event.menuItemId == MENU_ID_SAVE_SELECTED_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_SELECTED_TABS) { - const allTabs = await tabs.get({ currentWindow: true, highlighted: true }); - business.saveTabs(allTabs); + const tabs = await queryTabs({ currentWindow: true, highlighted: true }); + business.saveTabs(tabs); } if (event.menuItemId == MENU_ID_SAVE_UNPINNED_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_UNPINNED_TABS) { - const allTabs = await tabs.get({ currentWindow: true, pinned: false }); - business.saveTabs(allTabs); + const tabs = await queryTabs({ currentWindow: true, pinned: false }); + business.saveTabs(tabs); } if (event.menuItemId == MENU_ID_SAVE_ALL_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_ALL_TABS) { - const allTabs = await tabs.get({ currentWindow: true }); - business.saveTabs(allTabs); + const tabs = await queryTabs({ currentWindow: true }); + business.saveTabs(tabs); } if (event.menuItemId == MENU_ID_AUTO_SAVE_TAB) { const allTabsData = await tabsData.get(tab.id); @@ -471,14 +475,14 @@ async function initialize() { if (menusCreated) { pendingRefresh = true; } else { - (await tabs.get({})).forEach(async tab => await refreshTab(tab)); + (await browser.tabs.query({})).forEach(async tab => await refreshTab(tab)); } } } async function refreshExternalComponents(tab) { const allTabsData = await tabsData.get(tab.id); - await autosave.refreshTabs(); + await refreshAutoSaveTabs(); await button.refreshTab(tab); try { await browser.runtime.sendMessage({ method: "options.refresh", profileName: allTabsData.profileName }); diff --git a/rollup.config.dev.js b/rollup.config.dev.js index 65bdfcd3..66ef0daf 100644 --- a/rollup.config.dev.js +++ b/rollup.config.dev.js @@ -94,4 +94,18 @@ export default [{ format: "iife", plugins: [] }] +}, { + input: ["extension/core/bg/messages.js"], + output: [{ + file: "dist/extension-background.js", + format: "iife", + plugins: [] + }] +}, { + input: ["extension/lib/single-file/background.js"], + output: [{ + file: "dist/single-file-background.js", + format: "iife", + plugins: [] + }] }]; \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js index 2d5fe07b..a4fd2d4e 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -96,4 +96,18 @@ export default [{ format: "iife", plugins: [terser()] }] +}, { + input: ["extension/core/bg/messages.js"], + output: [{ + file: "dist/extension-background.js", + format: "iife", + plugins: [terser()] + }] +}, { + input: ["extension/lib/single-file/background.js"], + output: [{ + file: "dist/single-file-background.js", + format: "iife", + plugins: [terser()] + }] }]; \ No newline at end of file