summaryrefslogtreecommitdiffstats
path: root/browser/components/asrouter/tests/browser/browser_feature_callout_in_chrome.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /browser/components/asrouter/tests/browser/browser_feature_callout_in_chrome.js
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/asrouter/tests/browser/browser_feature_callout_in_chrome.js')
-rw-r--r--browser/components/asrouter/tests/browser/browser_feature_callout_in_chrome.js1122
1 files changed, 1122 insertions, 0 deletions
diff --git a/browser/components/asrouter/tests/browser/browser_feature_callout_in_chrome.js b/browser/components/asrouter/tests/browser/browser_feature_callout_in_chrome.js
new file mode 100644
index 0000000000..b7169d6be7
--- /dev/null
+++ b/browser/components/asrouter/tests/browser/browser_feature_callout_in_chrome.js
@@ -0,0 +1,1122 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { CustomizableUITestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/CustomizableUITestUtils.sys.mjs"
+);
+const { DefaultBrowserCheck } = ChromeUtils.importESModule(
+ "resource:///modules/BrowserGlue.sys.mjs"
+);
+
+const PDF_TEST_URL =
+ "https://example.com/browser/browser/components/newtab/test/browser/file_pdf.PDF";
+
+async function openURLInWindow(window, url) {
+ const { selectedBrowser } = window.gBrowser;
+ BrowserTestUtils.startLoadingURIString(selectedBrowser, url);
+ await BrowserTestUtils.browserLoaded(selectedBrowser, false, url);
+ return selectedBrowser;
+}
+
+async function openURLInNewTab(window, ...args) {
+ return BrowserTestUtils.openNewForegroundTab(window.gBrowser, ...args);
+}
+
+const pdfMatch = sinon.match(val => {
+ return (
+ val?.id === "pdfJsFeatureCalloutCheck" && val?.context?.source === "open"
+ );
+});
+
+const validateCalloutCustomPosition = (element, absolutePosition, doc) => {
+ const browserBox = doc.querySelector("hbox#browser");
+ for (let position in absolutePosition) {
+ if (Object.prototype.hasOwnProperty.call(absolutePosition, position)) {
+ // remove the `px` at the end of our absolute position strings
+ const relativePos = parseFloat(absolutePosition[position]);
+ const elPos = element.getBoundingClientRect()[position];
+ const browserPos = browserBox.getBoundingClientRect()[position];
+
+ if (position in ["top", "left"]) {
+ if (elPos !== browserPos + relativePos) {
+ return false;
+ }
+ } else if (position in ["right", "bottom"]) {
+ if (elPos !== browserPos - relativePos) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+};
+
+const validateCalloutRTLPosition = (element, absolutePosition) => {
+ for (let position in absolutePosition) {
+ if (Object.prototype.hasOwnProperty.call(absolutePosition, position)) {
+ const pixels = parseFloat(absolutePosition[position]);
+ if (position === "left") {
+ if (element.getBoundingClientRect().right !== pixels) {
+ return false;
+ }
+ } else if (position === "right") {
+ if (element.getBoundingClientRect().left !== pixels) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+};
+
+const testMessage = {
+ message: {
+ id: "TEST_MESSAGE",
+ template: "feature_callout",
+ content: {
+ id: "TEST_MESSAGE",
+ template: "multistage",
+ backdrop: "transparent",
+ transitions: false,
+ screens: [
+ {
+ id: "TEST_MESSAGE_1",
+ anchors: [
+ { selector: "#PanelUI-menu-button", arrow_position: "top-end" },
+ ],
+ content: {
+ position: "callout",
+ title: {
+ raw: "Test title",
+ },
+ subtitle: {
+ raw: "Test subtitle",
+ },
+ primary_button: {
+ label: {
+ raw: "Done",
+ },
+ action: {
+ navigate: true,
+ },
+ },
+ },
+ },
+ ],
+ },
+ priority: 1,
+ targeting: "true",
+ trigger: { id: "pdfJsFeatureCalloutCheck" },
+ },
+};
+
+const newtabTestMessage = {
+ id: "TEST_MESSAGE",
+ template: "feature_callout",
+ content: {
+ id: "TEST_MESSAGE",
+ template: "multistage",
+ backdrop: "transparent",
+ transitions: false,
+ disableHistoryUpdates: true,
+ tour_pref_name: "browser.newtab.feature-tour",
+ tour_pref_default_value: JSON.stringify({
+ screen: "TEST_MESSAGE_1",
+ complete: false,
+ }),
+ screens: [
+ {
+ id: "TEST_MESSAGE_1",
+ anchors: [
+ {
+ selector: "hbox#browser",
+ arrow_position: "top-end",
+ absolute_position: { top: "45px", right: "55px" },
+ },
+ ],
+ content: {
+ position: "callout",
+ title: "Test callout title",
+ subtitle: "Test callout subtitle",
+ primary_button: {
+ label: "Test callout button",
+ },
+ },
+ },
+ ],
+ },
+ priority: 1,
+ targeting: "true",
+ trigger: { id: "newtabFeatureCalloutCheck" },
+};
+
+const testMessageScreenId = testMessage.message.content.screens[0].id;
+const newtabTestMessageScreenId = newtabTestMessage.content.screens[0].id;
+
+const inChaosMode = !!parseInt(Services.env.get("MOZ_CHAOSMODE"), 16);
+
+add_setup(async function () {
+ let timeoutFactor = 3;
+ // Runtime increases in chaos mode on Mac.
+ if (inChaosMode && AppConstants.platform === "macosx") {
+ timeoutFactor = 5;
+ }
+ requestLongerTimeout(timeoutFactor);
+});
+
+// Test that a feature callout message can be loaded into ASRouter and displayed
+// via a standard trigger. Also test that the callout can be a feature tour,
+// even if its tour pref doesn't exist in Firefox. The tour pref will be created
+// and cleaned up automatically. This allows a feature callout to be implemented
+// entirely off-train in an experiment, without landing anything in tree.
+add_task(async function triggered_feature_tour_with_custom_pref() {
+ let sandbox = sinon.createSandbox();
+ const TEST_MESSAGES = [
+ {
+ id: "TEST_FEATURE_TOUR",
+ template: "feature_callout",
+ content: {
+ id: "TEST_FEATURE_TOUR",
+ template: "multistage",
+ backdrop: "transparent",
+ transitions: false,
+ disableHistoryUpdates: true,
+ tour_pref_name: "messaging-system-action.browser.test.feature-tour",
+ tour_pref_default_value: JSON.stringify({
+ screen: "FEATURE_CALLOUT_1",
+ complete: false,
+ }),
+ screens: [
+ {
+ id: "FEATURE_CALLOUT_1",
+ anchors: [
+ {
+ selector: "#PanelUI-menu-button",
+ arrow_position: "top-center-arrow-end",
+ },
+ ],
+ content: {
+ position: "callout",
+ title: { string_id: "callout-pdfjs-edit-title" },
+ subtitle: { string_id: "callout-pdfjs-edit-body-b" },
+ primary_button: {
+ label: { string_id: "callout-pdfjs-edit-button" },
+ action: {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: "messaging-system-action.browser.test.feature-tour",
+ value: JSON.stringify({
+ screen: "FEATURE_CALLOUT_2",
+ complete: false,
+ }),
+ },
+ },
+ },
+ },
+ dismiss_button: {
+ action: {
+ type: "MULTI_ACTION",
+ data: {
+ actions: [
+ {
+ type: "BLOCK_MESSAGE",
+ data: { id: "TEST_FEATURE_TOUR" },
+ },
+ {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: "messaging-system-action.browser.test.feature-tour",
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ id: "FEATURE_CALLOUT_2",
+ anchors: [
+ {
+ selector: "#back-button",
+ arrow_position: "top-center-arrow-start",
+ },
+ ],
+ content: {
+ position: "callout",
+ title: { string_id: "callout-pdfjs-draw-title" },
+ subtitle: { string_id: "callout-pdfjs-draw-body-b" },
+ primary_button: {
+ label: { string_id: "callout-pdfjs-draw-button" },
+ action: {
+ type: "MULTI_ACTION",
+ data: {
+ actions: [
+ {
+ type: "BLOCK_MESSAGE",
+ data: { id: "TEST_FEATURE_TOUR" },
+ },
+ {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: "messaging-system-action.browser.test.feature-tour",
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ dismiss_button: {
+ action: {
+ type: "MULTI_ACTION",
+ data: {
+ actions: [
+ {
+ type: "BLOCK_MESSAGE",
+ data: { id: "TEST_FEATURE_TOUR" },
+ },
+ {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: "messaging-system-action.browser.test.feature-tour",
+ },
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ ],
+ },
+ priority: 2,
+ targeting: `(('messaging-system-action.browser.test.feature-tour' | preferenceValue) ? (('messaging-system-action.browser.test.feature-tour' | preferenceValue | regExpMatch('(?<=complete":)(.*)(?=})')) ? ('messaging-system-action.browser.test.feature-tour' | preferenceValue | regExpMatch('(?<=complete":)(.*)(?=})')[1] != "true") : true) : true)`,
+ trigger: { id: "nthTabClosed" },
+ },
+ {
+ id: "TEST_FEATURE_TOUR_2",
+ template: "feature_callout",
+ content: {
+ id: "TEST_FEATURE_TOUR_2",
+ template: "multistage",
+ backdrop: "transparent",
+ transitions: false,
+ disableHistoryUpdates: true,
+ screens: [
+ {
+ id: "FEATURE_CALLOUT_TEST",
+ anchors: [
+ {
+ selector: "#PanelUI-menu-button",
+ arrow_position: "top-center-arrow-end",
+ },
+ ],
+ content: {
+ position: "callout",
+ title: { string_id: "callout-pdfjs-edit-title" },
+ subtitle: { string_id: "callout-pdfjs-edit-body-b" },
+ primary_button: {
+ label: { string_id: "callout-pdfjs-edit-button" },
+ action: { dismiss: true },
+ },
+ },
+ },
+ ],
+ },
+ priority: 1,
+ targeting: "true",
+ trigger: { id: "nthTabClosed" },
+ },
+ ];
+ const getMessagesStub = sandbox.stub(FeatureCalloutMessages, "getMessages");
+ getMessagesStub.returns(TEST_MESSAGES);
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders(
+ ASRouter.state.providers.filter(p => p.id === "onboarding")
+ );
+
+ // Test that callout is triggered and shown in browser chrome
+ const win1 = await BrowserTestUtils.openNewBrowserWindow();
+ win1.focus();
+ const tab1 = await BrowserTestUtils.openNewForegroundTab(win1.gBrowser);
+ await TestUtils.waitForTick();
+ win1.gBrowser.removeTab(tab1);
+ await waitForCalloutScreen(
+ win1.document,
+ TEST_MESSAGES[0].content.screens[0].id
+ );
+ ok(
+ win1.document.querySelector(calloutSelector),
+ "Feature Callout is rendered in the browser chrome when a message is available"
+ );
+
+ // Test that a callout does NOT appear if another is already shown in any window.
+ const showFeatureCalloutSpy = sandbox.spy(
+ FeatureCalloutBroker,
+ "showFeatureCallout"
+ );
+ const win2 = await BrowserTestUtils.openNewBrowserWindow();
+ win2.focus();
+ const tab2 = await BrowserTestUtils.openNewForegroundTab(win2.gBrowser);
+ await TestUtils.waitForTick();
+ win2.gBrowser.removeTab(tab2);
+ await BrowserTestUtils.waitForCondition(async () => {
+ const rvs = await Promise.all(showFeatureCalloutSpy.returnValues);
+ return (
+ showFeatureCalloutSpy.calledWith(
+ win2.gBrowser.selectedBrowser,
+ sinon.match(TEST_MESSAGES[0])
+ ) && rvs.every(rv => !rv)
+ );
+ }, "Waiting for showFeatureCallout to be called");
+ ok(
+ !win2.document.querySelector(calloutSelector),
+ "Feature Callout is not rendered when a callout is already shown in any window"
+ );
+ await BrowserTestUtils.closeWindow(win2);
+ win1.focus();
+ await BrowserTestUtils.waitForCondition(
+ async () => Services.focus.activeWindow === win1,
+ "Waiting for window 1 to be active"
+ );
+
+ // Test that the tour pref doesn't exist yet
+ ok(
+ !Services.prefs.prefHasUserValue(TEST_MESSAGES[0].content.tour_pref_name),
+ "Tour pref does not exist yet"
+ );
+
+ // Test that the callout advances screen and sets the tour pref
+ win1.document.querySelector(calloutCTASelector).click();
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ Services.prefs.prefHasUserValue(TEST_MESSAGES[0].content.tour_pref_name),
+ "Waiting for tour pref to be set"
+ );
+ SimpleTest.isDeeply(
+ JSON.parse(
+ Services.prefs.getStringPref(
+ TEST_MESSAGES[0].content.tour_pref_name,
+ "{}"
+ )
+ ),
+ { screen: "FEATURE_CALLOUT_2", complete: false },
+ "Tour pref is set correctly"
+ );
+ await waitForCalloutScreen(
+ win1.document,
+ TEST_MESSAGES[0].content.screens[1].id
+ );
+ ok(
+ win1.document.querySelector(calloutSelector),
+ "Feature Callout screen 2 is rendered"
+ );
+
+ // Test that the callout is dismissed and cleans up the tour pref
+ win1.document.querySelector(calloutCTASelector).click();
+ await waitForCalloutRemoved(win1.document);
+ ok(
+ !win1.document.querySelector(calloutSelector),
+ "Feature Callout is not rendered after being dismissed"
+ );
+ ok(
+ !Services.prefs.prefHasUserValue(TEST_MESSAGES[0].content.tour_pref_name),
+ "Tour pref is cleaned up correctly"
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => !FeatureCalloutBroker.isCalloutShowing,
+ "Waiting for all callouts to empty from the callout broker"
+ );
+
+ // Test that the message was blocked so a different callout is shown
+ const tab3 = await BrowserTestUtils.openNewForegroundTab(win1.gBrowser);
+ await TestUtils.waitForTick();
+ win1.gBrowser.removeTab(tab3);
+ await waitForCalloutScreen(
+ win1.document,
+ TEST_MESSAGES[1].content.screens[0].id
+ );
+ ok(
+ win1.document.querySelector(calloutSelector),
+ "A different Feature Callout is rendered"
+ );
+ win1.document.querySelector(calloutCTASelector).click();
+ await waitForCalloutRemoved(win1.document);
+ ok(!FeatureCalloutBroker.isCalloutShowing, "No Feature Callout is shown");
+
+ BrowserTestUtils.closeWindow(win1);
+
+ sandbox.restore();
+ await ASRouter.unblockMessageById(TEST_MESSAGES[0].id);
+ await ASRouter.resetMessageState();
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders(
+ ASRouter.state.providers.filter(p => p.id === "onboarding")
+ );
+});
+
+add_task(async function callout_not_shown_if_dialog_open() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ let dialogPromise = BrowserTestUtils.promiseAlertDialog(null, undefined, {
+ callback: async dialogWin => {
+ let rv = await FeatureCalloutBroker.showFeatureCallout(
+ win.gBrowser.selectedBrowser,
+ testMessage.message
+ );
+ ok(
+ !rv,
+ "Feature callout not shown when a dialog is open in the same window"
+ );
+ dialogWin.document.querySelector("dialog").getButton("cancel").click();
+ },
+ isSubDialog: true,
+ });
+ DefaultBrowserCheck.prompt(win);
+ await dialogPromise;
+
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function callout_not_shown_if_panel_open() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ const gCUITestUtils = new CustomizableUITestUtils(win);
+ await gCUITestUtils.openMainMenu();
+
+ let rv = await FeatureCalloutBroker.showFeatureCallout(
+ win.gBrowser.selectedBrowser,
+ testMessage.message
+ );
+ ok(!rv, "Feature callout not shown when a panel is open in the same window");
+
+ await gCUITestUtils.hideMainMenu();
+ await BrowserTestUtils.closeWindow(win);
+});
+
+add_task(async function feature_callout_renders_in_browser_chrome_for_pdf() {
+ const sandbox = sinon.createSandbox();
+ const sendTriggerStub = sandbox.stub(ASRouter, "sendTriggerMessage");
+ sendTriggerStub.withArgs(pdfMatch).resolves(testMessage);
+ sendTriggerStub.callThrough();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ await openURLInWindow(win, PDF_TEST_URL);
+ const doc = win.document;
+ await waitForCalloutScreen(doc, testMessageScreenId);
+ const container = doc.querySelector(calloutSelector);
+ ok(
+ container,
+ "Feature Callout is rendered in the browser chrome with a new window when a message is available"
+ );
+
+ // click CTA to close
+ doc.querySelector(calloutCTASelector).click();
+ await waitForCalloutRemoved(doc);
+ ok(
+ true,
+ "Feature callout removed from browser chrome after clicking button configured to navigate"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+});
+
+add_task(
+ async function feature_callout_renders_and_hides_in_chrome_when_switching_tabs() {
+ const sandbox = sinon.createSandbox();
+ const sendTriggerStub = sandbox.stub(ASRouter, "sendTriggerMessage");
+ sendTriggerStub.withArgs(pdfMatch).resolves(testMessage);
+ sendTriggerStub.callThrough();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ const doc = win.document;
+ const tab1 = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ PDF_TEST_URL
+ );
+ tab1.focus();
+ await waitForCalloutScreen(doc, testMessageScreenId);
+ ok(
+ doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout rendered when opening a new tab with PDF url"
+ );
+
+ const tab2 = await openURLInNewTab(win, "about:preferences");
+ tab2.focus();
+ await BrowserTestUtils.waitForCondition(() => {
+ return !doc.body.querySelector(
+ "#multi-stage-message-root.featureCallout"
+ );
+ });
+
+ ok(
+ !doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout removed when tab without PDF URL is navigated to"
+ );
+
+ const tab3 = await openURLInNewTab(win, PDF_TEST_URL);
+ tab3.focus();
+ await waitForCalloutScreen(doc, testMessageScreenId);
+ ok(
+ doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout still renders when opening a new tab with PDF url after being initially rendered on another tab"
+ );
+
+ tab1.focus();
+ await waitForCalloutScreen(doc, testMessageScreenId);
+ ok(
+ doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout rendered on original tab after switching tabs multiple times"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ }
+);
+
+add_task(
+ async function feature_callout_disappears_when_navigating_to_non_pdf_url_in_same_tab() {
+ const sandbox = sinon.createSandbox();
+ const sendTriggerStub = sandbox.stub(ASRouter, "sendTriggerMessage");
+ sendTriggerStub.withArgs(pdfMatch).resolves(testMessage);
+ sendTriggerStub.callThrough();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ const doc = win.document;
+ const tab1 = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ PDF_TEST_URL
+ );
+ tab1.focus();
+ await waitForCalloutScreen(doc, testMessageScreenId);
+ ok(
+ doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout rendered when opening a new tab with PDF url"
+ );
+
+ BrowserTestUtils.startLoadingURIString(win.gBrowser, "about:preferences");
+ await BrowserTestUtils.waitForLocationChange(
+ win.gBrowser,
+ "about:preferences"
+ );
+ await waitForCalloutRemoved(doc);
+
+ ok(
+ !doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout not rendered on original tab after navigating to non pdf URL"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ }
+);
+
+add_task(
+ async function feature_callout_disappears_when_closing_foreground_pdf_tab() {
+ const sandbox = sinon.createSandbox();
+ const sendTriggerStub = sandbox.stub(ASRouter, "sendTriggerMessage");
+ sendTriggerStub.withArgs(pdfMatch).resolves(testMessage);
+ sendTriggerStub.callThrough();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ const doc = win.document;
+ const tab1 = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ PDF_TEST_URL
+ );
+ tab1.focus();
+ await waitForCalloutScreen(doc, testMessageScreenId);
+ ok(
+ doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout rendered when opening a new tab with PDF url"
+ );
+
+ BrowserTestUtils.removeTab(tab1);
+ await waitForCalloutRemoved(doc);
+
+ ok(
+ !doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout disappears after closing foreground tab"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ }
+);
+
+add_task(
+ async function feature_callout_does_not_appear_when_opening_background_pdf_tab() {
+ const sandbox = sinon.createSandbox();
+ const getMessagesStub = sandbox.stub(FeatureCalloutMessages, "getMessages");
+ const TEST_MESSAGES = [newtabTestMessage];
+ getMessagesStub.returns(TEST_MESSAGES);
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders(
+ ASRouter.state.providers.filter(p => p.id === "onboarding")
+ );
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ const doc = win.document;
+
+ const tab1 = await BrowserTestUtils.addTab(win.gBrowser, PDF_TEST_URL);
+ ok(
+ !doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout not rendered when opening a background tab with PDF url"
+ );
+
+ BrowserTestUtils.removeTab(tab1);
+
+ ok(
+ !doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout still not rendered after closing background tab with PDF url"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+ }
+);
+
+add_task(
+ async function newtab_feature_callout_appears_in_browser_chrome_on_newtab() {
+ const sandbox = sinon.createSandbox();
+ const getMessagesStub = sandbox.stub(FeatureCalloutMessages, "getMessages");
+ const TEST_MESSAGES = [newtabTestMessage];
+ getMessagesStub.returns(TEST_MESSAGES);
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ const doc = win.document;
+ const tab1 = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ "about:newtab"
+ );
+ tab1.focus();
+ await waitForCalloutScreen(doc, newtabTestMessageScreenId);
+ ok(
+ doc.querySelector(`.${newtabTestMessageScreenId}`),
+ "Newtab feature callout rendered when opening a focused newtab"
+ );
+
+ BrowserTestUtils.removeTab(tab1);
+ await waitForCalloutRemoved(doc);
+ ok(
+ !doc.querySelector(`.${newtabTestMessageScreenId}`),
+ "Feature callout disappears after closing new tab"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+ }
+);
+
+add_task(
+ async function newtab_feature_callout_does_not_appear_when_opening_background_newtab_tab() {
+ const sandbox = sinon.createSandbox();
+ const getMessagesStub = sandbox.stub(FeatureCalloutMessages, "getMessages");
+ const TEST_MESSAGES = [newtabTestMessage];
+ getMessagesStub.returns(TEST_MESSAGES);
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ const doc = win.document;
+
+ await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ "about:preferences"
+ );
+ const tab2 = await BrowserTestUtils.addTab(win.gBrowser, "about:newtab");
+ ok(
+ !doc.querySelector(`.${newtabTestMessageScreenId}`),
+ "Newtab feature callout not rendered when opening a background newtab"
+ );
+
+ BrowserTestUtils.removeTab(tab2);
+ await waitForCalloutRemoved(doc);
+ ok(
+ !doc.querySelector(`.${newtabTestMessageScreenId}`),
+ "Feature callout still not rendered after closing background tab"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+ }
+);
+
+add_task(
+ async function newtab_feature_callout_does_not_appear_in_browser_chrome_on_new_window() {
+ const sandbox = sinon.createSandbox();
+ const getMessagesStub = sandbox.stub(FeatureCalloutMessages, "getMessages");
+ const TEST_MESSAGES = [newtabTestMessage];
+ getMessagesStub.returns(TEST_MESSAGES);
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ await openURLInWindow(win, "about:newtab");
+ const doc = win.document;
+
+ await waitForCalloutScreen(doc, newtabTestMessageScreenId);
+ ok(
+ doc.querySelector(`.${newtabTestMessageScreenId}`),
+ "Newtab Feature Callout is in the browser chrome of first window when a message is available"
+ );
+
+ const win2 = await BrowserTestUtils.openNewBrowserWindow();
+ await openURLInWindow(win2, "about:newtab");
+ const doc2 = win2.document;
+ ok(
+ !doc2.querySelector(`.${newtabTestMessageScreenId}`),
+ "Newtab Feature Callout is not in the browser chrome new window when a message is available"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ await BrowserTestUtils.closeWindow(win2);
+ sandbox.restore();
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+ }
+);
+
+add_task(
+ async function feature_callout_disappears_when_navigating_from_newtab_to_pdf_url_in_same_tab() {
+ const sandbox = sinon.createSandbox();
+ const getMessagesStub = sandbox.stub(FeatureCalloutMessages, "getMessages");
+ const TEST_MESSAGES = [newtabTestMessage];
+ getMessagesStub.returns(TEST_MESSAGES);
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ const doc = win.document;
+ const tab1 = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ "about:newtab"
+ );
+ tab1.focus();
+ await waitForCalloutScreen(doc, newtabTestMessageScreenId);
+ ok(
+ doc.querySelector(`.${newtabTestMessageScreenId}`),
+ "Feature callout rendered when opening a newtab"
+ );
+
+ BrowserTestUtils.startLoadingURIString(win.gBrowser, PDF_TEST_URL);
+ await BrowserTestUtils.waitForLocationChange(win.gBrowser, PDF_TEST_URL);
+ await waitForCalloutRemoved(doc);
+
+ ok(
+ !doc.querySelector(`.${testMessageScreenId}`),
+ "Feature callout not rendered on original tab after navigating to PDF"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+ }
+);
+
+add_task(
+ async function feature_callout_disappears_when_navigating_from_newtab_to_pdf_url_in_different_tab() {
+ const sandbox = sinon.createSandbox();
+ const getMessagesStub = sandbox.stub(FeatureCalloutMessages, "getMessages");
+ const TEST_MESSAGES = [newtabTestMessage];
+ getMessagesStub.returns(TEST_MESSAGES);
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+
+ const doc = win.document;
+ const tab1 = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ "about:newtab"
+ );
+ tab1.focus();
+ await waitForCalloutScreen(doc, newtabTestMessageScreenId);
+ ok(
+ doc.querySelector(`.${newtabTestMessageScreenId}`),
+ "Feature callout rendered when opening a newtab"
+ );
+
+ const tab2 = await BrowserTestUtils.openNewForegroundTab(
+ win.gBrowser,
+ PDF_TEST_URL
+ );
+ tab2.focus();
+ await waitForCalloutRemoved(doc);
+
+ ok(
+ !doc.querySelector(`.${testMessageScreenId}`),
+ "Newtab feature callout not rendered after navigating to PDF"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+ }
+);
+
+add_task(
+ async function feature_callout_is_positioned_relative_to_browser_window() {
+ // Deep copying our test message so we can alter it without disrupting future tests
+ const pdfTestMessage = JSON.parse(JSON.stringify(testMessage));
+ const pdfTestMessageCalloutSelector =
+ pdfTestMessage.message.content.screens[0].id;
+
+ pdfTestMessage.message.content.screens[0].anchors[0] = {
+ selector: "hbox#browser",
+ absolute_position: { top: "45px", right: "25px" },
+ };
+
+ const sandbox = sinon.createSandbox();
+ const getMessagesStub = sandbox.stub(FeatureCalloutMessages, "getMessages");
+ const TEST_MESSAGES = [pdfTestMessage.message];
+ getMessagesStub.returns(TEST_MESSAGES);
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ await openURLInWindow(win, PDF_TEST_URL);
+ const doc = win.document;
+ await waitForCalloutScreen(doc, pdfTestMessageCalloutSelector);
+
+ // Verify that callout renders in appropriate position (without infobar element)
+ const callout = doc.querySelector(`.${pdfTestMessageCalloutSelector}`);
+ ok(callout, "Callout is rendered when navigating to PDF file");
+ ok(
+ validateCalloutCustomPosition(
+ callout,
+ pdfTestMessage.message.content.screens[0].anchors[0].absolute_position,
+ doc
+ ),
+ "Callout custom position is as expected"
+ );
+
+ // Add height to the top of the browser to simulate an infobar or other element
+ const navigatorToolBox = doc.querySelector("#navigator-toolbox");
+ navigatorToolBox.style.height = "150px";
+ // We test in a new tab because the callout does not adjust itself
+ // when size of the navigator-toolbox-background box changes.
+ const tab = await openURLInNewTab(win, "https://example.com/some2.pdf");
+ // Verify that callout renders in appropriate position (with infobar element displayed)
+ ok(
+ validateCalloutCustomPosition(
+ callout,
+ pdfTestMessage.message.content.screens[0].anchors[0].absolute_position,
+ doc
+ ),
+ "Callout custom position is as expected while navigator toolbox height is extended"
+ );
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ await ASRouter._updateMessageProviders();
+ await ASRouter.loadMessagesFromAllProviders();
+ }
+);
+
+add_task(
+ async function custom_position_callout_is_horizontally_reversed_in_rtl_layouts() {
+ // Deep copying our test message so we can alter it without disrupting future tests
+ const pdfTestMessage = JSON.parse(JSON.stringify(testMessage));
+ const pdfTestMessageCalloutSelector =
+ pdfTestMessage.message.content.screens[0].id;
+
+ pdfTestMessage.message.content.screens[0].anchors[0] = {
+ selector: "hbox#browser",
+ absolute_position: { top: "45px", right: "25px" },
+ };
+
+ const sandbox = sinon.createSandbox();
+ const sendTriggerStub = sandbox.stub(ASRouter, "sendTriggerMessage");
+ sendTriggerStub.withArgs(pdfMatch).resolves(pdfTestMessage);
+ sendTriggerStub.callThrough();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ win.document.dir = "rtl";
+ Assert.strictEqual(
+ win.document.documentElement.getAttribute("dir"),
+ "rtl",
+ "browser window is in RTL"
+ );
+
+ await openURLInWindow(win, PDF_TEST_URL);
+ const doc = win.document;
+ await waitForCalloutScreen(doc, pdfTestMessageCalloutSelector);
+
+ const callout = doc.querySelector(`.${pdfTestMessageCalloutSelector}`);
+ ok(callout, "Callout is rendered when navigating to PDF file");
+ ok(
+ validateCalloutRTLPosition(
+ callout,
+ pdfTestMessage.message.content.screens[0].anchors[0].absolute_position
+ ),
+ "Callout custom position is rendered appropriately in RTL mode"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ }
+);
+
+add_task(async function feature_callout_dismissed_on_escape() {
+ const sandbox = sinon.createSandbox();
+ const sendTriggerStub = sandbox.stub(ASRouter, "sendTriggerMessage");
+ sendTriggerStub.withArgs(pdfMatch).resolves(testMessage);
+ sendTriggerStub.callThrough();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ await openURLInWindow(win, PDF_TEST_URL);
+ const doc = win.document;
+ await waitForCalloutScreen(doc, testMessageScreenId);
+ const container = doc.querySelector(calloutSelector);
+ ok(
+ container,
+ "Feature Callout is rendered in the browser chrome with a new window when a message is available"
+ );
+
+ // Ensure the browser is focused
+ win.gBrowser.selectedBrowser.focus();
+
+ // Press Escape to close
+ EventUtils.synthesizeKey("KEY_Escape", {}, win);
+ await waitForCalloutRemoved(doc);
+ ok(true, "Feature callout dismissed after pressing Escape");
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+});
+
+add_task(
+ async function feature_callout_not_dismissed_on_escape_with_interactive_elm_focused() {
+ const sandbox = sinon.createSandbox();
+ const sendTriggerStub = sandbox.stub(ASRouter, "sendTriggerMessage");
+ sendTriggerStub.withArgs(pdfMatch).resolves(testMessage);
+ sendTriggerStub.callThrough();
+
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ await openURLInWindow(win, PDF_TEST_URL);
+ const doc = win.document;
+ await waitForCalloutScreen(doc, testMessageScreenId);
+ const container = doc.querySelector(calloutSelector);
+ ok(
+ container,
+ "Feature Callout is rendered in the browser chrome with a new window when a message is available"
+ );
+
+ // Ensure an interactive element is focused
+ win.gURLBar.focus();
+
+ // Press Escape to close
+ EventUtils.synthesizeKey("KEY_Escape", {}, win);
+ await TestUtils.waitForTick();
+ // Wait 500ms for transition to complete
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(resolve => setTimeout(resolve, 500));
+ ok(
+ doc.querySelector(calloutSelector),
+ "Feature callout is not dismissed after pressing Escape because an interactive element is focused"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ }
+);
+
+add_task(async function first_anchor_selected_is_valid() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ const config = {
+ win,
+ location: "chrome",
+ context: "chrome",
+ browser: win.gBrowser.selectedBrowser,
+ theme: { preset: "chrome" },
+ };
+
+ const message = JSON.parse(JSON.stringify(testMessage.message));
+ const sandbox = sinon.createSandbox();
+
+ const doc = win.document;
+ const featureCallout = new FeatureCallout(config);
+ const getAnchorSpy = sandbox.spy(featureCallout, "_getAnchor");
+ featureCallout.showFeatureCallout(message);
+ await waitForCalloutScreen(doc, message.content.screens[0].id);
+ ok(
+ getAnchorSpy.alwaysReturned(
+ sandbox.match(message.content.screens[0].anchors[0])
+ ),
+ "The first anchor is selected"
+ );
+
+ win.document.querySelector(calloutCTASelector).click();
+ await waitForCalloutRemoved(win.document);
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+});
+
+add_task(async function first_anchor_selected_is_invalid() {
+ const win = await BrowserTestUtils.openNewBrowserWindow();
+ const doc = win.document;
+ const config = {
+ win,
+ location: "chrome",
+ context: "chrome",
+ browser: win.gBrowser.selectedBrowser,
+ theme: { preset: "chrome" },
+ };
+
+ let stopReloadButton = doc.getElementById("stop-reload-button");
+
+ await gCustomizeMode.addToPanel(stopReloadButton);
+
+ const message = JSON.parse(JSON.stringify(testMessage.message));
+ message.content.screens[0].anchors = [
+ // element that does not exist
+ { selector: "#some-fake-id.some-fake-class", arrow_position: "top" },
+ // element that exists but has no height/width
+ { selector: "#a11y-announcement", arrow_position: "top" },
+ // element that exists but is hidden by CSS
+ { selector: "#window-modal-dialog", arrow_position: "top" },
+ // customizable widget that's in the overflow panel
+ { selector: "#stop-reload-button", arrow_position: "top" },
+ // element that is fully visible
+ { selector: "#PanelUI-menu-button", arrow_position: "top" },
+ ];
+ const sandbox = sinon.createSandbox();
+
+ const featureCallout = new FeatureCallout(config);
+ const getAnchorSpy = sandbox.spy(featureCallout, "_getAnchor");
+ featureCallout.showFeatureCallout(message);
+ await waitForCalloutScreen(doc, message.content.screens[0].id);
+ is(
+ getAnchorSpy.lastCall.returnValue.selector,
+ message.content.screens[0].anchors[4].selector,
+ "The first valid anchor (anchor 5) is selected"
+ );
+
+ win.document.querySelector(calloutCTASelector).click();
+ await waitForCalloutRemoved(win.document);
+ CustomizableUI.reset();
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+});