forked from mirrors/gecko-dev
Backed out changeset 8781a0d1254d (bug 1810141) Backed out changeset 131037295784 (bug 1810141) Backed out changeset 3852fbe290f4 (bug 1810141) Backed out changeset 118f131a524a (bug 1810141) Backed out changeset ab5d76846e10 (bug 1810141) Backed out changeset dce3aa683445 (bug 1810141) Backed out changeset 4dc41d90dbb3 (bug 1810141) Backed out changeset 50b57ba1a061 (bug 1810141) Backed out changeset 569de94781e4 (bug 1810141)
270 lines
7.6 KiB
JavaScript
270 lines
7.6 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* 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/. */
|
|
|
|
"use strict";
|
|
|
|
var EXPORTED_SYMBOLS = ["HeadlessShell", "ScreenshotParent"];
|
|
|
|
const { E10SUtils } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/E10SUtils.sys.mjs"
|
|
);
|
|
const { HiddenFrame } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/HiddenFrame.sys.mjs"
|
|
);
|
|
|
|
// Refrences to the progress listeners to keep them from being gc'ed
|
|
// before they are called.
|
|
const progressListeners = new Set();
|
|
|
|
class ScreenshotParent extends JSWindowActorParent {
|
|
getDimensions(params) {
|
|
return this.sendQuery("GetDimensions", params);
|
|
}
|
|
}
|
|
|
|
ChromeUtils.registerWindowActor("Screenshot", {
|
|
parent: {
|
|
moduleURI: "resource:///modules/HeadlessShell.jsm",
|
|
},
|
|
child: {
|
|
moduleURI: "resource:///modules/ScreenshotChild.jsm",
|
|
},
|
|
});
|
|
|
|
function loadContentWindow(browser, url) {
|
|
let uri;
|
|
try {
|
|
uri = Services.io.newURI(url);
|
|
} catch (e) {
|
|
let msg = `Invalid URL passed to loadContentWindow(): ${url}`;
|
|
console.error(msg);
|
|
return Promise.reject(new Error(msg));
|
|
}
|
|
|
|
const principal = Services.scriptSecurityManager.getSystemPrincipal();
|
|
return new Promise((resolve, reject) => {
|
|
let oa = E10SUtils.predictOriginAttributes({
|
|
browser,
|
|
});
|
|
let loadURIOptions = {
|
|
triggeringPrincipal: principal,
|
|
remoteType: E10SUtils.getRemoteTypeForURI(
|
|
url,
|
|
true,
|
|
false,
|
|
E10SUtils.DEFAULT_REMOTE_TYPE,
|
|
null,
|
|
oa
|
|
),
|
|
};
|
|
browser.loadURI(uri.spec, loadURIOptions);
|
|
let { webProgress } = browser;
|
|
|
|
let progressListener = {
|
|
onLocationChange(progress, request, location, flags) {
|
|
// Ignore inner-frame events
|
|
if (!progress.isTopLevel) {
|
|
return;
|
|
}
|
|
// Ignore events that don't change the document
|
|
if (flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
|
|
return;
|
|
}
|
|
// Ignore the initial about:blank, unless about:blank is requested
|
|
if (location.spec == "about:blank" && uri.spec != "about:blank") {
|
|
return;
|
|
}
|
|
|
|
progressListeners.delete(progressListener);
|
|
webProgress.removeProgressListener(progressListener);
|
|
resolve();
|
|
},
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
"nsIWebProgressListener",
|
|
"nsISupportsWeakReference",
|
|
]),
|
|
};
|
|
progressListeners.add(progressListener);
|
|
webProgress.addProgressListener(
|
|
progressListener,
|
|
Ci.nsIWebProgress.NOTIFY_LOCATION
|
|
);
|
|
});
|
|
}
|
|
|
|
async function takeScreenshot(
|
|
fullWidth,
|
|
fullHeight,
|
|
contentWidth,
|
|
contentHeight,
|
|
path,
|
|
url
|
|
) {
|
|
let frame;
|
|
try {
|
|
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;`
|
|
);
|
|
browser.setAttribute("maychangeremoteness", "true");
|
|
doc.documentElement.appendChild(browser);
|
|
|
|
await loadContentWindow(browser, url);
|
|
|
|
let actor = browser.browsingContext.currentWindowGlobal.getActor(
|
|
"Screenshot"
|
|
);
|
|
let dimensions = await actor.getDimensions();
|
|
|
|
let canvas = doc.createElementNS(
|
|
"http://www.w3.org/1999/xhtml",
|
|
"html:canvas"
|
|
);
|
|
let context = canvas.getContext("2d");
|
|
let width = dimensions.innerWidth;
|
|
let height = dimensions.innerHeight;
|
|
if (fullWidth) {
|
|
width += dimensions.scrollMaxX - dimensions.scrollMinX;
|
|
}
|
|
if (fullHeight) {
|
|
height += dimensions.scrollMaxY - dimensions.scrollMinY;
|
|
}
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
let rect = new DOMRect(0, 0, width, height);
|
|
|
|
let snapshot = await browser.browsingContext.currentWindowGlobal.drawSnapshot(
|
|
rect,
|
|
1,
|
|
"rgb(255, 255, 255)"
|
|
);
|
|
context.drawImage(snapshot, 0, 0);
|
|
|
|
snapshot.close();
|
|
|
|
let blob = await new Promise(resolve => canvas.toBlob(resolve));
|
|
|
|
let reader = await new Promise(resolve => {
|
|
let fr = new FileReader();
|
|
fr.onloadend = () => resolve(fr);
|
|
fr.readAsArrayBuffer(blob);
|
|
});
|
|
|
|
await IOUtils.write(path, new Uint8Array(reader.result));
|
|
dump("Screenshot saved to: " + path + "\n");
|
|
} catch (e) {
|
|
dump("Failure taking screenshot: " + e + "\n");
|
|
} finally {
|
|
if (frame) {
|
|
frame.destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
let HeadlessShell = {
|
|
async handleCmdLineArgs(cmdLine, URLlist) {
|
|
try {
|
|
// Don't quit even though we don't create a window
|
|
Services.startup.enterLastWindowClosingSurvivalArea();
|
|
|
|
// Default options
|
|
let fullWidth = true;
|
|
let fullHeight = true;
|
|
// Most common screen resolution of Firefox users
|
|
let contentWidth = 1366;
|
|
let contentHeight = 768;
|
|
|
|
// Parse `window-size`
|
|
try {
|
|
var dimensionsStr = cmdLine.handleFlagWithParam("window-size", true);
|
|
} catch (e) {
|
|
dump("expected format: --window-size width[,height]\n");
|
|
return;
|
|
}
|
|
if (dimensionsStr) {
|
|
let success;
|
|
let dimensions = dimensionsStr.split(",", 2);
|
|
if (dimensions.length == 1) {
|
|
success = dimensions[0] > 0;
|
|
if (success) {
|
|
fullWidth = false;
|
|
fullHeight = true;
|
|
contentWidth = dimensions[0];
|
|
}
|
|
} else {
|
|
success = dimensions[0] > 0 && dimensions[1] > 0;
|
|
if (success) {
|
|
fullWidth = false;
|
|
fullHeight = false;
|
|
contentWidth = dimensions[0];
|
|
contentHeight = dimensions[1];
|
|
}
|
|
}
|
|
|
|
if (!success) {
|
|
dump("expected format: --window-size width[,height]\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
let urlOrFileToSave = null;
|
|
try {
|
|
urlOrFileToSave = cmdLine.handleFlagWithParam("screenshot", true);
|
|
} catch (e) {
|
|
// We know that the flag exists so we only get here if there was no parameter.
|
|
cmdLine.handleFlag("screenshot", true); // Remove `screenshot`
|
|
}
|
|
|
|
// Assume that the remaining arguments that do not start
|
|
// with a hyphen are URLs
|
|
for (let i = 0; i < cmdLine.length; ++i) {
|
|
const argument = cmdLine.getArgument(i);
|
|
if (argument.startsWith("-")) {
|
|
dump(`Warning: unrecognized command line flag ${argument}\n`);
|
|
// To emulate the pre-nsICommandLine behavior, we ignore
|
|
// the argument after an unrecognized flag.
|
|
++i;
|
|
} else {
|
|
URLlist.push(argument);
|
|
}
|
|
}
|
|
|
|
let path = null;
|
|
if (urlOrFileToSave && !URLlist.length) {
|
|
// URL was specified next to "-screenshot"
|
|
// Example: -screenshot https://www.example.com -attach-console
|
|
URLlist.push(urlOrFileToSave);
|
|
} else {
|
|
path = urlOrFileToSave;
|
|
}
|
|
|
|
if (!path) {
|
|
path = PathUtils.join(cmdLine.workingDirectory.path, "screenshot.png");
|
|
}
|
|
|
|
if (URLlist.length == 1) {
|
|
await takeScreenshot(
|
|
fullWidth,
|
|
fullHeight,
|
|
contentWidth,
|
|
contentHeight,
|
|
path,
|
|
URLlist[0]
|
|
);
|
|
} else {
|
|
dump("expected exactly one URL when using `screenshot`\n");
|
|
}
|
|
} finally {
|
|
Services.startup.exitLastWindowClosingSurvivalArea();
|
|
Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
|
|
}
|
|
},
|
|
};
|