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:
3d0bc4cf9f
- We properly inject into frames in the client if we ever include frames
This commit is contained in:
Fletcher Hazlehurst 2020-10-27 15:58:44 -06:00
parent 367fea1847
commit a2620b757d
5 changed files with 159 additions and 87 deletions

View file

@ -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;
},

@ -1 +1 @@
Subproject commit 369c194a945cfd2d442783d5b4f73a2ca5e54f18
Subproject commit da6994a142c12aab6fd6966f48f48307d53fdedd

View file

@ -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

View file

@ -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'
];

View file

@ -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("<html><!--\n Page saved with SingleFile"));
assert.include(contents, "<html><!--\n Page saved with SingleFile");
// Check attachment base64 contents
let expectedPath = getTestDataDirectory();
@ -421,7 +421,7 @@ describe("Zotero.Attachments", function() {
// test is much less useful.
// let needle = await Zotero.File.getBinaryContentsAsync(expectedPath);
// needle = '<img src=data:image/gif;base64,' + btoa(needle) + '>';
// assert.includes(contents, needle);
// assert.include(contents, needle);
});
it("should save a document with embedded files that throw errors", async function () {
@ -460,7 +460,58 @@ 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("<html><!--\n Page saved with SingleFile"));
assert.include(contents, "<html><!--\n Page saved with SingleFile");
});
it("should save a document but not save the iframe", async function () {
let item = await createDataObject('item');
let content = `<html><head><title>Test</title></head><body><iframe src="${testServerPath + "/iframe.html"}"/>`;
httpd.registerPathHandler(
'/' + prefix + '/index.html',
{
handle: function (request, response) {
response.setStatusLine(null, 200, "OK");
response.write(content);
}
}
);
let url = "file://" + OS.Path.join(getTestDataDirectory().path, "snapshot", "img.gif");
httpd.registerPathHandler(
'/' + prefix + '/iframe.html',
{
handle: function (request, response) {
response.setStatusLine(null, 200, "OK");
response.write(`<html><head><title>Test</title></head><body><img src="${url}"/>`);
}
}
);
let deferred = Zotero.Promise.defer();
win.addEventListener('pageshow', () => deferred.resolve());
win.loadURI(testServerPath + "/index.html");
await deferred.promise;
let attachment = await Zotero.Attachments.importFromDocument({
document: win.content.document,
parentItemID: item.id
});
assert.equal(attachment.getField('url'), testServerPath + "/index.html");
// Check for embedded files
var storageDir = Zotero.Attachments.getStorageDirectory(attachment).path;
var file = await attachment.getFilePathAsync();
assert.equal(OS.Path.basename(file), 'index.html');
assert.isFalse(await OS.File.exists(OS.Path.join(storageDir, 'images', '1.gif')));
// Check attachment html file contents
let path = OS.Path.join(storageDir, 'index.html');
assert.isTrue(await OS.File.exists(path));
let contents = await Zotero.File.getContentsAsync(path);
assert.include(contents, "><!--\n Page saved with SingleFile");
assert.notInclude(contents, "<img src=\"data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==\">'></iframe>");
});
});