Bug 1636508 - Make tabs.captureTab compatible with Fission r=mattwoodrow,robwu,geckoview-reviewers,agi

Also fix WindowGlobalParent.drawSnapshot() to render the currently visible
viewport when called with a null rect, and clarify the webidl comment.

Differential Revision: https://phabricator.services.mozilla.com/D87971
This commit is contained in:
Tomislav Jovanovic 2020-08-25 11:30:52 +00:00
parent aff64e4ea9
commit ccc7783ad3
10 changed files with 134 additions and 129 deletions

View File

@ -36,6 +36,7 @@ support-files =
file_title.html
file_indexedDB.html
file_serviceWorker.html
file_green.html
webNav_createdTarget.html
webNav_createdTargetSource.html
webNav_createdTargetSource_subframe.html

View File

@ -2,35 +2,13 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
async function runTest(options) {
options.neutral = [0xaa, 0xaa, 0xaa];
let html = `
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"></head>
<body style="background-color: rgb(${options.color})">
<!-- Fill most of the image with a neutral color to test edge-to-edge scaling. -->
<div style="position: absolute;
left: 2px;
right: 2px;
top: 2px;
bottom: 2px;
background: rgb(${options.neutral});"></div>
</body>
</html>
`;
let url = `data:text/html,${encodeURIComponent(html)}`;
async function runTest({ html, fullZoom = 1, coords }) {
let url = `data:text/html,${encodeURIComponent(html)}#scroll`;
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url, true);
tab.linkedBrowser.fullZoom = options.fullZoom;
async function background(options) {
browser.test.log(
`Test color ${options.color} at fullZoom=${options.fullZoom}`
);
tab.linkedBrowser.fullZoom = fullZoom;
async function background(coords) {
try {
let [tab] = await browser.tabs.query({
currentWindow: true,
@ -68,17 +46,18 @@ async function runTest(options) {
);
[jpeg, png] = await Promise.all(promises);
let tabDims = `${tab.width}\u00d7${tab.height}`;
let images = { jpeg, png };
for (let format of Object.keys(images)) {
let img = images[format];
let dims = `${img.width}\u00d7${img.height}`;
browser.test.assertEq(
tabDims,
dims,
`${format} dimensions are correct`
// WGP.drawSnapshot() deals in int coordinates, and rounds down.
browser.test.assertTrue(
Math.abs(tab.width - img.width) <= 1,
`${format} ok image width: ${img.width}, from a tab: ${tab.width}`
);
browser.test.assertTrue(
Math.abs(tab.height - img.height) <= 1,
`${format} ok image height ${img.height}, from a tab: ${tab.height}`
);
let canvas = document.createElement("canvas");
@ -89,19 +68,9 @@ async function runTest(options) {
let ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
// Check the colors of the first and last pixels of the image, to make
// sure we capture the entire frame, and scale it correctly.
let coords = [
{ x: 0, y: 0, color: options.color },
{ x: img.width - 1, y: img.height - 1, color: options.color },
{
x: (img.width / 2) | 0,
y: (img.height / 2) | 0,
color: options.neutral,
},
];
for (let { x, y, color } of coords) {
x = (x + img.width) % img.width;
y = (y + img.height) % img.height;
let imageData = ctx.getImageData(x, y, 1, 1).data;
if (format == "png") {
@ -153,7 +122,7 @@ async function runTest(options) {
permissions: ["<all_urls>"],
},
background: `(${background})(${JSON.stringify(options)})`,
background: `(${background})(${JSON.stringify(coords)})`,
});
await extension.startup();
@ -165,14 +134,77 @@ async function runTest(options) {
BrowserTestUtils.removeTab(tab);
}
add_task(async function testCaptureTab() {
await runTest({ color: [0, 0, 0], fullZoom: 1 });
function testEdgeToEdge({ color, fullZoom }) {
let neutral = [0xaa, 0xaa, 0xaa];
await runTest({ color: [0, 0, 0], fullZoom: 2 });
let html = `
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"></head>
<body style="background-color: rgb(${color})">
<!-- Fill most of the image with a neutral color to test edge-to-edge scaling. -->
<div style="position: absolute;
left: 2px;
right: 2px;
top: 2px;
bottom: 2px;
background: rgb(${neutral});"></div>
</body>
</html>
`;
await runTest({ color: [0, 0, 0], fullZoom: 0.5 });
// Check the colors of the first and last pixels of the image, to make
// sure we capture the entire frame, and scale it correctly.
let coords = [
{ x: 0, y: 0, color },
{ x: -1, y: -1, color },
{ x: 300, y: 200, color: neutral },
];
await runTest({ color: [255, 255, 255], fullZoom: 1 });
info(`Test edge to edge color ${color} at fullZoom=${fullZoom}`);
return runTest({ html, fullZoom, coords });
}
add_task(async function testCaptureEdgeToEdge() {
await testEdgeToEdge({ color: [0, 0, 0], fullZoom: 1 });
await testEdgeToEdge({ color: [0, 0, 0], fullZoom: 2 });
await testEdgeToEdge({ color: [0, 0, 0], fullZoom: 0.5 });
await testEdgeToEdge({ color: [255, 255, 255], fullZoom: 1 });
});
// Test currently visible viewport is captured if scrolling is involved.
add_task(async function testScrolledViewport() {
await runTest({
html: `<!DOCTYPE html>
<meta charset=utf-8>
<div style="background: yellow; width: 50%; height: 500px;"></div>
<div id=scroll style="background: red; width: 25%; height: 5000px;"></div>
Opened with the #scroll fragment, scrolls the div ^ into view.
`,
coords: [
{ x: 50, y: 50, color: [255, 0, 0] },
{ x: 50, y: -50, color: [255, 0, 0] },
{ x: -50, y: -50, color: [255, 255, 255] },
],
});
});
// Test OOP iframes are captured, for Fission compatibility.
add_task(async function testOOPiframe() {
await runTest({
html: `<!DOCTYPE html>
<meta charset=utf-8>
<iframe src="http://example.com/browser/browser/components/extensions/test/browser/file_green.html"></iframe>
`,
coords: [
{ x: 50, y: 50, color: [0, 255, 0] },
{ x: 50, y: -50, color: [255, 255, 255] },
{ x: -50, y: 50, color: [255, 255, 255] },
],
});
});
add_task(async function testCaptureTabPermissions() {

View File

@ -74,17 +74,18 @@ async function runTest(options) {
);
[jpeg, png] = await Promise.all(promises);
let tabDims = `${tab.width}\u00d7${tab.height}`;
let images = { jpeg, png };
for (let format of Object.keys(images)) {
let img = images[format];
let dims = `${img.width}\u00d7${img.height}`;
browser.test.assertEq(
tabDims,
dims,
`${format} dimensions are correct`
// WGP.drawSnapshot() deals in int coordinates, and rounds down.
browser.test.assertTrue(
Math.abs(tab.width - img.width) <= 1,
`${format} ok image width: ${img.width}, from a tab: ${tab.width}`
);
browser.test.assertTrue(
Math.abs(tab.height - img.height) <= 1,
`${format} ok image height ${img.height}, from a tab: ${tab.height}`
);
let canvas = document.createElement("canvas");

View File

@ -0,0 +1,3 @@
<meta charset=utf-8>
<title>Super green test page</title>
<body style="background: #0f0">

View File

@ -81,9 +81,8 @@ interface WindowGlobalParent : WindowContext {
/**
* Renders a region of the frame into an image bitmap.
*
* @param rect Specify the area of the window to render, in CSS pixels. This
* is relative to the current scroll position. If null, the entire viewport
* is rendered.
* @param rect Specify the area of the document to render, in CSS pixels,
* relative to the page. If null, the currently visible viewport is rendered.
* @param scale The scale to render the window at. Use devicePixelRatio
* to have comparable rendering to the OS.
* @param backgroundColor The background color to use.

View File

@ -623,8 +623,13 @@ already_AddRefed<mozilla::dom::Promise> WindowGlobalParent::DrawSnapshot(
return nullptr;
}
if (!gfx::CrossProcessPaint::Start(this, aRect, (float)aScale, color,
gfx::CrossProcessPaintFlags::None,
gfx::CrossProcessPaintFlags flags = gfx::CrossProcessPaintFlags::None;
if (!aRect) {
// If no explicit Rect was passed, we want the currently visible viewport.
flags = gfx::CrossProcessPaintFlags::DrawView;
}
if (!gfx::CrossProcessPaint::Start(this, aRect, (float)aScale, color, flags,
promise)) {
aRv = NS_ERROR_FAILURE;
return nullptr;

View File

@ -63,14 +63,20 @@ function* runTest(options) {
}));
[jpeg, png] = await Promise.all(promises);
const tabDims = `${tab.width}\u00d7${tab.height}`;
const images = {jpeg, png};
for (const format of Object.keys(images)) {
const img = images[format];
const dims = `${img.width}\u00d7${img.height}`;
browser.test.assertEq(tabDims, dims, `${format} dimensions are correct`);
// WGP.drawSnapshot() deals in int coordinates, and rounds down.
browser.test.assertTrue(
Math.abs(tab.width - img.width) <= 1,
`${format} ok image width: ${img.width}, from a tab: ${tab.width}`
);
browser.test.assertTrue(
Math.abs(tab.height - img.height) <= 1,
`${format} ok image height ${img.height}, from a tab: ${tab.height}`
);
const canvas = document.createElement("canvas");
canvas.width = img.width;

View File

@ -1101,34 +1101,6 @@ var ExtensionContent = {
return context;
},
handleExtensionCapture(global, width, height, options) {
let win = global.content;
const XHTML_NS = "http://www.w3.org/1999/xhtml";
let canvas = win.document.createElementNS(XHTML_NS, "canvas");
canvas.width = width;
canvas.height = height;
canvas.mozOpaque = true;
let ctx = canvas.getContext("2d");
// We need to scale the image to the visible size of the browser,
// in order for the result to appear as the user sees it when
// settings like full zoom come into play.
ctx.scale(canvas.width / win.innerWidth, canvas.height / win.innerHeight);
ctx.drawWindow(
win,
win.scrollX,
win.scrollY,
win.innerWidth,
win.innerHeight,
"#fff"
);
return canvas.toDataURL(`image/${options.format}`, options.quality / 100);
},
handleDetectLanguage(global, target) {
let doc = target.content.document;
@ -1217,13 +1189,6 @@ var ExtensionContent = {
async receiveMessage(global, name, target, data, recipient) {
switch (name) {
case "Extension:Capture":
return this.handleExtensionCapture(
global,
data.width,
data.height,
data.options
);
case "Extension:DetectLanguage":
return this.handleDetectLanguage(global, target);
case "WebNavigation:GetFrame":

View File

@ -69,7 +69,6 @@ class ExtensionGlobal {
this.frameData = null;
MessageChannel.addListener(global, "Extension:Capture", this);
MessageChannel.addListener(global, "Extension:DetectLanguage", this);
MessageChannel.addListener(global, "WebNavigation:GetFrame", this);
MessageChannel.addListener(global, "WebNavigation:GetAllFrames", this);

View File

@ -7,16 +7,11 @@
/* globals EventEmitter */
ChromeUtils.defineModuleGetter(
this,
"PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"Services",
"resource://gre/modules/Services.jsm"
);
XPCOMUtils.defineLazyModuleGetters(this, {
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
});
XPCOMUtils.defineLazyPreferenceGetter(
this,
"containersEnabled",
@ -112,7 +107,7 @@ class TabBase {
}
/**
* Capture the visible area of this tab, and return the result as a data: URL.
* Capture the visible area of this tab, and return the result as a data: URI.
*
* @param {BaseContext} context
* The extension context for which to perform the capture.
@ -127,24 +122,23 @@ class TabBase {
*
* @returns {Promise<string>}
*/
capture(context, options = null) {
if (!options) {
options = {};
}
if (options.format == null) {
options.format = "png";
}
if (options.quality == null) {
options.quality = 92;
}
async capture(context, options = null) {
let { ZoomManager, devicePixelRatio } = this.nativeTab.ownerGlobal;
let scale = ZoomManager.getZoomForBrowser(this.browser) * devicePixelRatio;
let message = {
options,
width: this.width,
height: this.height,
};
let wgp = this.browsingContext.currentWindowGlobal;
let image = await wgp.drawSnapshot(null, scale, "white");
return this.sendMessage(context, "Extension:Capture", message);
let win = Services.appShell.hiddenDOMWindow;
let canvas = win.document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
let ctx = canvas.getContext("2d", { alpha: false });
ctx.drawImage(image, 0, 0);
image.close();
return canvas.toDataURL(`image/${options?.format}`, options?.quality / 100);
}
/**