michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: (function () { // bug 673569 workaround :( michael@0: michael@0: const { classes: Cc, interfaces: Ci, utils: Cu } = Components; michael@0: michael@0: Cu.import("resource://gre/modules/PageThumbs.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: const STATE_LOADING = 1; michael@0: const STATE_CAPTURING = 2; michael@0: const STATE_CANCELED = 3; michael@0: michael@0: const backgroundPageThumbsContent = { michael@0: michael@0: init: function () { michael@0: Services.obs.addObserver(this, "document-element-inserted", true); michael@0: michael@0: // We want a low network priority for this service - lower than b/g tabs michael@0: // etc - so set it to the lowest priority available. michael@0: this._webNav.QueryInterface(Ci.nsIDocumentLoader). michael@0: loadGroup.QueryInterface(Ci.nsISupportsPriority). michael@0: priority = Ci.nsISupportsPriority.PRIORITY_LOWEST; michael@0: michael@0: docShell.allowMedia = false; michael@0: docShell.allowPlugins = false; michael@0: docShell.allowContentRetargeting = false; michael@0: let defaultFlags = Ci.nsIRequest.LOAD_ANONYMOUS | michael@0: Ci.nsIRequest.LOAD_BYPASS_CACHE | michael@0: Ci.nsIRequest.INHIBIT_CACHING | michael@0: Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY; michael@0: docShell.defaultLoadFlags = defaultFlags; michael@0: michael@0: addMessageListener("BackgroundPageThumbs:capture", michael@0: this._onCapture.bind(this)); michael@0: docShell. michael@0: QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIWebProgress). michael@0: addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW); michael@0: }, michael@0: michael@0: observe: function (subj, topic, data) { michael@0: // Arrange to prevent (most) popup dialogs for this window - popups done michael@0: // in the parent (eg, auth) aren't prevented, but alert() etc are. michael@0: // disableDialogs only works on the current inner window, so it has michael@0: // to be called every page load, but before scripts run. michael@0: if (subj == content.document) { michael@0: content. michael@0: QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils). michael@0: disableDialogs(); michael@0: } michael@0: }, michael@0: michael@0: get _webNav() { michael@0: return docShell.QueryInterface(Ci.nsIWebNavigation); michael@0: }, michael@0: michael@0: _onCapture: function (msg) { michael@0: this._nextCapture = { michael@0: id: msg.data.id, michael@0: url: msg.data.url, michael@0: }; michael@0: if (this._currentCapture) { michael@0: if (this._state == STATE_LOADING) { michael@0: // Cancel the current capture. michael@0: this._state = STATE_CANCELED; michael@0: this._loadAboutBlank(); michael@0: } michael@0: // Let the current capture finish capturing, or if it was just canceled, michael@0: // wait for onStateChange due to the about:blank load. michael@0: return; michael@0: } michael@0: this._startNextCapture(); michael@0: }, michael@0: michael@0: _startNextCapture: function () { michael@0: if (!this._nextCapture) michael@0: return; michael@0: this._currentCapture = this._nextCapture; michael@0: delete this._nextCapture; michael@0: this._state = STATE_LOADING; michael@0: this._currentCapture.pageLoadStartDate = new Date(); michael@0: this._webNav.loadURI(this._currentCapture.url, michael@0: Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT, michael@0: null, null, null); michael@0: }, michael@0: michael@0: onStateChange: function (webProgress, req, flags, status) { michael@0: if (webProgress.isTopLevel && michael@0: (flags & Ci.nsIWebProgressListener.STATE_STOP) && michael@0: this._currentCapture) { michael@0: if (req.name == "about:blank") { michael@0: if (this._state == STATE_CAPTURING) { michael@0: // about:blank has loaded, ending the current capture. michael@0: this._finishCurrentCapture(); michael@0: delete this._currentCapture; michael@0: this._startNextCapture(); michael@0: } michael@0: else if (this._state == STATE_CANCELED) { michael@0: // A capture request was received while the current capture's page michael@0: // was still loading. michael@0: delete this._currentCapture; michael@0: this._startNextCapture(); michael@0: } michael@0: } michael@0: else if (this._state == STATE_LOADING) { michael@0: // The requested page has loaded. Capture it. michael@0: this._state = STATE_CAPTURING; michael@0: this._captureCurrentPage(); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _captureCurrentPage: function () { michael@0: let capture = this._currentCapture; michael@0: capture.finalURL = this._webNav.currentURI.spec; michael@0: capture.pageLoadTime = new Date() - capture.pageLoadStartDate; michael@0: michael@0: let canvas = PageThumbs._createCanvas(content); michael@0: let canvasDrawDate = new Date(); michael@0: PageThumbs._captureToCanvas(content, canvas); michael@0: capture.canvasDrawTime = new Date() - canvasDrawDate; michael@0: michael@0: canvas.toBlob(blob => { michael@0: capture.imageBlob = blob; michael@0: // Load about:blank to finish the capture and wait for onStateChange. michael@0: this._loadAboutBlank(); michael@0: }); michael@0: }, michael@0: michael@0: _finishCurrentCapture: function () { michael@0: let capture = this._currentCapture; michael@0: let fileReader = Cc["@mozilla.org/files/filereader;1"]. michael@0: createInstance(Ci.nsIDOMFileReader); michael@0: fileReader.onloadend = () => { michael@0: sendAsyncMessage("BackgroundPageThumbs:didCapture", { michael@0: id: capture.id, michael@0: imageData: fileReader.result, michael@0: finalURL: capture.finalURL, michael@0: telemetry: { michael@0: CAPTURE_PAGE_LOAD_TIME_MS: capture.pageLoadTime, michael@0: CAPTURE_CANVAS_DRAW_TIME_MS: capture.canvasDrawTime, michael@0: }, michael@0: }); michael@0: }; michael@0: fileReader.readAsArrayBuffer(capture.imageBlob); michael@0: }, michael@0: michael@0: // We load about:blank to finish all captures, even canceled captures. Two michael@0: // reasons: GC the captured page, and ensure it can't possibly load any more michael@0: // resources. michael@0: _loadAboutBlank: function _loadAboutBlank() { michael@0: this._webNav.loadURI("about:blank", michael@0: Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT, michael@0: null, null, null); michael@0: }, michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([ michael@0: Ci.nsIWebProgressListener, michael@0: Ci.nsISupportsWeakReference, michael@0: Ci.nsIObserver, michael@0: ]), michael@0: }; michael@0: michael@0: backgroundPageThumbsContent.init(); michael@0: michael@0: })();