зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1791086 - Select elements within iframes in screenshots. r=sfoster,mconley
Differential Revision: https://phabricator.services.mozilla.com/D195165
This commit is contained in:
Родитель
fa0e1f01ae
Коммит
0aaf858381
|
@ -713,6 +713,17 @@ let JSWINDOWACTORS = {
|
|||
enablePreference: "screenshots.browser.component.enabled",
|
||||
},
|
||||
|
||||
ScreenshotsHelper: {
|
||||
parent: {
|
||||
esModuleURI: "resource:///modules/ScreenshotsUtils.sys.mjs",
|
||||
},
|
||||
child: {
|
||||
esModuleURI: "resource:///modules/ScreenshotsHelperChild.sys.mjs",
|
||||
},
|
||||
allFrames: true,
|
||||
enablePreference: "screenshots.browser.component.enabled",
|
||||
},
|
||||
|
||||
SearchSERPTelemetry: {
|
||||
parent: {
|
||||
esModuleURI: "resource:///actors/SearchSERPTelemetryParent.sys.mjs",
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/* 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/. */
|
||||
|
||||
import {
|
||||
getBestRectForElement,
|
||||
getElementFromPoint,
|
||||
} from "chrome://browser/content/screenshots/overlayHelpers.mjs";
|
||||
|
||||
/**
|
||||
* This class is used to get the dimensions of hovered elements within iframes.
|
||||
* The main content process cannot get the dimensions of elements within
|
||||
* iframes so a message will be send to this actor to get the dimensions of the
|
||||
* element for a given point inside the iframe.
|
||||
*/
|
||||
export class ScreenshotsHelperChild extends JSWindowActorChild {
|
||||
receiveMessage(message) {
|
||||
if (message.name === "ScreenshotsHelper:GetElementRectFromPoint") {
|
||||
return this.getBestElementRectFromPoint(message.data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async getBestElementRectFromPoint(data) {
|
||||
let { x, y } = data;
|
||||
|
||||
x -= this.contentWindow.mozInnerScreenX;
|
||||
y -= this.contentWindow.mozInnerScreenY;
|
||||
|
||||
let { ele, rect } = await getElementFromPoint(x, y, this.document);
|
||||
|
||||
if (!rect) {
|
||||
rect = getBestRectForElement(ele, this.document);
|
||||
}
|
||||
|
||||
if (rect) {
|
||||
rect = {
|
||||
left: rect.left + this.contentWindow.mozInnerScreenX,
|
||||
right: rect.right + this.contentWindow.mozInnerScreenX,
|
||||
top: rect.top + this.contentWindow.mozInnerScreenY,
|
||||
bottom: rect.bottom + this.contentWindow.mozInnerScreenY,
|
||||
};
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
}
|
|
@ -30,6 +30,7 @@ import {
|
|||
setMaxDetectHeight,
|
||||
setMaxDetectWidth,
|
||||
getBestRectForElement,
|
||||
getElementFromPoint,
|
||||
Region,
|
||||
WindowDimensions,
|
||||
} from "chrome://browser/content/screenshots/overlayHelpers.mjs";
|
||||
|
@ -1386,18 +1387,27 @@ export class ScreenshotsOverlay {
|
|||
* @param {Number} clientX The x position relative to the viewport
|
||||
* @param {Number} clientY The y position relative to the viewport
|
||||
*/
|
||||
handleElementHover(clientX, clientY) {
|
||||
async handleElementHover(clientX, clientY) {
|
||||
this.setPointerEventsNone();
|
||||
let ele = this.document.elementFromPoint(clientX, clientY);
|
||||
let promise = getElementFromPoint(clientX, clientY, this.document);
|
||||
this.resetPointerEvents();
|
||||
let { ele, rect } = await promise;
|
||||
|
||||
if (this.#cachedEle && this.#cachedEle === ele) {
|
||||
if (
|
||||
this.#cachedEle &&
|
||||
!this.window.HTMLIFrameElement.isInstance(this.#cachedEle) &&
|
||||
this.#cachedEle === ele
|
||||
) {
|
||||
// Still hovering over the same element
|
||||
return;
|
||||
}
|
||||
this.#cachedEle = ele;
|
||||
|
||||
let rect = getBestRectForElement(ele, this.document);
|
||||
if (!rect) {
|
||||
// this means we found an element that wasn't an iframe
|
||||
rect = getBestRectForElement(ele, this.document);
|
||||
}
|
||||
|
||||
if (rect) {
|
||||
let { scrollX, scrollY } = this.windowDimensions.dimensions;
|
||||
let { left, top, right, bottom } = rect;
|
||||
|
|
|
@ -109,6 +109,19 @@ export class ScreenshotsComponentParent extends JSWindowActorParent {
|
|||
}
|
||||
}
|
||||
|
||||
export class ScreenshotsHelperParent extends JSWindowActorParent {
|
||||
receiveMessage(message) {
|
||||
switch (message.name) {
|
||||
case "ScreenshotsHelper:GetElementRectFromPoint":
|
||||
let cxt = BrowsingContext.get(message.data.bcId);
|
||||
return cxt.currentWindowGlobal
|
||||
.getActor("ScreenshotsHelper")
|
||||
.sendQuery("ScreenshotsHelper:GetElementRectFromPoint", message.data);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const UIPhases = {
|
||||
CLOSED: 0, // nothing showing
|
||||
INITIAL: 1, // panel and overlay showing
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
"ScreenshotsHelperChild.sys.mjs",
|
||||
"ScreenshotsOverlayChild.sys.mjs",
|
||||
"ScreenshotsUtils.sys.mjs",
|
||||
]
|
||||
|
|
|
@ -42,6 +42,64 @@ export function setMaxDetectWidth(maxWidth) {
|
|||
MAX_DETECT_WIDTH = maxWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will try to get an element from a given point in the doc.
|
||||
* This function is recursive because when sending a message to the
|
||||
* ScreenshotsHelper, the ScreenshotsHelper will call into this function.
|
||||
* This only occurs when the element at the given point is an iframe.
|
||||
*
|
||||
* If the element is an iframe, we will send a message to the ScreenshotsHelper
|
||||
* actor in the correct context to get the element at the given point.
|
||||
* The message will return the "getBestRectForElement" for the element at the
|
||||
* given point.
|
||||
*
|
||||
* If the element is not an iframe, then we will just return the element.
|
||||
*
|
||||
* @param {Number} x The x coordinate
|
||||
* @param {Number} y The y coordinate
|
||||
* @param {Document} doc The document
|
||||
* @returns {Object}
|
||||
* ele: The element for a given point (x, y)
|
||||
* rect: The rect for the given point if ele is an iframe
|
||||
* otherwise null
|
||||
*/
|
||||
export async function getElementFromPoint(x, y, doc) {
|
||||
let ele = null;
|
||||
let rect = null;
|
||||
try {
|
||||
ele = doc.elementFromPoint(x, y);
|
||||
// if the element is an iframe, we need to send a message to that browsing context
|
||||
// to get the coordinates of the element in the iframe
|
||||
if (doc.defaultView.HTMLIFrameElement.isInstance(ele)) {
|
||||
let actor =
|
||||
ele.browsingContext.parentWindowContext.windowGlobalChild.getActor(
|
||||
"ScreenshotsHelper"
|
||||
);
|
||||
rect = await actor.sendQuery(
|
||||
"ScreenshotsHelper:GetElementRectFromPoint",
|
||||
{
|
||||
x: x + ele.ownerGlobal.mozInnerScreenX,
|
||||
y: y + ele.ownerGlobal.mozInnerScreenY,
|
||||
bcId: ele.browsingContext.id,
|
||||
}
|
||||
);
|
||||
|
||||
if (rect) {
|
||||
rect = {
|
||||
left: rect.left - ele.ownerGlobal.mozInnerScreenX,
|
||||
right: rect.right - ele.ownerGlobal.mozInnerScreenX,
|
||||
top: rect.top - ele.ownerGlobal.mozInnerScreenY,
|
||||
bottom: rect.bottom - ele.ownerGlobal.mozInnerScreenY,
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return { ele, rect };
|
||||
}
|
||||
|
||||
/**
|
||||
* This function takes an element and finds a suitable rect to draw the hover box on
|
||||
* @param {Element} ele The element to find a suitale rect of
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
[DEFAULT]
|
||||
support-files = [
|
||||
"head.js",
|
||||
"iframe-test-page.html",
|
||||
"first-iframe.html",
|
||||
"second-iframe.html",
|
||||
"test-page.html",
|
||||
"short-test-page.html",
|
||||
"large-test-page.html",
|
||||
|
@ -11,6 +14,8 @@ prefs = [
|
|||
"screenshots.browser.component.enabled=true",
|
||||
]
|
||||
|
||||
["browser_iframe_test.js"]
|
||||
|
||||
["browser_overlay_keyboard_test.js"]
|
||||
|
||||
["browser_screenshots_drag_scroll_test.js"]
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_selectingElementsInIframes() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: IFRAME_TEST_PAGE,
|
||||
},
|
||||
async browser => {
|
||||
let helper = new ScreenshotsHelper(browser);
|
||||
helper.triggerUIFromToolbar();
|
||||
|
||||
// There are two iframes in the test page. One iframe is nested in the
|
||||
// other so we SpecialPowers.spawn into the iframes to get the
|
||||
// dimension/position of the elements within each iframe.
|
||||
let elementDimensions = await SpecialPowers.spawn(
|
||||
browser,
|
||||
[],
|
||||
async () => {
|
||||
let divDims = content.document
|
||||
.querySelector("div")
|
||||
.getBoundingClientRect();
|
||||
|
||||
let iframe = content.document.querySelector("iframe");
|
||||
let iframesDivsDimArr = await SpecialPowers.spawn(
|
||||
iframe,
|
||||
[],
|
||||
async () => {
|
||||
let iframeDivDims = content.document
|
||||
.querySelector("div")
|
||||
.getBoundingClientRect();
|
||||
|
||||
// Element within the first iframe
|
||||
iframeDivDims = {
|
||||
left: iframeDivDims.left + content.window.mozInnerScreenX,
|
||||
top: iframeDivDims.top + content.window.mozInnerScreenY,
|
||||
width: iframeDivDims.width,
|
||||
height: iframeDivDims.height,
|
||||
};
|
||||
|
||||
let nestedIframe = content.document.querySelector("iframe");
|
||||
let nestedIframeDivDims = await SpecialPowers.spawn(
|
||||
nestedIframe,
|
||||
[],
|
||||
async () => {
|
||||
let secondIframeDivDims = content.document
|
||||
.querySelector("div")
|
||||
.getBoundingClientRect();
|
||||
|
||||
// Element within the nested iframe
|
||||
secondIframeDivDims = {
|
||||
left:
|
||||
secondIframeDivDims.left +
|
||||
content.document.defaultView.mozInnerScreenX,
|
||||
top:
|
||||
secondIframeDivDims.top +
|
||||
content.document.defaultView.mozInnerScreenY,
|
||||
width: secondIframeDivDims.width,
|
||||
height: secondIframeDivDims.height,
|
||||
};
|
||||
|
||||
return secondIframeDivDims;
|
||||
}
|
||||
);
|
||||
|
||||
return [iframeDivDims, nestedIframeDivDims];
|
||||
}
|
||||
);
|
||||
|
||||
// Offset each element position for the browser window
|
||||
for (let dims of iframesDivsDimArr) {
|
||||
dims.left -= content.window.mozInnerScreenX;
|
||||
dims.top -= content.window.mozInnerScreenY;
|
||||
}
|
||||
|
||||
return [divDims].concat(iframesDivsDimArr);
|
||||
}
|
||||
);
|
||||
|
||||
info(JSON.stringify(elementDimensions, null, 2));
|
||||
|
||||
for (let el of elementDimensions) {
|
||||
let x = el.left + el.width / 2;
|
||||
let y = el.top + el.height / 2;
|
||||
|
||||
mouse.move(x, y);
|
||||
await helper.waitForHoverElementRect(el.width, el.height);
|
||||
mouse.click(x, y);
|
||||
|
||||
await helper.waitForStateChange("selected");
|
||||
|
||||
let dimensions = await helper.getSelectionRegionDimensions();
|
||||
|
||||
is(
|
||||
dimensions.left,
|
||||
el.left,
|
||||
"The region left position matches the elements left position"
|
||||
);
|
||||
is(
|
||||
dimensions.top,
|
||||
el.top,
|
||||
"The region top position matches the elements top position"
|
||||
);
|
||||
is(
|
||||
dimensions.width,
|
||||
el.width,
|
||||
"The region width matches the elements width"
|
||||
);
|
||||
is(
|
||||
dimensions.height,
|
||||
el.height,
|
||||
"The region height matches the elements height"
|
||||
);
|
||||
|
||||
mouse.click(500, 500);
|
||||
await helper.waitForStateChange("crosshairs");
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
div {
|
||||
font-size: 40px;
|
||||
margin: 30px;
|
||||
width: 234px;
|
||||
height: 51px;
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>Hello world!</div>
|
||||
<iframe
|
||||
width="300"
|
||||
height="300"
|
||||
src="https://example.org/browser/browser/components/screenshots/tests/browser/second-iframe.html"
|
||||
></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -18,6 +18,7 @@ const TEST_ROOT = getRootDirectory(gTestPath).replace(
|
|||
const TEST_PAGE = TEST_ROOT + "test-page.html";
|
||||
const SHORT_TEST_PAGE = TEST_ROOT + "short-test-page.html";
|
||||
const LARGE_TEST_PAGE = TEST_ROOT + "large-test-page.html";
|
||||
const IFRAME_TEST_PAGE = TEST_ROOT + "iframe-test-page.html";
|
||||
|
||||
const { MAX_CAPTURE_DIMENSION, MAX_CAPTURE_AREA } = ChromeUtils.importESModule(
|
||||
"resource:///modules/ScreenshotsUtils.sys.mjs"
|
||||
|
@ -195,11 +196,22 @@ class ScreenshotsHelper {
|
|||
});
|
||||
}
|
||||
|
||||
async waitForHoverElementRect() {
|
||||
return TestUtils.waitForCondition(async () => {
|
||||
let rect = await this.getHoverElementRect();
|
||||
return rect;
|
||||
});
|
||||
async waitForHoverElementRect(expectedWidth, expectedHeight) {
|
||||
return SpecialPowers.spawn(
|
||||
this.browser,
|
||||
[expectedWidth, expectedHeight],
|
||||
async (width, height) => {
|
||||
let screenshotsChild = content.windowGlobalChild.getActor(
|
||||
"ScreenshotsComponent"
|
||||
);
|
||||
let dimensions;
|
||||
await ContentTaskUtils.waitForCondition(() => {
|
||||
dimensions = screenshotsChild.overlay.hoverElementRegion.dimensions;
|
||||
return dimensions.width === width && dimensions.height === height;
|
||||
}, "The hover element region is the expected width and height");
|
||||
return dimensions;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async waitForSelectionRegionSizeChange(currentWidth) {
|
||||
|
@ -394,7 +406,7 @@ class ScreenshotsHelper {
|
|||
let y = Math.floor(rect.y + rect.height / 2);
|
||||
|
||||
mouse.move(x, y);
|
||||
await this.waitForHoverElementRect();
|
||||
await this.waitForHoverElementRect(rect.width, rect.height);
|
||||
mouse.down(x, y);
|
||||
await this.assertStateChange("draggingReady");
|
||||
mouse.up(x, y);
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Screenshots</title>
|
||||
<style>
|
||||
div {
|
||||
font-size: 40px;
|
||||
margin: 30px;
|
||||
width: 233px;
|
||||
height: 50px;
|
||||
color: green;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>Hello world!</div>
|
||||
<iframe
|
||||
width="500"
|
||||
height="500"
|
||||
src="https://example.com/browser/browser/components/screenshots/tests/browser/first-iframe.html"
|
||||
></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
div {
|
||||
font-size: 40px;
|
||||
margin: 30px;
|
||||
width: 235px;
|
||||
height: 52px;
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>Hello world!</div>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче