Bug 1960078 - Add universal type to InfoBar to show notification across all current and new windows r=omc-reviewers,mimi

Differential Revision: https://phabricator.services.mozilla.com/D245309
This commit is contained in:
Meg Viar
2025-04-23 20:58:27 +00:00
parent 3683f1b7da
commit 81929eb72f
6 changed files with 383 additions and 12 deletions

View File

@@ -45,6 +45,8 @@ tags = "remote-settings"
tags = "remote-settings"
skip-if = ["a11y_checks"] # Bug 1854515 and 1858041 to investigate intermittent a11y_checks results (fails on Autoland, passes on Try)
["browser_asrouter_universal_infobar.js"]
["browser_bookmarks_bar_button.js"]
["browser_feature_callout.js"]

View File

@@ -368,3 +368,28 @@ add_task(async function test_specialMessageAction_onLinkClick() {
handleStub.restore();
});
add_task(async function test_showInfoBarMessage_skipsPrivateWindow() {
const { PrivateBrowsingUtils } = ChromeUtils.importESModule(
"resource://gre/modules/PrivateBrowsingUtils.sys.mjs"
);
sinon.stub(PrivateBrowsingUtils, "isWindowPrivate").returns(true);
let browser = BrowserWindowTracker.getTopWindow().gBrowser.selectedBrowser;
let dispatch = sinon.stub();
let result = await InfoBar.showInfoBarMessage(
browser,
{
id: "Private Win Test",
content: { type: "global", buttons: [], text: "t", dismissable: true },
},
dispatch
);
Assert.equal(result, null);
Assert.equal(dispatch.callCount, 0);
// Cleanup
sinon.restore();
});

View File

@@ -0,0 +1,263 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { InfoBar } = ChromeUtils.importESModule(
"resource:///modules/asrouter/InfoBar.sys.mjs"
);
const { CFRMessageProvider } = ChromeUtils.importESModule(
"resource:///modules/asrouter/CFRMessageProvider.sys.mjs"
);
const { ASRouter } = ChromeUtils.importESModule(
"resource:///modules/asrouter/ASRouter.sys.mjs"
);
const UNIVERSAL_MESSAGE = {
id: "universal-infobar",
content: {
type: "universal",
text: "t",
buttons: [],
},
};
const cleanupInfobars = () => {
InfoBar._universalInfobars = [];
InfoBar._activeInfobar = null;
};
add_task(async function showNotificationAllWindows() {
let fakeNotification = { showNotification: sinon.stub().resolves() };
let fakeWins = [
{ gBrowser: { selectedBrowser: "win1" } },
{ gBrowser: { selectedBrowser: "win2" } },
{ gBrowser: { selectedBrowser: "win3" } },
];
let origWinManager = Services.wm;
// Using sinon.stub wont work here, because Services.wm is a frozen,
// non-configurable object and its methods cannot be replaced via typical JS
// property assignment.
Object.defineProperty(Services, "wm", {
value: { getEnumerator: () => fakeWins[Symbol.iterator]() },
configurable: true,
writable: true,
});
await InfoBar.showNotificationAllWindows(fakeNotification);
Assert.equal(fakeNotification.showNotification.callCount, 3);
Assert.ok(fakeNotification.showNotification.calledWith("win1"));
Assert.ok(fakeNotification.showNotification.calledWith("win2"));
Assert.ok(fakeNotification.showNotification.calledWith("win3"));
// Cleanup
cleanupInfobars();
sinon.restore();
Object.defineProperty(Services, "wm", {
value: origWinManager,
configurable: true,
writable: true,
});
});
add_task(async function removeUniversalInfobars() {
let browser = BrowserWindowTracker.getTopWindow().gBrowser.selectedBrowser;
let origBox = browser.ownerGlobal.gNotificationBox;
browser.ownerGlobal.gNotificationBox = {
appendNotification: sinon.stub().resolves({}),
removeNotification: sinon.stub(),
};
sinon
.stub(InfoBar, "showNotificationAllWindows")
.callsFake(async notification => {
await notification.showNotification(browser);
});
let notification = await InfoBar.showInfoBarMessage(
browser,
UNIVERSAL_MESSAGE,
sinon.stub()
);
Assert.equal(InfoBar._universalInfobars.length, 1);
notification.removeUniversalInfobars();
Assert.ok(
browser.ownerGlobal.gNotificationBox.removeNotification.calledWith(
notification.notification
)
);
Assert.deepEqual(InfoBar._universalInfobars, []);
// Cleanup
cleanupInfobars();
browser.ownerGlobal.gNotificationBox = origBox;
sinon.restore();
});
add_task(async function initialUniversal_showsAllWindows_andSendsTelemetry() {
let browser = BrowserWindowTracker.getTopWindow().gBrowser.selectedBrowser;
let origBox = browser.ownerGlobal.gNotificationBox;
browser.ownerGlobal.gNotificationBox = {
appendNotification: sinon.stub().resolves({}),
removeNotification: sinon.stub(),
};
let showAll = sinon
.stub(InfoBar, "showNotificationAllWindows")
.callsFake(async notification => {
await notification.showNotification(browser);
});
let dispatch1 = sinon.stub();
let dispatch2 = sinon.stub();
await InfoBar.showInfoBarMessage(browser, UNIVERSAL_MESSAGE, dispatch1);
await InfoBar.showInfoBarMessage(browser, UNIVERSAL_MESSAGE, dispatch2, true);
Assert.ok(showAll.calledOnce);
Assert.equal(InfoBar._universalInfobars.length, 2);
// Dispatch impression (as this is the first universal infobar) and telemetry
// ping
Assert.equal(dispatch1.callCount, 2);
// Do not send telemetry for subsequent appearance of the message
Assert.equal(dispatch2.callCount, 0);
// Cleanup
cleanupInfobars();
browser.ownerGlobal.gNotificationBox = origBox;
sinon.restore();
Services.obs.removeObserver(InfoBar, "domwindowopened");
});
add_task(async function observe_domwindowopened_withLoadEvent() {
let stub = sinon.stub(InfoBar, "showInfoBarMessage").resolves();
InfoBar._activeInfobar = {
message: { content: { type: "universal" } },
dispatch: sinon.stub(),
};
let subject = {
document: { readyState: "loading" },
gBrowser: { selectedBrowser: "b" },
addEventListener(event, cb) {
subject.document.readyState = "complete";
cb();
},
};
InfoBar.observe(subject, "domwindowopened");
Assert.ok(stub.calledOnce);
// Called with universalInNewWin true
Assert.equal(stub.firstCall.args[3], true);
// Cleanup
cleanupInfobars();
sinon.restore();
});
add_task(async function observe_domwindowopened() {
let stub = sinon.stub(InfoBar, "showInfoBarMessage").resolves();
InfoBar._activeInfobar = {
message: { content: { type: "universal" } },
dispatch: sinon.stub(),
};
let win = BrowserWindowTracker.getTopWindow();
InfoBar.observe(win, "domwindowopened");
Assert.ok(stub.calledOnce);
Assert.equal(stub.firstCall.args[3], true);
// Cleanup
cleanupInfobars();
sinon.restore();
});
add_task(async function observe_skips_nonUniversal() {
let stub = sinon.stub(InfoBar, "showInfoBarMessage").resolves();
InfoBar._activeInfobar = {
message: { content: { type: "global" } },
dispatch: sinon.stub(),
};
InfoBar.observe({}, "domwindowopened");
Assert.ok(stub.notCalled);
// Cleanup
cleanupInfobars();
stub.restore();
});
add_task(async function infobarCallback_dismissed_universal() {
const browser = BrowserWindowTracker.getTopWindow().gBrowser.selectedBrowser;
const dispatch = sinon.stub();
sinon
.stub(InfoBar, "showNotificationAllWindows")
.callsFake(async notif => await notif.showNotification(browser));
let infobar = await InfoBar.showInfoBarMessage(
browser,
UNIVERSAL_MESSAGE,
dispatch
);
// Reset the dispatch count to just watch for the DISMISSED ping
dispatch.reset();
infobar.infobarCallback("notremovedevent");
Assert.equal(dispatch.callCount, 1);
Assert.equal(dispatch.firstCall.args[0].data.event, "DISMISSED");
Assert.deepEqual(InfoBar._universalInfobars, []);
// Cleanup
cleanupInfobars();
sinon.restore();
});
add_task(async function removeObserver_on_removeUniversalInfobars() {
const sandbox = sinon.createSandbox();
sandbox.stub(InfoBar, "showNotificationAllWindows").resolves();
let browser = BrowserWindowTracker.getTopWindow().gBrowser.selectedBrowser;
let dispatch = sandbox.stub();
// Show the universal infobar so it registers the observer
let infobar = await InfoBar.showInfoBarMessage(
browser,
UNIVERSAL_MESSAGE,
dispatch
);
Assert.ok(infobar, "Got an InfoBar notification");
// Swap out Services.obs so removeObserver is spyable
let origObs = Services.obs;
let removeSpy = sandbox.spy();
Services.obs = {
addObserver: origObs.addObserver.bind(origObs),
removeObserver: removeSpy,
notifyObservers: origObs.notifyObservers.bind(origObs),
};
infobar.removeUniversalInfobars();
Assert.ok(
removeSpy.calledWith(InfoBar, "domwindowopened"),
"removeObserver was invoked for domwindowopened"
);
// Cleanup
Services.obs = origObs;
sandbox.restore();
cleanupInfobars();
});