Bug 1403686 - Crops screenshots to a region based on a selector. Initial Impl of cropping with Chris Cho. r=jaws

Can now crop screenshots to a given list of XUL elements, which is specified by CSS selectors or custom functions.
Also changed behavior so that if different window types are given, the application exits.

MozReview-Commit-ID: CqmIJFufONw

--HG--
extra : rebase_source : ede33b1c25a8507cdc6abcc6f4bc697e3acd9f0d
This commit is contained in:
Robin Miller 2017-10-14 17:00:37 -04:00
Родитель f6f94ad8e2
Коммит 189e35c016
8 изменённых файлов: 158 добавлений и 23 удалений

Просмотреть файл

@ -4,4 +4,5 @@ support-files =
head.js
[browser_screenshots.js]
[browser_screenshots_cropping.js]
[browser_boundingbox.js]

Просмотреть файл

@ -0,0 +1,82 @@
/* 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";
Cu.import("resource://gre/modules/Geometry.jsm", this);
async function draw(window, src) {
const { document, Image } = window;
const promise = new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
// Create a new offscreen canvas
const canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
resolve(canvas);
};
img.onerror = function() {
reject(`error loading image ${src}`);
};
// Load the src image for drawing
img.src = src;
});
return promise;
}
async function compareImages(window, expected, test) {
const testCanvas = await draw(window, test);
const expectedCanvas = await draw(window, expected);
is(testCanvas.width, expectedCanvas.width, "The test and expected images must be the same size");
is(testCanvas.height, expectedCanvas.height, "The test and expected images must be the same size");
const nsIDOMWindowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
return nsIDOMWindowUtils.compareCanvases(expectedCanvas, testCanvas, {});
}
async function cropAndCompare(window, src, expected, test, region) {
await TestRunner._cropImage(window, src, region, test);
return compareImages(window, expected, OS.Path.toFileURI(test));
}
add_task(async function crop() {
const window = Services.wm.getMostRecentWindow("navigator:browser");
const tmp = OS.Constants.Path.tmpDir;
is(await cropAndCompare(
window,
"chrome://mozscreenshots/content/lib/robot.png",
"chrome://mozscreenshots/content/lib/robot_upperleft.png",
OS.Path.join(tmp, "test_cropped_upperleft.png"),
new Rect(0, 0, 32, 32)
), 0, "The image should be cropped to the upper left quadrant");
is(await cropAndCompare(
window,
"chrome://mozscreenshots/content/lib/robot.png",
"chrome://mozscreenshots/content/lib/robot_center.png",
OS.Path.join(tmp, "test_cropped_center.png"),
new Rect(16, 16, 32, 32)
), 0, "The image should be cropped to the center of the image");
is(await cropAndCompare(
window,
"chrome://mozscreenshots/content/lib/robot.png",
"chrome://mozscreenshots/content/lib/robot_uncropped.png",
OS.Path.join(tmp, "test_uncropped.png"),
new Rect(-8, -9, 80, 80)
), 0, "The image should be not be cropped, and the cropping region should be clipped to the size of the image");
});

Просмотреть файл

@ -64,11 +64,11 @@ this.Screenshot = {
},
// Capture the whole screen using an external application.
captureExternal(filename) {
async captureExternal(filename) {
let imagePath = this._buildImagePath(filename);
return this._screenshotFunction(imagePath).then(() => {
log.debug("saved screenshot: " + filename);
});
await this._screenshotFunction(imagePath);
log.debug("saved screenshot: " + filename);
return imagePath;
},
// helpers
@ -102,12 +102,6 @@ this.Screenshot = {
// Run the process.
let args = ["-x", "-t", "png"];
// Darwin version number for OS X 10.6 is 10.x
if (windowID && Services.sysinfo.getProperty("version").indexOf("10.") !== 0) {
// Capture only that window on 10.7+
args.push("-l");
args.push(windowID);
}
args.push(filename);
process.runAsync(args, args.length, this._processObserver(resolve, reject));
});

Просмотреть файл

@ -20,7 +20,7 @@ Cu.import("resource://gre/modules/Geometry.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "BrowserTestUtils",
"resource://testing-common/BrowserTestUtils.jsm");
// Screenshot.jsm must be imported this way for xpcshell tests to work
XPCOMUtils.defineLazyModuleGetter(this, "Screenshot", "chrome://mozscreenshots/content/Screenshot.jsm");
// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
@ -324,21 +324,34 @@ this.TestRunner = {
return;
}
await this._onConfigurationReady(combo);
// Collect selectors from combo configs for cropping region
let windowType;
const finalSelectors = [];
for (const obj of combo) {
if (!windowType) {
windowType = obj.windowType;
} else if (windowType !== obj.windowType) {
log.warn("\tConfigurations with multiple window types are not allowed");
return;
}
for (const selector of obj.selectors) {
finalSelectors.push(selector);
}
}
const rect = this._findBoundingBox(finalSelectors, windowType);
await this._onConfigurationReady(combo, rect);
},
_onConfigurationReady(combo) {
let delayedScreenshot = () => {
let filename = padLeft(this.currentComboIndex + 1,
String(this.combos.length).length) + this._comboName(combo);
return Screenshot.captureExternal(filename)
.then(() => {
this.completedCombos++;
});
};
async _onConfigurationReady(combo, rect) {
let filename = padLeft(this.currentComboIndex + 1,
String(this.combos.length).length) + this._comboName(combo);
const imagePath = await Screenshot.captureExternal(filename);
let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
await this._cropImage(browserWindow, OS.Path.toFileURI(imagePath), rect, imagePath);
this.completedCombos++;
log.debug("_onConfigurationReady");
return delayedScreenshot();
},
_comboName(combo) {
@ -347,6 +360,51 @@ this.TestRunner = {
}, "");
},
async _cropImage(window, srcPath, rect, targetPath) {
const { document, Image } = window;
const promise = new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
// Clip the cropping region to the size of the screenshot
// This is necessary mostly to deal with offscreen windows, since we
// are capturing an image of the operating system's desktop.
rect.left = Math.max(0, rect.left);
rect.right = Math.min(img.naturalWidth, rect.right);
rect.top = Math.max(0, rect.top);
rect.bottom = Math.min(img.naturalHeight, rect.bottom);
// Create a new offscreen canvas with the width and height given by the
// size of the region we want to crop to
const canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas.width = rect.width;
canvas.height = rect.height;
const ctx = canvas.getContext("2d");
// By drawing the image with the negative offset, the unwanted regions
// are drawn off canvas, and are not captured when the canvas is saved.
ctx.drawImage(img, -rect.x, -rect.y);
// Converts the canvas to a binary blob, which can be saved to a png
canvas.toBlob((blob) => {
// Use a filereader to convert the raw binary blob into a writable buffer
const fr = new FileReader();
fr.onload = function(e) {
const buffer = new Uint8Array(e.target.result);
// Save the file and complete the promise
OS.File.writeAtomic(targetPath, buffer, {}).then(resolve);
};
// Do the conversion
fr.readAsArrayBuffer(blob);
});
};
img.onerror = function() {
reject(`error loading image ${srcPath}`);
};
// Load the src image for drawing
img.src = srcPath;
});
return promise;
},
/**
* Finds the index of the first comma that is not enclosed within square brackets.
* @param {String} envVar - the string that needs to be searched

Просмотреть файл

@ -29,7 +29,7 @@ this.TabsInTitlebar = {
},
tabsOutsideTitlebar: {
selectors: ["#navigator-toolbox", "#titlebar"],
selectors: ["#navigator-toolbox"].concat(Services.appinfo.OS == "Linux" ? [] : ["#titlebar"]),
async applyConfig() {
Services.prefs.setBoolPref(PREF_TABS_IN_TITLEBAR, false);
},

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.4 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 9.6 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.1 KiB