From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../browser/browser_feature_callout_in_chrome.js | 487 +++++++++++++++++++++ 1 file changed, 487 insertions(+) create mode 100644 browser/components/newtab/test/browser/browser_feature_callout_in_chrome.js (limited to 'browser/components/newtab/test/browser/browser_feature_callout_in_chrome.js') 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(); + } +); -- cgit v1.2.3