mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 698371 - Add async thumbnail support for remote browsers. r=dao
This commit is contained in:
parent
57a7dc5a9d
commit
dd646c1d81
@ -10,13 +10,14 @@ const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
||||
const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
|
||||
const LATEST_STORAGE_VERSION = 3;
|
||||
|
||||
const EXPIRATION_MIN_CHUNK_SIZE = 50;
|
||||
const EXPIRATION_INTERVAL_SECS = 3600;
|
||||
|
||||
var gRemoteThumbId = 0;
|
||||
|
||||
// If a request for a thumbnail comes in and we find one that is "stale"
|
||||
// (or don't find one at all) we automatically queue a request to generate a
|
||||
// new one.
|
||||
@ -27,11 +28,6 @@ const MAX_THUMBNAIL_AGE_SECS = 172800; // 2 days == 60*60*24*2 == 172800 secs.
|
||||
*/
|
||||
const THUMBNAIL_DIRECTORY = "thumbnails";
|
||||
|
||||
/**
|
||||
* The default background color for page thumbnails.
|
||||
*/
|
||||
const THUMBNAIL_BG_COLOR = "#fff";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
||||
Cu.import("resource://gre/modules/PromiseWorker.jsm", this);
|
||||
Cu.import("resource://gre/modules/Promise.jsm", this);
|
||||
@ -69,6 +65,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
|
||||
"resource://gre/modules/Deprecated.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown",
|
||||
"resource://gre/modules/AsyncShutdown.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageThumbUtils",
|
||||
"resource://gre/modules/PageThumbUtils.jsm");
|
||||
|
||||
/**
|
||||
* Utilities for dealing with promises and Task.jsm
|
||||
@ -168,72 +166,76 @@ this.PageThumbs = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Captures a thumbnail for the given window.
|
||||
* @param aWindow The DOM window to capture a thumbnail from.
|
||||
* @param aCallback The function to be called when the thumbnail has been
|
||||
* captured. The first argument will be the data stream
|
||||
* containing the image data.
|
||||
*/
|
||||
capture: function PageThumbs_capture(aWindow, aCallback) {
|
||||
if (!this._prefEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
let canvas = this.createCanvas();
|
||||
this.captureToCanvas(aWindow, canvas);
|
||||
|
||||
// Fetch the canvas data on the next event loop tick so that we allow
|
||||
// some event processing in between drawing to the canvas and encoding
|
||||
// its data. We want to block the UI as short as possible. See bug 744100.
|
||||
Services.tm.currentThread.dispatch(function () {
|
||||
canvas.mozFetchAsStream(aCallback, this.contentType);
|
||||
}.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Captures a thumbnail for the given window.
|
||||
* Asynchronously returns a thumbnail as a blob for the given
|
||||
* window.
|
||||
*
|
||||
* @param aWindow The DOM window to capture a thumbnail from.
|
||||
* @param aBrowser The <browser> to capture a thumbnail from.
|
||||
* @return {Promise}
|
||||
* @resolve {Blob} The thumbnail, as a Blob.
|
||||
*/
|
||||
captureToBlob: function PageThumbs_captureToBlob(aWindow) {
|
||||
captureToBlob: function PageThumbs_captureToBlob(aBrowser) {
|
||||
if (!this._prefEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let canvas = this.createCanvas();
|
||||
this.captureToCanvas(aWindow, canvas);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let type = this.contentType;
|
||||
// Fetch the canvas data on the next event loop tick so that we allow
|
||||
// some event processing in between drawing to the canvas and encoding
|
||||
// its data. We want to block the UI as short as possible. See bug 744100.
|
||||
canvas.toBlob(function asBlob(blob) {
|
||||
deferred.resolve(blob, type);
|
||||
|
||||
let canvas = this.createCanvas();
|
||||
this.captureToCanvas(aBrowser, canvas, () => {
|
||||
canvas.toBlob(blob => {
|
||||
deferred.resolve(blob, this.contentType);
|
||||
});
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Captures a thumbnail from a given window and draws it to the given canvas.
|
||||
* @param aWindow The DOM window to capture a thumbnail from.
|
||||
* Note, when dealing with remote content, this api draws into the passed
|
||||
* canvas asynchronously. Pass aCallback to receive an async callback after
|
||||
* canvas painting has completed.
|
||||
* @param aBrowser The browser to capture a thumbnail from.
|
||||
* @param aCanvas The canvas to draw to.
|
||||
* @param aCallback (optional) A callback invoked once the thumbnail has been
|
||||
* rendered to aCanvas.
|
||||
*/
|
||||
captureToCanvas: function PageThumbs_captureToCanvas(aWindow, aCanvas) {
|
||||
captureToCanvas: function PageThumbs_captureToCanvas(aBrowser, aCanvas, aCallback) {
|
||||
let telemetryCaptureTime = new Date();
|
||||
this._captureToCanvas(aWindow, aCanvas);
|
||||
let telemetry = Services.telemetry;
|
||||
telemetry.getHistogramById("FX_THUMBNAILS_CAPTURE_TIME_MS")
|
||||
.add(new Date() - telemetryCaptureTime);
|
||||
this._captureToCanvas(aBrowser, aCanvas, function () {
|
||||
Services.telemetry
|
||||
.getHistogramById("FX_THUMBNAILS_CAPTURE_TIME_MS")
|
||||
.add(new Date() - telemetryCaptureTime);
|
||||
if (aCallback) {
|
||||
aCallback(aCanvas);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// The background thumbnail service captures to canvas but doesn't want to
|
||||
// participate in this service's telemetry, which is why this method exists.
|
||||
_captureToCanvas: function PageThumbs__captureToCanvas(aWindow, aCanvas) {
|
||||
let [sw, sh, scale] = this._determineCropSize(aWindow, aCanvas);
|
||||
_captureToCanvas: function (aBrowser, aCanvas, aCallback) {
|
||||
if (aBrowser.isRemoteBrowser) {
|
||||
let [sw, sh, scale] =
|
||||
PageThumbUtils.determineCropSize(aBrowser.contentWindowAsCPOW, aCanvas);
|
||||
Task.spawn(function () {
|
||||
let data =
|
||||
yield this._captureRemoteThumbnail(aBrowser, sw, sh, scale,
|
||||
PageThumbUtils.THUMBNAIL_BG_COLOR);
|
||||
let canvas = data.thumbnail;
|
||||
let ctx = canvas.getContext("2d");
|
||||
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
aCanvas.getContext("2d").putImageData(imgData, 0, 0);
|
||||
if (aCallback) {
|
||||
aCallback(aCanvas);
|
||||
}
|
||||
}.bind(this));
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate in-process content thumbnail
|
||||
let [sw, sh, scale] =
|
||||
PageThumbUtils.determineCropSize(aBrowser.contentWindow, aCanvas);
|
||||
let ctx = aCanvas.getContext("2d");
|
||||
|
||||
// Scale the canvas accordingly.
|
||||
@ -242,15 +244,88 @@ this.PageThumbs = {
|
||||
|
||||
try {
|
||||
// Draw the window contents to the canvas.
|
||||
ctx.drawWindow(aWindow, 0, 0, sw, sh, THUMBNAIL_BG_COLOR,
|
||||
ctx.drawWindow(aBrowser.contentWindow, 0, 0, sw, sh,
|
||||
PageThumbUtils.THUMBNAIL_BG_COLOR,
|
||||
ctx.DRAWWINDOW_DO_NOT_FLUSH);
|
||||
} catch (e) {
|
||||
// We couldn't draw to the canvas for some reason.
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
if (aCallback) {
|
||||
aCallback(aCanvas);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Request a thumbnail using requested bounds and scale factor.
|
||||
* @param aWidth - (optional) a width value less than or equal to the
|
||||
* innerWidth of the dom window. Defaults to the visible frame.
|
||||
* @param aHeight - (optional) a height value less than or equal to the
|
||||
* innerHeight of the dom window. Defaults to the visible frame.
|
||||
* @param aScaleFactor - (optional) 0.0 - 1.0 scale factor applied to the
|
||||
* returned thumbnail. Defaults to 1.0.
|
||||
* @param aCssBackground - (optional) a css '#fff' color value to use as
|
||||
* the background color of the thumbnail.
|
||||
*/
|
||||
_captureRemoteThumbnail: function (aBrowser, aWidth, aHeight,
|
||||
aScaleFactor, aCssBackground) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
// The index we send with the request so we can identify the
|
||||
// correct response.
|
||||
let index = gRemoteThumbId++;
|
||||
|
||||
// Thumbnail request response handler
|
||||
let mm = aBrowser.messageManager;
|
||||
|
||||
// Browser:Thumbnail:Response handler
|
||||
let thumbFunc = function (aMsg) {
|
||||
// Ignore events unrelated to our request
|
||||
if (aMsg.data.id != index) {
|
||||
return;
|
||||
}
|
||||
|
||||
mm.removeMessageListener("Browser:Thumbnail:Response", thumbFunc);
|
||||
let imageBlob = aMsg.data.thumbnail;
|
||||
let doc = aBrowser.parentElement.ownerDocument;
|
||||
let reader = Cc["@mozilla.org/files/filereader;1"].
|
||||
createInstance(Ci.nsIDOMFileReader);
|
||||
reader.addEventListener("loadend", function() {
|
||||
let image = doc.createElementNS(PageThumbUtils.HTML_NAMESPACE, "img");
|
||||
image.onload = function () {
|
||||
let thumbnail = doc.createElementNS(PageThumbUtils.HTML_NAMESPACE, "canvas");
|
||||
thumbnail.width = image.naturalWidth;
|
||||
thumbnail.height = image.naturalHeight;
|
||||
let ctx = thumbnail.getContext("2d");
|
||||
ctx.drawImage(image, 0, 0);
|
||||
deferred.resolve({
|
||||
thumbnail: thumbnail
|
||||
});
|
||||
}
|
||||
image.src = reader.result;
|
||||
});
|
||||
// xxx wish there was a way to skip this encoding step
|
||||
reader.readAsDataURL(imageBlob);
|
||||
}
|
||||
mm.addMessageListener("Browser:Thumbnail:Response", thumbFunc);
|
||||
|
||||
// Send a thumbnail request
|
||||
let width = aWidth || 0;
|
||||
let height = aHeight || 0;
|
||||
let scale = aScaleFactor || 1.0;
|
||||
let background = aCssBackground || "#fff";
|
||||
mm.sendAsyncMessage("Browser:Thumbnail:Request", {
|
||||
width: width,
|
||||
height: height,
|
||||
scale: scale,
|
||||
background: background,
|
||||
id: index
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Captures a thumbnail for the given browser and stores it to the cache.
|
||||
* @param aBrowser The browser to capture a thumbnail for.
|
||||
@ -262,19 +337,27 @@ this.PageThumbs = {
|
||||
}
|
||||
|
||||
let url = aBrowser.currentURI.spec;
|
||||
let channel = aBrowser.docShell.currentDocumentChannel;
|
||||
let originalURL = channel.originalURI.spec;
|
||||
let originalURL;
|
||||
let channelError = false;
|
||||
|
||||
// see if this was an error response.
|
||||
let wasError = this._isChannelErrorResponse(channel);
|
||||
if (!aBrowser.isRemoteBrowser) {
|
||||
let channel = aBrowser.docShell.currentDocumentChannel;
|
||||
originalURL = channel.originalURI.spec;
|
||||
// see if this was an error response.
|
||||
channelError = this._isChannelErrorResponse(channel);
|
||||
} else {
|
||||
// We need channel info (bug 1073957)
|
||||
originalURL = url;
|
||||
}
|
||||
|
||||
Task.spawn((function task() {
|
||||
let isSuccess = true;
|
||||
try {
|
||||
let blob = yield this.captureToBlob(aBrowser.contentWindow);
|
||||
let blob = yield this.captureToBlob(aBrowser);
|
||||
let buffer = yield TaskUtils.readBlob(blob);
|
||||
yield this._store(originalURL, url, buffer, wasError);
|
||||
} catch (_) {
|
||||
yield this._store(originalURL, url, buffer, channelError);
|
||||
} catch (ex) {
|
||||
Components.utils.reportError("Exception thrown during thumbnail capture: '" + ex + "'");
|
||||
isSuccess = false;
|
||||
}
|
||||
if (aCallback) {
|
||||
|
@ -2,13 +2,11 @@
|
||||
* 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/. */
|
||||
|
||||
(function () { // bug 673569 workaround :(
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
|
||||
|
||||
Cu.importGlobalProperties(['Blob']);
|
||||
|
||||
Cu.import("resource://gre/modules/PageThumbs.jsm");
|
||||
Cu.import("resource://gre/modules/PageThumbUtils.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
@ -49,7 +47,7 @@ const backgroundPageThumbsContent = {
|
||||
// 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 (subj == content.document) {
|
||||
if (content && subj == content.document) {
|
||||
content.
|
||||
QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIDOMWindowUtils).
|
||||
@ -129,9 +127,19 @@ const backgroundPageThumbsContent = {
|
||||
capture.finalURL = this._webNav.currentURI.spec;
|
||||
capture.pageLoadTime = new Date() - capture.pageLoadStartDate;
|
||||
|
||||
let canvas = PageThumbs.createCanvas(content);
|
||||
let canvasDrawDate = new Date();
|
||||
PageThumbs._captureToCanvas(content, canvas);
|
||||
|
||||
let canvas = PageThumbUtils.createCanvas(content);
|
||||
let [sw, sh, scale] = PageThumbUtils.determineCropSize(content, canvas);
|
||||
|
||||
let ctx = canvas.getContext("2d");
|
||||
ctx.save();
|
||||
ctx.scale(scale, scale);
|
||||
ctx.drawWindow(content, 0, 0, sw, sh,
|
||||
PageThumbUtils.THUMBNAIL_BG_COLOR,
|
||||
ctx.DRAWWINDOW_DO_NOT_FLUSH);
|
||||
ctx.restore();
|
||||
|
||||
capture.canvasDrawTime = new Date() - canvasDrawDate;
|
||||
|
||||
canvas.toBlob(blob => {
|
||||
@ -184,5 +192,3 @@ const backgroundPageThumbsContent = {
|
||||
};
|
||||
|
||||
backgroundPageThumbsContent.init();
|
||||
|
||||
})();
|
||||
|
@ -11,6 +11,8 @@ Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import("resource://gre/modules/RemoteAddonsChild.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
|
||||
const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
#ifdef MOZ_CRASHREPORTER
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
|
||||
"@mozilla.org/xre/app-info;1",
|
||||
@ -368,6 +370,36 @@ addMessageListener("UpdateCharacterSet", function (aMessage) {
|
||||
docShell.gatherCharsetMenuTelemetry();
|
||||
});
|
||||
|
||||
/**
|
||||
* Remote thumbnail request handler for PageThumbs thumbnails.
|
||||
*/
|
||||
addMessageListener("Browser:Thumbnail:Request", function (aMessage) {
|
||||
let thumbnail = content.document.createElementNS(HTML_NAMESPACE, "canvas");
|
||||
thumbnail.mozOpaque = true;
|
||||
thumbnail.mozImageSmoothingEnabled = true;
|
||||
|
||||
// width and height are crop dims
|
||||
let width = aMessage.data.width || content.innerWidth;
|
||||
let height = aMessage.data.height || content.innerHeight;
|
||||
thumbnail.width = Math.round(width * aMessage.data.scale);
|
||||
thumbnail.height = Math.round(height * aMessage.data.scale);
|
||||
|
||||
let ctx = thumbnail.getContext("2d");
|
||||
ctx.save();
|
||||
ctx.scale(aMessage.data.scale, aMessage.data.scale);
|
||||
ctx.drawWindow(content, 0, 0, width, height,
|
||||
aMessage.data.background,
|
||||
ctx.DRAWWINDOW_DO_NOT_FLUSH);
|
||||
ctx.restore();
|
||||
|
||||
thumbnail.toBlob(function (aBlob) {
|
||||
sendAsyncMessage("Browser:Thumbnail:Response", {
|
||||
thumbnail: aBlob,
|
||||
id: aMessage.data.id
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// The AddonsChild needs to be rooted so that it stays alive as long as
|
||||
// the tab.
|
||||
let AddonsChild;
|
||||
|
Loading…
Reference in New Issue
Block a user