Bug 1951426 - Request messages from OMC in newtab r=home-newtab-reviewers,omc-reviewers,mconley,aminomancer,maxx

Differential Revision: https://phabricator.services.mozilla.com/D240175
This commit is contained in:
Nathan Barrett
2025-03-24 17:42:04 +00:00
parent 273a13f421
commit 84c498dc3b
21 changed files with 335 additions and 0 deletions

View File

@@ -50,6 +50,10 @@ export class AboutNewTabParent extends JSWindowActorParent {
id: "defaultBrowserCheck",
context: { source: "newtab" },
});
lazy.ASRouter.sendTriggerMessage({
browser: this.browsingContext.top.embedderElement,
id: "newtabMessageCheck",
});
break;
case "Init": {

View File

@@ -139,6 +139,10 @@ async function getMessageValidators(skipValidation) {
"./content-src/templates/OnboardingMessage/MenuMessage.schema.json",
{ common: true }
),
newtab_message: await getValidator(
"./content-src/templates/OnboardingMessage/NewtabMessage.schema.json",
{ common: true }
),
};
messageValidators.milestone_message = messageValidators.cfr_doorhanger;

View File

@@ -924,6 +924,41 @@
"targeting"
]
},
"NewtabMessage": {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "file:///NewtabMessage.schema.json",
"title": "NewtabMessage",
"description": "A template for messages that are rendered within newtab",
"allOf": [
{
"$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/Message"
}
],
"type": "object",
"properties": {
"content": {
"type": "object",
"properties": {
"messageType": {
"type": "string",
"description": "The subtype of the message."
}
},
"additionalProperties": true,
"required": [
"messageType"
]
},
"template": {
"type": "string",
"const": "newtab_message"
}
},
"additionalProperties": true,
"required": [
"targeting"
]
},
"Spotlight": {
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "file:///Spotlight.schema.json",
@@ -1278,6 +1313,7 @@
"infobar",
"menu_message",
"pb_newtab",
"newtab_message",
"spotlight",
"feature_callout",
"toast_notification",
@@ -1523,6 +1559,25 @@
"$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/NewtabPromoMessage"
}
},
{
"if": {
"type": "object",
"properties": {
"template": {
"type": "string",
"enum": [
"newtab_message"
]
}
},
"required": [
"template"
]
},
"then": {
"$ref": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json#/$defs/NewtabMessage"
}
},
{
"if": {
"type": "object",

View File

@@ -79,6 +79,9 @@ SCHEMAS = [
"NewtabPromoMessage": (
SCHEMA_DIR / "PBNewtab" / "NewtabPromoMessage.schema.json"
),
"NewtabMessage": (
SCHEMA_DIR / "OnboardingMessage" / "NewtabMessage.schema.json"
),
"Spotlight": SCHEMA_DIR / "OnboardingMessage" / "Spotlight.schema.json",
"ToastNotification": (
SCHEMA_DIR / "ToastNotification" / "ToastNotification.schema.json"

View File

@@ -0,0 +1,27 @@
{
"$schema": "https://json-schema.org/draft/2019-09/schema",
"$id": "file:///NewtabMessage.schema.json",
"title": "NewtabMessage",
"description": "A template for messages that are rendered within newtab",
"allOf": [{ "$ref": "file:///FxMSCommon.schema.json#/$defs/Message" }],
"type": "object",
"properties": {
"content": {
"type": "object",
"properties": {
"messageType": {
"type": "string",
"description": "The subtype of the message."
}
},
"additionalProperties": true,
"required": ["messageType"]
},
"template": {
"type": "string",
"const": "newtab_message"
}
},
"additionalProperties": true,
"required": ["targeting"]
}

View File

@@ -154,6 +154,7 @@ const MESSAGE_TYPE_LIST = [
"SPOTLIGHT_TELEMETRY",
"TOAST_NOTIFICATION_TELEMETRY",
"MENU_MESSAGE_TELEMETRY",
"NEWTAB_MESSAGE_TELEMETRY",
"AS_ROUTER_TELEMETRY_USER_EVENT",
// Admin types

View File

@@ -1477,6 +1477,16 @@ export class _ASRouter {
case "menu_message":
lazy.MenuMessage.showMenuMessage(browser, message, trigger, force);
break;
case "newtab_message": {
let targetBrowser = force ? null : browser;
let messageWithBrowser = {
targetBrowser,
message,
dispatch: this.dispatchCFRAction,
};
Services.obs.notifyObservers(messageWithBrowser, "newtab-message");
break;
}
}
return { message };

View File

@@ -31,6 +31,7 @@ export class ASRouterParentProcessMessageHandler {
case msg.DOORHANGER_TELEMETRY:
case msg.SPOTLIGHT_TELEMETRY:
case msg.MENU_MESSAGE_TELEMETRY:
case msg.NEWTAB_MESSAGE_TELEMETRY:
case msg.TOAST_NOTIFICATION_TELEMETRY: {
return this.handleTelemetry({ type, data });
}

View File

@@ -125,6 +125,9 @@ export class ASRouterTelemetry {
case "asrouter_undesired_event":
event = this.applyUndesiredEventPolicy(event);
break;
case "newtab_message_user_event":
event = await this.applyNewtabMessagePolicy(event);
break;
default:
event = { ping: event };
break;
@@ -215,6 +218,15 @@ export class ASRouterTelemetry {
return { ping, pingType: "moments" };
}
async applyNewtabMessagePolicy(ping) {
ping.client_id = await this.telemetryClientId;
ping.browser_session_id = lazy.browserSessionId;
ping.addon_version = Services.appinfo.appBuildID;
ping.locale = Services.locale.appLocaleAsBCP47;
delete ping.action;
return { ping, pingType: "newtab_message" };
}
applyUndesiredEventPolicy(ping) {
ping.impression_id = this._impressionId;
delete ping.action;
@@ -264,6 +276,8 @@ export class ASRouterTelemetry {
// Intentional fall-through
case msg.MENU_MESSAGE_TELEMETRY:
// Intentional fall-through
case msg.NEWTAB_MESSAGE_TELEMETRY:
// Intentional fall-through
case msg.AS_ROUTER_TELEMETRY_USER_EVENT:
this.handleASRouterUserEvent(action);
break;

View File

@@ -17,6 +17,7 @@ export const MESSAGE_TYPE_LIST = [
"SPOTLIGHT_TELEMETRY",
"TOAST_NOTIFICATION_TELEMETRY",
"MENU_MESSAGE_TELEMETRY",
"NEWTAB_MESSAGE_TELEMETRY",
"AS_ROUTER_TELEMETRY_USER_EVENT",
// Admin types

View File

@@ -1350,6 +1350,17 @@ const MESSAGES = () => [
},
testingTriggerContext: "app_menu",
},
{
id: "TEST_NEWTAB_MESSAGE",
template: "newtab_message",
content: {
messageType: "CustomWallpaperHighlight",
},
trigger: {
id: "newtabMessageCheck",
},
groups: [],
},
];
export const PanelTestProvider = {

View File

@@ -61,6 +61,7 @@ TESTING_JS_MODULES += [
"content-src/templates/CFR/templates/InfoBar.schema.json",
"content-src/templates/OnboardingMessage/BookmarksBarButton.schema.json",
"content-src/templates/OnboardingMessage/MenuMessage.schema.json",
"content-src/templates/OnboardingMessage/NewtabMessage.schema.json",
"content-src/templates/OnboardingMessage/Spotlight.schema.json",
"content-src/templates/OnboardingMessage/ToolbarBadgeMessage.schema.json",
"content-src/templates/OnboardingMessage/UpdateAction.schema.json",

View File

@@ -69,6 +69,10 @@ async function makeValidators() {
"resource://testing-common/MenuMessage.schema.json",
{ common: true }
),
newtab_message: await schemaValidatorFor(
"resource://testing-common/NewtabMessage.schema.json",
{ common: true }
),
pb_newtab: await schemaValidatorFor(
"resource://testing-common/NewtabPromoMessage.schema.json",
{ common: true }

View File

@@ -28,6 +28,7 @@ add_task(async function test_PanelTestProvider() {
toast_notification: 3,
bookmarks_bar_button: 1,
menu_message: 1,
newtab_message: 1,
};
const EXPECTED_TOTAL_MESSAGE_COUNT = Object.values(

View File

@@ -100,6 +100,11 @@ for (const type of [
"INIT",
"INLINE_SELECTION_CLICK",
"INLINE_SELECTION_IMPRESSION",
"MESSAGE_CLICK",
"MESSAGE_DISMISS",
"MESSAGE_IMPRESSION",
"MESSAGE_SET",
"MESSAGE_TOGGLE_VISIBILITY",
"NEW_TAB_INIT",
"NEW_TAB_INITIAL_STATE",
"NEW_TAB_LOAD",

View File

@@ -103,6 +103,15 @@ export const INITIAL_STATE = {
recentSavesEnabled: false,
showTopicSelection: false,
},
// Messages received from ASRouter to render in newtab
Messages: {
// messages received from ASRouter are initially visible
isHidden: false,
// portID for that tab that was sent the message
portID: "",
// READONLY Message data received from ASRouter
messageData: {},
},
Notifications: {
showNotifications: false,
toastCounter: 0,
@@ -534,6 +543,24 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
}
}
function Messages(prevState = INITIAL_STATE.Messages, action) {
switch (action.type) {
case at.MESSAGE_SET:
if (prevState.messageData.messageType) {
return prevState;
}
return {
...prevState,
messageData: action.data.message,
portID: action.data.portID || "",
};
case at.MESSAGE_TOGGLE_VISIBILITY:
return { ...prevState, isHidden: action.data };
default:
return prevState;
}
}
function Pocket(prevState = INITIAL_STATE.Pocket, action) {
switch (action.type) {
case at.POCKET_WAITING_FOR_SPOC:
@@ -979,6 +1006,7 @@ export const reducers = {
Prefs,
Dialog,
Sections,
Messages,
Notifications,
Pocket,
Personalization,

View File

@@ -780,6 +780,7 @@ export const Base = connect(state => ({
Prefs: state.Prefs,
Sections: state.Sections,
DiscoveryStream: state.DiscoveryStream,
Messages: state.Messages,
Notifications: state.Notifications,
Search: state.Search,
Wallpapers: state.Wallpapers,

View File

@@ -173,6 +173,11 @@ for (const type of [
"INIT",
"INLINE_SELECTION_CLICK",
"INLINE_SELECTION_IMPRESSION",
"MESSAGE_CLICK",
"MESSAGE_DISMISS",
"MESSAGE_IMPRESSION",
"MESSAGE_SET",
"MESSAGE_TOGGLE_VISIBILITY",
"NEW_TAB_INIT",
"NEW_TAB_INITIAL_STATE",
"NEW_TAB_LOAD",
@@ -6805,6 +6810,15 @@ const INITIAL_STATE = {
recentSavesEnabled: false,
showTopicSelection: false,
},
// Messages received from ASRouter to render in newtab
Messages: {
// messages received from ASRouter are initially visible
isHidden: false,
// portID for that tab that was sent the message
portID: "",
// READONLY Message data received from ASRouter
messageData: {},
},
Notifications: {
showNotifications: false,
toastCounter: 0,
@@ -7236,6 +7250,24 @@ function Sections(prevState = INITIAL_STATE.Sections, action) {
}
}
function Messages(prevState = INITIAL_STATE.Messages, action) {
switch (action.type) {
case actionTypes.MESSAGE_SET:
if (prevState.messageData.messageType) {
return prevState;
}
return {
...prevState,
messageData: action.data.message,
portID: action.data.portID || "",
};
case actionTypes.MESSAGE_TOGGLE_VISIBILITY:
return { ...prevState, isHidden: action.data };
default:
return prevState;
}
}
function Pocket(prevState = INITIAL_STATE.Pocket, action) {
switch (action.type) {
case actionTypes.POCKET_WAITING_FOR_SPOC:
@@ -7681,6 +7713,7 @@ const reducers = {
Prefs,
Dialog,
Sections,
Messages,
Notifications,
Pocket,
Personalization: Reducers_sys_Personalization,
@@ -13920,6 +13953,7 @@ const Base = (0,external_ReactRedux_namespaceObject.connect)(state => ({
Prefs: state.Prefs,
Sections: state.Sections,
DiscoveryStream: state.DiscoveryStream,
Messages: state.Messages,
Notifications: state.Notifications,
Search: state.Search,
Wallpapers: state.Wallpapers,

View File

@@ -24,6 +24,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
FaviconFeed: "resource://newtab/lib/FaviconFeed.sys.mjs",
HighlightsFeed: "resource://newtab/lib/HighlightsFeed.sys.mjs",
NewTabInit: "resource://newtab/lib/NewTabInit.sys.mjs",
NewTabMessaging: "resource://newtab/lib/NewTabMessaging.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
PrefsFeed: "resource://newtab/lib/PrefsFeed.sys.mjs",
PlacesFeed: "resource://newtab/lib/PlacesFeed.sys.mjs",
@@ -1218,6 +1219,12 @@ const FEEDS_DATA = [
title: "Handles fetching and caching ads data",
value: true,
},
{
name: "newtabmessaging",
factory: () => new lazy.NewTabMessaging(),
title: "Handles fetching and triggering ASRouter messages in newtab",
value: true,
},
];
const FEEDS_CONFIG = new Map();

View File

@@ -0,0 +1,122 @@
/* 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 https://mozilla.org/MPL/2.0/. */
import {
actionTypes as at,
actionCreators as ac,
} from "resource://newtab/common/Actions.mjs";
export class NewTabMessaging {
constructor() {
this.initialized = false;
this.ASRouterDispatch = null;
}
init() {
if (!this.initialized) {
Services.obs.addObserver(this, "newtab-message");
this.initialized = true;
}
}
uninit() {
Services.obs.removeObserver(this, "newtab-message");
}
observe(subject, topic, _data) {
if (topic === "newtab-message") {
let { targetBrowser, message, dispatch } = subject.wrappedJSObject;
this.ASRouterDispatch = dispatch;
this.showMessage(targetBrowser, message);
}
}
async showMessage(targetBrowser, message) {
if (targetBrowser) {
let actor =
targetBrowser.browsingContext.currentWindowGlobal.getActor(
"AboutNewTab"
);
if (actor) {
let tabDetails = actor.getTabDetails();
if (tabDetails) {
// Only send the message for the tab that triggered the message
this.store.dispatch(
ac.OnlyToOneContent(
{
type: at.MESSAGE_SET,
// send along portID as well so that the child process can easily just update the single tab
data: {
message,
portID: tabDetails.portID,
},
},
tabDetails.portID
)
);
}
}
} else {
// if targetBrowser is null, send to the preloaded tab / main process
// This should only run if message is triggered from asrouter during dev
this.store.dispatch(
ac.AlsoToPreloaded({
type: at.MESSAGE_SET,
data: {
message,
},
})
);
}
}
/**
* Send impression to ASRouter
* @param {Object} message
*/
handleImpression(message) {
this.sendTelemetry("IMPRESSION", message);
this.ASRouterDispatch?.({
type: "IMPRESSION",
data: message,
});
}
/**
* Sends telemetry data through ASRouter to
* match pattern with ASRouterTelemetry
*/
sendTelemetry(event, message, source = "newtab") {
const data = {
action: "newtab_message_user_event",
event,
event_context: { source, page: "about:newtab" },
message_id: message.id,
};
this.ASRouterDispatch?.({
type: "NEWTAB_MESSAGE_TELEMETRY",
data,
});
}
async onAction(action) {
switch (action.type) {
case at.INIT:
this.init();
break;
case at.UNINIT:
this.uninit();
break;
case at.MESSAGE_IMPRESSION:
this.handleImpression(action.data);
break;
case at.MESSAGE_DISMISS:
this.sendTelemetry("DISMISS", action.data.message);
break;
case at.MESSAGE_CLICK:
this.sendTelemetry("CLICK", action.data.source, action.data.message);
break;
}
}
}

View File

@@ -169,6 +169,7 @@ Triggers and actions
.. _whats_new_schema: https://searchfox.org/mozilla-central/source/browser/components/asrouter/content-src/templates/OnboardingMessage/WhatsNewMessage.schema.json
.. _protections_panel_schema: https://searchfox.org/mozilla-central/source/browser/components/asrouter/content-src/templates/OnboardingMessage/ProtectionsPanelMessage.schema.json
.. _pbnewtab_promo_schema: https://searchfox.org/mozilla-central/source/browser/components/asrouter/content-src/templates/PBNewtab/NewtabPromoMessage.schema.json
.. _newtab_message_schema: https://searchfox.org/mozilla-central/source/browser/components/asrouter/content-src/templates/OnboardingMessage/NewtabMessage.schema.json
.. _messaging_experiments_schema: https://searchfox.org/mozilla-central/source/browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json
.. _common_schema: https://searchfox.org/mozilla-central/source/browser/components/asrouter/content-src/schemas/FxMSCommon.schema.json