summaryrefslogtreecommitdiffstats
path: root/browser/components/asrouter/tests/browser/browser_trigger_listeners.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/asrouter/tests/browser/browser_trigger_listeners.js')
-rw-r--r--browser/components/asrouter/tests/browser/browser_trigger_listeners.js430
1 files changed, 430 insertions, 0 deletions
diff --git a/browser/components/asrouter/tests/browser/browser_trigger_listeners.js b/browser/components/asrouter/tests/browser/browser_trigger_listeners.js
new file mode 100644
index 0000000000..7c86645221
--- /dev/null
+++ b/browser/components/asrouter/tests/browser/browser_trigger_listeners.js
@@ -0,0 +1,430 @@
+/* eslint-disable @microsoft/sdl/no-insecure-url */
+const { ASRouterTriggerListeners } = ChromeUtils.importESModule(
+ "resource:///modules/asrouter/ASRouterTriggerListeners.sys.mjs"
+);
+
+const { ASRouter } = ChromeUtils.importESModule(
+ "resource:///modules/asrouter/ASRouter.sys.mjs"
+);
+
+const mockIdleService = {
+ _observers: new Set(),
+ _fireObservers(state) {
+ for (let observer of this._observers.values()) {
+ observer.observe(this, state, null);
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIUserIdleService"]),
+ idleTime: 1200000,
+ addIdleObserver(observer, time) {
+ this._observers.add(observer);
+ },
+ removeIdleObserver(observer, time) {
+ this._observers.delete(observer);
+ },
+};
+
+const sleepMs = (ms = 0) => new Promise(resolve => setTimeout(resolve, ms)); // eslint-disable-line mozilla/no-arbitrary-setTimeout
+
+const inChaosMode = !!parseInt(Services.env.get("MOZ_CHAOSMODE"), 16);
+
+async function waitForUrlLoad(url) {
+ let browser = gBrowser.selectedBrowser;
+ BrowserTestUtils.startLoadingURIString(browser, url);
+ await BrowserTestUtils.browserLoaded(browser, false, url);
+}
+
+add_setup(async function () {
+ // Runtime increases in chaos mode on Mac.
+ if (inChaosMode && AppConstants.platform === "macosx") {
+ requestLongerTimeout(2);
+ }
+
+ registerCleanupFunction(() => {
+ const trigger = ASRouterTriggerListeners.get("openURL");
+ trigger.uninit();
+ });
+});
+
+add_task(async function test_openURL_visit_counter() {
+ const trigger = ASRouterTriggerListeners.get("openURL");
+ const stub = sinon.stub();
+ trigger.uninit();
+
+ trigger.init(stub, ["example.com"]);
+
+ await waitForUrlLoad("about:blank");
+ await waitForUrlLoad("https://example.com/");
+ await waitForUrlLoad("about:blank");
+ await waitForUrlLoad("http://example.com/");
+ await waitForUrlLoad("about:blank");
+ await waitForUrlLoad("http://example.com/");
+
+ Assert.equal(stub.callCount, 3, "Stub called 3 times for example.com host");
+ Assert.equal(
+ stub.firstCall.args[1].context.visitsCount,
+ 1,
+ "First call should have count 1"
+ );
+ Assert.equal(
+ stub.thirdCall.args[1].context.visitsCount,
+ 2,
+ "Third call should have count 2 for http://example.com"
+ );
+});
+
+add_task(async function test_openURL_visit_counter_withPattern() {
+ const trigger = ASRouterTriggerListeners.get("openURL");
+ const stub = sinon.stub();
+ trigger.uninit();
+
+ // Match any valid URL
+ trigger.init(stub, [], ["*://*/*"]);
+
+ await waitForUrlLoad("about:blank");
+ await waitForUrlLoad("https://example.com/");
+ await waitForUrlLoad("about:blank");
+ await waitForUrlLoad("http://example.com/");
+ await waitForUrlLoad("about:blank");
+ await waitForUrlLoad("http://example.com/");
+
+ Assert.equal(stub.callCount, 3, "Stub called 3 times for example.com host");
+ Assert.equal(
+ stub.firstCall.args[1].context.visitsCount,
+ 1,
+ "First call should have count 1"
+ );
+ Assert.equal(
+ stub.thirdCall.args[1].context.visitsCount,
+ 2,
+ "Third call should have count 2 for http://example.com"
+ );
+});
+
+add_task(async function test_captivePortalLogin() {
+ const stub = sinon.stub();
+ const captivePortalTrigger =
+ ASRouterTriggerListeners.get("captivePortalLogin");
+
+ captivePortalTrigger.init(stub);
+
+ Services.obs.notifyObservers(this, "captive-portal-login-success", {});
+
+ Assert.ok(stub.called, "Called after login event");
+
+ captivePortalTrigger.uninit();
+
+ Services.obs.notifyObservers(this, "captive-portal-login-success", {});
+
+ Assert.equal(stub.callCount, 1, "Not called after uninit");
+});
+
+add_task(async function test_preferenceObserver() {
+ const stub = sinon.stub();
+ const poTrigger = ASRouterTriggerListeners.get("preferenceObserver");
+
+ poTrigger.uninit();
+
+ poTrigger.init(stub, ["foo.bar", "bar.foo"]);
+
+ Services.prefs.setStringPref("foo.bar", "foo.bar");
+
+ Assert.ok(stub.calledOnce, "Called for pref foo.bar");
+ Assert.deepEqual(
+ stub.firstCall.args[1],
+ {
+ id: "preferenceObserver",
+ param: { type: "foo.bar" },
+ },
+ "Called with expected arguments"
+ );
+
+ Services.prefs.setStringPref("bar.foo", "bar.foo");
+ Assert.ok(stub.calledTwice, "Called again for second pref.");
+ Services.prefs.clearUserPref("foo.bar");
+ Assert.ok(stub.calledThrice, "Called when clearing the pref as well.");
+
+ stub.resetHistory();
+ poTrigger.uninit();
+
+ Services.prefs.clearUserPref("bar.foo");
+ Assert.ok(stub.notCalled, "Not called after uninit");
+});
+
+add_task(async function test_nthTabClosed() {
+ const handlerStub = sinon.stub();
+ const tabClosedTrigger = ASRouterTriggerListeners.get("nthTabClosed");
+ tabClosedTrigger.uninit();
+ tabClosedTrigger.init(handlerStub);
+
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+
+ BrowserTestUtils.removeTab(tab1);
+ Assert.ok(handlerStub.calledOnce, "Called once after first tab closed");
+
+ BrowserTestUtils.removeTab(tab2);
+ Assert.ok(handlerStub.calledTwice, "Called twice after second tab closed");
+
+ handlerStub.resetHistory();
+ tabClosedTrigger.uninit();
+
+ Assert.ok(handlerStub.notCalled, "Not called after uninit");
+});
+
+add_task(async function test_cookieBannerDetected() {
+ const handlerStub = sinon.stub();
+ const bannerDetectedTrigger = ASRouterTriggerListeners.get(
+ "cookieBannerDetected"
+ );
+ bannerDetectedTrigger.uninit();
+ bannerDetectedTrigger.init(handlerStub);
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ let eventWait = BrowserTestUtils.waitForEvent(win, "cookiebannerdetected");
+ win.dispatchEvent(new Event("cookiebannerdetected"));
+ await eventWait;
+ let closeWindow = BrowserTestUtils.closeWindow(win);
+
+ Assert.ok(
+ handlerStub.called,
+ "Called after `cookiebannerdetected` event fires"
+ );
+
+ handlerStub.resetHistory();
+ bannerDetectedTrigger.uninit();
+
+ Assert.ok(handlerStub.notCalled, "Not called after uninit");
+ await closeWindow;
+});
+
+add_task(async function test_cookieBannerHandled() {
+ const handlerStub = sinon.stub();
+ const bannerHandledTrigger = ASRouterTriggerListeners.get(
+ "cookieBannerHandled"
+ );
+ bannerHandledTrigger.uninit();
+ bannerHandledTrigger.init(handlerStub);
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ win.focus();
+ let eventWait = BrowserTestUtils.waitForEvent(win, "cookiebannerhandled");
+ win.windowUtils.dispatchEventToChromeOnly(
+ win,
+ new CustomEvent("cookiebannerhandled", {
+ bubbles: true,
+ cancelable: false,
+ detail: {
+ windowContext: {
+ rootFrameLoader: { ownerElement: win.gBrowser.selectedBrowser },
+ },
+ },
+ })
+ );
+ await eventWait;
+ let closeWindow = BrowserTestUtils.closeWindow(win);
+
+ Assert.ok(
+ handlerStub.called,
+ "Called after `cookiebannerhandled` event fires"
+ );
+
+ handlerStub.resetHistory();
+ bannerHandledTrigger.uninit();
+
+ Assert.ok(handlerStub.notCalled, "Not called after uninit");
+ await closeWindow;
+});
+
+function getIdleTriggerMock() {
+ const idleTrigger = ASRouterTriggerListeners.get("activityAfterIdle");
+ idleTrigger.uninit();
+ const sandbox = sinon.createSandbox();
+ const handlerStub = sandbox.stub();
+ sandbox.stub(idleTrigger, "_triggerDelay").value(0);
+ sandbox.stub(idleTrigger, "_wakeDelay").value(30);
+ sandbox.stub(idleTrigger, "_idleService").value(mockIdleService);
+ let restored = false;
+ const restore = () => {
+ if (restored) {
+ return;
+ }
+ restored = true;
+ idleTrigger.uninit();
+ sandbox.restore();
+ };
+ registerCleanupFunction(restore);
+ idleTrigger.init(handlerStub);
+ return { idleTrigger, handlerStub, restore };
+}
+
+// Test that the trigger fires under normal conditions.
+add_task(async function test_activityAfterIdle() {
+ const { handlerStub, restore } = getIdleTriggerMock();
+ let firedOnActive = new Promise(resolve =>
+ handlerStub.callsFake(() => resolve(true))
+ );
+ mockIdleService._fireObservers("idle");
+ await TestUtils.waitForTick();
+ ok(handlerStub.notCalled, "Not called when idle");
+ mockIdleService._fireObservers("active");
+ ok(await firedOnActive, "Called once when active after idle");
+ restore();
+});
+
+// Test that the trigger does not fire when the active window is private.
+add_task(async function test_activityAfterIdlePrivateWindow() {
+ const { handlerStub, restore } = getIdleTriggerMock();
+ let privateWin = await BrowserTestUtils.openNewBrowserWindow({
+ private: true,
+ });
+ ok(PrivateBrowsingUtils.isWindowPrivate(privateWin), "Window is private");
+ await TestUtils.waitForTick();
+ mockIdleService._fireObservers("idle");
+ await TestUtils.waitForTick();
+ mockIdleService._fireObservers("active");
+ await TestUtils.waitForTick();
+ ok(handlerStub.notCalled, "Not called when active window is private");
+ await BrowserTestUtils.closeWindow(privateWin);
+ restore();
+});
+
+// Test that the trigger does not fire when the window is minimized, but does
+// fire after the window is restored.
+add_task(async function test_activityAfterIdleHiddenWindow() {
+ const { handlerStub, restore } = getIdleTriggerMock();
+ let firedOnRestore = new Promise(resolve =>
+ handlerStub.callsFake(() => resolve(true))
+ );
+ window.minimize();
+ await BrowserTestUtils.waitForCondition(
+ () => window.windowState === window.STATE_MINIMIZED,
+ "Window should be minimized"
+ );
+ mockIdleService._fireObservers("idle");
+ await TestUtils.waitForTick();
+ mockIdleService._fireObservers("active");
+ await TestUtils.waitForTick();
+ ok(handlerStub.notCalled, "Not called when window is minimized");
+ window.restore();
+ ok(await firedOnRestore, "Called once after restoring minimized window");
+ restore();
+});
+
+// Test that the trigger does not fire immediately after waking from sleep.
+add_task(async function test_activityAfterIdleWake() {
+ const { handlerStub, restore } = getIdleTriggerMock();
+ let firedAfterWake = new Promise(resolve =>
+ handlerStub.callsFake(() => resolve(true))
+ );
+ mockIdleService._fireObservers("wake_notification");
+ mockIdleService._fireObservers("idle");
+ await sleepMs(1);
+ mockIdleService._fireObservers("active");
+ await sleepMs(inChaosMode ? 32 : 300);
+ ok(handlerStub.notCalled, "Not called immediately after waking from sleep");
+
+ mockIdleService._fireObservers("idle");
+ await TestUtils.waitForTick();
+ mockIdleService._fireObservers("active");
+ ok(
+ await firedAfterWake,
+ "Called once after waiting for wake delay before firing idle"
+ );
+ restore();
+});
+
+add_task(async function test_formAutofillTrigger() {
+ const sandbox = sinon.createSandbox();
+ const handlerStub = sandbox.stub();
+ const formAutofillTrigger = ASRouterTriggerListeners.get("formAutofill");
+ sandbox.stub(formAutofillTrigger, "_triggerDelay").value(0);
+ formAutofillTrigger.uninit();
+ formAutofillTrigger.init(handlerStub);
+
+ function notifyCreditCardSaved() {
+ Services.obs.notifyObservers(
+ {
+ wrappedJSObject: { sourceSync: false, collectionName: "creditCards" },
+ },
+ formAutofillTrigger._topic,
+ "add"
+ );
+ }
+
+ // Saving credit cards for autofill currently fails for some hardware
+ // configurations, so mock the event instead of really adding a card.
+ notifyCreditCardSaved();
+ await sleepMs(1);
+ Assert.ok(handlerStub.called, "Called after event");
+
+ // Test that the trigger doesn't fire when the credit card manager is open.
+ handlerStub.resetHistory();
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:preferences#privacy" },
+ async browser => {
+ await SpecialPowers.spawn(browser, [], async () =>
+ (
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.querySelector("#creditCardAutofill button"),
+ "Waiting for credit card manager button"
+ )
+ )?.click()
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => browser.contentWindow?.gSubDialog?.dialogs.length
+ );
+ notifyCreditCardSaved();
+ await sleepMs(1);
+ Assert.ok(
+ handlerStub.notCalled,
+ "Not called when credit card manager is open"
+ );
+ }
+ );
+
+ formAutofillTrigger.uninit();
+ handlerStub.resetHistory();
+ notifyCreditCardSaved();
+ await sleepMs(1);
+ Assert.ok(handlerStub.notCalled, "Not called after uninit");
+
+ sandbox.restore();
+ formAutofillTrigger.uninit();
+});
+
+add_task(async function test_pageActionInUrlbarTrigger() {
+ const sandbox = sinon.createSandbox();
+ const receivedTrigger = new Promise(resolve => {
+ sandbox
+ .stub(ASRouter, "sendTriggerMessage")
+ .callsFake(({ id, context }) => {
+ if (
+ id === "pageActionInUrlbar" &&
+ context?.pageAction === "picture-in-picture-button"
+ ) {
+ resolve(true);
+ }
+ });
+ });
+ sandbox
+ .stub(PictureInPicture, "getEligiblePipVideoCount")
+ .returns({ totalPipCount: 1, totalPipDisabled: 0 });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.videocontrols.picture-in-picture.enabled", true],
+ ["media.videocontrols.picture-in-picture.urlbar-button.enabled", true],
+ ],
+ });
+
+ PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);
+
+ let pageAction = await receivedTrigger;
+ ok(pageAction, "pageActionInUrlbar trigger sent with PiP button id");
+
+ await SpecialPowers.popPrefEnv();
+ sandbox.restore();
+
+ PictureInPicture.updateUrlbarToggle(gBrowser.selectedBrowser);
+});