forked from mirrors/gecko-dev
It has some properties which make it footgunny, especially in the face of Fission. Callers should use WindowGlobalChild.innerWindowId instead. Differential Revision: https://phabricator.services.mozilla.com/D82801
210 lines
6.1 KiB
JavaScript
210 lines
6.1 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";
|
|
const { Cu, Cc, Ci } = require("chrome");
|
|
const Services = require("Services");
|
|
const { LocalizationHelper } = require("devtools/shared/l10n");
|
|
|
|
loader.lazyRequireGetter(this, "getRect", "devtools/shared/layout/utils", true);
|
|
|
|
const CONTAINER_FLASHING_DURATION = 500;
|
|
const STRINGS_URI = "devtools/shared/locales/screenshot.properties";
|
|
const L10N = new LocalizationHelper(STRINGS_URI);
|
|
|
|
// These values are used to truncate the resulting image if the captured area is bigger.
|
|
// This is to avoid failing to produce a screenshot at all.
|
|
// It is recommended to keep these values in sync with the corresponding screenshots addon
|
|
// values in /browser/extensions/screenshots/build/buildSettings.js
|
|
const MAX_IMAGE_WIDTH = 10000;
|
|
const MAX_IMAGE_HEIGHT = 10000;
|
|
|
|
/**
|
|
* This function is called to simulate camera effects
|
|
* @param object document
|
|
* The target document.
|
|
*/
|
|
function simulateCameraFlash(document) {
|
|
const window = document.defaultView;
|
|
const frames = Cu.cloneInto({ opacity: [0, 1] }, window);
|
|
document.documentElement.animate(frames, CONTAINER_FLASHING_DURATION);
|
|
}
|
|
|
|
/**
|
|
* This function simply handles the --delay argument before calling
|
|
* createScreenshotData
|
|
*/
|
|
function captureScreenshot(args, document) {
|
|
if (args.help) {
|
|
return null;
|
|
}
|
|
if (args.delay > 0) {
|
|
return new Promise((resolve, reject) => {
|
|
document.defaultView.setTimeout(() => {
|
|
createScreenshotDataURL(document, args).then(resolve, reject);
|
|
}, args.delay * 1000);
|
|
});
|
|
}
|
|
return createScreenshotDataURL(document, args);
|
|
}
|
|
|
|
exports.captureScreenshot = captureScreenshot;
|
|
|
|
/**
|
|
* This does the dirty work of creating a base64 string out of an
|
|
* area of the browser window
|
|
*/
|
|
function createScreenshotDataURL(document, args) {
|
|
let window = document.defaultView;
|
|
let left = 0;
|
|
let top = 0;
|
|
let width;
|
|
let height;
|
|
const currentX = window.scrollX;
|
|
const currentY = window.scrollY;
|
|
|
|
let filename = getFilename(args.filename);
|
|
|
|
if (args.fullpage) {
|
|
// Bug 961832: Screenshot shows fixed position element in wrong
|
|
// position if we don't scroll to top
|
|
window.scrollTo(0, 0);
|
|
width = window.innerWidth + window.scrollMaxX - window.scrollMinX;
|
|
height = window.innerHeight + window.scrollMaxY - window.scrollMinY;
|
|
filename = filename.replace(".png", "-fullpage.png");
|
|
} else if (args.rawNode) {
|
|
window = args.rawNode.ownerDocument.defaultView;
|
|
({ top, left, width, height } = getRect(window, args.rawNode, window));
|
|
} else if (args.selector) {
|
|
const node = window.document.querySelector(args.selector);
|
|
({ top, left, width, height } = getRect(window, node, window));
|
|
} else {
|
|
left = window.scrollX;
|
|
top = window.scrollY;
|
|
width = window.innerWidth;
|
|
height = window.innerHeight;
|
|
}
|
|
|
|
// Only adjust for scrollbars when considering the full window
|
|
if (args.fullpage) {
|
|
const winUtils = window.windowUtils;
|
|
const scrollbarHeight = {};
|
|
const scrollbarWidth = {};
|
|
winUtils.getScrollbarSize(false, scrollbarWidth, scrollbarHeight);
|
|
width -= scrollbarWidth.value;
|
|
height -= scrollbarHeight.value;
|
|
}
|
|
|
|
let ratio;
|
|
if (args.fullpage) {
|
|
// Always take fullpage screenshots at dpr=1 to avoid failures.
|
|
ratio = 1;
|
|
// Warn the user if they had provided a higher dpr or have a high resolution monitor.
|
|
if ((args.dpr && args.dpr > 1) || window.devicePixelRatio > 1) {
|
|
logWarningInPage(L10N.getStr("screenshotDPRDecreasedWarning"), window);
|
|
}
|
|
} else {
|
|
ratio = args.dpr ? args.dpr : window.devicePixelRatio;
|
|
}
|
|
|
|
// Truncate the width and height if necessary.
|
|
if (width > MAX_IMAGE_WIDTH || height > MAX_IMAGE_HEIGHT) {
|
|
width = Math.min(width, MAX_IMAGE_WIDTH);
|
|
height = Math.min(height, MAX_IMAGE_HEIGHT);
|
|
logWarningInPage(
|
|
L10N.getFormatStr("screenshotTruncationWarning", width, height),
|
|
window
|
|
);
|
|
}
|
|
|
|
const canvas = document.createElementNS(
|
|
"http://www.w3.org/1999/xhtml",
|
|
"canvas"
|
|
);
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
// Even after decreasing width, height and ratio, there may still be cases where the
|
|
// hardware fails at creating the image. Let's catch this so we can at least show an
|
|
// error message to the user.
|
|
let data = null;
|
|
try {
|
|
canvas.width = width * ratio;
|
|
canvas.height = height * ratio;
|
|
ctx.scale(ratio, ratio);
|
|
ctx.drawWindow(window, left, top, width, height, "#fff");
|
|
data = canvas.toDataURL("image/png", "");
|
|
} catch (e) {
|
|
logErrorInPage(L10N.getStr("screenshotRenderingError"), window);
|
|
}
|
|
|
|
// See comment above on bug 961832
|
|
if (args.fullpage) {
|
|
window.scrollTo(currentX, currentY);
|
|
}
|
|
|
|
if (data) {
|
|
simulateCameraFlash(document);
|
|
}
|
|
|
|
return Promise.resolve({
|
|
destinations: [],
|
|
data,
|
|
height,
|
|
width,
|
|
filename,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* We may have a filename specified in args, or we might have to generate
|
|
* one.
|
|
*/
|
|
function getFilename(defaultName) {
|
|
// Create a name for the file if not present
|
|
if (defaultName) {
|
|
return defaultName;
|
|
}
|
|
|
|
const date = new Date();
|
|
let dateString =
|
|
date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
|
|
dateString = dateString
|
|
.split("-")
|
|
.map(function(part) {
|
|
if (part.length == 1) {
|
|
part = "0" + part;
|
|
}
|
|
return part;
|
|
})
|
|
.join("-");
|
|
|
|
const timeString = date
|
|
.toTimeString()
|
|
.replace(/:/g, ".")
|
|
.split(" ")[0];
|
|
return (
|
|
L10N.getFormatStr("screenshotGeneratedFilename", dateString, timeString) +
|
|
".png"
|
|
);
|
|
}
|
|
|
|
function logInPage(text, flags, window) {
|
|
const scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(
|
|
Ci.nsIScriptError
|
|
);
|
|
scriptError.initWithWindowID(
|
|
text,
|
|
null,
|
|
null,
|
|
0,
|
|
0,
|
|
flags,
|
|
"screenshot",
|
|
window.windowGlobalChild.innerWindowId
|
|
);
|
|
Services.console.logMessage(scriptError);
|
|
}
|
|
|
|
const logErrorInPage = (text, window) => logInPage(text, 0, window);
|
|
const logWarningInPage = (text, window) => logInPage(text, 1, window);
|