gecko-dev/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
Valentin Gosu e828cd7d68 Bug 1611469 - backgroundPageThumbsContent.js mixes nsIRequest and nsIWebNavigation load flags r=markh
The patch ensures we don't pass a nsIWebNavigation load flag to
nsIDocShell.defaultLoadFlags which is supposed to get nsLoadFlags (nsIRequest).

Differential Revision: https://phabricator.services.mozilla.com/D61168

--HG--
extra : moz-landing-system : lando
2020-01-28 14:47:23 +00:00

289 lines
9.2 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-env mozilla/frame-script */
const { PageThumbUtils } = ChromeUtils.import(
"resource://gre/modules/PageThumbUtils.jsm"
);
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGlobalGetters(this, ["Blob", "FileReader"]);
// Let the page settle for this amount of milliseconds before capturing to allow
// for any in-page changes or redirects.
const SETTLE_WAIT_TIME = 2500;
// For testing, the above timeout is excessive, and makes our tests overlong.
const TESTING_SETTLE_WAIT_TIME = 0;
const STATE_LOADING = 1;
const STATE_CAPTURING = 2;
const STATE_CANCELED = 3;
// NOTE: Copied from nsSandboxFlags.h
/**
* This flag prevents content from creating new auxiliary browsing contexts,
* e.g. using the target attribute, or the window.open() method.
*/
const SANDBOXED_AUXILIARY_NAVIGATION = 0x2;
const backgroundPageThumbsContent = {
init() {
Services.obs.addObserver(this, "document-element-inserted", true);
// We want a low network priority for this service - lower than b/g tabs
// etc - so set it to the lowest priority available.
this._webNav
.QueryInterface(Ci.nsIDocumentLoader)
.loadGroup.QueryInterface(Ci.nsISupportsPriority).priority =
Ci.nsISupportsPriority.PRIORITY_LOWEST;
docShell.allowMedia = false;
docShell.allowPlugins = false;
docShell.allowContentRetargeting = false;
let defaultFlags =
Ci.nsIRequest.LOAD_ANONYMOUS |
Ci.nsIRequest.LOAD_BYPASS_CACHE |
Ci.nsIRequest.INHIBIT_CACHING;
docShell.defaultLoadFlags = defaultFlags;
BrowsingContext.getFromWindow(
content
).sandboxFlags |= SANDBOXED_AUXILIARY_NAVIGATION;
docShell.useTrackingProtection = true;
addMessageListener(
"BackgroundPageThumbs:capture",
this._onCapture.bind(this)
);
docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress)
.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
},
observe(subj, topic, data) {
// Arrange to prevent (most) popup dialogs for this window - popups done
// in the parent (eg, auth) aren't prevented, but alert() etc are.
// disableDialogs only works on the current inner window, so it has
// to be called every page load, but before scripts run.
if (content && subj == content.document) {
content.windowUtils.disableDialogs();
}
},
get _webNav() {
return docShell.QueryInterface(Ci.nsIWebNavigation);
},
_onCapture(msg) {
this._nextCapture = {
id: msg.data.id,
url: msg.data.url,
isImage: msg.data.isImage,
targetWidth: msg.data.targetWidth,
backgroundColor: msg.data.backgroundColor,
};
if (this._currentCapture) {
if (this._state == STATE_LOADING) {
// Cancel the current capture.
this._state = STATE_CANCELED;
this._loadAboutBlank();
}
// Let the current capture finish capturing, or if it was just canceled,
// wait for onStateChange due to the about:blank load.
return;
}
this._startNextCapture();
},
_startNextCapture() {
if (!this._nextCapture) {
return;
}
this._currentCapture = this._nextCapture;
delete this._nextCapture;
this._state = STATE_LOADING;
this._currentCapture.pageLoadStartDate = new Date();
try {
// Bug 1498603 verify usages of systemPrincipal here
let loadURIOptions = {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
};
this._webNav.loadURI(this._currentCapture.url, loadURIOptions);
} catch (e) {
this._failCurrentCapture("BAD_URI");
}
},
onStateChange(webProgress, req, flags, status) {
if (
webProgress.isTopLevel &&
flags & Ci.nsIWebProgressListener.STATE_STOP &&
this._currentCapture
) {
if (req.name == "about:blank") {
if (this._state == STATE_CAPTURING) {
// about:blank has loaded, ending the current capture.
this._finishCurrentCapture();
delete this._currentCapture;
this._startNextCapture();
} else if (this._state == STATE_CANCELED) {
delete this._currentCapture;
this._startNextCapture();
}
} else if (
this._state == STATE_LOADING &&
(Components.isSuccessCode(status) || status === Cr.NS_BINDING_ABORTED)
) {
let waitTime = Cu.isInAutomation
? TESTING_SETTLE_WAIT_TIME
: SETTLE_WAIT_TIME;
// The requested page has loaded or stopped/aborted, so capture the page
// soon but first let it settle in case of in-page redirects
if (this._captureTimer) {
// There was additional activity, so restart the wait timer
this._captureTimer.delay = waitTime;
} else {
// Stay in LOADING until we're actually ready to be CAPTURING
this._captureTimer = Cc["@mozilla.org/timer;1"].createInstance(
Ci.nsITimer
);
this._captureTimer.init(
() => {
this._state = STATE_CAPTURING;
this._captureCurrentPage();
delete this._captureTimer;
},
waitTime,
Ci.nsITimer.TYPE_ONE_SHOT
);
}
} else if (this._state != STATE_CANCELED) {
// Something went wrong. Cancel the capture. Loading about:blank
// while onStateChange is still on the stack does not actually stop
// the request if it redirects, so do it asyncly.
this._state = STATE_CANCELED;
if (!this._cancelTimer) {
this._cancelTimer = Cc["@mozilla.org/timer;1"].createInstance(
Ci.nsITimer
);
this._cancelTimer.init(
() => {
this._loadAboutBlank();
delete this._cancelTimer;
},
0,
Ci.nsITimer.TYPE_ONE_SHOT
);
}
}
}
},
_captureCurrentPage() {
const doCapture = async () => {
let capture = this._currentCapture;
capture.finalURL = this._webNav.currentURI.spec;
capture.pageLoadTime = new Date() - capture.pageLoadStartDate;
let canvasDrawDate = new Date();
docShell.isActive = true;
let finalCanvas;
if (
capture.isImage ||
content.document instanceof content.ImageDocument
) {
finalCanvas = await PageThumbUtils.createImageThumbnailCanvas(
content,
capture.url,
capture.targetWidth,
capture.backgroundColor
);
} else {
finalCanvas = PageThumbUtils.createSnapshotThumbnail(content, null);
}
docShell.isActive = false;
capture.canvasDrawTime = new Date() - canvasDrawDate;
finalCanvas.toBlob(blob => {
capture.imageBlob = new Blob([blob]);
// Load about:blank to finish the capture and wait for onStateChange.
this._loadAboutBlank();
});
};
let win = docShell.domWindow;
let runCapture = () => {
doCapture().catch(ex => this._failCurrentCapture(ex.message));
};
// When testing, especially on debug builds, this idle callback might
// be called too late (or never called at all - see bug 1596781), and
// the test will time out. So if we're running in automation, we begin
// the capture on the next tick.
if (Cu.isInAutomation) {
Services.tm.dispatchToMainThread(runCapture);
} else {
win.requestIdleCallback(runCapture);
}
},
_finishCurrentCapture() {
let capture = this._currentCapture;
let fileReader = new FileReader();
fileReader.onloadend = () => {
sendAsyncMessage("BackgroundPageThumbs:didCapture", {
id: capture.id,
imageData: fileReader.result,
finalURL: capture.finalURL,
telemetry: {
CAPTURE_PAGE_LOAD_TIME_MS: capture.pageLoadTime,
CAPTURE_CANVAS_DRAW_TIME_MS: capture.canvasDrawTime,
},
});
};
fileReader.readAsArrayBuffer(capture.imageBlob);
},
_failCurrentCapture(reason) {
let capture = this._currentCapture;
sendAsyncMessage("BackgroundPageThumbs:didCapture", {
id: capture.id,
failReason: reason,
});
delete this._currentCapture;
this._startNextCapture();
},
// We load about:blank to finish all captures, even canceled captures. Two
// reasons: GC the captured page, and ensure it can't possibly load any more
// resources.
_loadAboutBlank: function _loadAboutBlank() {
// It's possible we've been destroyed by now, if so don't do anything:
if (!docShell) {
return;
}
let loadURIOptions = {
triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
};
this._webNav.loadURI("about:blank", loadURIOptions);
},
QueryInterface: ChromeUtils.generateQI([
Ci.nsIWebProgressListener,
Ci.nsISupportsWeakReference,
Ci.nsIObserver,
]),
};
backgroundPageThumbsContent.init();