mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1890721 - Prevent events from reaching the content page. r=sfoster,smaug
Differential Revision: https://phabricator.services.mozilla.com/D207749
This commit is contained in:
parent
592ca16351
commit
da75251680
@ -10,10 +10,14 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ScreenshotsOverlay: "resource:///modules/ScreenshotsOverlayChild.sys.mjs",
|
||||
});
|
||||
|
||||
const SCREENSHOTS_PREVENT_CONTENT_EVENTS_PREF =
|
||||
"screenshots.browser.component.preventContentEvents";
|
||||
|
||||
export class ScreenshotsComponentChild extends JSWindowActorChild {
|
||||
#resizeTask;
|
||||
#scrollTask;
|
||||
#overlay;
|
||||
#preventableEventsAdded = false;
|
||||
|
||||
static OVERLAY_EVENTS = [
|
||||
"click",
|
||||
@ -24,6 +28,21 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
|
||||
"keydown",
|
||||
];
|
||||
|
||||
// The following events are only listened to so we can prevent them from
|
||||
// reaching the content page. The events in OVERLAY_EVENTS are also prevented.
|
||||
static PREVENTABLE_EVENTS = [
|
||||
"mousemove",
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"touchstart",
|
||||
"touchmove",
|
||||
"touchend",
|
||||
"dblclick",
|
||||
"auxclick",
|
||||
"keypress",
|
||||
"contextmenu",
|
||||
];
|
||||
|
||||
get overlay() {
|
||||
return this.#overlay;
|
||||
}
|
||||
@ -62,19 +81,28 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle overlay events here
|
||||
if (
|
||||
ScreenshotsComponentChild.OVERLAY_EVENTS.includes(event.type) ||
|
||||
ScreenshotsComponentChild.PREVENTABLE_EVENTS.includes(event.type)
|
||||
) {
|
||||
if (!this.overlay?.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Preventing a pointerdown event throws an error in debug builds.
|
||||
// See https://searchfox.org/mozilla-central/rev/b41bb321fe4bd7d03926083698ac498ebec0accf/widget/WidgetEventImpl.cpp#566-572
|
||||
// Don't prevent the default context menu.
|
||||
if (!["contextmenu", "pointerdown"].includes(event.type)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
event.stopImmediatePropagation();
|
||||
this.overlay.handleEvent(event);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case "click":
|
||||
case "pointerdown":
|
||||
case "pointermove":
|
||||
case "pointerup":
|
||||
case "keyup":
|
||||
case "keydown":
|
||||
case "selectionchange":
|
||||
if (!this.overlay?.initialized) {
|
||||
return;
|
||||
}
|
||||
this.overlay.handleEvent(event);
|
||||
break;
|
||||
case "beforeunload":
|
||||
this.requestCancelScreenshot("navigation");
|
||||
break;
|
||||
@ -226,7 +254,16 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
|
||||
for (let event of ScreenshotsComponentChild.OVERLAY_EVENTS) {
|
||||
chromeEventHandler.addEventListener(event, this, true);
|
||||
}
|
||||
|
||||
this.document.addEventListener("selectionchange", this);
|
||||
|
||||
if (Services.prefs.getBoolPref(SCREENSHOTS_PREVENT_CONTENT_EVENTS_PREF)) {
|
||||
for (let event of ScreenshotsComponentChild.PREVENTABLE_EVENTS) {
|
||||
chromeEventHandler.addEventListener(event, this, true);
|
||||
}
|
||||
|
||||
this.#preventableEventsAdded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -264,7 +301,16 @@ export class ScreenshotsComponentChild extends JSWindowActorChild {
|
||||
for (let event of ScreenshotsComponentChild.OVERLAY_EVENTS) {
|
||||
chromeEventHandler.removeEventListener(event, this, true);
|
||||
}
|
||||
|
||||
this.document.removeEventListener("selectionchange", this);
|
||||
|
||||
if (this.#preventableEventsAdded) {
|
||||
for (let event of ScreenshotsComponentChild.PREVENTABLE_EVENTS) {
|
||||
chromeEventHandler.removeEventListener(event, this, true);
|
||||
}
|
||||
}
|
||||
|
||||
this.#preventableEventsAdded = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2441,6 +2441,9 @@ pref("screenshots.browser.component.enabled", true);
|
||||
// Preference that determines what button to focus
|
||||
pref("screenshots.browser.component.last-saved-method", "download");
|
||||
|
||||
// Preference that prevents events from reaching the content page.
|
||||
pref("screenshots.browser.component.preventContentEvents", true);
|
||||
|
||||
// DoH Rollout: whether to clear the mode value at shutdown.
|
||||
pref("doh-rollout.clearModeOnShutdown", false);
|
||||
|
||||
|
@ -74,6 +74,8 @@ skip-if = ["!crashreporter"]
|
||||
|
||||
["browser_test_moving_tab_to_new_window.js"]
|
||||
|
||||
["browser_test_prevent_events.js"]
|
||||
|
||||
["browser_test_resize.js"]
|
||||
|
||||
["browser_test_selection_size_text.js"]
|
||||
|
@ -16,13 +16,6 @@ add_task(async function test() {
|
||||
},
|
||||
async browser => {
|
||||
await clearAllTelemetryEvents();
|
||||
await SpecialPowers.spawn(browser, [SHORT_TEST_PAGE], url => {
|
||||
let a = content.document.createElement("a");
|
||||
a.id = "clickMe";
|
||||
a.href = url;
|
||||
a.textContent = "Click me to unload page";
|
||||
content.document.querySelector("body").appendChild(a);
|
||||
});
|
||||
|
||||
let helper = new ScreenshotsHelper(browser);
|
||||
|
||||
@ -31,7 +24,7 @@ add_task(async function test() {
|
||||
await helper.waitForOverlay();
|
||||
|
||||
await SpecialPowers.spawn(browser, [], () => {
|
||||
content.document.querySelector("#clickMe").click();
|
||||
content.location.reload();
|
||||
});
|
||||
|
||||
await helper.waitForOverlayClosed();
|
||||
|
@ -0,0 +1,84 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
https://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_task(async function test_events_prevented() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: SHORT_TEST_PAGE,
|
||||
},
|
||||
async browser => {
|
||||
let helper = new ScreenshotsHelper(browser);
|
||||
|
||||
await ContentTask.spawn(browser, null, async () => {
|
||||
let { ScreenshotsComponentChild } = ChromeUtils.importESModule(
|
||||
"resource:///actors/ScreenshotsComponentChild.sys.mjs"
|
||||
);
|
||||
let allOverlayEvents = ScreenshotsComponentChild.OVERLAY_EVENTS.concat(
|
||||
ScreenshotsComponentChild.PREVENTABLE_EVENTS
|
||||
);
|
||||
|
||||
content.eventsReceived = [];
|
||||
|
||||
function eventListener(event) {
|
||||
content.window.eventsReceived.push(event.type);
|
||||
}
|
||||
|
||||
for (let eventName of [...allOverlayEvents, "wheel"]) {
|
||||
content.addEventListener(eventName, eventListener, true);
|
||||
}
|
||||
});
|
||||
|
||||
helper.triggerUIFromToolbar();
|
||||
await helper.waitForOverlay();
|
||||
|
||||
// key events
|
||||
await key.down("s");
|
||||
await key.up("s");
|
||||
await key.press("s");
|
||||
|
||||
// touch events
|
||||
await touch.start(10, 10);
|
||||
await touch.move(20, 20);
|
||||
await touch.end(20, 20);
|
||||
|
||||
// pointermove/mousemove, pointerdown/mousedown, pointerup/mouseup events
|
||||
await helper.clickTestPageElement();
|
||||
|
||||
// click events and contextmenu
|
||||
await mouse.dblclick(100, 100);
|
||||
await mouse.auxclick(100, 100, { button: 1 });
|
||||
await mouse.click(100, 100);
|
||||
await mouse.contextmenu(100, 100);
|
||||
|
||||
let wheelEventPromise = helper.waitForContentEventOnce("wheel");
|
||||
await ContentTask.spawn(browser, null, () => {
|
||||
content.dispatchEvent(new content.WheelEvent("wheel"));
|
||||
});
|
||||
await wheelEventPromise;
|
||||
|
||||
let contentEventsReceived = await ContentTask.spawn(
|
||||
browser,
|
||||
null,
|
||||
async () => {
|
||||
return content.eventsReceived;
|
||||
}
|
||||
);
|
||||
|
||||
// Events are synchronous so if we only have 1 wheel at the end,
|
||||
// we did not receive any other events
|
||||
is(
|
||||
contentEventsReceived.length,
|
||||
1,
|
||||
"Only 1 wheel event should reach the content document because everything else was prevent and stopped propagation"
|
||||
);
|
||||
is(
|
||||
contentEventsReceived[0],
|
||||
"wheel",
|
||||
"Only 1 wheel event should reach the content document because everything else was prevent and stopped propagation"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
@ -34,16 +34,31 @@ const gScreenshotUISelectors = {
|
||||
copyButton: "button.#copy",
|
||||
};
|
||||
|
||||
// MouseEvents is for the mouse events on the Anonymous content
|
||||
const MouseEvents = {
|
||||
// AnonymousContentEvents is for the mouse, keyboard, and touch events on the Anonymous content
|
||||
const AnonymousContentEvents = {
|
||||
mouse: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, name) =>
|
||||
async function (x, y, options = {}, browser) {
|
||||
if (name === "click") {
|
||||
if (name.includes("click")) {
|
||||
this.down(x, y, options, browser);
|
||||
this.up(x, y, options, browser);
|
||||
if (name.includes("dbl")) {
|
||||
this.down(x, y, options, browser);
|
||||
this.up(x, y, options, browser);
|
||||
}
|
||||
} else if (name === "contextmenu") {
|
||||
await safeSynthesizeMouseEventInContentPage(
|
||||
":root",
|
||||
x,
|
||||
y,
|
||||
{
|
||||
type: name,
|
||||
...options,
|
||||
},
|
||||
browser
|
||||
);
|
||||
} else {
|
||||
await safeSynthesizeMouseEventInContentPage(
|
||||
":root",
|
||||
@ -59,9 +74,40 @@ const MouseEvents = {
|
||||
},
|
||||
}
|
||||
),
|
||||
key: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, name) =>
|
||||
async function (key, options = {}, browser) {
|
||||
await safeSynthesizeKeyEventInContentPage(
|
||||
key,
|
||||
{ type: "key" + name, ...options },
|
||||
browser
|
||||
);
|
||||
},
|
||||
}
|
||||
),
|
||||
touch: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, name) =>
|
||||
async function (x, y, options = {}, browser) {
|
||||
await safeSynthesizeTouchEventInContentPage(
|
||||
":root",
|
||||
x,
|
||||
y,
|
||||
{
|
||||
type: "touch" + name,
|
||||
...options,
|
||||
},
|
||||
browser
|
||||
);
|
||||
},
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
const { mouse } = MouseEvents;
|
||||
const { mouse, key, touch } = AnonymousContentEvents;
|
||||
|
||||
class ScreenshotsHelper {
|
||||
constructor(browser) {
|
||||
@ -821,6 +867,14 @@ class ScreenshotsHelper {
|
||||
});
|
||||
}
|
||||
|
||||
waitForContentEventOnce(event) {
|
||||
return ContentTask.spawn(this.browser, event, eventType => {
|
||||
return new Promise(resolve => {
|
||||
content.addEventListener(eventType, resolve, { once: true });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copied from screenshots extension
|
||||
* A helper that returns the size of the image that was just put into the clipboard by the
|
||||
@ -955,7 +1009,7 @@ function getRawClipboardData(flavor) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a mouse event on an element, after ensuring that it is visible
|
||||
* Synthesize a mouse event on an element
|
||||
* in the viewport.
|
||||
*
|
||||
* @param {String} selector: The node selector to get the node target for the event.
|
||||
@ -976,7 +1030,49 @@ async function safeSynthesizeMouseEventInContentPage(
|
||||
} else {
|
||||
context = browser.browsingContext;
|
||||
}
|
||||
BrowserTestUtils.synthesizeMouse(selector, x, y, options, context);
|
||||
await BrowserTestUtils.synthesizeMouse(selector, x, y, options, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a key event on an element
|
||||
* in the viewport.
|
||||
*
|
||||
* @param {string} key The key
|
||||
* @param {object} options: Options that will be passed to BrowserTestUtils.synthesizeKey
|
||||
*/
|
||||
async function safeSynthesizeKeyEventInContentPage(aKey, options, browser) {
|
||||
let context;
|
||||
if (!browser) {
|
||||
context = gBrowser.selectedBrowser.browsingContext;
|
||||
} else {
|
||||
context = browser.browsingContext;
|
||||
}
|
||||
await BrowserTestUtils.synthesizeKey(aKey, options, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthesize a touch event on an element
|
||||
* in the viewport.
|
||||
*
|
||||
* @param {String} selector: The node selector to get the node target for the event.
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {object} options: Options that will be passed to BrowserTestUtils.synthesizeTouch
|
||||
*/
|
||||
async function safeSynthesizeTouchEventInContentPage(
|
||||
selector,
|
||||
x,
|
||||
y,
|
||||
options = {},
|
||||
browser
|
||||
) {
|
||||
let context;
|
||||
if (!browser) {
|
||||
context = gBrowser.selectedBrowser.browsingContext;
|
||||
} else {
|
||||
context = browser.browsingContext;
|
||||
}
|
||||
await BrowserTestUtils.synthesizeTouch(selector, x, y, options, context);
|
||||
}
|
||||
|
||||
add_setup(async () => {
|
||||
|
@ -3,6 +3,6 @@
|
||||
<title>Screenshots</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="height:500px; width:500px; background-color: blue;"></div>
|
||||
<div id="testPageElement" style="height:500px; width:500px; background-color: blue;"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user