Bug 1569135 Fix --screenshot r=kmag

This patch ressurects HiddenFrame.jsm and uses it when handling
the --screenshot command line argument to load the requested page
in a content process.  The actual logic for grabbing the image is
also ported to a JSWindowActor.  The test for this feature remains
suboptimal as described in the bug.

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

--HG--
rename : browser/components/shell/HeadlessShell.jsm => browser/components/shell/ScreenshotChild.jsm
extra : moz-landing-system : lando
This commit is contained in:
Andrew Swan 2019-08-07 21:33:49 +00:00
parent 724b9427aa
commit 036b82a357
8 changed files with 118 additions and 74 deletions

View file

@ -103,9 +103,6 @@ var whitelist = [
{ file: "resource://gre/res/fonts/mathfontSTIXGeneral.properties" },
{ file: "resource://gre/res/fonts/mathfontUnicode.properties" },
// Needed by HiddenFrame.jsm, but can't be packaged test-only
{ file: "chrome://global/content/win.xul" },
// The l10n build system can't package string files only for some platforms.
{
file:

View file

@ -397,7 +397,7 @@ add_task(async function checkAllTheCSS() {
// chrome URI so that it's allowed to load and parse any styles.
let testFile = getRootDirectory(gTestPath) + "dummy_page.html";
let HiddenFrame = ChromeUtils.import(
"resource://testing-common/HiddenFrame.jsm",
"resource://gre/modules/HiddenFrame.jsm",
{}
).HiddenFrame;
let hiddenFrame = new HiddenFrame();

View file

@ -5,7 +5,7 @@
/* import-globals-from ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js */
loadTestSubscript("head_webrequest.js");
ChromeUtils.import("resource://testing-common/HiddenFrame.jsm", this);
ChromeUtils.import("resource://gre/modules/HiddenFrame.jsm", this);
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
SimpleTest.requestCompleteLog();

View file

@ -4,16 +4,38 @@
"use strict";
var EXPORTED_SYMBOLS = ["HeadlessShell"];
var EXPORTED_SYMBOLS = ["HeadlessShell", "ScreenshotParent"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { E10SUtils } = ChromeUtils.import(
"resource://gre/modules/E10SUtils.jsm"
);
const { HiddenFrame } = ChromeUtils.import(
"resource://gre/modules/HiddenFrame.jsm"
);
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
// Refrences to the progress listeners to keep them from being gc'ed
// before they are called.
const progressListeners = new Map();
const progressListeners = new Set();
function loadContentWindow(webNavigation, url, principal) {
class ScreenshotParent extends JSWindowActorParent {
takeScreenshot(params) {
return this.sendQuery("TakeScreenshot", params);
}
}
ChromeUtils.registerWindowActor("Screenshot", {
parent: {
moduleURI: "resource:///modules/HeadlessShell.jsm",
},
child: {
moduleURI: "resource:///modules/ScreenshotChild.jsm",
messages: ["TakeScreenshot"],
},
});
function loadContentWindow(browser, url) {
let uri;
try {
uri = Services.io.newURI(url);
@ -22,21 +44,20 @@ function loadContentWindow(webNavigation, url, principal) {
Cu.reportError(msg);
return Promise.reject(new Error(msg));
}
const principal = Services.scriptSecurityManager.getSystemPrincipal();
return new Promise((resolve, reject) => {
let loadURIOptions = {
triggeringPrincipal: principal,
remoteType: E10SUtils.getRemoteTypeForURI(url, true, false),
};
webNavigation.loadURI(uri.spec, loadURIOptions);
let docShell = webNavigation
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell);
let webProgress = docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebProgress);
browser.loadURI(uri.spec, loadURIOptions);
let { webProgress } = browser;
let progressListener = {
onLocationChange(progress, request, location, flags) {
// Ignore inner-frame events
if (progress != webProgress) {
if (!progress.isTopLevel) {
return;
}
// Ignore events that don't change the document
@ -47,23 +68,17 @@ function loadContentWindow(webNavigation, url, principal) {
if (location.spec == "about:blank" && uri.spec != "about:blank") {
return;
}
let contentWindow = docShell.domWindow;
progressListeners.delete(progressListener);
webProgress.removeProgressListener(progressListener);
contentWindow.addEventListener(
"load",
event => {
resolve(contentWindow);
},
{ once: true }
);
resolve();
},
QueryInterface: ChromeUtils.generateQI([
"nsIWebProgressListener",
"nsISupportsWeakReference",
]),
};
progressListeners.set(progressListener, progressListener);
progressListeners.add(progressListener);
webProgress.addProgressListener(
progressListener,
Ci.nsIWebProgress.NOTIFY_LOCATION
@ -79,56 +94,37 @@ async function takeScreenshot(
path,
url
) {
let frame;
try {
var windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
// nsIWindowlessBrowser inherits from nsIWebNavigation.
let contentWindow = await loadContentWindow(
windowlessBrowser,
url,
Services.scriptSecurityManager.getSystemPrincipal()
frame = new HiddenFrame();
let windowlessBrowser = await frame.get();
let doc = windowlessBrowser.document;
let browser = doc.createXULElement("browser");
browser.setAttribute("remote", "true");
browser.setAttribute("type", "content");
browser.setAttribute(
"style",
`width: ${contentWidth}px; min-width: ${contentWidth}px; height: ${contentHeight}px; min-height: ${contentHeight}px;`
);
contentWindow.resizeTo(contentWidth, contentHeight);
doc.documentElement.appendChild(browser);
let canvas = contentWindow.document.createElementNS(
"http://www.w3.org/1999/xhtml",
"html:canvas"
);
let context = canvas.getContext("2d");
let width = fullWidth
? contentWindow.innerWidth +
contentWindow.scrollMaxX -
contentWindow.scrollMinX
: contentWindow.innerWidth;
let height = fullHeight
? contentWindow.innerHeight +
contentWindow.scrollMaxY -
contentWindow.scrollMinY
: contentWindow.innerHeight;
canvas.width = width;
canvas.height = height;
context.drawWindow(
contentWindow,
0,
0,
width,
height,
"rgb(255, 255, 255)"
await loadContentWindow(browser, url);
let actor = browser.browsingContext.currentWindowGlobal.getActor(
"Screenshot"
);
let blob = await actor.takeScreenshot({
fullWidth,
fullHeight,
});
function getBlob() {
return new Promise(resolve => canvas.toBlob(resolve));
}
let reader = await new Promise(resolve => {
let fr = new FileReader();
fr.onloadend = () => resolve(fr);
fr.readAsArrayBuffer(blob);
});
function readBlob(blob) {
return new Promise(resolve => {
let reader = new FileReader();
reader.onloadend = () => resolve(reader);
reader.readAsArrayBuffer(blob);
});
}
let blob = await getBlob();
let reader = await readBlob(blob);
await OS.File.writeAtomic(path, new Uint8Array(reader.result), {
flush: true,
});
@ -136,8 +132,8 @@ async function takeScreenshot(
} catch (e) {
dump("Failure taking screenshot: " + e + "\n");
} finally {
if (windowlessBrowser) {
windowlessBrowser.close();
if (frame) {
frame.destroy();
}
}
}

View file

@ -0,0 +1,50 @@
"use strict";
const EXPORTED_SYMBOLS = ["ScreenshotChild"];
class ScreenshotChild extends JSWindowActorChild {
receiveMessage(message) {
if (message.name == "TakeScreenshot") {
return this.takeScreenshot(message.data);
}
return null;
}
async takeScreenshot(params) {
if (this.document.readyState != "complete") {
await new Promise(resolve =>
this.contentWindow.addEventListener("load", resolve, { once: true })
);
}
let { fullWidth, fullHeight } = params;
let { contentWindow } = this;
let canvas = contentWindow.document.createElementNS(
"http://www.w3.org/1999/xhtml",
"html:canvas"
);
let context = canvas.getContext("2d");
let width = contentWindow.innerWidth;
let height = contentWindow.innerHeight;
if (fullWidth) {
width += contentWindow.scrollMaxX - contentWindow.scrollMinX;
}
if (fullHeight) {
height += contentWindow.scrollMaxY - contentWindow.scrollMinY;
}
canvas.width = width;
canvas.height = height;
context.drawWindow(
contentWindow,
0,
0,
width,
height,
"rgb(255, 255, 255)"
);
return new Promise(resolve => canvas.toBlob(resolve));
}
}

View file

@ -49,6 +49,7 @@ if SOURCES:
EXTRA_JS_MODULES += [
'HeadlessShell.jsm',
'ScreenshotChild.jsm',
'ShellService.jsm',
]

View file

@ -4,7 +4,7 @@
"use strict";
var HiddenFrame = ChromeUtils.import(
"resource://testing-common/HiddenFrame.jsm",
"resource://gre/modules/HiddenFrame.jsm",
{}
).HiddenFrame;

View file

@ -147,7 +147,6 @@ BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
TESTING_JS_MODULES += [
'HiddenFrame.jsm',
'tests/modules/MockDocument.jsm',
'tests/modules/PromiseTestUtils.jsm',
'tests/modules/Task.jsm',
@ -194,6 +193,7 @@ EXTRA_JS_MODULES += [
'GMPExtractorWorker.js',
'GMPInstallManager.jsm',
'GMPUtils.jsm',
'HiddenFrame.jsm',
'Http.jsm',
'IgnoreLists.jsm',
'IndexedDB.jsm',