summaryrefslogtreecommitdiffstats
path: root/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/thumbnails/content/backgroundPageThumbsContent.js')
-rw-r--r--toolkit/components/thumbnails/content/backgroundPageThumbsContent.js205
1 files changed, 205 insertions, 0 deletions
diff --git a/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js b/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
new file mode 100644
index 000000000..2103833b7
--- /dev/null
+++ b/toolkit/components/thumbnails/content/backgroundPageThumbsContent.js
@@ -0,0 +1,205 @@
+/* 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/. */
+
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.importGlobalProperties(['Blob', 'FileReader']);
+
+Cu.import("resource://gre/modules/PageThumbUtils.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+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, the window.open() method, or the
+ * showModalDialog() method.
+ */
+const SANDBOXED_AUXILIARY_NAVIGATION = 0x2;
+
+const backgroundPageThumbsContent = {
+
+ init: function () {
+ 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 |
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY;
+ docShell.defaultLoadFlags = defaultFlags;
+ docShell.sandboxFlags |= SANDBOXED_AUXILIARY_NAVIGATION;
+
+ addMessageListener("BackgroundPageThumbs:capture",
+ this._onCapture.bind(this));
+ docShell.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebProgress).
+ addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
+ },
+
+ observe: function (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.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ disableDialogs();
+ }
+ },
+
+ get _webNav() {
+ return docShell.QueryInterface(Ci.nsIWebNavigation);
+ },
+
+ _onCapture: function (msg) {
+ this._nextCapture = {
+ id: msg.data.id,
+ url: msg.data.url,
+ };
+ 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: function () {
+ if (!this._nextCapture)
+ return;
+ this._currentCapture = this._nextCapture;
+ delete this._nextCapture;
+ this._state = STATE_LOADING;
+ this._currentCapture.pageLoadStartDate = new Date();
+
+ try {
+ this._webNav.loadURI(this._currentCapture.url,
+ Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
+ null, null, null);
+ } catch (e) {
+ this._failCurrentCapture("BAD_URI");
+ delete this._currentCapture;
+ this._startNextCapture();
+ }
+ },
+
+ onStateChange: function (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)) {
+ // The requested page has loaded. Capture it.
+ this._state = STATE_CAPTURING;
+ this._captureCurrentPage();
+ }
+ 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: function () {
+ let capture = this._currentCapture;
+ capture.finalURL = this._webNav.currentURI.spec;
+ capture.pageLoadTime = new Date() - capture.pageLoadStartDate;
+
+ let canvasDrawDate = new Date();
+
+ let finalCanvas = PageThumbUtils.createSnapshotThumbnail(content, null);
+ 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();
+ });
+ },
+
+ _finishCurrentCapture: function () {
+ 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: function (reason) {
+ let capture = this._currentCapture;
+ sendAsyncMessage("BackgroundPageThumbs:didCapture", {
+ id: capture.id,
+ failReason: reason,
+ });
+ },
+
+ // 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() {
+ this._webNav.loadURI("about:blank",
+ Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
+ null, null, null);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIObserver,
+ ]),
+};
+
+backgroundPageThumbsContent.init();