Skip to content

Commit 466b1d2

Browse files
committed
Bug 1786647 - Split up Feature Callout messages and fetch from ASRouter on page load, focus, and screen advance r=Mardak
Differential Revision: https://phabricator.services.mozilla.com/D155524
1 parent 353baa4 commit 466b1d2

File tree

8 files changed

+487
-202
lines changed

8 files changed

+487
-202
lines changed

browser/components/firefoxview/featureCallout.mjs

Lines changed: 26 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,13 @@ const lazy = {};
1212

1313
XPCOMUtils.defineLazyModuleGetters(lazy, {
1414
AboutWelcomeParent: "resource:///actors/AboutWelcomeParent.jsm",
15+
ASRouter: "resource://activity-stream/lib/ASRouter.jsm",
1516
});
1617

18+
// When expanding the use of Feature Callout
19+
// to new about: pages, make `progressPref` a
20+
// configurable field on callout messages and
21+
// use it to determine which pref to observe
1722
XPCOMUtils.defineLazyPreferenceGetter(
1823
lazy,
1924
"featureTourProgress",
@@ -36,7 +41,7 @@ async function _handlePrefChange() {
3641
container?.classList.add("hidden");
3742
// wait for fade out transition
3843
setTimeout(async () => {
39-
_loadConfig(lazy.featureTourProgress.message);
44+
await _loadConfig();
4045
container?.remove();
4146
await _renderCallout();
4247
}, TRANSITION_MS);
@@ -78,170 +83,6 @@ let READY = false;
7883

7984
const TRANSITION_MS = 500;
8085
const CONTAINER_ID = "root";
81-
const MESSAGES = [
82-
{
83-
id: "FIREFOX_VIEW_FEATURE_TOUR",
84-
template: "multistage",
85-
backdrop: "transparent",
86-
transitions: false,
87-
disableHistoryUpdates: true,
88-
screens: [
89-
{
90-
id: "FEATURE_CALLOUT_1",
91-
parent_selector: "#tabpickup-steps",
92-
content: {
93-
position: "callout",
94-
arrow_position: "top",
95-
title: {
96-
string_id: "callout-firefox-view-tab-pickup-title",
97-
},
98-
subtitle: {
99-
string_id: "callout-firefox-view-tab-pickup-subtitle",
100-
},
101-
logo: {
102-
imageURL: "chrome://browser/content/callout-tab-pickup.svg",
103-
darkModeImageURL:
104-
"chrome://browser/content/callout-tab-pickup-dark.svg",
105-
height: "128px",
106-
},
107-
primary_button: {
108-
label: {
109-
string_id: "callout-primary-advance-button-label",
110-
},
111-
action: {
112-
type: "SET_PREF",
113-
data: {
114-
pref: {
115-
name: "browser.firefox-view.feature-tour",
116-
value: JSON.stringify({
117-
message: "FIREFOX_VIEW_FEATURE_TOUR",
118-
screen: "FEATURE_CALLOUT_2",
119-
complete: false,
120-
}),
121-
},
122-
},
123-
},
124-
},
125-
dismiss_button: {
126-
action: {
127-
type: "SET_PREF",
128-
data: {
129-
pref: {
130-
name: "browser.firefox-view.feature-tour",
131-
value: JSON.stringify({
132-
message: "FIREFOX_VIEW_FEATURE_TOUR",
133-
screen: "FEATURE_CALLOUT_1",
134-
complete: true,
135-
}),
136-
},
137-
},
138-
},
139-
},
140-
},
141-
},
142-
{
143-
id: "FEATURE_CALLOUT_2",
144-
parent_selector: "#recently-closed-tabs-container",
145-
content: {
146-
position: "callout",
147-
arrow_position: "bottom",
148-
title: {
149-
string_id: "callout-firefox-view-recently-closed-title",
150-
},
151-
subtitle: {
152-
string_id: "callout-firefox-view-recently-closed-subtitle",
153-
},
154-
primary_button: {
155-
label: {
156-
string_id: "callout-primary-advance-button-label",
157-
},
158-
action: {
159-
type: "SET_PREF",
160-
data: {
161-
pref: {
162-
name: "browser.firefox-view.feature-tour",
163-
value: JSON.stringify({
164-
message: "FIREFOX_VIEW_FEATURE_TOUR",
165-
screen: "FEATURE_CALLOUT_3",
166-
complete: false,
167-
}),
168-
},
169-
},
170-
},
171-
},
172-
dismiss_button: {
173-
action: {
174-
type: "SET_PREF",
175-
data: {
176-
pref: {
177-
name: "browser.firefox-view.feature-tour",
178-
value: JSON.stringify({
179-
message: "FIREFOX_VIEW_FEATURE_TOUR",
180-
screen: "FEATURE_CALLOUT_2",
181-
complete: true,
182-
}),
183-
},
184-
},
185-
},
186-
},
187-
},
188-
},
189-
{
190-
id: "FEATURE_CALLOUT_3",
191-
parent_selector: "#colorways.content-container",
192-
content: {
193-
position: "callout",
194-
arrow_position: "end",
195-
title: {
196-
string_id: "callout-firefox-view-colorways-title",
197-
},
198-
subtitle: {
199-
string_id: "callout-firefox-view-colorways-subtitle",
200-
},
201-
logo: {
202-
imageURL: "chrome://browser/content/callout-colorways.svg",
203-
darkModeImageURL:
204-
"chrome://browser/content/callout-colorways-dark.svg",
205-
height: "128px",
206-
},
207-
primary_button: {
208-
label: {
209-
string_id: "callout-primary-complete-button-label",
210-
},
211-
action: {
212-
type: "SET_PREF",
213-
data: {
214-
pref: {
215-
name: "browser.firefox-view.feature-tour",
216-
value: JSON.stringify({
217-
message: "FIREFOX_VIEW_FEATURE_TOUR",
218-
screen: "",
219-
complete: true,
220-
}),
221-
},
222-
},
223-
},
224-
},
225-
dismiss_button: {
226-
action: {
227-
type: "SET_PREF",
228-
data: {
229-
pref: {
230-
name: "browser.firefox-view.feature-tour",
231-
value: JSON.stringify({
232-
message: "FIREFOX_VIEW_FEATURE_TOUR",
233-
screen: "FEATURE_CALLOUT_3",
234-
complete: true,
235-
}),
236-
},
237-
},
238-
},
239-
},
240-
},
241-
},
242-
],
243-
},
244-
];
24586

24687
function _createContainer() {
24788
let container = document.createElement("div");
@@ -253,6 +94,10 @@ function _createContainer() {
25394
);
25495
container.id = CONTAINER_ID;
25596
let parent = document.querySelector(CURRENT_SCREEN?.parent_selector);
97+
if (!parent) {
98+
container.remove();
99+
return false;
100+
}
256101
container.setAttribute("aria-describedby", `#${CONTAINER_ID} .welcome-text`);
257102
container.tabIndex = 0;
258103
parent.insertAdjacentElement("afterend", container);
@@ -504,38 +349,24 @@ function _observeRender(container) {
504349
RENDER_OBSERVER?.observe(container, { childList: true });
505350
}
506351

507-
function _loadConfig(messageId) {
508-
// If the parent element a screen describes doesn't exist, remove screen
509-
// and ensure last screen displays the final primary CTA
510-
// (for example, when there are no active colorways in about:firefoxview)
511-
function _getRelevantScreens(screens) {
512-
const finalCTA = screens[screens.length - 1].content.primary_button;
513-
screens = screens.filter((s, i) => {
514-
return document.querySelector(s.parent_selector);
515-
});
516-
if (screens.length) {
517-
screens[screens.length - 1].content.primary_button = finalCTA;
518-
}
519-
return screens;
520-
}
521-
522-
let content = MESSAGES.find(m => m.id === messageId);
523-
const screenId = lazy.featureTourProgress.screen;
524-
let screenIndex;
525-
if (content?.screens?.length && screenId) {
526-
content.screens = _getRelevantScreens(content.screens);
527-
screenIndex = content.screens.findIndex(s => s.id === screenId);
528-
content.startScreen = screenIndex;
529-
}
530-
CURRENT_SCREEN = content?.screens?.[screenIndex || 0];
531-
CONFIG = content;
352+
async function _loadConfig() {
353+
await lazy.ASRouter.waitForInitialized;
354+
let result = await lazy.ASRouter.sendTriggerMessage({
355+
// triggerId and triggerContext
356+
id: "featureCalloutCheck",
357+
context: { source: document.location.pathname.toLowerCase() },
358+
});
359+
CONFIG = result.message.content;
360+
CURRENT_SCREEN = CONFIG?.screens?.[CONFIG?.startScreen || 0];
532361
}
533362

534363
async function _renderCallout() {
535364
let container = _createContainer();
536-
// This results in rendering the Feature Callout
537-
await _addScriptsAndRender(container);
538-
_observeRender(container);
365+
if (container) {
366+
// This results in rendering the Feature Callout
367+
await _addScriptsAndRender(container);
368+
_observeRender(container);
369+
}
539370
}
540371
/**
541372
* Render content based on about:welcome multistage template.
@@ -546,9 +377,9 @@ async function showFeatureCallout(messageId) {
546377
return;
547378
}
548379

549-
_loadConfig(messageId);
380+
await _loadConfig();
550381

551-
if (!CONFIG) {
382+
if (!CONFIG?.screens?.length) {
552383
return;
553384
}
554385

browser/components/firefoxview/tests/browser/browser_feature_callout.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
/* Any copyright is dedicated to the Public Domain.
22
* http://creativecommons.org/publicdomain/zero/1.0/ */
3+
4+
"use strict";
5+
6+
const { BuiltInThemes } = ChromeUtils.import(
7+
"resource:///modules/BuiltInThemes.jsm"
8+
);
9+
310
const calloutId = "root";
411
const calloutSelector = `#${calloutId}.featureCallout`;
512
const primaryButtonSelector = `#${calloutId} .primary`;
@@ -327,6 +334,10 @@ add_task(async function feature_callout_only_highlights_existing_elements() {
327334
],
328335
});
329336

337+
const sandbox = sinon.createSandbox();
338+
// Return no active colorways
339+
sandbox.stub(BuiltInThemes, "findActiveColorwayCollection").returns(false);
340+
330341
await BrowserTestUtils.withNewTab(
331342
{
332343
gBrowser,
@@ -335,9 +346,6 @@ add_task(async function feature_callout_only_highlights_existing_elements() {
335346
async browser => {
336347
const { document } = browser.contentWindow;
337348
await waitForCalloutScreen(document, ".FEATURE_CALLOUT_1");
338-
339-
// Remove parent element for third screen in tour
340-
document.querySelector("#colorways.content-container").remove();
341349
// Advance to second screen
342350
await clickPrimaryButton(document);
343351
await waitForCalloutScreen(document, ".FEATURE_CALLOUT_2");
@@ -357,6 +365,8 @@ add_task(async function feature_callout_only_highlights_existing_elements() {
357365
!document.querySelector(`${calloutSelector}:not(.hidden)`),
358366
"Feature Callout screen does not render if its parent element does not exist"
359367
);
368+
369+
sandbox.restore();
360370
}
361371
);
362372
});

browser/components/newtab/content-src/asrouter/schemas/MessagingExperiment.schema.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,25 @@
129129
"$ref": "resource://activity-stream/schemas/MessagingExperiment.schema.json#/$defs/Spotlight"
130130
}
131131
},
132+
{
133+
"if": {
134+
"type": "object",
135+
"properties": {
136+
"template": {
137+
"type": "string",
138+
"enum": [
139+
"feature_callout"
140+
]
141+
}
142+
},
143+
"required": [
144+
"template"
145+
]
146+
},
147+
"then": {
148+
"$ref": "resource://activity-stream/schemas/MessagingExperiment.schema.json#/$defs/Spotlight"
149+
}
150+
},
132151
{
133152
"if": {
134153
"type": "object",
@@ -1258,7 +1277,7 @@
12581277
},
12591278
"template": {
12601279
"type": "string",
1261-
"const": "spotlight"
1280+
"enum": ["spotlight", "feature_callout"]
12621281
}
12631282
},
12641283
"additionalProperties": true,
@@ -1576,6 +1595,7 @@
15761595
"pb_newtab",
15771596
"protections_panel",
15781597
"spotlight",
1598+
"feature_callout",
15791599
"toast_notification",
15801600
"toolbar_badge",
15811601
"update_action",

browser/components/newtab/content-src/asrouter/templates/OnboardingMessage/Spotlight.schema.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@
161161
},
162162
"template": {
163163
"type": "string",
164-
"const": "spotlight"
164+
"description": "Specify whether the surface is shown as a Spotlight modal or an in-surface Feature Callout dialog",
165+
"enum": ["spotlight", "feature_callout"]
165166
}
166167
},
167168
"additionalProperties": true,

0 commit comments

Comments
 (0)