summaryrefslogtreecommitdiffstats
path: root/browser/components/newtab/test/browser/browser_feature_callout_in_chrome.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/newtab/test/browser/browser_feature_callout_in_chrome.js')
-rw-r--r--browser/components/newtab/test/browser/browser_feature_callout_in_chrome.js487
1 files changed, 487 insertions, 0 deletions
diff --git a/browser/components/newtab/test/browser/browser_feature_callout_in_chrome.js b/browser/components/newtab/test/browser/browser_feature_callout_in_chrome.js
new file mode 100644
index 0000000000..5eff75e31e
--- /dev/null
+++ b/browser/components/newtab/test/browser/browser_feature_callout_in_chrome.js
@@ -0,0 +1,487 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { ASRouter } = ChromeUtils.import(
+ "resource://activity-stream/lib/ASRouter.jsm"
+);
+
+const calloutId = "multi-stage-message-root";
+const calloutSelector = `#${calloutId}.featureCallout`;
+const primaryButtonSelector = `#${calloutId} .primary`;
+const PDF_TEST_URL =
+ "https://example.com/browser/browser/components/newtab/test/browser/file_pdf.PDF";
+
+const waitForCalloutScreen = async (doc, screenId) => {
+ await BrowserTestUtils.waitForCondition(() => {
+ return doc.querySelector(`${calloutSelector}:not(.hidden) .${screenId}`);
+ });
+};
+
+const waitForRemoved = async doc => {
+ await BrowserTestUtils.waitForCondition(() => {
+ return !doc.querySelector(calloutSelector);
+ });
+};
+
+async function openURLInWindow(window, url) {
+ const { selectedBrowser } = window.gBrowser;
+ BrowserTestUtils.loadURIString(selectedBrowser, url);
+ await BrowserTestUtils.browserLoaded(selectedBrowser, false, url);
+}
+
+async function openURLInNewTab(window, url) {
+ return BrowserTestUtils.openNewForegroundTab(window.gBrowser, url);
+}
+
+const pdfMatch = sinon.match(val => {
+ return val?.id === "featureCalloutCheck" && val?.context?.source === "chrome";
+});
+
+const validateCalloutCustomPosition = (element, positionOverride, doc) => {
+ const browserBox = doc.querySelector("hbox#browser");
+ for (let position in positionOverride) {
+ if (Object.prototype.hasOwnProperty.call(positionOverride, position)) {
+ // The substring here is to remove the `px` at the end of our position override strings
+ const relativePos = positionOverride[position].substring(
+ 0,
+ positionOverride[position].length - 2
+ );
+ 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, positionOverride) => {
+ for (let position in positionOverride) {
+ if (Object.prototype.hasOwnProperty.call(positionOverride, position)) {
+ const pixelPosition = positionOverride[position];
+ if (position === "left") {
+ const actualLeft = Number(
+ pixelPosition.substring(0, pixelPosition.length - 2)
+ );
+ if (element.getBoundingClientRect().right !== actualLeft) {
+ return false;
+ }
+ } else if (position === "right") {
+ const expectedLeft = Number(
+ pixelPosition.substring(0, pixelPosition.length - 2)
+ );
+ if (element.getBoundingClientRect().left !== expectedLeft) {
+ 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",
+ parent_selector: "#urlbar-container",
+ content: {
+ position: "callout",
+ arrow_position: "top-end",
+ title: {
+ raw: "Test title",
+ },
+ subtitle: {
+ raw: "Test subtitle",
+ },
+ primary_button: {
+ label: {
+ raw: "Done",
+ },
+ action: {
+ navigate: true,
+ },
+ },
+ },
+ },
+ ],
+ },
+ priority: 1,
+ targeting: "true",
+ trigger: { id: "featureCalloutCheck" },
+ },
+};
+
+const testMessageCalloutSelector = testMessage.message.content.screens[0].id;
+
+add_setup(async function () {
+ requestLongerTimeout(2);
+});
+
+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, testMessageCalloutSelector);
+ 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 primary button to close
+ doc.querySelector(primaryButtonSelector).click();
+ await waitForRemoved(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, testMessageCalloutSelector);
+ ok(
+ doc.querySelector(`.${testMessageCalloutSelector}`),
+ "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(`.${testMessageCalloutSelector}`),
+ "Feature callout removed when tab without PDF URL is navigated to"
+ );
+
+ const tab3 = await openURLInNewTab(win, PDF_TEST_URL);
+ tab3.focus();
+ await waitForCalloutScreen(doc, testMessageCalloutSelector);
+ ok(
+ doc.querySelector(`.${testMessageCalloutSelector}`),
+ "Feature callout still renders when opening a new tab with PDF url after being initially rendered on another tab"
+ );
+
+ tab1.focus();
+ await waitForCalloutScreen(doc, testMessageCalloutSelector);
+ ok(
+ doc.querySelector(`.${testMessageCalloutSelector}`),
+ "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, testMessageCalloutSelector);
+ ok(
+ doc.querySelector(`.${testMessageCalloutSelector}`),
+ "Feature callout rendered when opening a new tab with PDF url"
+ );
+
+ BrowserTestUtils.loadURIString(win.gBrowser, "about:preferences");
+ await BrowserTestUtils.waitForLocationChange(
+ win.gBrowser,
+ "about:preferences"
+ );
+ await waitForRemoved(doc);
+
+ ok(
+ !doc.querySelector(`.${testMessageCalloutSelector}`),
+ "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, testMessageCalloutSelector);
+ ok(
+ doc.querySelector(`.${testMessageCalloutSelector}`),
+ "Feature callout rendered when opening a new tab with PDF url"
+ );
+
+ BrowserTestUtils.removeTab(tab1);
+ await waitForRemoved(doc);
+
+ ok(
+ !doc.querySelector(`.${testMessageCalloutSelector}`),
+ "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 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.addTab(win.gBrowser, PDF_TEST_URL);
+ ok(
+ !doc.querySelector(`.${testMessageCalloutSelector}`),
+ "Feature callout not rendered when opening a background tab with PDF url"
+ );
+
+ BrowserTestUtils.removeTab(tab1);
+
+ ok(
+ !doc.querySelector(`.${testMessageCalloutSelector}`),
+ "Feature callout still not rendered after closing background tab with PDF url"
+ );
+
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ }
+);
+
+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].parent_selector = "hbox#browser";
+ pdfTestMessage.message.content.screens[0].content.callout_position_override =
+ {
+ 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();
+ 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].content
+ .callout_position_override,
+ 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-background");
+ 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].content
+ .callout_position_override,
+ doc
+ ),
+ "Callout custom position is as expected while navigator toolbox height is extended"
+ );
+ BrowserTestUtils.removeTab(tab);
+ await BrowserTestUtils.closeWindow(win);
+ sandbox.restore();
+ }
+);
+
+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].parent_selector = "hbox#browser";
+ pdfTestMessage.message.content.screens[0].content.callout_position_override =
+ {
+ 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";
+ ok(
+ 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].content
+ .callout_position_override
+ ),
+ "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, testMessageCalloutSelector);
+ 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 waitForRemoved(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, testMessageCalloutSelector);
+ 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();
+ }
+);