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/mathfontSTIXGeneral.properties" },
|
||||||
{ file: "resource://gre/res/fonts/mathfontUnicode.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.
|
// The l10n build system can't package string files only for some platforms.
|
||||||
{
|
{
|
||||||
file:
|
file:
|
||||||
|
|
|
||||||
|
|
@ -397,7 +397,7 @@ add_task(async function checkAllTheCSS() {
|
||||||
// chrome URI so that it's allowed to load and parse any styles.
|
// chrome URI so that it's allowed to load and parse any styles.
|
||||||
let testFile = getRootDirectory(gTestPath) + "dummy_page.html";
|
let testFile = getRootDirectory(gTestPath) + "dummy_page.html";
|
||||||
let HiddenFrame = ChromeUtils.import(
|
let HiddenFrame = ChromeUtils.import(
|
||||||
"resource://testing-common/HiddenFrame.jsm",
|
"resource://gre/modules/HiddenFrame.jsm",
|
||||||
{}
|
{}
|
||||||
).HiddenFrame;
|
).HiddenFrame;
|
||||||
let hiddenFrame = new HiddenFrame();
|
let hiddenFrame = new HiddenFrame();
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
/* import-globals-from ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js */
|
/* import-globals-from ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js */
|
||||||
loadTestSubscript("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";
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||||
|
|
||||||
SimpleTest.requestCompleteLog();
|
SimpleTest.requestCompleteLog();
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,38 @@
|
||||||
|
|
||||||
"use strict";
|
"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 { 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
|
// Refrences to the progress listeners to keep them from being gc'ed
|
||||||
// before they are called.
|
// 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;
|
let uri;
|
||||||
try {
|
try {
|
||||||
uri = Services.io.newURI(url);
|
uri = Services.io.newURI(url);
|
||||||
|
|
@ -22,21 +44,20 @@ function loadContentWindow(webNavigation, url, principal) {
|
||||||
Cu.reportError(msg);
|
Cu.reportError(msg);
|
||||||
return Promise.reject(new Error(msg));
|
return Promise.reject(new Error(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const principal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let loadURIOptions = {
|
let loadURIOptions = {
|
||||||
triggeringPrincipal: principal,
|
triggeringPrincipal: principal,
|
||||||
|
remoteType: E10SUtils.getRemoteTypeForURI(url, true, false),
|
||||||
};
|
};
|
||||||
webNavigation.loadURI(uri.spec, loadURIOptions);
|
browser.loadURI(uri.spec, loadURIOptions);
|
||||||
let docShell = webNavigation
|
let { webProgress } = browser;
|
||||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIDocShell);
|
|
||||||
let webProgress = docShell
|
|
||||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
||||||
.getInterface(Ci.nsIWebProgress);
|
|
||||||
let progressListener = {
|
let progressListener = {
|
||||||
onLocationChange(progress, request, location, flags) {
|
onLocationChange(progress, request, location, flags) {
|
||||||
// Ignore inner-frame events
|
// Ignore inner-frame events
|
||||||
if (progress != webProgress) {
|
if (!progress.isTopLevel) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Ignore events that don't change the document
|
// 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") {
|
if (location.spec == "about:blank" && uri.spec != "about:blank") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let contentWindow = docShell.domWindow;
|
|
||||||
progressListeners.delete(progressListener);
|
progressListeners.delete(progressListener);
|
||||||
webProgress.removeProgressListener(progressListener);
|
webProgress.removeProgressListener(progressListener);
|
||||||
contentWindow.addEventListener(
|
resolve();
|
||||||
"load",
|
|
||||||
event => {
|
|
||||||
resolve(contentWindow);
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
QueryInterface: ChromeUtils.generateQI([
|
QueryInterface: ChromeUtils.generateQI([
|
||||||
"nsIWebProgressListener",
|
"nsIWebProgressListener",
|
||||||
"nsISupportsWeakReference",
|
"nsISupportsWeakReference",
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
progressListeners.set(progressListener, progressListener);
|
progressListeners.add(progressListener);
|
||||||
webProgress.addProgressListener(
|
webProgress.addProgressListener(
|
||||||
progressListener,
|
progressListener,
|
||||||
Ci.nsIWebProgress.NOTIFY_LOCATION
|
Ci.nsIWebProgress.NOTIFY_LOCATION
|
||||||
|
|
@ -79,56 +94,37 @@ async function takeScreenshot(
|
||||||
path,
|
path,
|
||||||
url
|
url
|
||||||
) {
|
) {
|
||||||
|
let frame;
|
||||||
try {
|
try {
|
||||||
var windowlessBrowser = Services.appShell.createWindowlessBrowser(false);
|
frame = new HiddenFrame();
|
||||||
// nsIWindowlessBrowser inherits from nsIWebNavigation.
|
let windowlessBrowser = await frame.get();
|
||||||
let contentWindow = await loadContentWindow(
|
|
||||||
windowlessBrowser,
|
|
||||||
url,
|
|
||||||
Services.scriptSecurityManager.getSystemPrincipal()
|
|
||||||
);
|
|
||||||
contentWindow.resizeTo(contentWidth, contentHeight);
|
|
||||||
|
|
||||||
let canvas = contentWindow.document.createElementNS(
|
let doc = windowlessBrowser.document;
|
||||||
"http://www.w3.org/1999/xhtml",
|
let browser = doc.createXULElement("browser");
|
||||||
"html:canvas"
|
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;`
|
||||||
);
|
);
|
||||||
let context = canvas.getContext("2d");
|
doc.documentElement.appendChild(browser);
|
||||||
let width = fullWidth
|
|
||||||
? contentWindow.innerWidth +
|
await loadContentWindow(browser, url);
|
||||||
contentWindow.scrollMaxX -
|
|
||||||
contentWindow.scrollMinX
|
let actor = browser.browsingContext.currentWindowGlobal.getActor(
|
||||||
: contentWindow.innerWidth;
|
"Screenshot"
|
||||||
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)"
|
|
||||||
);
|
);
|
||||||
|
let blob = await actor.takeScreenshot({
|
||||||
function getBlob() {
|
fullWidth,
|
||||||
return new Promise(resolve => canvas.toBlob(resolve));
|
fullHeight,
|
||||||
}
|
});
|
||||||
|
|
||||||
function readBlob(blob) {
|
let reader = await new Promise(resolve => {
|
||||||
return new Promise(resolve => {
|
let fr = new FileReader();
|
||||||
let reader = new FileReader();
|
fr.onloadend = () => resolve(fr);
|
||||||
reader.onloadend = () => resolve(reader);
|
fr.readAsArrayBuffer(blob);
|
||||||
reader.readAsArrayBuffer(blob);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
let blob = await getBlob();
|
|
||||||
let reader = await readBlob(blob);
|
|
||||||
await OS.File.writeAtomic(path, new Uint8Array(reader.result), {
|
await OS.File.writeAtomic(path, new Uint8Array(reader.result), {
|
||||||
flush: true,
|
flush: true,
|
||||||
});
|
});
|
||||||
|
|
@ -136,8 +132,8 @@ async function takeScreenshot(
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dump("Failure taking screenshot: " + e + "\n");
|
dump("Failure taking screenshot: " + e + "\n");
|
||||||
} finally {
|
} finally {
|
||||||
if (windowlessBrowser) {
|
if (frame) {
|
||||||
windowlessBrowser.close();
|
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 += [
|
EXTRA_JS_MODULES += [
|
||||||
'HeadlessShell.jsm',
|
'HeadlessShell.jsm',
|
||||||
|
'ScreenshotChild.jsm',
|
||||||
'ShellService.jsm',
|
'ShellService.jsm',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var HiddenFrame = ChromeUtils.import(
|
var HiddenFrame = ChromeUtils.import(
|
||||||
"resource://testing-common/HiddenFrame.jsm",
|
"resource://gre/modules/HiddenFrame.jsm",
|
||||||
{}
|
{}
|
||||||
).HiddenFrame;
|
).HiddenFrame;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,6 @@ BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
|
||||||
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
|
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
|
||||||
|
|
||||||
TESTING_JS_MODULES += [
|
TESTING_JS_MODULES += [
|
||||||
'HiddenFrame.jsm',
|
|
||||||
'tests/modules/MockDocument.jsm',
|
'tests/modules/MockDocument.jsm',
|
||||||
'tests/modules/PromiseTestUtils.jsm',
|
'tests/modules/PromiseTestUtils.jsm',
|
||||||
'tests/modules/Task.jsm',
|
'tests/modules/Task.jsm',
|
||||||
|
|
@ -194,6 +193,7 @@ EXTRA_JS_MODULES += [
|
||||||
'GMPExtractorWorker.js',
|
'GMPExtractorWorker.js',
|
||||||
'GMPInstallManager.jsm',
|
'GMPInstallManager.jsm',
|
||||||
'GMPUtils.jsm',
|
'GMPUtils.jsm',
|
||||||
|
'HiddenFrame.jsm',
|
||||||
'Http.jsm',
|
'Http.jsm',
|
||||||
'IgnoreLists.jsm',
|
'IgnoreLists.jsm',
|
||||||
'IndexedDB.jsm',
|
'IndexedDB.jsm',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue