diff options
Diffstat (limited to '')
-rw-r--r-- | browser/components/newtab/lib/FeatureCalloutMessages.sys.mjs | 648 |
1 files changed, 648 insertions, 0 deletions
diff --git a/browser/components/newtab/lib/FeatureCalloutMessages.sys.mjs b/browser/components/newtab/lib/FeatureCalloutMessages.sys.mjs new file mode 100644 index 0000000000..3dbba0c212 --- /dev/null +++ b/browser/components/newtab/lib/FeatureCalloutMessages.sys.mjs @@ -0,0 +1,648 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Eventually, make this a messaging system +// provider instead of adding these message +// into OnboardingMessageProvider.jsm +const FIREFOX_VIEW_PREF = "browser.firefox-view.feature-tour"; +const PDFJS_PREF = "browser.pdfjs.feature-tour"; +// Empty screens are included as placeholders to ensure step +// indicator shows the correct number of total steps in the tour +const EMPTY_SCREEN = { content: {} }; +const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000; + +// Generate a JEXL targeting string based on the current screen +// id found in a given Feature Callout tour progress preference +// and the `complete` property being true +const matchCurrentScreenTargeting = (prefName, screenId) => { + const prefVal = `'${prefName}' | preferenceValue`; + //regExpMatch() is a JEXL filter expression. Here we check if 'screen' and 'complete' exist in the pref's value (which is stringified JSON), and return their values. Returns null otherwise + const screenRegEx = '(?<=screen":)"(.*)(?=",)'; + const completeRegEx = '(?<=complete":)(.*)(?=})'; + + const screenMatch = `${prefVal} | regExpMatch('${screenRegEx}')`; + const completeMatch = `${prefVal} | regExpMatch('${completeRegEx}')`; + //We are checking the return of regExpMatch() here. If it's truthy, we grab the matched string and compare it to the desired value + const screenVal = `(${screenMatch}) ? (${screenMatch}[1] == '${screenId}') : false`; + const completeVal = `(${completeMatch}) ? (${completeMatch}[1] != "true") : false`; + + return `(${screenVal}) && (${completeVal})`; +}; + +/** + * add24HourImpressionJEXLTargeting - + * Creates a "hasn't been viewed in > 24 hours" + * JEXL string and adds it to each message specified + * + * @param {array} messageIds - IDs of messages that the targeting string will be added to + * @param {string} prefix - The prefix of messageIDs that will used to create the JEXL string + * @param {array} messages - The array of messages that will be edited + * @returns {array} - The array of messages with the appropriate targeting strings edited + */ +function add24HourImpressionJEXLTargeting( + messageIds, + prefix, + uneditedMessages +) { + let noImpressionsIn24HoursString = uneditedMessages + .filter(message => message.id.startsWith(prefix)) + .map( + message => + // If the last impression is null or if epoch time + // of the impression is < current time - 24hours worth of MS + `(messageImpressions.${message.id}[messageImpressions.${ + message.id + } | length - 1] == null || messageImpressions.${ + message.id + }[messageImpressions.${message.id} | length - 1] < ${ + Date.now() - ONE_DAY_IN_MS + })` + ) + .join(" && "); + + // We're appending the string here instead of using + // template strings to avoid a recursion error from + // using the 'messages' variable within itself + return uneditedMessages.map(message => { + if (messageIds.includes(message.id)) { + message.targeting += `&& ${noImpressionsIn24HoursString}`; + } + + return message; + }); +} + +// Exporting the about:firefoxview messages as a method here +// acts as a safety guard against mutations of the original objects +const MESSAGES = () => { + let messages = [ + { + id: "FIREFOX_VIEW_SPOTLIGHT", + template: "spotlight", + content: { + id: "FIREFOX_VIEW_PROMO", + template: "multistage", + modal: "tab", + screens: [ + { + id: "DEFAULT_MODAL_UI", + content: { + title: { + fontSize: "32px", + fontWeight: 400, + string_id: "firefoxview-spotlight-promo-title", + }, + subtitle: { + fontSize: "15px", + fontWeight: 400, + marginBlock: "10px", + marginInline: "40px", + string_id: "firefoxview-spotlight-promo-subtitle", + }, + logo: { height: "48px" }, + primary_button: { + label: { + string_id: "firefoxview-spotlight-promo-primarybutton", + }, + action: { + type: "SET_PREF", + data: { + pref: { + name: FIREFOX_VIEW_PREF, + value: JSON.stringify({ + screen: "FEATURE_CALLOUT_1", + complete: false, + }), + }, + }, + navigate: true, + }, + }, + secondary_button: { + label: { + string_id: "firefoxview-spotlight-promo-secondarybutton", + }, + action: { + type: "SET_PREF", + data: { + pref: { + name: FIREFOX_VIEW_PREF, + value: JSON.stringify({ + screen: "", + complete: true, + }), + }, + }, + navigate: true, + }, + }, + }, + }, + ], + }, + priority: 3, + trigger: { + id: "featureCalloutCheck", + }, + frequency: { + // Add the highest possible cap to ensure impressions are recorded while allowing the Spotlight to sync across windows/tabs with Firefox View open + lifetime: 100, + }, + targeting: `!inMr2022Holdback && source == "about:firefoxview" && + !'browser.newtabpage.activity-stream.asrouter.providers.cfr'|preferenceIsUserSet && + 'browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features'|preferenceValue && + ${matchCurrentScreenTargeting( + FIREFOX_VIEW_PREF, + "FIREFOX_VIEW_SPOTLIGHT" + )}`, + }, + { + id: "FIREFOX_VIEW_FEATURE_TOUR_1_NO_CWS", + template: "feature_callout", + content: { + id: "FIREFOX_VIEW_FEATURE_TOUR", + template: "multistage", + backdrop: "transparent", + transitions: false, + disableHistoryUpdates: true, + screens: [ + { + id: "FEATURE_CALLOUT_1", + parent_selector: "#tab-pickup-container", + content: { + position: "callout", + arrow_position: "top", + title: { + string_id: "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", + }, + primary_button: { + label: { + string_id: "callout-primary-advance-button-label", + }, + action: { + type: "SET_PREF", + data: { + pref: { + name: FIREFOX_VIEW_PREF, + value: JSON.stringify({ + screen: "FEATURE_CALLOUT_2", + complete: false, + }), + }, + }, + }, + }, + dismiss_button: { + action: { + type: "SET_PREF", + data: { + pref: { + name: FIREFOX_VIEW_PREF, + value: JSON.stringify({ + screen: "", + complete: true, + }), + }, + }, + }, + }, + }, + }, + EMPTY_SCREEN, + ], + }, + priority: 3, + targeting: `!inMr2022Holdback && source == "about:firefoxview" && ${matchCurrentScreenTargeting( + FIREFOX_VIEW_PREF, + "FEATURE_CALLOUT_1" + )}`, + trigger: { id: "featureCalloutCheck" }, + }, + { + id: "FIREFOX_VIEW_FEATURE_TOUR_2_NO_CWS", + template: "feature_callout", + content: { + id: "FIREFOX_VIEW_FEATURE_TOUR", + startScreen: 1, + template: "multistage", + backdrop: "transparent", + transitions: false, + disableHistoryUpdates: true, + screens: [ + EMPTY_SCREEN, + { + id: "FEATURE_CALLOUT_2", + parent_selector: "#recently-closed-tabs-container", + content: { + position: "callout", + arrow_position: "bottom", + title: { + string_id: "callout-firefox-view-recently-closed-title", + }, + subtitle: { + string_id: "callout-firefox-view-recently-closed-subtitle", + }, + primary_button: { + label: { + string_id: "callout-primary-complete-button-label", + }, + action: { + type: "SET_PREF", + data: { + pref: { + name: FIREFOX_VIEW_PREF, + value: JSON.stringify({ + screen: "", + complete: true, + }), + }, + }, + }, + }, + dismiss_button: { + action: { + type: "SET_PREF", + data: { + pref: { + name: FIREFOX_VIEW_PREF, + value: JSON.stringify({ + screen: "", + complete: true, + }), + }, + }, + }, + }, + }, + }, + ], + }, + priority: 3, + targeting: `!inMr2022Holdback && source == "about:firefoxview" && ${matchCurrentScreenTargeting( + FIREFOX_VIEW_PREF, + "FEATURE_CALLOUT_2" + )}`, + trigger: { id: "featureCalloutCheck" }, + }, + { + id: "FIREFOX_VIEW_TAB_PICKUP_REMINDER", + template: "feature_callout", + content: { + id: "FIREFOX_VIEW_TAB_PICKUP_REMINDER", + template: "multistage", + backdrop: "transparent", + transitions: false, + disableHistoryUpdates: true, + screens: [ + { + id: "FIREFOX_VIEW_TAB_PICKUP_REMINDER", + parent_selector: "#tab-pickup-container", + content: { + position: "callout", + arrow_position: "top", + title: { + string_id: + "continuous-onboarding-firefox-view-tab-pickup-title", + }, + subtitle: { + string_id: + "continuous-onboarding-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", + }, + primary_button: { + label: { + string_id: "mr1-onboarding-get-started-primary-button-label", + }, + action: { + type: "CLICK_ELEMENT", + navigate: true, + data: { + selector: + "#tab-pickup-container button.primary:not(#error-state-button)", + }, + }, + }, + dismiss_button: { + action: { + navigate: true, + }, + }, + }, + }, + ], + }, + priority: 2, + targeting: `!inMr2022Holdback && source == "about:firefoxview" && "browser.firefox-view.view-count" | preferenceValue > 2 + && (("identity.fxaccounts.enabled" | preferenceValue == false) || !(("services.sync.engine.tabs" | preferenceValue == true) && ("services.sync.username" | preferenceValue))) && (!messageImpressions.FIREFOX_VIEW_SPOTLIGHT[messageImpressions.FIREFOX_VIEW_SPOTLIGHT | length - 1] || messageImpressions.FIREFOX_VIEW_SPOTLIGHT[messageImpressions.FIREFOX_VIEW_SPOTLIGHT | length - 1] < currentDate|date - ${ONE_DAY_IN_MS})`, + frequency: { + lifetime: 1, + }, + trigger: { id: "featureCalloutCheck" }, + }, + { + id: "PDFJS_FEATURE_TOUR_1_A", + template: "feature_callout", + content: { + id: "PDFJS_FEATURE_TOUR", + template: "multistage", + backdrop: "transparent", + transitions: false, + disableHistoryUpdates: true, + screens: [ + { + id: "FEATURE_CALLOUT_1_A", + parent_selector: "hbox#browser", + content: { + position: "callout", + callout_position_override: { + top: "45px", + right: "55px", + }, + arrow_position: "top-end", + title: { + string_id: "callout-pdfjs-edit-title", + }, + subtitle: { + string_id: "callout-pdfjs-edit-body-a", + }, + primary_button: { + label: { + string_id: "callout-pdfjs-edit-button", + }, + action: { + type: "SET_PREF", + data: { + pref: { + name: PDFJS_PREF, + value: JSON.stringify({ + screen: "FEATURE_CALLOUT_2_A", + complete: false, + }), + }, + }, + }, + }, + dismiss_button: { + action: { + type: "SET_PREF", + data: { + pref: { + name: PDFJS_PREF, + value: JSON.stringify({ + screen: "", + complete: true, + }), + }, + }, + }, + }, + }, + }, + EMPTY_SCREEN, + ], + }, + priority: 1, + targeting: `source == "chrome" && ${matchCurrentScreenTargeting( + PDFJS_PREF, + "FEATURE_CALLOUT_1_A" + )}`, + trigger: { id: "featureCalloutCheck" }, + }, + { + id: "PDFJS_FEATURE_TOUR_2_A", + template: "feature_callout", + content: { + id: "PDFJS_FEATURE_TOUR", + startScreen: 1, + template: "multistage", + backdrop: "transparent", + transitions: false, + disableHistoryUpdates: true, + screens: [ + EMPTY_SCREEN, + { + id: "FEATURE_CALLOUT_2_A", + parent_selector: "hbox#browser", + content: { + position: "callout", + callout_position_override: { + top: "45px", + right: "25px", + }, + arrow_position: "top-end", + title: { + string_id: "callout-pdfjs-draw-title", + }, + subtitle: { + string_id: "callout-pdfjs-draw-body-a", + }, + primary_button: { + label: { + string_id: "callout-pdfjs-draw-button", + }, + action: { + type: "SET_PREF", + data: { + pref: { + name: PDFJS_PREF, + value: JSON.stringify({ + screen: "", + complete: true, + }), + }, + }, + }, + }, + dismiss_button: { + action: { + type: "SET_PREF", + data: { + pref: { + name: PDFJS_PREF, + value: JSON.stringify({ + screen: "", + complete: true, + }), + }, + }, + }, + }, + }, + }, + ], + }, + priority: 1, + targeting: `source == "chrome" && ${matchCurrentScreenTargeting( + PDFJS_PREF, + "FEATURE_CALLOUT_2_A" + )}`, + trigger: { id: "featureCalloutCheck" }, + }, + { + id: "PDFJS_FEATURE_TOUR_1_B", + template: "feature_callout", + content: { + id: "PDFJS_FEATURE_TOUR", + template: "multistage", + backdrop: "transparent", + transitions: false, + disableHistoryUpdates: true, + screens: [ + { + id: "FEATURE_CALLOUT_1_B", + parent_selector: "hbox#browser", + content: { + position: "callout", + callout_position_override: { + top: "45px", + right: "55px", + }, + arrow_position: "top-end", + title: { + string_id: "callout-pdfjs-edit-title", + }, + subtitle: { + string_id: "callout-pdfjs-edit-body-b", + }, + primary_button: { + label: { + string_id: "callout-pdfjs-edit-button", + }, + action: { + type: "SET_PREF", + data: { + pref: { + name: PDFJS_PREF, + value: JSON.stringify({ + screen: "FEATURE_CALLOUT_2_B", + complete: false, + }), + }, + }, + }, + }, + dismiss_button: { + action: { + type: "SET_PREF", + data: { + pref: { + name: PDFJS_PREF, + value: JSON.stringify({ + screen: "", + complete: true, + }), + }, + }, + }, + }, + }, + }, + EMPTY_SCREEN, + ], + }, + priority: 1, + targeting: `source == "chrome" && ${matchCurrentScreenTargeting( + PDFJS_PREF, + "FEATURE_CALLOUT_1_B" + )}`, + trigger: { id: "featureCalloutCheck" }, + }, + { + id: "PDFJS_FEATURE_TOUR_2_B", + template: "feature_callout", + content: { + id: "PDFJS_FEATURE_TOUR", + startScreen: 1, + template: "multistage", + backdrop: "transparent", + transitions: false, + disableHistoryUpdates: true, + screens: [ + EMPTY_SCREEN, + { + id: "FEATURE_CALLOUT_2_B", + parent_selector: "hbox#browser", + content: { + position: "callout", + callout_position_override: { + top: "45px", + right: "25px", + }, + arrow_position: "top-end", + title: { + string_id: "callout-pdfjs-draw-title", + }, + subtitle: { + string_id: "callout-pdfjs-draw-body-b", + }, + primary_button: { + label: { + string_id: "callout-pdfjs-draw-button", + }, + action: { + type: "SET_PREF", + data: { + pref: { + name: PDFJS_PREF, + value: JSON.stringify({ + screen: "", + complete: true, + }), + }, + }, + }, + }, + dismiss_button: { + action: { + type: "SET_PREF", + data: { + pref: { + name: PDFJS_PREF, + value: JSON.stringify({ + screen: "", + complete: true, + }), + }, + }, + }, + }, + }, + }, + ], + }, + priority: 1, + targeting: `source == "chrome" && ${matchCurrentScreenTargeting( + PDFJS_PREF, + "FEATURE_CALLOUT_2_B" + )}`, + trigger: { id: "featureCalloutCheck" }, + }, + ]; + messages = add24HourImpressionJEXLTargeting( + ["FIREFOX_VIEW_TAB_PICKUP_REMINDER"], + "FIREFOX_VIEW", + messages + ); + return messages; +}; + +export const FeatureCalloutMessages = { + getMessages() { + return MESSAGES(); + }, +}; |