forked from mirrors/gecko-dev
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:
parent
724b9427aa
commit
036b82a357
8 changed files with 118 additions and 74 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
50
browser/components/shell/ScreenshotChild.jsm
Normal file
50
browser/components/shell/ScreenshotChild.jsm
Normal 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@ if SOURCES:
|
|||
|
||||
EXTRA_JS_MODULES += [
|
||||
'HeadlessShell.jsm',
|
||||
'ScreenshotChild.jsm',
|
||||
'ShellService.jsm',
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"use strict";
|
||||
|
||||
var HiddenFrame = ChromeUtils.import(
|
||||
"resource://testing-common/HiddenFrame.jsm",
|
||||
"resource://gre/modules/HiddenFrame.jsm",
|
||||
{}
|
||||
).HiddenFrame;
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
Loading…
Reference in a new issue