Bug 1828508 - Clean up Firefox View Feature Tour + Notification r=omc-reviewers,fluent-reviewers,fxview-reviewers,nsharpley,bolsson,aminomancer
Differential Revision: https://phabricator.services.mozilla.com/D209404
This commit is contained in:
@@ -10,21 +10,6 @@ prefs = [
|
||||
|
||||
["browser_entrypoint_management.js"]
|
||||
|
||||
["browser_feature_callout.js"]
|
||||
skip-if = ["true"] # Bug 1869605 and # Bug 1870296
|
||||
|
||||
["browser_feature_callout_position.js"]
|
||||
skip-if = ["true"] # Bug 1869605 and # Bug 1870296
|
||||
|
||||
["browser_feature_callout_resize.js"]
|
||||
skip-if = ["true"] # Bug 1869605 and # Bug 1870296
|
||||
|
||||
["browser_feature_callout_targeting.js"]
|
||||
skip-if = ["true"] # Bug 1869605 and # Bug 1870296
|
||||
|
||||
["browser_feature_callout_theme.js"]
|
||||
skip-if = ["true"] # Bug 1869605 and # Bug 1870296
|
||||
|
||||
["browser_firefoxview.js"]
|
||||
|
||||
["browser_firefoxview_dragDrop_pinned_tab.js"]
|
||||
|
||||
@@ -1,746 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
const { MessageLoaderUtils } = ChromeUtils.importESModule(
|
||||
"resource:///modules/asrouter/ASRouter.sys.mjs"
|
||||
);
|
||||
|
||||
const { BuiltInThemes } = ChromeUtils.importESModule(
|
||||
"resource:///modules/BuiltInThemes.sys.mjs"
|
||||
);
|
||||
|
||||
const defaultPrefValue = getPrefValueByScreen(1);
|
||||
|
||||
add_setup(async function () {
|
||||
requestLongerTimeout(3);
|
||||
registerCleanupFunction(() => ASRouter.resetMessageState());
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_renders_in_firefox_view() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, defaultPrefValue]],
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
|
||||
ok(
|
||||
document.querySelector(calloutSelector),
|
||||
"Feature Callout element exists"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_is_not_shown_twice() {
|
||||
// Third comma-separated value of the pref is set to a string value once a user completes the tour
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, '{"screen":"","complete":true}']],
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
ok(
|
||||
!document.querySelector(calloutSelector),
|
||||
"Feature Callout tour does not render if the user finished it previously"
|
||||
);
|
||||
}
|
||||
);
|
||||
Services.prefs.clearUserPref(featureTourPref);
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_syncs_across_visits_and_tabs() {
|
||||
// Second comma-separated value of the pref is the id
|
||||
// of the last viewed screen of the feature tour
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, '{"screen":"FEATURE_CALLOUT_1","complete":false}']],
|
||||
});
|
||||
// Open an about:firefoxview tab
|
||||
let tab1 = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"about:firefoxview"
|
||||
);
|
||||
let tab1Doc = tab1.linkedBrowser.contentWindow.document;
|
||||
launchFeatureTourIn(tab1.linkedBrowser.contentWindow);
|
||||
await waitForCalloutScreen(tab1Doc, "FEATURE_CALLOUT_1");
|
||||
|
||||
ok(
|
||||
tab1Doc.querySelector(".FEATURE_CALLOUT_1"),
|
||||
"First tab's Feature Callout shows the tour screen saved in the user pref"
|
||||
);
|
||||
|
||||
// Open a second about:firefoxview tab
|
||||
let tab2 = await BrowserTestUtils.openNewForegroundTab(
|
||||
gBrowser,
|
||||
"about:firefoxview"
|
||||
);
|
||||
let tab2Doc = tab2.linkedBrowser.contentWindow.document;
|
||||
launchFeatureTourIn(tab2.linkedBrowser.contentWindow);
|
||||
await waitForCalloutScreen(tab2Doc, "FEATURE_CALLOUT_1");
|
||||
|
||||
ok(
|
||||
tab2Doc.querySelector(".FEATURE_CALLOUT_1"),
|
||||
"Second tab's Feature Callout shows the tour screen saved in the user pref"
|
||||
);
|
||||
|
||||
await clickCTA(tab2Doc);
|
||||
await waitForCalloutScreen(tab2Doc, "FEATURE_CALLOUT_2");
|
||||
|
||||
gBrowser.selectedTab = tab1;
|
||||
tab1.focus();
|
||||
await waitForCalloutScreen(tab1Doc, "FEATURE_CALLOUT_2");
|
||||
ok(
|
||||
tab1Doc.querySelector(".FEATURE_CALLOUT_2"),
|
||||
"First tab's Feature Callout advances to the next screen when the tour is advanced in second tab"
|
||||
);
|
||||
|
||||
await clickCTA(tab1Doc);
|
||||
gBrowser.selectedTab = tab1;
|
||||
await waitForCalloutRemoved(tab1Doc);
|
||||
|
||||
ok(
|
||||
!tab1Doc.body.querySelector(calloutSelector),
|
||||
"Feature Callout is removed in first tab after being dismissed in first tab"
|
||||
);
|
||||
|
||||
gBrowser.selectedTab = tab2;
|
||||
tab2.focus();
|
||||
await waitForCalloutRemoved(tab2Doc);
|
||||
|
||||
ok(
|
||||
!tab2Doc.body.querySelector(calloutSelector),
|
||||
"Feature Callout is removed in second tab after tour was dismissed in first tab"
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(tab1);
|
||||
BrowserTestUtils.removeTab(tab2);
|
||||
Services.prefs.clearUserPref(featureTourPref);
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_closes_on_dismiss() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, '{"screen":"FEATURE_CALLOUT_2","complete":false}']],
|
||||
});
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
const spy = new TelemetrySpy(sandbox);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_2");
|
||||
|
||||
document.querySelector(".dismiss-button").click();
|
||||
await waitForCalloutRemoved(document);
|
||||
|
||||
ok(
|
||||
!document.querySelector(calloutSelector),
|
||||
"Callout is removed from screen on dismiss"
|
||||
);
|
||||
|
||||
let tourComplete = JSON.parse(
|
||||
Services.prefs.getStringPref(featureTourPref)
|
||||
).complete;
|
||||
ok(
|
||||
tourComplete,
|
||||
`Tour is recorded as complete in ${featureTourPref} preference value`
|
||||
);
|
||||
|
||||
// Test that appropriate telemetry is sent
|
||||
spy.assertCalledWith({
|
||||
event: "CLICK_BUTTON",
|
||||
event_context: {
|
||||
source: "dismiss_button",
|
||||
page: "about:firefoxview",
|
||||
},
|
||||
message_id: sinon.match("FEATURE_CALLOUT_2"),
|
||||
});
|
||||
spy.assertCalledWith({
|
||||
event: "DISMISS",
|
||||
event_context: {
|
||||
source: "dismiss_button",
|
||||
page: "about:firefoxview",
|
||||
},
|
||||
message_id: sinon.match("FEATURE_CALLOUT_2"),
|
||||
});
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_arrow_position_attribute_exists() {
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
const callout = await BrowserTestUtils.waitForCondition(
|
||||
() =>
|
||||
document.querySelector(`${calloutSelector}[arrow-position="top"]`),
|
||||
"Waiting for callout to render"
|
||||
);
|
||||
is(
|
||||
callout.getAttribute("arrow-position"),
|
||||
"top",
|
||||
"Arrow position attribute exists on parent container"
|
||||
);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_arrow_is_not_flipped_on_ltr() {
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
testMessage.message.content.screens[0].anchors[0].arrow_position = "start";
|
||||
testMessage.message.content.screens[0].anchors[0].selector =
|
||||
"span.brand-feature-name";
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
const callout = await BrowserTestUtils.waitForCondition(
|
||||
() =>
|
||||
document.querySelector(
|
||||
`${calloutSelector}[arrow-position="inline-start"]:not(.hidden)`
|
||||
),
|
||||
"Waiting for callout to render"
|
||||
);
|
||||
is(
|
||||
callout.getAttribute("arrow-position"),
|
||||
"inline-start",
|
||||
"Feature callout has inline-start arrow position when arrow_position is set to 'start'"
|
||||
);
|
||||
ok(
|
||||
!callout.classList.contains("hidden"),
|
||||
"Feature Callout is not hidden"
|
||||
);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_respects_cfr_features_pref() {
|
||||
async function toggleCFRFeaturesPref(value) {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
[
|
||||
"browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features",
|
||||
value,
|
||||
],
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, defaultPrefValue]],
|
||||
});
|
||||
|
||||
await toggleCFRFeaturesPref(true);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
ok(
|
||||
document.querySelector(calloutSelector),
|
||||
"Feature Callout element exists"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
await toggleCFRFeaturesPref(false);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
ok(
|
||||
!document.querySelector(calloutSelector),
|
||||
"Feature Callout element was not created because CFR pref was disabled"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
add_task(
|
||||
async function feature_callout_tab_pickup_reminder_primary_click_elm() {
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.enabled", false);
|
||||
const testMessage = getCalloutMessageById(
|
||||
"FIREFOX_VIEW_TAB_PICKUP_REMINDER"
|
||||
);
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
const expectedUrl =
|
||||
await fxAccounts.constructor.config.promiseConnectAccountURI("fx-view");
|
||||
info(`Expected FxA URL: ${expectedUrl}`);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
let tabOpened = new Promise(resolve => {
|
||||
gBrowser.tabContainer.addEventListener(
|
||||
"TabOpen",
|
||||
event => {
|
||||
let newTab = event.target;
|
||||
let newBrowser = newTab.linkedBrowser;
|
||||
let result = newTab;
|
||||
BrowserTestUtils.waitForDocLoadAndStopIt(
|
||||
expectedUrl,
|
||||
newBrowser
|
||||
).then(() => resolve(result));
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
});
|
||||
|
||||
info("Waiting for callout to render");
|
||||
await waitForCalloutScreen(
|
||||
document,
|
||||
"FIREFOX_VIEW_TAB_PICKUP_REMINDER"
|
||||
);
|
||||
|
||||
info("Clicking primary button");
|
||||
let calloutRemoved = waitForCalloutRemoved(document);
|
||||
await clickCTA(document);
|
||||
let openedTab = await tabOpened;
|
||||
ok(openedTab, "FxA sign in page opened");
|
||||
// The callout should be removed when primary CTA is clicked
|
||||
await calloutRemoved;
|
||||
BrowserTestUtils.removeTab(openedTab);
|
||||
}
|
||||
);
|
||||
Services.prefs.clearUserPref("identity.fxaccounts.enabled");
|
||||
sandbox.restore();
|
||||
}
|
||||
);
|
||||
|
||||
add_task(async function feature_callout_dismiss_on_timeout() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, `{"screen":"","complete":true}`]],
|
||||
});
|
||||
const screenId = "FIREFOX_VIEW_TAB_PICKUP_REMINDER";
|
||||
let testMessage = getCalloutMessageById(screenId);
|
||||
// Configure message with a dismiss action on tab container click
|
||||
testMessage.message.content.screens[0].content.page_event_listeners = [
|
||||
{
|
||||
params: { type: "timeout", options: { once: true, interval: 5000 } },
|
||||
action: { dismiss: true, type: "CANCEL" },
|
||||
},
|
||||
];
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
const telemetrySpy = new TelemetrySpy(sandbox);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
let onInterval;
|
||||
let startedInterval = new Promise(resolve => {
|
||||
sandbox
|
||||
.stub(browser.contentWindow, "setInterval")
|
||||
.callsFake((fn, ms) => {
|
||||
Assert.strictEqual(
|
||||
ms,
|
||||
5000,
|
||||
"setInterval called with 5 second interval"
|
||||
);
|
||||
onInterval = fn;
|
||||
resolve();
|
||||
return 1;
|
||||
});
|
||||
});
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
info("Waiting for callout to render");
|
||||
await startedInterval;
|
||||
await waitForCalloutScreen(document, screenId);
|
||||
|
||||
info("Ending timeout");
|
||||
onInterval();
|
||||
await waitForCalloutRemoved(document);
|
||||
|
||||
// Test that appropriate telemetry is sent
|
||||
telemetrySpy.assertCalledWith({
|
||||
event: "PAGE_EVENT",
|
||||
event_context: {
|
||||
action: "CANCEL",
|
||||
reason: "TIMEOUT",
|
||||
source: "timeout",
|
||||
page: "about:firefoxview",
|
||||
},
|
||||
message_id: screenId,
|
||||
});
|
||||
telemetrySpy.assertCalledWith({
|
||||
event: "DISMISS",
|
||||
event_context: {
|
||||
source: "PAGE_EVENT:timeout",
|
||||
page: "about:firefoxview",
|
||||
},
|
||||
message_id: screenId,
|
||||
});
|
||||
}
|
||||
);
|
||||
Services.prefs.clearUserPref("browser.firefox-view.view-count");
|
||||
sandbox.restore();
|
||||
ASRouter.resetMessageState();
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_advance_tour_on_page_click() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
[
|
||||
featureTourPref,
|
||||
JSON.stringify({
|
||||
screen: "FEATURE_CALLOUT_1",
|
||||
complete: false,
|
||||
}),
|
||||
],
|
||||
],
|
||||
});
|
||||
|
||||
// Add page action listeners to the built-in messages.
|
||||
let testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
// Configure message with a dismiss action on tab container click
|
||||
testMessage.message.content.screens.forEach(screen => {
|
||||
screen.content.page_event_listeners = [
|
||||
{
|
||||
params: { type: "click", selectors: ".brand-logo" },
|
||||
action: JSON.parse(
|
||||
JSON.stringify(screen.content.primary_button.action)
|
||||
),
|
||||
},
|
||||
];
|
||||
});
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
info("Clicking page container");
|
||||
// We intentionally turn off a11y_checks, because the following click
|
||||
// is send to dismiss the feature callout using an alternative way of
|
||||
// the callout dismissal, where other ways are accessible, therefore
|
||||
// this test can be ignored.
|
||||
AccessibilityUtils.setEnv({
|
||||
mustHaveAccessibleRule: false,
|
||||
});
|
||||
document.querySelector(".brand-logo").click();
|
||||
AccessibilityUtils.resetEnv();
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_2");
|
||||
info("Clicking page container");
|
||||
// We intentionally turn off a11y_checks, because the following click
|
||||
// is send to dismiss the feature callout using an alternative way of
|
||||
// the callout dismissal, where other ways are accessible, therefore
|
||||
// this test can be ignored.
|
||||
AccessibilityUtils.setEnv({
|
||||
mustHaveAccessibleRule: false,
|
||||
});
|
||||
document.querySelector(".brand-logo").click();
|
||||
AccessibilityUtils.resetEnv();
|
||||
|
||||
await waitForCalloutRemoved(document);
|
||||
let tourComplete = JSON.parse(
|
||||
Services.prefs.getStringPref(featureTourPref)
|
||||
).complete;
|
||||
ok(
|
||||
tourComplete,
|
||||
`Tour is recorded as complete in ${featureTourPref} preference value`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
ASRouter.resetMessageState();
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_dismiss_on_escape() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, `{"screen":"","complete":true}`]],
|
||||
});
|
||||
const screenId = "FIREFOX_VIEW_TAB_PICKUP_REMINDER";
|
||||
let testMessage = getCalloutMessageById(screenId);
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
const spy = new TelemetrySpy(sandbox);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
info("Waiting for callout to render");
|
||||
await waitForCalloutScreen(document, screenId);
|
||||
|
||||
info("Pressing escape");
|
||||
// Press Escape to close
|
||||
EventUtils.synthesizeKey("KEY_Escape", {}, browser.contentWindow);
|
||||
await waitForCalloutRemoved(document);
|
||||
|
||||
// Test that appropriate telemetry is sent
|
||||
spy.assertCalledWith({
|
||||
event: "DISMISS",
|
||||
event_context: {
|
||||
source: "KEY_Escape",
|
||||
page: "about:firefoxview",
|
||||
},
|
||||
message_id: screenId,
|
||||
});
|
||||
}
|
||||
);
|
||||
Services.prefs.clearUserPref("browser.firefox-view.view-count");
|
||||
sandbox.restore();
|
||||
ASRouter.resetMessageState();
|
||||
});
|
||||
|
||||
add_task(async function test_firefox_view_spotlight_promo() {
|
||||
// Prevent attempts to fetch CFR messages remotely.
|
||||
const sandbox = sinon.createSandbox();
|
||||
let remoteSettingsStub = sandbox.stub(
|
||||
MessageLoaderUtils,
|
||||
"_remoteSettingsLoader"
|
||||
);
|
||||
remoteSettingsStub.resolves([]);
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
clear: [
|
||||
[featureTourPref],
|
||||
["browser.newtabpage.activity-stream.asrouter.providers.cfr"],
|
||||
],
|
||||
});
|
||||
ASRouter.resetMessageState();
|
||||
|
||||
let dialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen(
|
||||
null,
|
||||
"chrome://browser/content/spotlight.html",
|
||||
{ isSubDialog: true }
|
||||
);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
info("Waiting for the Fx View Spotlight promo to open");
|
||||
let dialogBrowser = await dialogOpenPromise;
|
||||
let primaryBtnSelector = ".action-buttons button.primary";
|
||||
await TestUtils.waitForCondition(
|
||||
() => dialogBrowser.document.querySelector("main.DEFAULT_MODAL_UI"),
|
||||
`Should render main.DEFAULT_MODAL_UI`
|
||||
);
|
||||
|
||||
dialogBrowser.document.querySelector(primaryBtnSelector).click();
|
||||
info("Fx View Spotlight promo clicked");
|
||||
|
||||
const { document } = browser.contentWindow;
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
ok(
|
||||
document.querySelector(calloutSelector),
|
||||
"Feature Callout element exists"
|
||||
);
|
||||
info("Feature tour started");
|
||||
await clickCTA(document);
|
||||
}
|
||||
);
|
||||
|
||||
ok(remoteSettingsStub.called, "Tried to load CFR messages");
|
||||
sandbox.restore();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
ASRouter.resetMessageState();
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_returns_default_fxview_focus_to_top() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, defaultPrefValue]],
|
||||
});
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
|
||||
ok(
|
||||
document.querySelector(calloutSelector),
|
||||
"Feature Callout element exists"
|
||||
);
|
||||
|
||||
document.querySelector(".dismiss-button").click();
|
||||
await waitForCalloutRemoved(document);
|
||||
|
||||
Assert.strictEqual(
|
||||
document.activeElement.localName,
|
||||
"body",
|
||||
"by default focus returns to the document body after callout closes"
|
||||
);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
ASRouter.resetMessageState();
|
||||
});
|
||||
|
||||
add_task(
|
||||
async function feature_callout_returns_moved_fxview_focus_to_previous() {
|
||||
const testMessage = getCalloutMessageById(
|
||||
"FIREFOX_VIEW_TAB_PICKUP_REMINDER"
|
||||
);
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(
|
||||
document,
|
||||
"FIREFOX_VIEW_TAB_PICKUP_REMINDER"
|
||||
);
|
||||
|
||||
// change focus to recently-closed-tabs-container
|
||||
let recentlyClosedHeaderSection = document.querySelector(
|
||||
"#recently-closed-tabs-header-section"
|
||||
);
|
||||
recentlyClosedHeaderSection.focus();
|
||||
|
||||
// close the callout dialog
|
||||
document.querySelector(".dismiss-button").click();
|
||||
await waitForCalloutRemoved(document);
|
||||
|
||||
// verify that the focus landed in the right place
|
||||
Assert.strictEqual(
|
||||
document.activeElement.id,
|
||||
"recently-closed-tabs-header-section",
|
||||
"when focus changes away from callout it reverts after callout closes"
|
||||
);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
}
|
||||
);
|
||||
|
||||
add_task(async function feature_callout_does_not_display_arrow_if_hidden() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, defaultPrefValue]],
|
||||
});
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
testMessage.message.content.screens[0].anchors[0].hide_arrow = true;
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
|
||||
is(
|
||||
getComputedStyle(
|
||||
document.querySelector(`${calloutSelector} .arrow-box`)
|
||||
).getPropertyValue("display"),
|
||||
"none",
|
||||
"callout arrow is not visible"
|
||||
);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
});
|
||||
@@ -1,445 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
requestLongerTimeout(2);
|
||||
|
||||
const defaultPrefValue = getPrefValueByScreen(1);
|
||||
|
||||
const squareWidth = 24;
|
||||
const arrowWidth = Math.hypot(squareWidth, squareWidth);
|
||||
const arrowHeight = arrowWidth / 2;
|
||||
let overlap = 5 - arrowHeight;
|
||||
|
||||
add_task(
|
||||
async function feature_callout_first_screen_positioned_below_element() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, defaultPrefValue]],
|
||||
});
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
let parentBottom = document
|
||||
.querySelector("#tab-pickup-container")
|
||||
.getBoundingClientRect().bottom;
|
||||
let containerTop = document
|
||||
.querySelector(calloutSelector)
|
||||
.getBoundingClientRect().top;
|
||||
|
||||
isfuzzy(
|
||||
parentBottom - containerTop,
|
||||
overlap,
|
||||
1, // add 1px fuzziness to account for possible subpixel rounding
|
||||
"Feature Callout is positioned below parent element with the arrow overlapping by 5px"
|
||||
);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function feature_callout_second_screen_positioned_right_of_element() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, getPrefValueByScreen(2)]],
|
||||
});
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
testMessage.message.content.screens[1].anchors = [
|
||||
{ selector: ".brand-logo", arrow_position: "start" },
|
||||
];
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_2");
|
||||
|
||||
let parentRight = document
|
||||
.querySelector(".brand-logo")
|
||||
.getBoundingClientRect().right;
|
||||
let containerLeft = document
|
||||
.querySelector(calloutSelector)
|
||||
.getBoundingClientRect().left;
|
||||
isfuzzy(
|
||||
parentRight - containerLeft,
|
||||
overlap,
|
||||
1,
|
||||
"Feature Callout is positioned right of parent element with the arrow overlapping by 5px"
|
||||
);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function feature_callout_second_screen_positioned_above_element() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, getPrefValueByScreen(2)]],
|
||||
});
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_2");
|
||||
let parentTop = document
|
||||
.querySelector("#recently-closed-tabs-container")
|
||||
.getBoundingClientRect().top;
|
||||
let containerBottom = document
|
||||
.querySelector(calloutSelector)
|
||||
.getBoundingClientRect().bottom;
|
||||
|
||||
Assert.greaterOrEqual(
|
||||
parentTop,
|
||||
containerBottom - 5 - 1,
|
||||
"Feature Callout is positioned above parent element with 5px overlap"
|
||||
);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function feature_callout_third_screen_position_respects_RTL_layouts() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
// Set layout direction to right to left
|
||||
["intl.l10n.pseudo", "bidi"],
|
||||
[featureTourPref, getPrefValueByScreen(2)],
|
||||
],
|
||||
});
|
||||
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
const parent = document.querySelector(
|
||||
"#recently-closed-tabs-container"
|
||||
);
|
||||
parent.style.gridArea = "1/2";
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_2");
|
||||
let parentRight = parent.getBoundingClientRect().right;
|
||||
let containerLeft = document
|
||||
.querySelector(calloutSelector)
|
||||
.getBoundingClientRect().left;
|
||||
|
||||
Assert.lessOrEqual(
|
||||
parentRight,
|
||||
containerLeft + 5 + 1,
|
||||
"Feature Callout is positioned right of parent element when callout is set to 'end' in RTL layouts"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
sandbox.restore();
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function feature_callout_is_repositioned_if_parent_container_is_toggled() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, defaultPrefValue]],
|
||||
});
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
const parentEl = document.querySelector("#tab-pickup-container");
|
||||
const calloutStartingTopPosition =
|
||||
document.querySelector(calloutSelector).style.top;
|
||||
|
||||
//container has been toggled/minimized
|
||||
parentEl.removeAttribute("open", "");
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
document.querySelector(calloutSelector),
|
||||
{ attributes: true },
|
||||
() =>
|
||||
document.querySelector(calloutSelector).style.top !=
|
||||
calloutStartingTopPosition
|
||||
);
|
||||
isnot(
|
||||
document.querySelector(calloutSelector).style.top,
|
||||
calloutStartingTopPosition,
|
||||
"Feature Callout position is recalculated when parent element is toggled"
|
||||
);
|
||||
await closeCallout(document);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
}
|
||||
);
|
||||
|
||||
// This test should be moved into a surface agnostic test suite with bug 1793656.
|
||||
add_task(async function feature_callout_top_end_positioning() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, defaultPrefValue]],
|
||||
});
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
testMessage.message.content.screens[0].anchors[0].arrow_position = "top-end";
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
let parent = document.querySelector("#tab-pickup-container");
|
||||
let container = document.querySelector(calloutSelector);
|
||||
let parentLeft = parent.getBoundingClientRect().left;
|
||||
let containerLeft = container.getBoundingClientRect().left;
|
||||
|
||||
is(
|
||||
container.getAttribute("arrow-position"),
|
||||
"top-end",
|
||||
"Feature Callout container has the expected top-end arrow-position attribute"
|
||||
);
|
||||
isfuzzy(
|
||||
containerLeft - parent.clientWidth + container.offsetWidth,
|
||||
parentLeft,
|
||||
1, // Display scaling can cause up to 1px difference in layout
|
||||
"Feature Callout's right edge is approximately aligned with parent element's right edge"
|
||||
);
|
||||
|
||||
await closeCallout(document);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
// This test should be moved into a surface agnostic test suite with bug 1793656.
|
||||
add_task(async function feature_callout_top_start_positioning() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, defaultPrefValue]],
|
||||
});
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
testMessage.message.content.screens[0].anchors[0].arrow_position =
|
||||
"top-start";
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
let parent = document.querySelector("#tab-pickup-container");
|
||||
let container = document.querySelector(calloutSelector);
|
||||
let parentLeft = parent.getBoundingClientRect().left;
|
||||
let containerLeft = container.getBoundingClientRect().left;
|
||||
|
||||
is(
|
||||
container.getAttribute("arrow-position"),
|
||||
"top-start",
|
||||
"Feature Callout container has the expected top-start arrow-position attribute"
|
||||
);
|
||||
isfuzzy(
|
||||
containerLeft,
|
||||
parentLeft,
|
||||
1, // Display scaling can cause up to 1px difference in layout
|
||||
"Feature Callout's left edge is approximately aligned with parent element's left edge"
|
||||
);
|
||||
|
||||
await closeCallout(document);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
// This test should be moved into a surface agnostic test suite with bug 1793656.
|
||||
add_task(
|
||||
async function feature_callout_top_end_position_respects_RTL_layouts() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
// Set layout direction to right to left
|
||||
["intl.l10n.pseudo", "bidi"],
|
||||
[featureTourPref, defaultPrefValue],
|
||||
],
|
||||
});
|
||||
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
testMessage.message.content.screens[0].anchors[0].arrow_position =
|
||||
"top-end";
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
let parent = document.querySelector("#tab-pickup-container");
|
||||
let container = document.querySelector(calloutSelector);
|
||||
let parentLeft = parent.getBoundingClientRect().left;
|
||||
let containerLeft = container.getBoundingClientRect().left;
|
||||
|
||||
is(
|
||||
container.getAttribute("arrow-position"),
|
||||
"top-start",
|
||||
"In RTL mode, the feature callout container has the expected top-start arrow-position attribute"
|
||||
);
|
||||
is(
|
||||
containerLeft,
|
||||
parentLeft,
|
||||
"In RTL mode, the feature Callout's left edge is aligned with parent element's left edge"
|
||||
);
|
||||
|
||||
await closeCallout(document);
|
||||
}
|
||||
);
|
||||
|
||||
await SpecialPowers.popPrefEnv();
|
||||
sandbox.restore();
|
||||
}
|
||||
);
|
||||
|
||||
add_task(async function feature_callout_is_larger_than_its_parent() {
|
||||
let testMessage = {
|
||||
message: {
|
||||
id: "FIREFOX_VIEW_FEATURE_TOUR",
|
||||
template: "feature_callout",
|
||||
content: {
|
||||
id: "FIREFOX_VIEW_FEATURE_TOUR",
|
||||
transitions: false,
|
||||
disableHistoryUpdates: true,
|
||||
screens: [
|
||||
{
|
||||
id: "FEATURE_CALLOUT_1",
|
||||
anchors: [
|
||||
{ selector: ".brand-feature-name", arrow_position: "end" },
|
||||
],
|
||||
content: {
|
||||
position: "callout",
|
||||
title: "callout-firefox-view-tab-pickup-title",
|
||||
subtitle: {
|
||||
string_id: "callout-firefox-view-tab-pickup-subtitle",
|
||||
},
|
||||
logo: {
|
||||
imageURL: "chrome://browser/content/callout-tab-pickup.svg",
|
||||
darkModeImageURL:
|
||||
"chrome://browser/content/callout-tab-pickup-dark.svg",
|
||||
height: "128px", // .brand-feature-name has a height of 32px
|
||||
},
|
||||
dismiss_button: {
|
||||
action: {
|
||||
navigate: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[featureTourPref, defaultPrefValue]],
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
let parent = document.querySelector(".brand-feature-name");
|
||||
let container = document.querySelector(calloutSelector);
|
||||
let parentHeight = parent.offsetHeight;
|
||||
let containerHeight = container.offsetHeight;
|
||||
|
||||
let parentPositionTop =
|
||||
parent.getBoundingClientRect().top + window.scrollY;
|
||||
let containerPositionTop =
|
||||
container.getBoundingClientRect().top + window.scrollY;
|
||||
Assert.greater(
|
||||
containerHeight,
|
||||
parentHeight,
|
||||
"Feature Callout is height is larger than parent element when callout is configured at end of callout"
|
||||
);
|
||||
Assert.less(
|
||||
containerPositionTop,
|
||||
parentPositionTop,
|
||||
"Feature Callout is positioned higher that parent element when callout is configured at end of callout"
|
||||
);
|
||||
isfuzzy(
|
||||
containerHeight / 2 + containerPositionTop,
|
||||
parentHeight / 2 + parentPositionTop,
|
||||
1, // Display scaling can cause up to 1px difference in layout
|
||||
"Feature Callout is centered equally to parent element when callout is configured at end of callout"
|
||||
);
|
||||
await ASRouter.resetMessageState();
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
});
|
||||
@@ -1,178 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
function getArrowPosition(doc) {
|
||||
let callout = doc.querySelector(calloutSelector);
|
||||
return callout.getAttribute("arrow-position");
|
||||
}
|
||||
|
||||
add_setup(async function setup() {
|
||||
let originalWidth = window.outerWidth;
|
||||
let originalHeight = window.outerHeight;
|
||||
registerCleanupFunction(async () => {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: "about:firefoxview" },
|
||||
async browser => window.FullZoom.reset(browser)
|
||||
);
|
||||
window.resizeTo(originalWidth, originalHeight);
|
||||
});
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: "about:firefoxview" },
|
||||
async browser => window.FullZoom.setZoom(0.5, browser)
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_is_repositioned_if_it_does_not_fit() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.sessionstore.max_tabs_undo", 1]],
|
||||
});
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: "about:firefoxview" },
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
browser.contentWindow.resizeTo(1550, 1000);
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
if (getArrowPosition(document) === "top") {
|
||||
return true;
|
||||
}
|
||||
browser.contentWindow.resizeTo(1550, 1000);
|
||||
return false;
|
||||
});
|
||||
is(
|
||||
getArrowPosition(document),
|
||||
"top",
|
||||
"On first screen at 1550x1000, the callout is positioned below the parent element"
|
||||
);
|
||||
|
||||
let startingTop = document.querySelector(calloutSelector).style.top;
|
||||
browser.contentWindow.resizeTo(1800, 400);
|
||||
// Wait for callout to be repositioned
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
document.querySelector(calloutSelector),
|
||||
{ attributeFilter: ["style"], attributes: true },
|
||||
() => document.querySelector(calloutSelector).style.top != startingTop
|
||||
);
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
if (getArrowPosition(document) === "inline-start") {
|
||||
return true;
|
||||
}
|
||||
browser.contentWindow.resizeTo(1800, 400);
|
||||
return false;
|
||||
});
|
||||
is(
|
||||
getArrowPosition(document),
|
||||
"inline-start",
|
||||
"On first screen at 1800x400, the callout is positioned to the right of the parent element"
|
||||
);
|
||||
|
||||
startingTop = document.querySelector(calloutSelector).style.top;
|
||||
browser.contentWindow.resizeTo(1100, 600);
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
document.querySelector(calloutSelector),
|
||||
{ attributeFilter: ["style"], attributes: true },
|
||||
() => document.querySelector(calloutSelector).style.top != startingTop
|
||||
);
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
if (getArrowPosition(document) === "top") {
|
||||
return true;
|
||||
}
|
||||
browser.contentWindow.resizeTo(1100, 600);
|
||||
return false;
|
||||
});
|
||||
is(
|
||||
getArrowPosition(document),
|
||||
"top",
|
||||
"On first screen at 1100x600, the callout is positioned below the parent element"
|
||||
);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_is_repositioned_rtl() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
// Set layout direction to right to left
|
||||
["intl.l10n.pseudo", "bidi"],
|
||||
["browser.sessionstore.max_tabs_undo", 1],
|
||||
],
|
||||
});
|
||||
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
const sandbox = createSandboxWithCalloutTriggerStub(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{ gBrowser, url: "about:firefoxview" },
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
browser.contentWindow.resizeTo(1550, 1000);
|
||||
await waitForCalloutScreen(document, "FEATURE_CALLOUT_1");
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
if (getArrowPosition(document) === "top") {
|
||||
return true;
|
||||
}
|
||||
browser.contentWindow.resizeTo(1550, 1000);
|
||||
return false;
|
||||
});
|
||||
is(
|
||||
getArrowPosition(document),
|
||||
"top",
|
||||
"On first screen at 1550x1000, the callout is positioned below the parent element"
|
||||
);
|
||||
|
||||
let startingTop = document.querySelector(calloutSelector).style.top;
|
||||
browser.contentWindow.resizeTo(1800, 400);
|
||||
// Wait for callout to be repositioned
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
document.querySelector(calloutSelector),
|
||||
{ attributeFilter: ["style"], attributes: true },
|
||||
() => document.querySelector(calloutSelector).style.top != startingTop
|
||||
);
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
if (getArrowPosition(document) === "inline-end") {
|
||||
return true;
|
||||
}
|
||||
browser.contentWindow.resizeTo(1800, 400);
|
||||
return false;
|
||||
});
|
||||
is(
|
||||
getArrowPosition(document),
|
||||
"inline-end",
|
||||
"On first screen at 1800x400, the callout is positioned to the right of the parent element"
|
||||
);
|
||||
|
||||
startingTop = document.querySelector(calloutSelector).style.top;
|
||||
browser.contentWindow.resizeTo(1100, 600);
|
||||
await BrowserTestUtils.waitForMutationCondition(
|
||||
document.querySelector(calloutSelector),
|
||||
{ attributeFilter: ["style"], attributes: true },
|
||||
() => document.querySelector(calloutSelector).style.top != startingTop
|
||||
);
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
if (getArrowPosition(document) === "top") {
|
||||
return true;
|
||||
}
|
||||
browser.contentWindow.resizeTo(1100, 600);
|
||||
return false;
|
||||
});
|
||||
is(
|
||||
getArrowPosition(document),
|
||||
"top",
|
||||
"On first screen at 1100x600, the callout is positioned below the parent element"
|
||||
);
|
||||
}
|
||||
);
|
||||
sandbox.restore();
|
||||
});
|
||||
@@ -1,175 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
add_task(
|
||||
async function test_firefox_view_tab_pick_up_not_signed_in_targeting() {
|
||||
ASRouter.resetMessageState();
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.firefox-view.feature-tour", `{"screen":"","complete":true}`],
|
||||
],
|
||||
});
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.firefox-view.view-count", 3]],
|
||||
});
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["identity.fxaccounts.enabled", false]],
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(
|
||||
document,
|
||||
"FIREFOX_VIEW_TAB_PICKUP_REMINDER"
|
||||
);
|
||||
ok(
|
||||
document.querySelector(".featureCallout"),
|
||||
"Firefox:View Tab Pickup should be displayed."
|
||||
);
|
||||
|
||||
SpecialPowers.popPrefEnv();
|
||||
SpecialPowers.popPrefEnv();
|
||||
SpecialPowers.popPrefEnv();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function test_firefox_view_tab_pick_up_sync_not_enabled_targeting() {
|
||||
ASRouter.resetMessageState();
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.firefox-view.feature-tour", `{"screen":"","complete":true}`],
|
||||
],
|
||||
});
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.firefox-view.view-count", 3]],
|
||||
});
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["identity.fxaccounts.enabled", true]],
|
||||
});
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["services.sync.engine.tabs", false]],
|
||||
});
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["services.sync.username", false]],
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(
|
||||
document,
|
||||
"FIREFOX_VIEW_TAB_PICKUP_REMINDER"
|
||||
);
|
||||
ok(
|
||||
document.querySelector(".featureCallout"),
|
||||
"Firefox:View Tab Pickup should be displayed."
|
||||
);
|
||||
|
||||
SpecialPowers.popPrefEnv();
|
||||
SpecialPowers.popPrefEnv();
|
||||
SpecialPowers.popPrefEnv();
|
||||
SpecialPowers.popPrefEnv();
|
||||
SpecialPowers.popPrefEnv();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function test_firefox_view_tab_pick_up_wait_24_hours_after_spotlight() {
|
||||
const TWENTY_FIVE_HOURS_IN_MS = 25 * 60 * 60 * 1000;
|
||||
|
||||
ASRouter.resetMessageState();
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["browser.firefox-view.feature-tour", `{"screen":"","complete":true}`],
|
||||
],
|
||||
});
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.firefox-view.view-count", 3]],
|
||||
});
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["identity.fxaccounts.enabled", false]],
|
||||
});
|
||||
|
||||
ASRouter.setState({
|
||||
messageImpressions: { FIREFOX_VIEW_SPOTLIGHT: [Date.now()] },
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
ok(
|
||||
!document.querySelector(".featureCallout"),
|
||||
"Tab Pickup reminder should not be displayed when the Spotlight message introducing the tour was viewed less than 24 hours ago."
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ASRouter.setState({
|
||||
messageImpressions: {
|
||||
FIREFOX_VIEW_SPOTLIGHT: [Date.now() - TWENTY_FIVE_HOURS_IN_MS],
|
||||
},
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
|
||||
launchFeatureTourIn(browser.contentWindow);
|
||||
|
||||
await waitForCalloutScreen(
|
||||
document,
|
||||
"FIREFOX_VIEW_TAB_PICKUP_REMINDER"
|
||||
);
|
||||
ok(
|
||||
document.querySelector(".featureCallout"),
|
||||
"Tab Pickup reminder can be displayed when the Spotlight message introducing the tour was viewed over 24 hours ago."
|
||||
);
|
||||
|
||||
SpecialPowers.popPrefEnv();
|
||||
SpecialPowers.popPrefEnv();
|
||||
SpecialPowers.popPrefEnv();
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -1,80 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { FeatureCallout } = ChromeUtils.importESModule(
|
||||
"resource:///modules/asrouter/FeatureCallout.sys.mjs"
|
||||
);
|
||||
|
||||
async function testCallout(config) {
|
||||
const featureCallout = new FeatureCallout(config);
|
||||
const testMessage = getCalloutMessageById("FIREFOX_VIEW_FEATURE_TOUR");
|
||||
const screen = testMessage.message.content.screens[1];
|
||||
screen.anchors[0].selector = "body";
|
||||
testMessage.message.content.screens = [screen];
|
||||
featureCallout.showFeatureCallout(testMessage.message);
|
||||
await waitForCalloutScreen(config.win.document, screen.id);
|
||||
testStyles(config);
|
||||
return { featureCallout };
|
||||
}
|
||||
|
||||
function testStyles({ win, theme }) {
|
||||
const calloutEl = win.document.querySelector(calloutSelector);
|
||||
const calloutStyle = win.getComputedStyle(calloutEl);
|
||||
for (const type of ["light", "dark", "hcm"]) {
|
||||
const appliedTheme = Object.assign(
|
||||
{},
|
||||
FeatureCallout.themePresets[theme.preset],
|
||||
theme
|
||||
);
|
||||
const scheme = appliedTheme[type];
|
||||
for (const name of FeatureCallout.themePropNames) {
|
||||
Assert.equal(
|
||||
!!calloutStyle.getPropertyValue(`--fc-${name}-${type}`),
|
||||
!!(scheme?.[name] || appliedTheme.all?.[name]),
|
||||
`Theme property --fc-${name}-${type} is set`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_task(async function feature_callout_chrome_theme() {
|
||||
const win = await BrowserTestUtils.openNewBrowserWindow();
|
||||
await testCallout({
|
||||
win,
|
||||
location: "chrome",
|
||||
context: "chrome",
|
||||
browser: win.gBrowser.selectedBrowser,
|
||||
theme: { preset: "chrome" },
|
||||
});
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_pdfjs_theme() {
|
||||
const win = await BrowserTestUtils.openNewBrowserWindow();
|
||||
await testCallout({
|
||||
win,
|
||||
location: "pdfjs",
|
||||
context: "chrome",
|
||||
browser: win.gBrowser.selectedBrowser,
|
||||
theme: { preset: "pdfjs", simulateContent: true },
|
||||
});
|
||||
await BrowserTestUtils.closeWindow(win);
|
||||
});
|
||||
|
||||
add_task(async function feature_callout_content_theme() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
browser =>
|
||||
testCallout({
|
||||
win: browser.contentWindow,
|
||||
location: "about:firefoxview",
|
||||
context: "content",
|
||||
theme: { preset: "themed-content" },
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -52,10 +52,6 @@ ChromeUtils.defineESModuleGetters(this, {
|
||||
TabStateFlusher: "resource:///modules/sessionstore/TabStateFlusher.sys.mjs",
|
||||
});
|
||||
|
||||
const calloutId = "feature-callout";
|
||||
const calloutSelector = `#${calloutId}.featureCallout`;
|
||||
const CTASelector = `#${calloutId} :is(.primary, .secondary)`;
|
||||
|
||||
/**
|
||||
* URLs used for browser_recently_closed_tabs_keyboard and
|
||||
* browser_firefoxview_accessibility
|
||||
@@ -337,174 +333,6 @@ async function tearDown(sandbox) {
|
||||
Services.prefs.clearUserPref("services.sync.lastTabFetch");
|
||||
}
|
||||
|
||||
const featureTourPref = "browser.firefox-view.feature-tour";
|
||||
const launchFeatureTourIn = win => {
|
||||
const { FeatureCallout } = ChromeUtils.importESModule(
|
||||
"resource:///modules/asrouter/FeatureCallout.sys.mjs"
|
||||
);
|
||||
let callout = new FeatureCallout({
|
||||
win,
|
||||
pref: { name: featureTourPref },
|
||||
location: "about:firefoxview",
|
||||
context: "content",
|
||||
theme: { preset: "themed-content" },
|
||||
});
|
||||
callout.showFeatureCallout();
|
||||
return callout;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a value that can be used to set
|
||||
* `browser.firefox-view.feature-tour` to change the feature tour's
|
||||
* UI state.
|
||||
*
|
||||
* @see FeatureCalloutMessages.sys.mjs for valid values of "screen"
|
||||
*
|
||||
* @param {number} screen The full ID of the feature callout screen
|
||||
* @returns {string} JSON string used to set
|
||||
* `browser.firefox-view.feature-tour`
|
||||
*/
|
||||
const getPrefValueByScreen = screen => {
|
||||
return JSON.stringify({
|
||||
screen: `FEATURE_CALLOUT_${screen}`,
|
||||
complete: false,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Wait for a feature callout screen of given parameters to be shown
|
||||
*
|
||||
* @param {Document} doc the document where the callout appears.
|
||||
* @param {string} screenPostfix The full ID of the feature callout screen.
|
||||
*/
|
||||
const waitForCalloutScreen = async (doc, screenPostfix) => {
|
||||
await BrowserTestUtils.waitForCondition(() =>
|
||||
doc.querySelector(`${calloutSelector}:not(.hidden) .${screenPostfix}`)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits for the feature callout screen to be removed.
|
||||
*
|
||||
* @param {Document} doc The document where the callout appears.
|
||||
*/
|
||||
const waitForCalloutRemoved = async doc => {
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
return !doc.body.querySelector(calloutSelector);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* NOTE: Should be replaced with synthesizeMouseAtCenter for
|
||||
* simulating user input. See Bug 1798322
|
||||
*
|
||||
* Clicks the primary button in the feature callout dialog
|
||||
*
|
||||
* @param {document} doc Firefox View document
|
||||
*/
|
||||
const clickCTA = async doc => {
|
||||
doc.querySelector(CTASelector).click();
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes a feature callout via a click to the dismiss button.
|
||||
*
|
||||
* @param {Document} doc The document where the callout appears.
|
||||
*/
|
||||
const closeCallout = async doc => {
|
||||
// close the callout dialog
|
||||
const dismissBtn = doc.querySelector(`${calloutSelector} .dismiss-button`);
|
||||
if (!dismissBtn) {
|
||||
return;
|
||||
}
|
||||
doc.querySelector(`${calloutSelector} .dismiss-button`).click();
|
||||
await BrowserTestUtils.waitForCondition(() => {
|
||||
return !document.querySelector(calloutSelector);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a Feature Callout message by id.
|
||||
*
|
||||
* @param {string} id
|
||||
* The message id.
|
||||
*/
|
||||
const getCalloutMessageById = id => {
|
||||
return {
|
||||
message: FeatureCalloutMessages.getMessages().find(m => m.id === id),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a sinon sandbox with `sendTriggerMessage` stubbed
|
||||
* to return a specified test message for featureCalloutCheck.
|
||||
*
|
||||
* @param {object} testMessage
|
||||
* @param {string} [source="about:firefoxview"]
|
||||
*/
|
||||
const createSandboxWithCalloutTriggerStub = (
|
||||
testMessage,
|
||||
source = "about:firefoxview"
|
||||
) => {
|
||||
const firefoxViewMatch = sinon.match({
|
||||
id: "featureCalloutCheck",
|
||||
context: { source },
|
||||
});
|
||||
const sandbox = sinon.createSandbox();
|
||||
const sendTriggerStub = sandbox.stub(ASRouter, "sendTriggerMessage");
|
||||
sendTriggerStub.withArgs(firefoxViewMatch).resolves(testMessage);
|
||||
sendTriggerStub.callThrough();
|
||||
return sandbox;
|
||||
};
|
||||
|
||||
/**
|
||||
* A helper to check that correct telemetry was sent by AWSendEventTelemetry.
|
||||
* This is a wrapper around sinon's spy functionality.
|
||||
*
|
||||
* @example
|
||||
* let spy = new TelemetrySpy();
|
||||
* element.click();
|
||||
* spy.assertCalledWith({ event: "CLICK" });
|
||||
* spy.restore();
|
||||
*/
|
||||
class TelemetrySpy {
|
||||
/**
|
||||
* @param {object} [sandbox] A pre-existing sinon sandbox to build the spy in.
|
||||
* If not provided, a new sandbox will be created.
|
||||
*/
|
||||
constructor(sandbox = sinon.createSandbox()) {
|
||||
this.sandbox = sandbox;
|
||||
this.spy = this.sandbox
|
||||
.spy(AboutWelcomeParent.prototype, "onContentMessage")
|
||||
.withArgs("AWPage:TELEMETRY_EVENT");
|
||||
registerCleanupFunction(() => this.restore());
|
||||
}
|
||||
/**
|
||||
* Assert that AWSendEventTelemetry sent the expected telemetry object.
|
||||
*
|
||||
* @param {object} expectedData
|
||||
*/
|
||||
assertCalledWith(expectedData) {
|
||||
let match = this.spy.calledWith("AWPage:TELEMETRY_EVENT", expectedData);
|
||||
if (match) {
|
||||
ok(true, "Expected telemetry sent");
|
||||
} else if (this.spy.called) {
|
||||
ok(
|
||||
false,
|
||||
"Wrong telemetry sent: " + JSON.stringify(this.spy.lastCall.args)
|
||||
);
|
||||
} else {
|
||||
ok(false, "No telemetry sent");
|
||||
}
|
||||
}
|
||||
reset() {
|
||||
this.spy.resetHistory();
|
||||
}
|
||||
restore() {
|
||||
this.sandbox.restore();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to open and close a tab so the recently
|
||||
* closed tabs list can have data.
|
||||
|
||||
Reference in New Issue
Block a user