From a2620b757df3b950a4d7b3c4fdd0c72892a2f069 Mon Sep 17 00:00:00 2001 From: Fletcher Hazlehurst Date: Tue, 27 Oct 2020 15:58:44 -0600 Subject: [PATCH 1/3] Update SingleFile and fix several bugs - Using `sandboxPrototype` properly uses window as prototype - This commit removes the need for our patch in babel-worker.js: https://github.com/gildas-lormeau/SingleFile/commit/3d0bc4cf9fd23e4fe8f5b684a89c69521c306b60 - We properly inject into frames in the client if we ever include frames --- .../zotero/xpcom/utilities_internal.js | 173 +++++++++++------- resource/SingleFile | 2 +- scripts/babel-worker.js | 12 -- scripts/config.js | 2 - test/tests/attachmentsTest.js | 57 +++++- 5 files changed, 159 insertions(+), 87 deletions(-) diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index 71495092d6..f512f9fc38 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -560,11 +560,112 @@ Zotero.Utilities.Internal = { snapshotDocument: async function (document) { // Create sandbox for SingleFile var view = document.defaultView; - var sandbox = new Components.utils.Sandbox(view, { wantGlobalProperties: ["XMLHttpRequest", "fetch"] }); + let sandbox = Zotero.Utilities.Internal.createSnapshotSandbox(view); + + const SCRIPTS = [ + // This first script replace in the INDEX_SCRIPTS from the single file cli loader + "lib/single-file/index.js", + + // Rest of the scripts (does not include WEB_SCRIPTS, those are handled in build process) + "lib/single-file/processors/hooks/content/content-hooks.js", + "lib/single-file/processors/hooks/content/content-hooks-frames.js", + "lib/single-file/processors/frame-tree/content/content-frame-tree.js", + "lib/single-file/processors/lazy/content/content-lazy-loader.js", + "lib/single-file/single-file-util.js", + "lib/single-file/single-file-helper.js", + "lib/single-file/vendor/css-tree.js", + "lib/single-file/vendor/html-srcset-parser.js", + "lib/single-file/vendor/css-minifier.js", + "lib/single-file/vendor/css-font-property-parser.js", + "lib/single-file/vendor/css-unescape.js", + "lib/single-file/vendor/css-media-query-parser.js", + "lib/single-file/modules/html-minifier.js", + "lib/single-file/modules/css-fonts-minifier.js", + "lib/single-file/modules/css-fonts-alt-minifier.js", + "lib/single-file/modules/css-matched-rules.js", + "lib/single-file/modules/css-medias-alt-minifier.js", + "lib/single-file/modules/css-rules-minifier.js", + "lib/single-file/modules/html-images-alt-minifier.js", + "lib/single-file/modules/html-serializer.js", + "lib/single-file/single-file-core.js", + "lib/single-file/single-file.js", + + // Web SCRIPTS + "lib/single-file/processors/hooks/content/content-hooks-frames-web.js", + "lib/single-file/processors/hooks/content/content-hooks-web.js", + ]; + + const { loadSubScript } = Components.classes['@mozilla.org/moz/jssubscript-loader;1'] + .getService(Ci.mozIJSSubScriptLoader); + + Zotero.debug('Injecting single file scripts'); + // Run all the scripts of SingleFile scripts in Sandbox + SCRIPTS.forEach( + script => loadSubScript('resource://zotero/SingleFile/' + script, sandbox) + ); + // Import config + loadSubScript('chrome://zotero/content/xpcom/singlefile.js', sandbox); + + // In the client we turn off this auto-zooming feature because it does not work + // since the hidden browser does not have a clientHeight. + Components.utils.evalInSandbox( + 'Zotero.SingleFile.CONFIG.loadDeferredImagesKeepZoomLevel = true;', + sandbox + ); + + Zotero.debug('Injecting single file scripts into frames'); + + // List of scripts from: + // resource/SingleFile/extension/lib/single-file/core/bg/scripts.js + const frameScripts = [ + "lib/single-file/index.js", + "lib/single-file/single-file-helper.js", + "lib/single-file/vendor/css-unescape.js", + "lib/single-file/processors/hooks/content/content-hooks-frames.js", + "lib/single-file/processors/frame-tree/content/content-frame-tree.js", + ]; + + // Create sandboxes for all the frames we find + const frameSandboxes = []; + for (let i = 0; i < sandbox.window.frames.length; ++i) { + let frameSandbox = Zotero.Utilities.Internal.createSnapshotSandbox(sandbox.window.frames[i]); + + // Run all the scripts of SingleFile scripts in Sandbox + frameScripts.forEach( + script => loadSubScript('resource://zotero/SingleFile/' + script, frameSandbox) + ); + + frameSandboxes.push(frameSandbox); + } + + // Use SingleFile to retrieve the html + const pageData = await Components.utils.evalInSandbox( + `this.singlefile.lib.getPageData( + Zotero.SingleFile.CONFIG, + { fetch: ZoteroFetch } + );`, + sandbox + ); + + // Clone so we can nuke the sandbox + let content = pageData.content; + + // Nuke frames and then main sandbox + frameSandboxes.forEach(frameSandbox => Components.utils.nukeSandbox(frameSandbox)); + Components.utils.nukeSandbox(sandbox); + + return content; + }, + + + createSnapshotSandbox: function (view) { + let sandbox = new Components.utils.Sandbox(view, { + wantGlobalProperties: ["XMLHttpRequest", "fetch"], + sandboxPrototype: view + }); sandbox.window = view.window; sandbox.document = sandbox.window.document; sandbox.browser = false; - sandbox.__proto__ = sandbox.window; sandbox.Zotero = Components.utils.cloneInto({ HTTP: {} }, sandbox); sandbox.Zotero.debug = Components.utils.exportFunction(Zotero.debug, sandbox); @@ -635,74 +736,8 @@ Zotero.Utilities.Internal = { };`, sandbox ); - - const SCRIPTS = [ - // This first script replace in the INDEX_SCRIPTS from the single file cli loader - "lib/single-file/index.js", - // Rest of the scripts (does not include WEB_SCRIPTS, those are handled in build process) - "lib/single-file/processors/hooks/content/content-hooks.js", - "lib/single-file/processors/hooks/content/content-hooks-frames.js", - "lib/single-file/processors/frame-tree/content/content-frame-tree.js", - "lib/single-file/processors/lazy/content/content-lazy-loader.js", - "lib/single-file/single-file-util.js", - "lib/single-file/single-file-helper.js", - "lib/single-file/vendor/css-tree.js", - "lib/single-file/vendor/html-srcset-parser.js", - "lib/single-file/vendor/css-minifier.js", - "lib/single-file/vendor/css-font-property-parser.js", - "lib/single-file/vendor/css-unescape.js", - "lib/single-file/vendor/css-media-query-parser.js", - "lib/single-file/modules/html-minifier.js", - "lib/single-file/modules/css-fonts-minifier.js", - "lib/single-file/modules/css-fonts-alt-minifier.js", - "lib/single-file/modules/css-matched-rules.js", - "lib/single-file/modules/css-medias-alt-minifier.js", - "lib/single-file/modules/css-rules-minifier.js", - "lib/single-file/modules/html-images-alt-minifier.js", - "lib/single-file/modules/html-serializer.js", - "lib/single-file/single-file-core.js", - "lib/single-file/single-file.js", - - // Web SCRIPTS - "lib/single-file/processors/hooks/content/content-hooks-frames-web.js", - "lib/single-file/processors/hooks/content/content-hooks-web.js", - ]; - - const { loadSubScript } = Components.classes['@mozilla.org/moz/jssubscript-loader;1'] - .getService(Ci.mozIJSSubScriptLoader); - - Zotero.debug('Injecting single file scripts'); - // Run all the scripts of SingleFile scripts in Sandbox - SCRIPTS.forEach( - script => loadSubScript('resource://zotero/SingleFile/' + script, sandbox) - ); - // Import config - loadSubScript('chrome://zotero/content/xpcom/singlefile.js', sandbox); - - // In the client we turn off this auto-zooming feature because it does not work - // since the hidden browser does not have a clientHeight. - Components.utils.evalInSandbox( - 'Zotero.SingleFile.CONFIG.loadDeferredImagesKeepZoomLevel = true;', - sandbox - ); - - await Zotero.Promise.delay(1500); - - // Use SingleFile to retrieve the html - const pageData = await Components.utils.evalInSandbox( - `this.singlefile.lib.getPageData( - Zotero.SingleFile.CONFIG, - { fetch: ZoteroFetch } - );`, - sandbox - ); - - // Clone so we can nuke the sandbox - let content = pageData.content; - Components.utils.nukeSandbox(sandbox); - - return content; + return sandbox; }, diff --git a/resource/SingleFile b/resource/SingleFile index 369c194a94..da6994a142 160000 --- a/resource/SingleFile +++ b/resource/SingleFile @@ -1 +1 @@ -Subproject commit 369c194a945cfd2d442783d5b4f73a2ca5e54f18 +Subproject commit da6994a142c12aab6fd6966f48f48307d53fdedd diff --git a/scripts/babel-worker.js b/scripts/babel-worker.js index 8f839e1156..adf9601440 100644 --- a/scripts/babel-worker.js +++ b/scripts/babel-worker.js @@ -48,18 +48,6 @@ async function babelWorker(ev) { .replace('document.body.removeChild(scrollDiv)', 'document.documentElement.removeChild(scrollDiv)'); } - // Patch content-frame-tree - // In Chrome sometimes frames would not have access to the browser object. I could - // not replicate this in firefox so is possibly a bug with injected content_scripts - // in Chrome that was easier to work around than track down. SingleFile has this - // backup mechanism for message so we simply remove the check that implies that if - // the top window has the browser object the frame will as well. - else if (sourcefile === 'resource/SingleFile/lib/single-file/processors/frame-tree/content/content-frame-tree.js') { - transformed = contents - .replace('} else if ((!browser || !browser.runtime) && message.method == INIT_RESPONSE_MESSAGE) {', - '} else if (message.method == INIT_RESPONSE_MESSAGE) {'); - } - // Patch single-file else if (sourcefile === 'resource/SingleFile/lib/single-file/single-file.js') { // We need to add this bit that is done for the cli implementation of singleFile diff --git a/scripts/config.js b/scripts/config.js index 3e9137e986..21aeb9d988 100644 --- a/scripts/config.js +++ b/scripts/config.js @@ -37,7 +37,6 @@ const symlinkFiles = [ '!resource/SingleFile/**/*', 'resource/SingleFile/lib/**/*', 'resource/SingleFile/extension/lib/single-file/fetch/content/content-fetch.js', - '!resource/SingleFile/lib/single-file/processors/frame-tree/content/content-frame-tree.js', '!resource/SingleFile/lib/single-file/single-file.js', 'update.rdf' ]; @@ -91,7 +90,6 @@ const jsFiles = [ 'resource/react.js', 'resource/react-dom.js', 'resource/react-virtualized.js', - 'resource/SingleFile/lib/single-file/processors/frame-tree/content/content-frame-tree.js', 'resource/SingleFile/lib/single-file/single-file.js' ]; diff --git a/test/tests/attachmentsTest.js b/test/tests/attachmentsTest.js index 9ae0289bab..aadf70793d 100644 --- a/test/tests/attachmentsTest.js +++ b/test/tests/attachmentsTest.js @@ -408,7 +408,7 @@ describe("Zotero.Attachments", function() { let path = OS.Path.join(storageDir, 'index.html'); assert.isTrue(await OS.File.exists(path)); let contents = await Zotero.File.getContentsAsync(path); - assert.isTrue(contents.startsWith("