Bug 1899747 - Add option to extend feature callout page_event_listener to all windows r=omc-reviewers,mviar,aminomancer

Differential Revision: https://phabricator.services.mozilla.com/D213239
This commit is contained in:
hanna alemu
2024-07-02 23:44:49 +00:00
parent 06f19b6fd1
commit 6e75b37b22
4 changed files with 180 additions and 5 deletions

View File

@@ -349,6 +349,9 @@ interface FeatureCallout {
// don't set up real event listeners, but instead invoke the
// action on a timer.
interval?: number;
// Extend addEventListener to all windows? Not compatible with
// `interval`.
every_window: boolean;
};
};
action: {

View File

@@ -1577,6 +1577,8 @@ export class FeatureCallout {
* @property {Number} [interval] Used only for `timeout` and `interval` event
* types. These don't set up real event listeners, but instead invoke the
* action on a timer.
* @property {Boolean} [every_window] Extend addEventListener to all windows?
* Not compatible with `interval`.
*
* @typedef {Object} PageEventListenerAction Action sent to AboutWelcomeParent
* @property {String} [type] Action type, e.g. `OPEN_URL`
@@ -1657,7 +1659,8 @@ export class FeatureCallout {
.map(attr => `[${attr.name}="${attr.value}"]`)
.join("")}`;
}
if (this.doc.querySelectorAll(source).length > 1) {
let doc = target.ownerDocument;
if (doc.querySelectorAll(source).length > 1) {
let uniqueAncestor = target.closest(`[id]:not(:scope, :root, body)`);
if (uniqueAncestor) {
source = `${this._getUniqueElementIdentifier(
@@ -1665,6 +1668,12 @@ export class FeatureCallout {
)} > ${source}`;
}
}
if (doc !== this.doc) {
let windowIndex = [
...Services.wm.getEnumerator("navigator:browser"),
].indexOf(target.ownerGlobal);
source = `window${windowIndex + 1}: ${source}`;
}
}
return source;
}

View File

@@ -1,7 +1,10 @@
/* 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/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
EveryWindow: "resource:///modules/EveryWindow.sys.mjs",
});
/**
* Methods for setting up and tearing down page event listeners. These are used
* to dismiss Feature Callouts when the callout's anchor element is clicked.
@@ -64,9 +67,28 @@ export class PageEventManager {
passive: !options.preventDefault,
signal: controller.signal,
};
const targets = this.doc.querySelectorAll(selectors);
for (const target of targets) {
target.addEventListener(type, callback, opt);
if (options?.every_window) {
// Using unique ID for each listener in case there are multiple
// listeners with the same type
let uuid = Services.uuid.generateUUID().number;
lazy.EveryWindow.registerCallback(
uuid,
win => {
for (const target of win.document.querySelectorAll(selectors)) {
target.addEventListener(type, callback, opt);
}
},
win => {
for (const target of win.document.querySelectorAll(selectors)) {
target.removeEventListener(type, callback, opt);
}
}
);
listener.uninit = () => lazy.EveryWindow.unregisterCallback(uuid, true);
} else {
for (const target of this.doc.querySelectorAll(selectors)) {
target.addEventListener(type, callback, opt);
}
}
listener.controller = controller;
} else if (["timeout", "interval"].includes(type) && options.interval) {
@@ -94,6 +116,7 @@ export class PageEventManager {
if (!listener) {
return;
}
listener.uninit?.();
listener.controller?.abort();
this._listeners.delete(params);
}

View File

@@ -289,6 +289,7 @@ add_task(async function feature_callout_dismiss_on_escape() {
},
message_id: "TEST_CALLOUT",
});
sandbox.restore();
await BrowserTestUtils.closeWindow(win);
});
@@ -423,3 +424,142 @@ add_task(async function feature_callout_pdfjs_theme() {
});
await BrowserTestUtils.closeWindow(win);
});
add_task(async function page_event_listeners_every_window() {
const testMessage2 = getTestMessage();
testMessage2.content.screens[0].content.page_event_listeners = [
{
params: {
type: "TabPinned",
selectors: "#main-window",
options: {
every_window: true,
},
},
action: {
dismiss: true,
},
},
];
const sandbox = sinon.createSandbox();
const spy = new TelemetrySpy(sandbox);
const win = await BrowserTestUtils.openNewBrowserWindow();
const doc = win.document;
const browser = win.gBrowser.selectedBrowser;
await showFeatureCallout(browser, testMessage2);
info("Waiting for callout to render");
const newWin = await BrowserTestUtils.openNewBrowserWindow();
const newTab = await BrowserTestUtils.openNewForegroundTab(
newWin.gBrowser,
"about:mozilla"
);
info("Waiting to pin tab in new window");
let newTabPinned = BrowserTestUtils.waitForEvent(newTab, "TabPinned");
newWin.gBrowser.pinTab(newTab);
await newTabPinned;
ok(newTab.pinned, "Tab should be pinned");
info("Waiting to dismiss callout");
await waitForCalloutRemoved(doc);
ok(
!doc.querySelector(calloutSelector),
"Callout is removed from screen on page_event"
);
//Check dismiss telemetry
spy.assertCalledWith({
event: "DISMISS",
event_context: {
source: sinon.match("tab.tabbrowser-tab"),
page: "chrome",
},
message_id: "TEST_CALLOUT",
});
await BrowserTestUtils.closeWindow(newWin);
await BrowserTestUtils.closeWindow(win);
sandbox.restore();
});
add_task(async function multiple_page_event_listeners_every_window() {
const testMessage2 = getTestMessage();
testMessage2.content.screens[0].content.page_event_listeners = [
{
params: {
type: "click",
selectors: "#PanelUI-menu-button",
options: {
every_window: true,
},
},
action: {
dismiss: true,
},
},
{
params: {
type: "click",
selectors: "#unified-extensions-button",
options: {
every_window: true,
},
},
action: {
dismiss: true,
},
},
{
params: {
type: "click",
selectors: "#fxa-toolbar-menu-button",
options: {
every_window: true,
},
},
action: {
dismiss: true,
},
},
];
const sandbox = sinon.createSandbox();
const spy = new TelemetrySpy(sandbox);
const win = await BrowserTestUtils.openNewBrowserWindow();
const doc = win.document;
const browser = win.gBrowser.selectedBrowser;
await showFeatureCallout(browser, testMessage2);
info("Waiting for callout to render");
const newWin = await BrowserTestUtils.openNewBrowserWindow();
const doc2 = newWin.document;
info("Clicking on button - second page event listener");
doc2.querySelector("#unified-extensions-button").click();
info("Waiting to dismiss callout");
await waitForCalloutRemoved(doc);
ok(
!doc.querySelector(calloutSelector),
"Callout is removed from screen on page_event"
);
//Check dismiss telemetry
spy.assertCalledWith({
event: "DISMISS",
event_context: {
source: sinon.match("#unified-extensions-button"),
page: "chrome",
},
message_id: "TEST_CALLOUT",
});
await BrowserTestUtils.closeWindow(newWin);
await BrowserTestUtils.closeWindow(win);
sandbox.restore();
});