Bug 1614465 - Replacing ASRouter calls to MessageChannel with JSWindowActors to eliminate ASRouterFeed r=k88hudson

Patch by Bernard Igiri <bigiri@mozilla.com>

Replacing async events with async method calls that use JSWindowActors to communicate with the parent process.
This will simplify these calls, bring the relevant code into local scope, and eliminate the need for MessageChannel.
Eliminating the MessageChannel dependency allows us to move the ASRouter initialization out of ASRouterFeed and into
JSWindowActors.

Differential Revision: https://phabricator.services.mozilla.com/D71796
This commit is contained in:
Bernard Igiri 2020-10-21 20:04:13 +00:00
Родитель 1025d32cc8
Коммит 24f9294ade
75 изменённых файлов: 3488 добавлений и 3634 удалений

Просмотреть файл

@ -16,7 +16,16 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"ASRouterDefaultConfig",
"resource://activity-stream/lib/ASRouterDefaultConfig.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"ASRouterNewTabHook",
"resource://activity-stream/lib/ASRouterNewTabHook.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"ActorManagerParent",
@ -617,6 +626,22 @@ let JSWINDOWACTORS = {
matches: ["about:studies"],
},
ASRouter: {
parent: {
moduleURI: "resource:///actors/ASRouterParent.jsm",
},
child: {
moduleURI: "resource:///actors/ASRouterChild.jsm",
events: {
// This is added so the actor instantiates immediately and makes
// methods available to the page js on load.
DOMWindowCreated: {},
},
},
matches: ["about:home*", "about:newtab*", "about:welcome*"],
remoteTypes: ["privilegedabout"],
},
SwitchDocumentDirection: {
child: {
moduleURI: "resource:///actors/SwitchDocumentDirectionChild.jsm",
@ -2142,6 +2167,7 @@ BrowserGlue.prototype = {
Normandy.uninit();
RFPHelper.uninit();
ASRouterNewTabHook.destroy();
},
// Set up a listener to enable/disable the screenshots extension
@ -2662,6 +2688,12 @@ BrowserGlue.prototype = {
},
},
{
task: () => {
ASRouterNewTabHook.createInstance(ASRouterDefaultConfig());
},
},
// Marionette needs to be initialized as very last step
{
task: () => {

Просмотреть файл

@ -0,0 +1,120 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["ASRouterChild"];
const { MESSAGE_TYPE_LIST, MESSAGE_TYPE_HASH: msg } = ChromeUtils.import(
"resource://activity-stream/common/ActorConstants.jsm"
);
const { ASRouterTelemetry } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouterTelemetry.jsm"
);
const VALID_TYPES = new Set(MESSAGE_TYPE_LIST);
class ASRouterChild extends JSWindowActorChild {
constructor() {
super();
this.observers = new Set();
this.telemetry = new ASRouterTelemetry({ isParentProcess: false });
}
didDestroy() {
this.observers.clear();
}
handleEvent(event) {
switch (event.type) {
case "DOMWindowCreated": {
const window = this.contentWindow;
Cu.exportFunction(this.asRouterMessage.bind(this), window, {
defineAs: "ASRouterMessage",
});
Cu.exportFunction(this.addParentListener.bind(this), window, {
defineAs: "ASRouterAddParentListener",
});
Cu.exportFunction(this.removeParentListener.bind(this), window, {
defineAs: "ASRouterRemoveParentListener",
});
break;
}
}
}
addParentListener(listener) {
this.observers.add(listener);
}
removeParentListener(listener) {
this.observers.delete(listener);
}
receiveMessage({ name, data }) {
switch (name) {
case "UpdateAdminState":
case "ClearMessages": {
this.observers.forEach(listener => {
let result = Cu.cloneInto(
{
type: name,
data,
},
this.contentWindow
);
listener(result);
});
break;
}
}
}
wrapPromise(promise) {
return new this.contentWindow.Promise((resolve, reject) =>
promise.then(resolve, reject)
);
}
sendQuery(aName, aData = null) {
return this.wrapPromise(
new Promise(resolve => {
super.sendQuery(aName, aData).then(result => {
resolve(Cu.cloneInto(result, this.contentWindow));
});
})
);
}
asRouterMessage({ type, data }) {
if (VALID_TYPES.has(type)) {
switch (type) {
// these messages are telemetry and can be done client side
case msg.AS_ROUTER_TELEMETRY_USER_EVENT:
case msg.TOOLBAR_BADGE_TELEMETRY:
case msg.TOOLBAR_PANEL_TELEMETRY:
case msg.MOMENTS_PAGE_TELEMETRY:
case msg.DOORHANGER_TELEMETRY: {
return this.telemetry.sendTelemetry(data);
}
// these messages don't need a repsonse
case msg.DISABLE_PROVIDER:
case msg.ENABLE_PROVIDER:
case msg.EXPIRE_QUERY_CACHE:
case msg.FORCE_WHATSNEW_PANEL:
case msg.IMPRESSION:
case msg.RESET_PROVIDER_PREF:
case msg.SET_PROVIDER_USER_PREF:
case msg.USER_ACTION: {
return this.sendAsyncMessage(type, data);
}
default: {
// these messages need a response
return this.sendQuery(type, data);
}
}
}
throw new Error(`Unexpected type "${type}"`);
}
}

Просмотреть файл

@ -0,0 +1,114 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["ASRouterParent", "ASRouterTabs"];
const {
MESSAGE_TYPE_HASH: { BLOCK_MESSAGE_BY_ID },
} = ChromeUtils.import("resource://activity-stream/common/ActorConstants.jsm");
const { ASRouterNewTabHook } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouterNewTabHook.jsm"
);
const { ASRouterDefaultConfig } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouterDefaultConfig.jsm"
);
class ASRouterTabs {
constructor({ asRouterNewTabHook }) {
this.actors = new Set();
this.destroy = () => {};
asRouterNewTabHook.createInstance(ASRouterDefaultConfig());
this.loadingMessageHandler = asRouterNewTabHook
.getInstance()
.then(initializer => {
const parentProcessMessageHandler = initializer.connect({
clearChildMessages: ids => this.messageAll("ClearMessages", ids),
updateAdminState: state => this.messageAll("UpdateAdminState", state),
});
this.destroy = () => {
initializer.disconnect();
};
return parentProcessMessageHandler;
});
}
get size() {
return this.actors.size;
}
messageAll(message, data) {
return Promise.all(
[...this.actors].map(a => a.sendAsyncMessage(message, data))
);
}
messagePreloaded(message, data) {
return Promise.all(
[...this.actors]
.filter(a =>
a.browsingContext.embedderElement.getAttribute("preloaded")
)
.map(a => a.sendAsyncMessage(message, data))
);
}
registerActor(actor) {
this.actors.add(actor);
}
unregisterActor(actor) {
this.actors.delete(actor);
}
}
const defaultTabsFactory = () =>
new ASRouterTabs({ asRouterNewTabHook: ASRouterNewTabHook });
class ASRouterParent extends JSWindowActorParent {
static tabs = null;
static nextTabId = 0;
constructor({ tabsFactory } = { tabsFactory: defaultTabsFactory }) {
super();
this.tabsFactory = tabsFactory;
}
actorCreated() {
ASRouterParent.tabs = ASRouterParent.tabs || this.tabsFactory();
this.tabsFactory = null;
this.tabId = ++ASRouterParent.nextTabId;
ASRouterParent.tabs.registerActor(this);
}
didDestroy() {
ASRouterParent.tabs.unregisterActor(this);
if (ASRouterParent.tabs.size < 1) {
ASRouterParent.tabs.destroy();
ASRouterParent.tabs = null;
}
}
getTab() {
return {
id: this.tabId,
browser: this.browsingContext.embedderElement,
};
}
receiveMessage({ name, data }) {
return ASRouterParent.tabs.loadingMessageHandler.then(handler => {
if (name === BLOCK_MESSAGE_BY_ID && data.preloadedOnly) {
return Promise.all([
handler.handleMessage(name, data, this.getTab()),
ASRouterParent.tabs.messagePreloaded("ClearMessages", [data.id]),
]).then(() => false);
}
return handler.handleMessage(name, data, this.getTab());
});
}
}

Просмотреть файл

@ -0,0 +1,43 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
const MESSAGE_TYPE_LIST = [
"BLOCK_MESSAGE_BY_ID",
"USER_ACTION",
"IMPRESSION",
"TRIGGER",
"NEWTAB_MESSAGE_REQUEST",
"DOORHANGER_TELEMETRY",
"TOOLBAR_BADGE_TELEMETRY",
"TOOLBAR_PANEL_TELEMETRY",
"MOMENTS_PAGE_TELEMETRY",
"AS_ROUTER_TELEMETRY_USER_EVENT",
// Admin types
"ADMIN_CONNECT_STATE",
"UNBLOCK_MESSAGE_BY_ID",
"UNBLOCK_ALL",
"BLOCK_BUNDLE",
"UNBLOCK_BUNDLE",
"DISABLE_PROVIDER",
"ENABLE_PROVIDER",
"EVALUATE_JEXL_EXPRESSION",
"EXPIRE_QUERY_CACHE",
"FORCE_ATTRIBUTION",
"FORCE_WHATSNEW_PANEL",
"OVERRIDE_MESSAGE",
"MODIFY_MESSAGE_JSON",
"RESET_PROVIDER_PREF",
"SET_PROVIDER_USER_PREF",
"RESET_GROUPS_STATE",
];
const MESSAGE_TYPE_HASH = MESSAGE_TYPE_LIST.reduce((hash, value) => {
hash[value] = value;
return hash;
}, {});
const EXPORTED_SYMBOLS = ["MESSAGE_TYPE_LIST", "MESSAGE_TYPE_HASH"];

Просмотреть файл

@ -2,8 +2,9 @@
* 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/. */
import { MESSAGE_TYPE_HASH as msg } from "common/ActorConstants.jsm";
import { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
import { OUTGOING_MESSAGE_NAME as AS_GENERAL_OUTGOING_MESSAGE_NAME } from "content-src/lib/init-store";
import { ASRouterUtils } from "./asrouter-utils";
import { generateBundles } from "./rich-text-strings";
import { ImpressionsWrapper } from "./components/ImpressionsWrapper/ImpressionsWrapper";
import { LocalizationProvider } from "fluent-react";
@ -13,96 +14,9 @@ import ReactDOM from "react-dom";
import { SnippetsTemplates } from "./templates/template-manifest";
import { FirstRun } from "./templates/FirstRun/FirstRun";
const INCOMING_MESSAGE_NAME = "ASRouter:parent-to-child";
const OUTGOING_MESSAGE_NAME = "ASRouter:child-to-parent";
const TEMPLATES_ABOVE_PAGE = ["extended_triplets"];
const TEMPLATES_BELOW_SEARCH = ["simple_below_search_snippet"];
export const ASRouterUtils = {
addListener(listener) {
if (global.RPMAddMessageListener) {
global.RPMAddMessageListener(INCOMING_MESSAGE_NAME, listener);
}
},
removeListener(listener) {
if (global.RPMRemoveMessageListener) {
global.RPMRemoveMessageListener(INCOMING_MESSAGE_NAME, listener);
}
},
sendMessage(action) {
if (global.RPMSendAsyncMessage) {
global.RPMSendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
}
},
blockById(id, options) {
ASRouterUtils.sendMessage({
type: "BLOCK_MESSAGE_BY_ID",
data: { id, ...options },
});
},
modifyMessageJson(content) {
ASRouterUtils.sendMessage({
type: "MODIFY_MESSAGE_JSON",
data: { content },
});
},
dismissById(id) {
ASRouterUtils.sendMessage({ type: "DISMISS_MESSAGE_BY_ID", data: { id } });
},
executeAction(button_action) {
ASRouterUtils.sendMessage({
type: "USER_ACTION",
data: button_action,
});
},
unblockById(id) {
ASRouterUtils.sendMessage({ type: "UNBLOCK_MESSAGE_BY_ID", data: { id } });
},
blockBundle(bundle) {
ASRouterUtils.sendMessage({ type: "BLOCK_BUNDLE", data: { bundle } });
},
unblockBundle(bundle) {
ASRouterUtils.sendMessage({ type: "UNBLOCK_BUNDLE", data: { bundle } });
},
overrideMessage(id) {
ASRouterUtils.sendMessage({ type: "OVERRIDE_MESSAGE", data: { id } });
},
sendTelemetry(ping) {
if (global.RPMSendAsyncMessage) {
const payload = ac.ASRouterUserEvent(ping);
global.RPMSendAsyncMessage(AS_GENERAL_OUTGOING_MESSAGE_NAME, payload);
}
},
getPreviewEndpoint() {
if (global.location && global.location.href.includes("endpoint")) {
const params = new URLSearchParams(
global.location.href.slice(global.location.href.indexOf("endpoint"))
);
try {
const endpoint = new URL(params.get("endpoint"));
return {
url: endpoint.href,
snippetId: params.get("snippetId"),
theme: this.getPreviewTheme(),
dir: this.getPreviewDir(),
};
} catch (e) {}
}
return null;
},
getPreviewTheme() {
return new URLSearchParams(
global.location.href.slice(global.location.href.indexOf("theme"))
).get("theme");
},
getPreviewDir() {
return new URLSearchParams(
global.location.href.slice(global.location.href.indexOf("dir"))
).get("dir");
},
};
// Note: nextProps/prevProps refer to props passed to <ImpressionsWrapper />, not <ASRouterUISurface />
function shouldSendImpressionOnUpdate(nextProps, prevProps) {
return (
@ -114,12 +28,15 @@ function shouldSendImpressionOnUpdate(nextProps, prevProps) {
export class ASRouterUISurface extends React.PureComponent {
constructor(props) {
super(props);
this.onMessageFromParent = this.onMessageFromParent.bind(this);
this.sendClick = this.sendClick.bind(this);
this.sendImpression = this.sendImpression.bind(this);
this.sendUserActionTelemetry = this.sendUserActionTelemetry.bind(this);
this.onUserAction = this.onUserAction.bind(this);
this.fetchFlowParams = this.fetchFlowParams.bind(this);
this.onBlockSelected = this.onBlockSelected.bind(this);
this.onBlockById = this.onBlockById.bind(this);
this.onDismiss = this.onDismiss.bind(this);
this.onMessageFromParent = this.onMessageFromParent.bind(this);
this.state = { message: {} };
if (props.document) {
@ -188,11 +105,14 @@ export class ASRouterUISurface extends React.PureComponent {
sendImpression(extraProps) {
if (this.state.message.provider === "preview") {
return;
return Promise.resolve();
}
ASRouterUtils.sendMessage({ type: "IMPRESSION", data: this.state.message });
this.sendUserActionTelemetry({ event: "IMPRESSION", ...extraProps });
return ASRouterUtils.sendMessage({
type: msg.IMPRESSION,
data: this.state.message,
});
}
// If link has a `metric` data attribute send it as part of the `event_context`
@ -225,19 +145,27 @@ export class ASRouterUISurface extends React.PureComponent {
!this.state.message.content.do_not_autoblock &&
!dataset.do_not_autoblock
) {
ASRouterUtils.blockById(this.state.message.id);
this.onBlockById(this.state.message.id);
}
if (this.state.message.provider !== "preview") {
this.sendUserActionTelemetry({ event: "CLICK_BUTTON", ...metric });
}
}
onBlockById(id) {
return options => ASRouterUtils.blockById(id, options);
onBlockSelected(options) {
return this.onBlockById(this.state.message.id, options);
}
onDismissById(id) {
return () => ASRouterUtils.dismissById(id);
onBlockById(id, options) {
return ASRouterUtils.blockById(id, options).then(clearAll => {
if (clearAll) {
this.setState({ message: {} });
}
});
}
onDismiss() {
this.clearMessage(this.state.message.id);
}
clearMessage(id) {
@ -256,25 +184,13 @@ export class ASRouterUISurface extends React.PureComponent {
}
}
onMessageFromParent({ data: action }) {
switch (action.type) {
case "SET_MESSAGE":
this.setState({ message: action.data });
onMessageFromParent({ type, data }) {
// These only exists due to onPrefChange events in ASRouter
switch (type) {
case "ClearMessages": {
data.forEach(id => this.clearMessage(id));
break;
case "CLEAR_MESSAGE":
this.clearMessage(action.data.id);
break;
case "CLEAR_PROVIDER":
if (action.data.id === this.state.message.provider) {
this.setState({ message: {} });
}
break;
case "CLEAR_ALL":
this.setState({ message: {} });
break;
case "AS_ROUTER_TARGETING_UPDATE":
action.data.forEach(id => this.clearMessage(id));
break;
}
}
@ -282,7 +198,7 @@ export class ASRouterUISurface extends React.PureComponent {
ASRouterUtils.sendMessage({
type: "NEWTAB_MESSAGE_REQUEST",
data: { endpoint },
});
}).then(state => this.setState(state));
}
componentWillMount() {
@ -306,6 +222,22 @@ export class ASRouterUISurface extends React.PureComponent {
ASRouterUtils.removeListener(this.onMessageFromParent);
}
componentDidUpdate(prevProps) {
if (
prevProps.adminContent &&
JSON.stringify(prevProps.adminContent) !==
JSON.stringify(this.props.adminContent)
) {
this.updateContent();
}
}
updateContent() {
this.setState({
...this.props.adminContent,
});
}
async getMonitorUrl({ url, flowRequestParams = {} }) {
const flowValues = await this.fetchFlowParams(flowRequestParams);
@ -353,8 +285,8 @@ export class ASRouterUISurface extends React.PureComponent {
<SnippetComponent
{...this.state.message}
UISurface="NEWTAB_FOOTER_BAR"
onBlock={this.onBlockById(this.state.message.id)}
onDismiss={this.onDismissById(this.state.message.id)}
onBlock={this.onBlockSelected}
onDismiss={this.onDismiss}
onAction={this.onUserAction}
sendClick={this.sendClick}
sendUserActionTelemetry={this.sendUserActionTelemetry}
@ -395,7 +327,7 @@ export class ASRouterUISurface extends React.PureComponent {
sendUserActionTelemetry={this.sendUserActionTelemetry}
executeAction={ASRouterUtils.executeAction}
onBlockById={ASRouterUtils.blockById}
onDismiss={this.onDismissById(this.state.message.id)}
onDismiss={this.onDismiss}
fxaEndpoint={this.props.fxaEndpoint}
appUpdateChannel={this.props.appUpdateChannel}
fetchFlowParams={this.fetchFlowParams}

Просмотреть файл

@ -0,0 +1,114 @@
/* 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/. */
import { MESSAGE_TYPE_HASH as msg } from "common/ActorConstants.jsm";
import { actionCreators as ac } from "common/Actions.jsm";
export const ASRouterUtils = {
addListener(listener) {
if (global.ASRouterAddParentListener) {
global.ASRouterAddParentListener(listener);
}
},
removeListener(listener) {
if (global.ASRouterRemoveParentListener) {
global.ASRouterRemoveParentListener(listener);
}
},
sendMessage(action) {
if (global.ASRouterMessage) {
return global.ASRouterMessage(action);
}
throw new Error(`Unexpected call:\n${JSON.stringify(action, null, 3)}`);
},
blockById(id, options) {
return ASRouterUtils.sendMessage({
type: msg.BLOCK_MESSAGE_BY_ID,
data: { id, ...options },
});
},
modifyMessageJson(content) {
return ASRouterUtils.sendMessage({
type: msg.MODIFY_MESSAGE_JSON,
data: { content },
});
},
dismissById(id) {
return ASRouterUtils.sendMessage({
type: msg.DISMISS_MESSAGE_BY_ID,
data: { id },
});
},
executeAction(button_action) {
return ASRouterUtils.sendMessage({
type: msg.USER_ACTION,
data: button_action,
});
},
unblockById(id) {
return ASRouterUtils.sendMessage({
type: msg.UNBLOCK_MESSAGE_BY_ID,
data: { id },
});
},
blockBundle(bundle) {
return ASRouterUtils.sendMessage({
type: msg.BLOCK_BUNDLE,
data: { bundle },
});
},
unblockBundle(bundle) {
return ASRouterUtils.sendMessage({
type: msg.UNBLOCK_BUNDLE,
data: { bundle },
});
},
overrideMessage(id) {
return ASRouterUtils.sendMessage({
type: msg.OVERRIDE_MESSAGE,
data: { id },
});
},
sendTelemetry(ping) {
return ASRouterUtils.sendMessage(ac.ASRouterUserEvent(ping));
},
getPreviewEndpoint() {
if (
global.document &&
global.document.location &&
global.document.location.href.includes("endpoint")
) {
const params = new URLSearchParams(
global.document.location.href.slice(
global.document.location.href.indexOf("endpoint")
)
);
try {
const endpoint = new URL(params.get("endpoint"));
return {
url: endpoint.href,
snippetId: params.get("snippetId"),
theme: this.getPreviewTheme(),
dir: this.getPreviewDir(),
};
} catch (e) {}
}
return null;
},
getPreviewTheme() {
return new URLSearchParams(
global.document.location.href.slice(
global.document.location.href.indexOf("theme")
)
).get("theme");
},
getPreviewDir() {
return new URLSearchParams(
global.document.location.href.slice(
global.document.location.href.indexOf("dir")
)
).get("dir");
},
};

Просмотреть файл

@ -14,6 +14,14 @@
margin-inline-start: 20px;
}
&.test-only {
width: 0;
height: 0;
overflow: hidden;
display: block;
visibility: hidden;
}
&.primary {
border: 1px solid var(--newtab-button-primary-color);
background-color: var(--newtab-button-primary-color);

Просмотреть файл

@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
import { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
import { ASRouterUtils } from "../../asrouter/asrouter-content";
import { ASRouterUtils } from "../../asrouter/asrouter-utils";
import { connect } from "react-redux";
import { ModalOverlay } from "../../asrouter/components/ModalOverlay/ModalOverlay";
import React from "react";
@ -479,13 +479,13 @@ export class DiscoveryStreamAdmin extends React.PureComponent {
export class ASRouterAdminInner extends React.PureComponent {
constructor(props) {
super(props);
this.onMessage = this.onMessage.bind(this);
this.handleEnabledToggle = this.handleEnabledToggle.bind(this);
this.handleUserPrefToggle = this.handleUserPrefToggle.bind(this);
this.onChangeMessageFilter = this.onChangeMessageFilter.bind(this);
this.onChangeMessageGroupsFilter = this.onChangeMessageGroupsFilter.bind(
this
);
this.unblockAll = this.unblockAll.bind(this);
this.handleClearAllImpressionsByProvider = this.handleClearAllImpressionsByProvider.bind(
this
);
@ -509,6 +509,9 @@ export class ASRouterAdminInner extends React.PureComponent {
this.toggleJSON = this.toggleJSON.bind(this);
this.toggleAllMessages = this.toggleAllMessages.bind(this);
this.resetGroups = this.resetGroups.bind(this);
this.onMessageFromParent = this.onMessageFromParent.bind(this);
this.setStateFromParent = this.setStateFromParent.bind(this);
this.setState = this.setState.bind(this);
this.state = {
messageFilter: "all",
messageGroupsFilter: "all",
@ -533,14 +536,23 @@ export class ASRouterAdminInner extends React.PureComponent {
};
}
onMessage({ data: action }) {
if (action.type === "ADMIN_SET_STATE") {
this.setState(action.data);
onMessageFromParent({ type, data }) {
// These only exists due to onPrefChange events in ASRouter
switch (type) {
case "UpdateAdminState": {
this.setStateFromParent(data);
break;
}
}
}
setStateFromParent(data) {
this.setState(data);
if (!this.state.stringTargetingParameters) {
const stringTargetingParameters = {};
for (const param of Object.keys(action.data.targetingParameters)) {
for (const param of Object.keys(data.targetingParameters)) {
stringTargetingParameters[param] = JSON.stringify(
action.data.targetingParameters[param],
data.targetingParameters[param],
null,
2
);
@ -548,19 +560,14 @@ export class ASRouterAdminInner extends React.PureComponent {
this.setState({ stringTargetingParameters });
}
}
}
componentWillMount() {
ASRouterUtils.addListener(this.onMessageFromParent);
const endpoint = ASRouterUtils.getPreviewEndpoint();
ASRouterUtils.sendMessage({
type: "ADMIN_CONNECT_STATE",
data: { endpoint },
});
ASRouterUtils.addListener(this.onMessage);
}
componentWillUnmount() {
ASRouterUtils.removeListener(this.onMessage);
}).then(this.setStateFromParent);
}
findOtherBundledMessagesOfSameTemplate(template) {
@ -605,7 +612,13 @@ export class ASRouterAdminInner extends React.PureComponent {
}
handleOverride(id) {
return () => ASRouterUtils.overrideMessage(id);
return () =>
ASRouterUtils.overrideMessage(id).then(state => {
this.setStateFromParent(state);
this.props.notifyContent({
message: state.message,
});
});
}
async handleUpdateWNMessages() {
@ -632,7 +645,7 @@ export class ASRouterAdminInner extends React.PureComponent {
resetGroups(id, value) {
ASRouterUtils.sendMessage({
type: "RESET_GROUPS_STATE",
});
}).then(this.setStateFromParent);
}
handleExpressionEval() {
@ -647,7 +660,7 @@ export class ASRouterAdminInner extends React.PureComponent {
expression: this.refs.expressionInput.value,
context,
},
});
}).then(this.setStateFromParent);
}
onChangeTargetingParameters(event) {
@ -674,6 +687,12 @@ export class ASRouterAdminInner extends React.PureComponent {
});
}
unblockAll() {
return ASRouterUtils.sendMessage({
type: "UNBLOCK_ALL",
}).then(this.setStateFromParent);
}
handleClearAllImpressionsByProvider() {
const providerId = this.state.messageFilter;
if (!providerId) {
@ -815,12 +834,6 @@ export class ASRouterAdminInner extends React.PureComponent {
}
}
modifyJson(msg) {
ASRouterUtils.modifyMessageJson(
JSON.parse(document.getElementById(`${msg.id}-textarea`).value)
);
}
handleChange(msgId) {
if (!this.state.modifiedMessages.includes(msgId)) {
this.setState(prevState => ({
@ -951,6 +964,18 @@ export class ASRouterAdminInner extends React.PureComponent {
});
}
modifyJson(content) {
const message = JSON.parse(
document.getElementById(`${content.id}-textarea`).value
);
return ASRouterUtils.modifyMessageJson(message).then(state => {
this.setStateFromParent(state);
this.props.notifyContent({
message: state.message,
});
});
}
renderWNMessageItem(msg) {
const isBlocked =
this.state.messageBlockList.includes(msg.id) ||
@ -1094,6 +1119,12 @@ export class ASRouterAdminInner extends React.PureComponent {
return (
<p>
<button
className="unblock-all ASRouterButton test-only"
onClick={this.unblockAll}
>
Unblock All Snippets
</button>
{/* eslint-disable-next-line prettier/prettier */}
Show messages from {/* eslint-disable-next-line jsx-a11y/no-onchange */}
<select
@ -1414,7 +1445,7 @@ export class ASRouterAdminInner extends React.PureComponent {
ASRouterUtils.sendMessage({
type: "FORCE_ATTRIBUTION",
data: this.state.attributionParameters,
});
}).then(this.setStateFromParent);
}
_getGroupImpressionsCount(id, frequency) {
@ -1891,6 +1922,7 @@ export class CollapseToggle extends React.PureComponent {
componentWillUnmount() {
global.document.body.classList.remove("no-scroll");
ASRouterUtils.removeListener(this.onMessageFromParent);
}
render() {

Просмотреть файл

@ -42,6 +42,18 @@ function debounce(func, wait) {
}
export class _Base extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
message: {},
};
this.notifyContent = this.notifyContent.bind(this);
}
notifyContent(state) {
this.setState(state);
}
componentWillUnmount() {
this.updateTheme();
}
@ -76,8 +88,10 @@ export class _Base extends React.PureComponent {
return (
<ErrorBoundary className="base-content-fallback">
<React.Fragment>
<BaseContent {...this.props} />
{isDevtoolsEnabled ? <ASRouterAdmin /> : null}
<BaseContent {...this.props} adminContent={this.state} />
{isDevtoolsEnabled ? (
<ASRouterAdmin notifyContent={this.notifyContent} />
) : null}
</React.Fragment>
</ErrorBoundary>
);
@ -163,6 +177,7 @@ export class BaseContent extends React.PureComponent {
</div>
)}
<ASRouterUISurface
adminContent={this.props.adminContent}
appUpdateChannel={this.props.Prefs.values.appUpdateChannel}
fxaEndpoint={this.props.Prefs.values.fxa_endpoint}
dispatch={this.props.dispatch}

Просмотреть файл

@ -3383,6 +3383,12 @@ main {
cursor: pointer; }
.tall .ASRouterButton {
margin-inline-start: 20px; }
.ASRouterButton.test-only {
width: 0;
height: 0;
overflow: hidden;
display: block;
visibility: hidden; }
.ASRouterButton.primary {
border: 1px solid var(--newtab-button-primary-color);
background-color: var(--newtab-button-primary-color);

Просмотреть файл

@ -3386,6 +3386,12 @@ main {
cursor: pointer; }
.tall .ASRouterButton {
margin-inline-start: 20px; }
.ASRouterButton.test-only {
width: 0;
height: 0;
overflow: hidden;
display: block;
visibility: hidden; }
.ASRouterButton.primary {
border: 1px solid var(--newtab-button-primary-color);
background-color: var(--newtab-button-primary-color);

Просмотреть файл

@ -3383,6 +3383,12 @@ main {
cursor: pointer; }
.tall .ASRouterButton {
margin-inline-start: 20px; }
.ASRouterButton.test-only {
width: 0;
height: 0;
overflow: hidden;
display: block;
visibility: hidden; }
.ASRouterButton.primary {
border: 1px solid var(--newtab-button-primary-color);
background-color: var(--newtab-button-primary-color);

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -68,6 +68,42 @@ module.exports = function(config) {
functions: 100,
branches: 66,
overrides: {
"lib/ASRouter.jsm": {
statements: 75,
lines: 75,
functions: 64,
branches: 66,
},
"lib/ASRouterDefaultConfig.jsm": {
statements: 0,
lines: 0,
functions: 0,
branches: 0,
},
"content-src/asrouter/asrouter-utils.js": {
statements: 68,
lines: 68,
functions: 100,
branches: 63,
},
"lib/TelemetryFeed.jsm": {
statements: 99,
lines: 99,
functions: 100,
branches: 96,
},
"lib/ASRouterParentProcessMessageHandler.jsm": {
statements: 98,
lines: 98,
functions: 100,
branches: 88,
},
"content-src/lib/init-store.js": {
statements: 98,
lines: 98,
functions: 100,
branches: 100,
},
"lib/ActivityStreamStorage.jsm": {
statements: 100,
lines: 100,
@ -187,6 +223,7 @@ module.exports = function(config) {
], // require("babel-plugin-jsm-to-commonjs")
"@babel/plugin-proposal-nullish-coalescing-operator",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-class-properties",
],
},
},

Просмотреть файл

@ -1,3 +1,4 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
@ -18,7 +19,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
ToolbarPanelHub: "resource://activity-stream/lib/ToolbarPanelHub.jsm",
MomentsPageHub: "resource://activity-stream/lib/MomentsPageHub.jsm",
ASRouterTargeting: "resource://activity-stream/lib/ASRouterTargeting.jsm",
QueryCache: "resource://activity-stream/lib/ASRouterTargeting.jsm",
ASRouterPreferences: "resource://activity-stream/lib/ASRouterPreferences.jsm",
TARGETING_PREFERENCES:
"resource://activity-stream/lib/ASRouterPreferences.jsm",
@ -43,7 +43,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
"browser.aboutwelcome.enabled",
true
);
const { actionTypes: at, actionCreators: ac } = ChromeUtils.import(
const { actionCreators: ac } = ChromeUtils.import(
"resource://activity-stream/common/Actions.jsm"
);
@ -68,8 +68,6 @@ const TRAILHEAD_CONFIG = {
DYNAMIC_TRIPLET_BUNDLE_LENGTH: 3,
};
const INCOMING_MESSAGE_NAME = "ASRouter:child-to-parent";
const OUTGOING_MESSAGE_NAME = "ASRouter:parent-to-child";
// List of hosts for endpoints that serve router messages.
// Key is allowed host, value is a name for the endpoint host.
const DEFAULT_ALLOWLIST_HOSTS = {
@ -284,7 +282,7 @@ const MessageLoaderUtils = {
* @param {obj} provider An AS router provider
* @param {string} provider.id The id of the provider
* @param {string} provider.bucket The name of the Remote Settings bucket
* @param {func} options.dispatchToAS dispatch an action the main AS Store
* @param {func} options.dispatchCFRAction dispatch an action the main AS Store
* @returns {Promise} resolves with an array of messages, or an empty array if none could be fetched
*/
async _remoteSettingsLoader(provider, options) {
@ -298,7 +296,7 @@ const MessageLoaderUtils = {
MessageLoaderUtils._handleRemoteSettingsUndesiredEvent(
"ASR_RS_NO_MESSAGES",
provider.id,
options.dispatchToAS
options.dispatchCFRAction
);
} else if (RS_PROVIDERS_WITH_L10N.includes(provider.id)) {
const locale = Services.locale.appLocaleAsBCP47;
@ -324,7 +322,7 @@ const MessageLoaderUtils = {
MessageLoaderUtils._handleRemoteSettingsUndesiredEvent(
"ASR_RS_NO_MESSAGES",
RS_COLLECTION_L10N,
options.dispatchToAS
options.dispatchCFRAction
);
}
}
@ -332,7 +330,7 @@ const MessageLoaderUtils = {
MessageLoaderUtils._handleRemoteSettingsUndesiredEvent(
"ASR_RS_ERROR",
provider.id,
options.dispatchToAS
options.dispatchCFRAction
);
MessageLoaderUtils.reportError(e);
}
@ -398,9 +396,9 @@ const MessageLoaderUtils = {
return experiments;
},
_handleRemoteSettingsUndesiredEvent(event, providerId, dispatchToAS) {
if (dispatchToAS) {
dispatchToAS(
_handleRemoteSettingsUndesiredEvent(event, providerId, dispatchCFRAction) {
if (dispatchCFRAction) {
dispatchCFRAction(
ac.ASRouterUserEvent({
action: "asrouter_undesired_event",
event,
@ -471,7 +469,7 @@ const MessageLoaderUtils = {
* @param {obj} provider An AS Router provider
* @param {string} provider.type An AS Router provider type (defaults to "local")
* @param {obj} options.storage A storage object with get() and set() methods for caching.
* @param {func} options.dispatchToAS dispatch an action the main AS Store
* @param {func} options.dispatchCFRAction dispatch an action the main AS Store
* @returns {obj} Returns an object with .messages (an array of messages) and .lastUpdated (the time the messages were updated)
*/
async loadMessagesForProvider(provider, options) {
@ -545,8 +543,10 @@ this.MessageLoaderUtils = MessageLoaderUtils;
class _ASRouter {
constructor(localProviders = LOCAL_MESSAGE_PROVIDERS) {
this.initialized = false;
this.messageChannel = null;
this.dispatchToAS = null;
this.clearChildMessages = null;
this.updateAdminState = null;
this.sendTelemetry = null;
this.dispatchCFRAction = null;
this._storage = null;
this._resetInitialization();
this._state = {
@ -562,23 +562,22 @@ class _ASRouter {
this._localProviders = localProviders;
this.blockMessageById = this.blockMessageById.bind(this);
this.unblockMessageById = this.unblockMessageById.bind(this);
this.onMessage = this.onMessage.bind(this);
this.handleMessageRequest = this.handleMessageRequest.bind(this);
this.addImpression = this.addImpression.bind(this);
this._handleTargetingError = this._handleTargetingError.bind(this);
this.onPrefChange = this.onPrefChange.bind(this);
this.dispatch = this.dispatch.bind(this);
this._onLocaleChanged = this._onLocaleChanged.bind(this);
this.isUnblockedMessage = this.isUnblockedMessage.bind(this);
this.unblockAll = this.unblockAll.bind(this);
this.renderWNMessages = this.renderWNMessages.bind(this);
this.forceWNPanel = this.forceWNPanel.bind(this);
Services.telemetry.setEventRecordingEnabled(REACH_EVENT_CATEGORY, true);
}
async onPrefChange(prefName) {
let invalidMessages = [];
if (TARGETING_PREFERENCES.includes(prefName)) {
// Notify all tabs of messages that have become invalid after pref change
const invalidMessages = [];
const context = this._getMessagesContext();
const targetingContext = new TargetingContext(context);
@ -591,39 +590,42 @@ class _ASRouter {
invalidMessages.push(msg.id);
}
}
this.messageChannel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
type: at.AS_ROUTER_TARGETING_UPDATE,
data: invalidMessages,
});
} else {
// Update message providers and fetch new messages on pref change
this._loadLocalProviders();
this._updateMessageProviders();
invalidMessages = await this._updateMessageProviders();
await this.loadMessagesFromAllProviders();
// Any change in user prefs can disable or enable groups
await this.setState(state => ({
groups: state.groups.map(this._checkGroupEnabled),
}));
}
this.clearChildMessages(invalidMessages);
}
// Fetch and decode the message provider pref JSON, and update the message providers
_updateMessageProviders() {
async _updateMessageProviders() {
const previousProviders = this.state.providers;
const providers = [
const providers = await Promise.all(
[
// If we have added a `preview` provider, hold onto it
...previousProviders.filter(p => p.id === "preview"),
// The provider should be enabled and not have a user preference set to false
...ASRouterPreferences.providers.filter(
p => p.enabled && ASRouterPreferences.getUserPreference(p.id) !== false
p =>
p.enabled && ASRouterPreferences.getUserPreference(p.id) !== false
),
].map(_provider => {
].map(async _provider => {
// make a copy so we don't modify the source of the pref
const provider = { ..._provider };
if (provider.type === "local" && !provider.messages) {
// Get the messages from the local message provider
const localProvider = this._localProviders[provider.localProvider];
provider.messages = localProvider ? localProvider.getMessages() : [];
provider.messages = [];
if (localProvider) {
provider.messages = await localProvider.getMessages();
}
}
if (provider.type === "remote" && provider.url) {
provider.url = provider.url.replace(
@ -635,17 +637,16 @@ class _ASRouter {
// Reset provider update timestamp to force message refresh
provider.lastUpdated = undefined;
return provider;
});
})
);
const providerIDs = providers.map(p => p.id);
let invalidMessages = [];
// Clear old messages for providers that are no longer enabled
for (const prevProvider of previousProviders) {
if (!providerIDs.includes(prevProvider.id)) {
this.messageChannel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
type: "CLEAR_PROVIDER",
data: { id: prevProvider.id },
});
invalidMessages.push(prevProvider.id);
}
}
@ -657,7 +658,7 @@ class _ASRouter {
providerIDs.includes(message.provider)
),
],
}));
})).then(() => invalidMessages);
}
get state() {
@ -759,7 +760,7 @@ class _ASRouter {
provider,
{
storage: this._storage,
dispatchToAS: this.dispatchToAS,
dispatchCFRAction: this.dispatchCFRAction,
}
);
remoteMessages = messages;
@ -791,7 +792,7 @@ class _ASRouter {
errors,
} = await MessageLoaderUtils.loadMessagesForProvider(provider, {
storage: this._storage,
dispatchToAS: this.dispatchToAS,
dispatchCFRAction: this.dispatchCFRAction,
});
newState.providers.push({ ...provider, lastUpdated, errors });
newState.messages = [...newState.messages, ...messages];
@ -827,6 +828,7 @@ class _ASRouter {
await this.setState(this._removePreviewEndpoint(newState));
await this.cleanupImpressions();
}
return this.state;
}
async _maybeUpdateL10nAttachment() {
@ -850,6 +852,7 @@ class _ASRouter {
await this.loadMessagesFromAllProviders();
}
}
return this.state;
}
async _onLocaleChanged(subject, topic, data) {
@ -864,48 +867,54 @@ class _ASRouter {
}
}
toWaitForInitFunc(func) {
return (...args) => this.waitForInitialized.then(() => func(...args));
}
/**
* init - Initializes the MessageRouter.
* It is ready when it has been connected to a RemotePageManager instance.
*
* @param {RemotePageManager} channel a RemotePageManager instance
* @param {obj} storage an AS storage instance
* @param {func} dispatchToAS dispatch an action the main AS Store
* @param {obj} parameters parameters to initialize ASRouter
* @memberof _ASRouter
*/
async init(channel, storage, dispatchToAS) {
this.messageChannel = channel;
this.messageChannel.addMessageListener(
INCOMING_MESSAGE_NAME,
this.onMessage
);
async init({
storage,
sendTelemetry,
clearChildMessages,
updateAdminState,
dispatchCFRAction,
}) {
this._storage = storage;
this.ALLOWLIST_HOSTS = this._loadSnippetsAllowHosts();
this.dispatchToAS = dispatchToAS;
this.clearChildMessages = this.toWaitForInitFunc(clearChildMessages);
// NOTE: This is only necessary to sync devtools and snippets when devtools is active.
this.updateAdminState = this.toWaitForInitFunc(updateAdminState);
this.sendTelemetry = sendTelemetry;
this.dispatchCFRAction = this.toWaitForInitFunc(dispatchCFRAction);
ASRouterPreferences.init();
ASRouterPreferences.addListener(this.onPrefChange);
BookmarkPanelHub.init(
this.handleMessageRequest,
this.addImpression,
this.dispatch
this.sendTelemetry
);
ToolbarBadgeHub.init(this.waitForInitialized, {
handleMessageRequest: this.handleMessageRequest,
addImpression: this.addImpression,
blockMessageById: this.blockMessageById,
unblockMessageById: this.unblockMessageById,
dispatch: this.dispatch,
sendTelemetry: this.sendTelemetry,
});
ToolbarPanelHub.init(this.waitForInitialized, {
getMessages: this.handleMessageRequest,
dispatch: this.dispatch,
sendTelemetry: this.sendTelemetry,
});
MomentsPageHub.init(this.waitForInitialized, {
handleMessageRequest: this.handleMessageRequest,
addImpression: this.addImpression,
blockMessageById: this.blockMessageById,
dispatch: this.dispatch,
sendTelemetry: this.sendTelemetry,
});
this._loadLocalProviders();
@ -923,41 +932,28 @@ class _ASRouter {
groupImpressions,
messageImpressions,
previousSessionEnd,
...(ASRouterPreferences.specialConditions || {}),
initialized: false,
});
this._updateMessageProviders();
await this._updateMessageProviders();
await this.loadMessagesFromAllProviders();
await MessageLoaderUtils.cleanupCache(this.state.providers, storage);
// set necessary state in the rest of AS
this.dispatchToAS(
ac.BroadcastToContent({
type: at.AS_ROUTER_INITIALIZED,
data: ASRouterPreferences.specialConditions,
meta: {
isStartup: true,
},
})
);
SpecialMessageActions.blockMessageById = this.blockMessageById;
Services.obs.addObserver(this._onLocaleChanged, TOPIC_INTL_LOCALE_CHANGED);
Services.prefs.addObserver(USE_REMOTE_L10N_PREF, this);
// sets .initialized to true and resolves .waitForInitialized promise
this._finishInitializing();
return this.state;
}
uninit() {
this._storage.set("previousSessionEnd", Date.now());
this.messageChannel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
type: "CLEAR_ALL",
});
this.messageChannel.removeMessageListener(
INCOMING_MESSAGE_NAME,
this.onMessage
);
this.messageChannel = null;
this.dispatchToAS = null;
this.clearChildMessages = null;
this.updateAdminState = null;
this.sendTelemetry = null;
this.dispatchCFRAction = null;
ASRouterPreferences.removeListener(this.onPrefChange);
ASRouterPreferences.uninit();
@ -985,23 +981,37 @@ class _ASRouter {
typeof callbackOrObj === "function"
? callbackOrObj(this.state)
: callbackOrObj;
this._state = { ...this.state, ...newState };
return new Promise(resolve => {
this._onStateChanged(this.state);
resolve();
this._state = {
...this.state,
...newState,
};
if (ASRouterPreferences.devtoolsEnabled) {
return this.updateTargetingParameters().then(state => {
this.updateAdminState(state);
return state;
});
}
return Promise.resolve(this.state);
}
updateTargetingParameters() {
return this.getTargetingParameters(
ASRouterTargeting.Environment,
this._getMessagesContext()
).then(targetingParameters => ({
...this.state,
providerPrefs: ASRouterPreferences.providers,
userPrefs: ASRouterPreferences.getAllUserPreferences(),
targetingParameters,
trailhead: ASRouterPreferences.trailhead,
errors: this.errors,
}));
}
getMessageById(id) {
return this.state.messages.find(message => message.id === id);
}
_onStateChanged(state) {
if (ASRouterPreferences.devtoolsEnabled) {
this._updateAdminState();
}
}
_loadLocalProviders() {
// If we're in ASR debug mode add the local test providers
if (ASRouterPreferences.devtoolsEnabled) {
@ -1029,36 +1039,17 @@ class _ASRouter {
return targetingParameters;
}
async _updateAdminState(target) {
const channel = target || this.messageChannel;
channel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
type: "ADMIN_SET_STATE",
data: {
...this.state,
providerPrefs: ASRouterPreferences.providers,
userPrefs: ASRouterPreferences.getAllUserPreferences(),
targetingParameters: await this.getTargetingParameters(
ASRouterTargeting.Environment,
this._getMessagesContext()
),
trailheadTriplet: ASRouterPreferences.trailheadTriplet,
errors: this.errors,
},
});
}
_handleTargetingError(error, message) {
_handleTargetingError(type, error, message) {
Cu.reportError(error);
if (this.dispatchToAS) {
this.dispatchToAS(
this.dispatchCFRAction(
ac.ASRouterUserEvent({
message_id: message.id,
action: "asrouter_undesired_event",
event: "TARGETING_EXPRESSION_ERROR",
event_context: type,
})
);
}
}
// Return an object containing targeting parameters used to select messages
_getMessagesContext() {
@ -1074,8 +1065,7 @@ class _ASRouter {
};
}
async evaluateExpression(target, { expression, context }) {
const channel = target || this.messageChannel;
async evaluateExpression({ expression, context }) {
const targetingContext = new TargetingContext(context);
let evaluationStatus;
try {
@ -1086,20 +1076,17 @@ class _ASRouter {
} catch (e) {
evaluationStatus = { result: e.message, success: false };
}
channel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
type: "ADMIN_SET_STATE",
data: {
...this.state,
evaluationStatus,
},
});
return Promise.resolve({ evaluationStatus });
}
_orderBundle(bundle) {
return bundle.sort((a, b) => a.order - b.order);
}
unblockAll() {
return this.setState({ messageBlockList: [] });
}
isUnblockedMessage(message) {
let { state } = this;
return (
@ -1155,7 +1142,7 @@ class _ASRouter {
return true;
}
async _getBundledMessages(originalMessage, target, trigger, force = false) {
async _getBundledMessages(originalMessage, trigger, force = false) {
let result = [];
let bundleLength;
let bundleTemplate;
@ -1262,44 +1249,49 @@ class _ASRouter {
];
}
/**
* Route messages based on template to the correct module that can display them
*/
routeMessageToTarget(message, target, trigger, force = false) {
routeCFRMessage(message, browser, trigger, force = false) {
switch (message.template) {
case "whatsnew_panel_message":
if (force) {
ToolbarPanelHub.forceShowMessage(target, message);
ToolbarPanelHub.forceShowMessage(browser, message);
}
break;
case "cfr_doorhanger":
case "milestone_message":
if (force) {
CFRPageActions.forceRecommendation(target, message, this.dispatch);
CFRPageActions.forceRecommendation(
browser,
message,
this.dispatchCFRAction
);
} else {
CFRPageActions.addRecommendation(
target,
browser,
trigger.param && trigger.param.host,
message,
this.dispatch
this.dispatchCFRAction
);
}
break;
case "cfr_urlbar_chiclet":
if (force) {
CFRPageActions.forceRecommendation(target, message, this.dispatch);
CFRPageActions.forceRecommendation(
browser,
message,
this.dispatchCFRAction
);
} else {
CFRPageActions.addRecommendation(
target,
browser,
null,
message,
this.dispatch
this.dispatchCFRAction
);
}
break;
case "fxa_bookmark_panel":
if (force) {
BookmarkPanelHub._forceShowMessage(target, message);
BookmarkPanelHub.forceShowMessage(browser, message);
}
break;
case "toolbar_badge":
@ -1309,62 +1301,38 @@ class _ASRouter {
MomentsPageHub.executeAction(message);
break;
default:
try {
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
type: "SET_MESSAGE",
data: message,
});
} catch (e) {}
break;
}
}
async _sendMessageToTarget(message, target, trigger, force = false) {
// No message is available, so send CLEAR_ALL.
async sendMessage(message, trigger, force, browser) {
if (!message) {
try {
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, { type: "CLEAR_ALL" });
} catch (e) {}
// For bundled messages, look for the rest of the bundle or else send CLEAR_ALL
return { message: {} };
} else if (message.bundled) {
const bundledMessages = await this._getBundledMessages(
message,
target,
trigger,
force
);
const action = bundledMessages
? { type: "SET_BUNDLED_MESSAGES", data: bundledMessages }
: { type: "CLEAR_ALL" };
try {
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
} catch (e) {}
// For nested bundled messages, look for the desired bundle
const bundle =
(await this._getBundledMessages(message, trigger, force)) || {};
return { message: bundle };
} else if (message.includeBundle) {
const bundledMessages = await this._getBundledMessages(
message,
target,
message.includeBundle.trigger,
force
);
try {
target.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
type: "SET_MESSAGE",
data: {
return {
message: {
...message,
trailheadTriplet: ASRouterPreferences.trailheadTriplet || "",
trailheadTriplet:
ASRouterPreferences.trailhead.trailheadTriplet || "",
bundle: bundledMessages && bundledMessages.bundle,
},
});
} catch (e) {}
} else {
this.routeMessageToTarget(message, target, trigger, force);
};
}
this.routeCFRMessage(message, browser, trigger, force);
return { message };
}
async addImpression(message) {
addImpression(message) {
const groupsWithFrequency = this.state.groups.filter(
({ frequency, id }) => frequency && message.groups.includes(id)
);
@ -1372,7 +1340,7 @@ class _ASRouter {
// that have providers that have frequency
if (message.frequency || groupsWithFrequency.length) {
const time = Date.now();
await this.setState(state => {
return this.setState(state => {
const messageImpressions = this._addImpressionForItem(
state,
message,
@ -1391,6 +1359,7 @@ class _ASRouter {
return { messageImpressions, groupImpressions };
});
}
return Promise.resolve();
}
// Helper for addImpression - calculate the updated impressions object for the given
@ -1435,8 +1404,8 @@ class _ASRouter {
* 2. If the item has time-bound frequency caps but no lifetime cap, any item impressions older
* than the longest time period will be cleared.
*/
async cleanupImpressions() {
await this.setState(state => {
cleanupImpressions() {
return this.setState(state => {
const messageImpressions = this._cleanupImpressionsForItems(
state,
state.messages,
@ -1550,20 +1519,14 @@ class _ASRouter {
});
}
async modifyMessageJson(content, target, force = true, action = {}) {
await this._sendMessageToTarget(content, target, action.data, force);
setMessageById({ id, ...data }, force, browser) {
return this.sendMessage(this.getMessageById(id), data, force, browser);
}
async setMessageById(id, target, force = true, action = {}) {
const newMessage = this.getMessageById(id);
await this._sendMessageToTarget(newMessage, target, action.data, force);
}
async blockMessageById(idOrIds) {
blockMessageById(idOrIds) {
const idsToBlock = Array.isArray(idOrIds) ? idOrIds : [idOrIds];
await this.setState(state => {
return this.setState(state => {
const messageBlockList = [...state.messageBlockList];
const messageImpressions = { ...state.messageImpressions };
@ -1642,9 +1605,6 @@ class _ASRouter {
addonInstallObs,
"webextension-install-notify"
);
this.messageChannel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
type: "CLEAR_INTERRUPT",
});
};
Services.obs.addObserver(addonInstallObs, "webextension-install-notify");
}
@ -1684,15 +1644,12 @@ class _ASRouter {
}
// To be passed to ASRouterTriggerListeners
async _triggerHandler(target, trigger) {
_triggerHandler(browser, trigger) {
// Disable ASRouterTriggerListeners in kiosk mode.
if (BrowserHandler.kiosk) {
return;
return Promise.resolve();
}
await this.onMessage({
target,
data: { type: "TRIGGER", data: { trigger } },
});
return this.sendTriggerMessage({ ...trigger, browser });
}
_removePreviewEndpoint(state) {
@ -1700,16 +1657,13 @@ class _ASRouter {
return state;
}
async _addPreviewEndpoint(url, portID) {
addPreviewEndpoint(url) {
// When you view a preview snippet we want to hide all real content
const providers = [...this.state.providers];
if (
this._validPreviewEndpoint(url) &&
!providers.find(p => p.url === url)
) {
this.dispatchToAS(
ac.OnlyToOneContent({ type: at.SNIPPETS_PREVIEW_MODE }, portID)
);
providers.push({
id: "preview",
type: "remote",
@ -1717,8 +1671,9 @@ class _ASRouter {
url,
updateCycleInMs: 0,
});
await this.setState({ providers });
return this.setState({ providers });
}
return Promise.resolve();
}
/**
@ -1763,67 +1718,16 @@ class _ASRouter {
// Clear and refresh Attribution, and then fetch the messages again to update
AttributionCode._clearCache();
await AttributionCode.getAttrDataAsync();
this._updateMessageProviders();
await this.loadMessagesFromAllProviders();
await this._updateMessageProviders();
return this.loadMessagesFromAllProviders();
}
/**
* sendAsyncMessageToPreloaded - Sends an action to each preloaded browser, if any
*
* @param {obj} action An action to be sent to content
*/
sendAsyncMessageToPreloaded(action) {
const preloadedBrowsers = this.getPreloadedBrowser();
if (preloadedBrowsers) {
for (let preloadedBrowser of preloadedBrowsers) {
try {
preloadedBrowser.sendAsyncMessage(OUTGOING_MESSAGE_NAME, action);
} catch (e) {
// The preloaded page is no longer available, so just ignore.
}
}
}
}
/**
* getPreloadedBrowser - Retrieve the port of any preloaded browsers
*
* @return {Array|null} An array of ports belonging to the preloaded browsers, or null
* if there aren't any preloaded browsers
*/
getPreloadedBrowser() {
let preloadedPorts = [];
for (let port of this.messageChannel.messagePorts) {
if (this.isPreloadedBrowser(port.browser)) {
preloadedPorts.push(port);
}
}
return preloadedPorts.length ? preloadedPorts : null;
}
/**
* isPreloadedBrowser - Returns true if the passed browser has been preloaded
* for faster rendering of new tabs.
*
* @param {<browser>} A <browser> to check.
* @return {boolean} True if the browser is preloaded.
* False if there aren't any preloaded browsers
*/
isPreloadedBrowser(browser) {
return browser.getAttribute("preloadedState") === "preloaded";
}
dispatch(action, target) {
this.onMessage({ data: action, target });
}
async sendNewTabMessage(target, options = {}) {
const { endpoint } = options;
async sendNewTabMessage({ endpoint, tabId, browser }) {
let message;
// Load preview endpoint for snippets if one is sent
if (endpoint) {
await this._addPreviewEndpoint(endpoint.url, target.portID);
await this.addPreviewEndpoint(endpoint.url);
}
// Load all messages
@ -1839,7 +1743,7 @@ class _ASRouter {
}));
}
} else {
const telemetryObject = { port: target.portID };
const telemetryObject = { tabId };
TelemetryStopwatch.start("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
// On new tab, send cards if they match and not part of default multistage onboarding experience;
// othwerise send a snippet
@ -1855,7 +1759,7 @@ class _ASRouter {
TelemetryStopwatch.finish("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
}
await this._sendMessageToTarget(message, target);
return this.sendMessage(message, undefined, false, browser);
}
_recordReachEvent(message) {
@ -1872,10 +1776,15 @@ class _ASRouter {
);
}
async sendTriggerMessage(target, trigger) {
async sendTriggerMessage({ tabId, browser, ...trigger }) {
await this.loadMessagesFromAllProviders();
const telemetryObject = { port: target.portID };
if (trigger.id === "firstRun") {
// On about welcome, set trailhead message seen on receiving firstrun trigger
await this.setTrailHeadMessageSeen();
}
const telemetryObject = { tabId };
TelemetryStopwatch.start("MS_MESSAGE_REQUEST_TIME_MS", telemetryObject);
// Return all the messages so that it can record the Reach event
const messages =
@ -1900,11 +1809,11 @@ class _ASRouter {
nonReachMessages.push(message);
}
}
await this._sendMessageToTarget(
return this.sendMessage(
nonReachMessages[0] || null,
target,
trigger
trigger,
false,
browser
);
}
@ -1916,142 +1825,6 @@ class _ASRouter {
async forceWNPanel(browserWindow) {
await ToolbarPanelHub.enableToolbarButton();
browserWindow.PanelUI.showSubView(
"PanelUI-whatsNew",
browserWindow.document.getElementById("whats-new-menu-button")
);
}
async onMessage({ data: action, target }) {
switch (action.type) {
case "USER_ACTION":
// This is to support ReturnToAMO
if (action.data.type === "INSTALL_ADDON_FROM_URL") {
this._updateOnboardingState();
}
await SpecialMessageActions.handleAction(action.data, target.browser);
break;
case "NEWTAB_MESSAGE_REQUEST":
await this.waitForInitialized;
await this.sendNewTabMessage(target, action.data);
break;
case "TRIGGER":
await this.waitForInitialized;
await this.sendTriggerMessage(
target,
action.data && action.data.trigger
);
break;
case "BLOCK_MESSAGE_BY_ID":
await this.blockMessageById(action.data.id);
// Block the message but don't dismiss it in case the action taken has
// another state that needs to be visible
if (action.data.preventDismiss) {
break;
}
const outgoingMessage = {
type: "CLEAR_MESSAGE",
data: { id: action.data.id },
};
if (action.data.preloadedOnly) {
this.sendAsyncMessageToPreloaded(outgoingMessage);
} else {
this.messageChannel.sendAsyncMessage(
OUTGOING_MESSAGE_NAME,
outgoingMessage
);
}
break;
case "MODIFY_MESSAGE_JSON":
await this.modifyMessageJson(action.data.content, target, true, action);
break;
case "DISMISS_MESSAGE_BY_ID":
this.messageChannel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
type: "CLEAR_MESSAGE",
data: { id: action.data.id },
});
break;
case "BLOCK_BUNDLE":
await this.blockMessageById(action.data.bundle.map(b => b.id));
this.messageChannel.sendAsyncMessage(OUTGOING_MESSAGE_NAME, {
type: "CLEAR_BUNDLE",
});
break;
case "UNBLOCK_MESSAGE_BY_ID":
this.unblockMessageById(action.data.id);
break;
case "UNBLOCK_BUNDLE":
await this.setState(state => {
const messageBlockList = [...state.messageBlockList];
for (let message of action.data.bundle) {
messageBlockList.splice(messageBlockList.indexOf(message.id), 1);
}
this._storage.set("messageBlockList", messageBlockList);
return { messageBlockList };
});
break;
case "OVERRIDE_MESSAGE":
await this.setMessageById(action.data.id, target, true, action);
break;
case "ADMIN_CONNECT_STATE":
if (action.data && action.data.endpoint) {
this._addPreviewEndpoint(action.data.endpoint.url, target.portID);
await this.loadMessagesFromAllProviders();
} else {
await this._updateAdminState(target);
}
break;
case "IMPRESSION":
await this.addImpression(action.data);
break;
case "DOORHANGER_TELEMETRY":
case "TOOLBAR_BADGE_TELEMETRY":
case "TOOLBAR_PANEL_TELEMETRY":
case "MOMENTS_PAGE_TELEMETRY":
if (this.dispatchToAS) {
this.dispatchToAS(ac.ASRouterUserEvent(action.data));
}
break;
case "EXPIRE_QUERY_CACHE":
QueryCache.expireAll();
break;
case "ENABLE_PROVIDER":
ASRouterPreferences.enableOrDisableProvider(action.data, true);
break;
case "DISABLE_PROVIDER":
ASRouterPreferences.enableOrDisableProvider(action.data, false);
break;
case "RESET_PROVIDER_PREF":
ASRouterPreferences.resetProviderPref();
break;
case "SET_PROVIDER_USER_PREF":
ASRouterPreferences.setUserPreference(
action.data.id,
action.data.value
);
break;
case "RESET_GROUPS_STATE":
await this.resetGroupsState(action.data);
await this.loadMessagesFromAllProviders();
break;
case "EVALUATE_JEXL_EXPRESSION":
this.evaluateExpression(target, action.data);
break;
case "FORCE_ATTRIBUTION":
this.forceAttribution(action.data);
break;
case "FORCE_WHATSNEW_PANEL":
this.forceWNPanel(target.browser.ownerGlobal);
break;
case "RENDER_WHATSNEW_MESSAGES":
this.renderWNMessages(target.browser.ownerGlobal, action.data);
break;
default:
Cu.reportError("Unknown message received");
break;
}
}
}
this._ASRouter = _ASRouter;

Просмотреть файл

@ -0,0 +1,62 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["ASRouterDefaultConfig"];
const { ASRouter } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouter.jsm"
);
const { ASRouterTelemetry } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouterTelemetry.jsm"
);
const { ASRouterParentProcessMessageHandler } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouterParentProcessMessageHandler.jsm"
);
const { SpecialMessageActions } = ChromeUtils.import(
"resource://messaging-system/lib/SpecialMessageActions.jsm"
);
const { ASRouterPreferences } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouterPreferences.jsm"
);
const { QueryCache } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouterTargeting.jsm"
);
const { ActivityStreamStorage } = ChromeUtils.import(
"resource://activity-stream/lib/ActivityStreamStorage.jsm"
);
const createStorage = async () => {
const dbStore = new ActivityStreamStorage({
storeNames: ["snippets"],
});
// Accessing the db causes the object stores to be created / migrated.
// This needs to happen before other instances try to access the db, which
// would update only a subset of the stores to the latest version.
try {
await dbStore.db; // eslint-disable-line no-unused-expressions
} catch (e) {
return Promise.reject(e);
}
return dbStore.getDbTable("snippets");
};
const ASRouterDefaultConfig = () => {
const router = ASRouter;
const telemetry = new ASRouterTelemetry();
const messageHandler = new ASRouterParentProcessMessageHandler({
router,
preferences: ASRouterPreferences,
specialMessageActions: SpecialMessageActions,
queryCache: QueryCache,
sendTelemetry: telemetry.sendTelemetry,
});
return {
router,
messageHandler,
createStorage,
};
};

Просмотреть файл

@ -1,50 +0,0 @@
/* 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/. */
const { actionTypes: at } = ChromeUtils.import(
"resource://activity-stream/common/Actions.jsm"
);
const { ASRouter } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouter.jsm"
);
/**
* @class ASRouterFeed - Connects ASRouter singleton (see above) to Activity Stream's
* store so that it can use the RemotePageManager.
*/
class ASRouterFeed {
constructor(options = {}) {
this.router = options.router || ASRouter;
}
async enable() {
if (!this.router.initialized) {
await this.router.init(
this.store._messageChannel.channel,
this.store.dbStorage.getDbTable("snippets"),
this.store.dispatch
);
}
}
disable() {
if (this.router.initialized) {
this.router.uninit();
}
}
onAction(action) {
switch (action.type) {
case at.INIT:
this.enable();
break;
case at.UNINIT:
this.disable();
break;
}
}
}
this.ASRouterFeed = ASRouterFeed;
const EXPORTED_SYMBOLS = ["ASRouterFeed"];

Просмотреть файл

@ -0,0 +1,116 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["ASRouterNewTabHook"];
class ASRouterNewTabHookInstance {
constructor() {
this._newTabMessageHandler = null;
this._parentProcessMessageHandler = null;
this._router = null;
this._clearChildMessages = (...params) =>
this._newTabMessageHandler === null
? Promise.resolve()
: this._newTabMessageHandler.clearChildMessages(...params);
this._updateAdminState = (...params) =>
this._newTabMessageHandler === null
? Promise.resolve()
: this._newTabMessageHandler.updateAdminState(...params);
}
/**
* Params:
* object - {
* messageHandler: message handler for parent process messages
* {
* handleCFRAction: Responds to CFR action and returns a Promise
* handleTelemetry: Logs telemetry events and returns nothing
* },
* router: ASRouter instance
* createStorage: function to create DB storage for ASRouter
* }
*/
async initialize({ messageHandler, router, createStorage }) {
this._parentProcessMessageHandler = messageHandler;
this._router = router;
if (!this._router.initialized) {
const storage = await createStorage();
await this._router.init({
storage,
sendTelemetry: this._parentProcessMessageHandler.handleTelemetry,
dispatchCFRAction: this._parentProcessMessageHandler.handleCFRAction,
clearChildMessages: this._clearChildMessages,
updateAdminState: this._updateAdminState,
});
}
}
/**
* Note: Should only ever be called on an initialized instance
*/
destroy() {
this.disconnect();
this._router.uninit();
}
/**
* Connects new tab message handler to hook.
* Note: Should only ever be called on an initialized instance
* Params:
* newTabMessageHandler - {
* clearChildMessages: clears child messages and returns Promise
* updateAdminState: updates admin state and returns Promise
* }
* Returns: parentProcessMessageHandler
*/
connect(newTabMessageHandler) {
this._newTabMessageHandler = newTabMessageHandler;
return this._parentProcessMessageHandler;
}
/**
* Disconnects new tab message handler from hook.
* Note: Should only ever be called on an initialized instance
*/
disconnect() {
this._newTabMessageHandler = null;
}
}
class AwaitSingleton {
constructor() {
this.instance = null;
const initialized = new Promise(resolve => {
this.setInstance = instance => {
this.setInstance = () => {};
this.instance = instance;
resolve(instance);
};
});
this.getInstance = () => initialized;
}
}
const ASRouterNewTabHook = (() => {
const singleton = new AwaitSingleton();
const instance = new ASRouterNewTabHookInstance();
return {
getInstance: singleton.getInstance,
/**
* Param:
* params - see ASRouterNewTabHookInstance.init
*/
createInstance: async params => {
await instance.initialize(params);
singleton.setInstance(instance);
},
destroy: () => {
instance.destroy();
},
};
})();

Просмотреть файл

@ -0,0 +1,153 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["ASRouterParentProcessMessageHandler"];
const { MESSAGE_TYPE_HASH: msg } = ChromeUtils.import(
"resource://activity-stream/common/ActorConstants.jsm"
);
class ASRouterParentProcessMessageHandler {
constructor({
router,
preferences,
specialMessageActions,
queryCache,
sendTelemetry,
}) {
this._router = router;
this._preferences = preferences;
this._specialMessageActions = specialMessageActions;
this._queryCache = queryCache;
this.handleTelemetry = sendTelemetry;
this.handleMessage = this.handleMessage.bind(this);
this.handleCFRAction = this.handleCFRAction.bind(this);
}
handleCFRAction({ type, data }, browser) {
switch (type) {
case msg.AS_ROUTER_TELEMETRY_USER_EVENT:
case msg.TOOLBAR_BADGE_TELEMETRY:
case msg.TOOLBAR_PANEL_TELEMETRY:
case msg.MOMENTS_PAGE_TELEMETRY:
case msg.DOORHANGER_TELEMETRY: {
return this.handleTelemetry({ type, data });
}
default: {
return this.handleMessage(type, data, { browser });
}
}
}
handleMessage(name, data, { id: tabId, browser } = { browser: null }) {
switch (name) {
case msg.BLOCK_MESSAGE_BY_ID: {
// Block the message but don't dismiss it in case the action taken has
// another state that needs to be visible
return this._router
.blockMessageById(data.id)
.then(() => !data.preventDismiss);
}
case msg.USER_ACTION: {
// This is to support ReturnToAMO
if (data.type === "INSTALL_ADDON_FROM_URL") {
this._router._updateOnboardingState();
}
return this._specialMessageActions.handleAction(data, browser);
}
case msg.IMPRESSION: {
return this._router.addImpression(data);
}
case msg.TRIGGER: {
return this._router.sendTriggerMessage({
...(data && data.trigger),
tabId,
browser,
});
}
case msg.NEWTAB_MESSAGE_REQUEST: {
return this._router.sendNewTabMessage({
...data,
tabId,
browser,
});
}
// ADMIN Messages
case msg.ADMIN_CONNECT_STATE: {
if (data && data.endpoint) {
return this._router
.addPreviewEndpoint(data.endpoint.url)
.then(() => this._router.loadMessagesFromAllProviders());
}
return this._router.updateTargetingParameters();
}
case msg.UNBLOCK_MESSAGE_BY_ID: {
return this._router.unblockMessageById(data.id);
}
case msg.UNBLOCK_ALL: {
return this._router.unblockAll();
}
case msg.BLOCK_BUNDLE: {
return this._router.blockMessageById(data.bundle.map(b => b.id));
}
case msg.UNBLOCK_BUNDLE: {
return this._router.setState(state => {
const messageBlockList = [...state.messageBlockList];
for (let message of data.bundle) {
messageBlockList.splice(messageBlockList.indexOf(message.id), 1);
}
this._router._storage.set("messageBlockList", messageBlockList);
return { messageBlockList };
});
}
case msg.DISABLE_PROVIDER: {
this._preferences.enableOrDisableProvider(data, false);
return Promise.resolve();
}
case msg.ENABLE_PROVIDER: {
this._preferences.enableOrDisableProvider(data, true);
return Promise.resolve();
}
case msg.EVALUATE_JEXL_EXPRESSION: {
return this._router.evaluateExpression(data);
}
case msg.EXPIRE_QUERY_CACHE: {
this._queryCache.expireAll();
return Promise.resolve();
}
case msg.FORCE_ATTRIBUTION: {
return this._router.forceAttribution(data);
}
case msg.FORCE_WHATSNEW_PANEL: {
return this._router.forceWNPanel(browser);
}
case msg.MODIFY_MESSAGE_JSON: {
return this._router.sendMessage(data.content, data, false, browser);
}
case msg.OVERRIDE_MESSAGE: {
return this._router.setMessageById(data, true, browser);
}
case msg.RESET_PROVIDER_PREF: {
this._preferences.resetProviderPref();
return Promise.resolve();
}
case msg.SET_PROVIDER_USER_PREF: {
this._preferences.setUserPreference(data.id, data.value);
return Promise.resolve();
}
case msg.RESET_GROUPS_STATE: {
return this._router
.resetGroupsState(data)
.then(() => this._router.loadMessagesFromAllProviders());
}
default: {
return Promise.reject(new Error(`Unknown message received: ${name}`));
}
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* 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/. */
"use strict";
const EXPORTED_SYMBOLS = ["ASRouterTelemetry"];
ChromeUtils.defineModuleGetter(
this,
"TelemetryFeed",
"resource://activity-stream/lib/TelemetryFeed.jsm"
);
class ASRouterTelemetry {
constructor(options) {
this.telemetryFeed = new TelemetryFeed(options);
this.sendTelemetry = this.sendTelemetry.bind(this);
}
sendTelemetry(action) {
return this.telemetryFeed.onAction(action);
}
}

Просмотреть файл

@ -98,11 +98,6 @@ ChromeUtils.defineModuleGetter(
"HighlightsFeed",
"resource://activity-stream/lib/HighlightsFeed.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"ASRouterFeed",
"resource://activity-stream/lib/ASRouterFeed.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"DiscoveryStreamFeed",
@ -653,12 +648,6 @@ const FEEDS_DATA = [
title: "Queries places and gets metadata for Top Sites section",
value: true,
},
{
name: "asrouterfeed",
factory: () => new ASRouterFeed(),
title: "Handles AS Router messages, such as snippets and onboaridng",
value: true,
},
{
name: "recommendationproviderswitcher",
factory: () => new RecommendationProviderSwitcher(),

Просмотреть файл

@ -4,6 +4,8 @@
"use strict";
// TODO delete this?
ChromeUtils.defineModuleGetter(
this,
"AboutNewTab",

Просмотреть файл

@ -25,7 +25,7 @@ class _BookmarkPanelHub {
this._trigger = { id: "bookmark-panel" };
this._handleMessageRequest = null;
this._addImpression = null;
this._dispatch = null;
this._sendTelemetry = null;
this._initialized = false;
this._response = null;
this._l10n = null;
@ -39,12 +39,12 @@ class _BookmarkPanelHub {
/**
* @param {function} handleMessageRequest
* @param {function} addImpression
* @param {function} dispatch - Used for sending user telemetry information
* @param {function} sendTelemetry - Used for sending user telemetry information
*/
init(handleMessageRequest, addImpression, dispatch) {
init(handleMessageRequest, addImpression, sendTelemetry) {
this._handleMessageRequest = handleMessageRequest;
this._addImpression = addImpression;
this._dispatch = dispatch;
this._sendTelemetry = sendTelemetry;
this._l10n = new DOMLocalization([]);
this._initialized = true;
}
@ -54,7 +54,7 @@ class _BookmarkPanelHub {
this._initialized = false;
this._handleMessageRequest = null;
this._addImpression = null;
this._dispatch = null;
this._sendTelemetry = null;
this._response = null;
}
@ -272,9 +272,9 @@ class _BookmarkPanelHub {
this._response = null;
}
_forceShowMessage(target, message) {
const doc = target.browser.ownerGlobal.gBrowser.ownerDocument;
const win = target.browser.ownerGlobal.window;
forceShowMessage(browser, message) {
const doc = browser.ownerGlobal.gBrowser.ownerDocument;
const win = browser.ownerGlobal.window;
const panelTarget = {
container: doc.getElementById("editBookmarkPanelRecommendation"),
infoButton: doc.getElementById("editBookmarkPanelInfoButton"),
@ -305,7 +305,7 @@ class _BookmarkPanelHub {
win.ownerGlobal.gBrowser.selectedBrowser
)
) {
this._sendTelemetry({
this._sendPing({
message_id: this._response.id,
bucket_id: this._response.id,
event,
@ -313,8 +313,8 @@ class _BookmarkPanelHub {
}
}
_sendTelemetry(ping) {
this._dispatch({
_sendPing(ping) {
this._sendTelemetry({
type: "DOORHANGER_TELEMETRY",
data: { action: "cfr_user_event", source: "CFR", ...ping },
});

Просмотреть файл

@ -1048,7 +1048,7 @@ const CFR_MESSAGES = [
const CFRMessageProvider = {
getMessages() {
return CFR_MESSAGES.filter(msg => !msg.exclude);
return Promise.resolve(CFR_MESSAGES.filter(msg => !msg.exclude));
},
};
this.CFRMessageProvider = CFRMessageProvider;

Просмотреть файл

@ -65,7 +65,7 @@ let PageActionMap = new WeakMap();
* We need one PageAction for each window
*/
class PageAction {
constructor(win, dispatchToASRouter) {
constructor(win, dispatchCFRAction) {
this.window = win;
this.urlbar = win.gURLBar; // The global URLBar object
@ -79,7 +79,7 @@ class PageAction {
// This should NOT be use directly to dispatch message-defined actions attached to buttons.
// Please use dispatchUserAction instead.
this._dispatchToASRouter = dispatchToASRouter;
this._dispatchCFRAction = dispatchCFRAction;
this._popupStateChange = this._popupStateChange.bind(this);
this._collapse = this._collapse.bind(this);
@ -283,25 +283,25 @@ class PageAction {
}
dispatchUserAction(action) {
this._dispatchToASRouter(
this._dispatchCFRAction(
{ type: "USER_ACTION", data: action },
{ browser: this.window.gBrowser.selectedBrowser }
this.window.gBrowser.selectedBrowser
);
}
_dispatchImpression(message) {
this._dispatchToASRouter({ type: "IMPRESSION", data: message });
this._dispatchCFRAction({ type: "IMPRESSION", data: message });
}
_sendTelemetry(ping) {
this._dispatchToASRouter({
this._dispatchCFRAction({
type: "DOORHANGER_TELEMETRY",
data: { action: "cfr_user_event", source: "CFR", ...ping },
});
}
_blockMessage(messageID) {
this._dispatchToASRouter({
this._dispatchCFRAction({
type: "BLOCK_MESSAGE_BY_ID",
data: { id: messageID },
});
@ -915,7 +915,7 @@ class PageAction {
_executeNotifierAction(browser, message) {
switch (message.content.layout) {
case "chiclet_open_url":
this._dispatchToASRouter(
this._dispatchCFRAction(
{
type: "USER_ACTION",
data: {
@ -1074,21 +1074,21 @@ const CFRPageActions = {
* Force a recommendation to be shown. Should only happen via the Admin page.
* @param browser The browser for the recommendation
* @param recommendation The recommendation to show
* @param dispatchToASRouter A function to dispatch resulting actions to
* @param dispatchCFRAction A function to dispatch resulting actions to
* @return Did adding the recommendation succeed?
*/
async forceRecommendation(browser, recommendation, dispatchToASRouter) {
async forceRecommendation(browser, recommendation, dispatchCFRAction) {
// If we are forcing via the Admin page, the browser comes in a different format
const win = browser.browser.ownerGlobal;
const win = browser.ownerGlobal;
const { id, content, personalizedModelVersion } = recommendation;
RecommendationMap.set(browser.browser, {
RecommendationMap.set(browser, {
id,
content,
retain: true,
modelVersion: personalizedModelVersion,
});
if (!PageActionMap.has(win)) {
PageActionMap.set(win, new PageAction(win, dispatchToASRouter));
PageActionMap.set(win, new PageAction(win, dispatchCFRAction));
}
if (content.skip_address_bar_notifier) {
@ -1110,10 +1110,10 @@ const CFRPageActions = {
* @param browser The browser for the recommendation
* @param host The host for the recommendation
* @param recommendation The recommendation to show
* @param dispatchToASRouter A function to dispatch resulting actions to
* @param dispatchCFRAction A function to dispatch resulting actions to
* @return Did adding the recommendation succeed?
*/
async addRecommendation(browser, host, recommendation, dispatchToASRouter) {
async addRecommendation(browser, host, recommendation, dispatchCFRAction) {
const win = browser.ownerGlobal;
if (PrivateBrowsingUtils.isWindowPrivate(win)) {
return false;
@ -1138,7 +1138,7 @@ const CFRPageActions = {
modelVersion: personalizedModelVersion,
});
if (!PageActionMap.has(win)) {
PageActionMap.set(win, new PageAction(win, dispatchToASRouter));
PageActionMap.set(win, new PageAction(win, dispatchCFRAction));
}
if (content.skip_address_bar_notifier) {

Просмотреть файл

@ -33,12 +33,12 @@ class _MomentsPageHub {
async init(
waitForInitialized,
{ handleMessageRequest, addImpression, blockMessageById, dispatch }
{ handleMessageRequest, addImpression, blockMessageById, sendTelemetry }
) {
this._handleMessageRequest = handleMessageRequest;
this._addImpression = addImpression;
this._blockMessageById = blockMessageById;
this._dispatch = dispatch;
this._sendTelemetry = sendTelemetry;
// Need to wait for ASRouter to initialize before trying to fetch messages
await waitForInitialized;
@ -55,15 +55,15 @@ class _MomentsPageHub {
this.state = { _intervalId };
}
_sendTelemetry(ping) {
this._dispatch({
_sendPing(ping) {
this._sendTelemetry({
type: "MOMENTS_PAGE_TELEMETRY",
data: { action: "moments_user_event", ...ping },
});
}
sendUserEventTelemetry(message) {
this._sendTelemetry({
this._sendPing({
message_id: message.id,
bucket_id: message.id,
event: "MOMENTS_PAGE_SET",

Просмотреть файл

@ -524,10 +524,12 @@ const MESSAGES = () => [
const PanelTestProvider = {
getMessages() {
return MESSAGES().map(message => ({
return Promise.resolve(
MESSAGES().map(message => ({
...message,
targeting: `providerCohorts.panel_local_testing == "SHOW_TEST"`,
}));
}))
);
},
};
this.PanelTestProvider = PanelTestProvider;

Просмотреть файл

@ -706,7 +706,7 @@ const MESSAGES = () => [
const SnippetsTestMessageProvider = {
getMessages() {
return (
return Promise.resolve(
MESSAGES()
// Ensures we never actually show test except when triggered by debug tools
.map(message => ({

Просмотреть файл

@ -8,6 +8,9 @@ const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { MESSAGE_TYPE_HASH: msg } = ChromeUtils.import(
"resource://activity-stream/common/ActorConstants.jsm"
);
const { actionTypes: at, actionUtils: au } = ChromeUtils.import(
"resource://activity-stream/common/Actions.jsm"
@ -115,15 +118,18 @@ XPCOMUtils.defineLazyGetter(
);
this.TelemetryFeed = class TelemetryFeed {
constructor(options) {
constructor({ isParentProcess = true } = {}) {
this.sessions = new Map();
this._prefs = new Prefs();
this._impressionId = this.getOrCreateImpressionId();
this._aboutHomeSeen = false;
this._classifySite = classifySite;
this._addWindowListeners = this._addWindowListeners.bind(this);
this._browserOpenNewtabStart = null;
this.handleEvent = this.handleEvent.bind(this);
if (isParentProcess && !this._prefs.get(PREF_IMPRESSION_ID)) {
const id = String(gUUIDGenerator.generateUUID());
this._prefs.set(PREF_IMPRESSION_ID, id);
}
}
get telemetryEnabled() {
@ -225,13 +231,8 @@ this.TelemetryFeed = class TelemetryFeed {
return pinnedTabs;
}
getOrCreateImpressionId() {
let impressionId = this._prefs.get(PREF_IMPRESSION_ID);
if (!impressionId) {
impressionId = String(gUUIDGenerator.generateUUID());
this._prefs.set(PREF_IMPRESSION_ID, impressionId);
}
return impressionId;
get _impressionId() {
return this._prefs.get(PREF_IMPRESSION_ID);
}
browserOpenNewtabStart() {
@ -952,6 +953,16 @@ this.TelemetryFeed = class TelemetryFeed {
case at.TELEMETRY_USER_EVENT:
this.handleUserEvent(action);
break;
// The next few action types come from ASRouter, which doesn't use
// Actions from Actions.jsm, but uses these other custom strings.
case msg.TOOLBAR_BADGE_TELEMETRY:
// Intentional fall-through
case msg.TOOLBAR_PANEL_TELEMETRY:
// Intentional fall-through
case msg.MOMENTS_PAGE_TELEMETRY:
// Intentional fall-through
case msg.DOORHANGER_TELEMETRY:
// Intentional fall-through
case at.AS_ROUTER_TELEMETRY_USER_EVENT:
this.handleASRouterUserEvent(action);
break;

Просмотреть файл

@ -30,13 +30,13 @@ class _ToolbarBadgeHub {
this.removeToolbarNotification = this.removeToolbarNotification.bind(this);
this.addToolbarNotification = this.addToolbarNotification.bind(this);
this.registerBadgeToAllWindows = this.registerBadgeToAllWindows.bind(this);
this._sendTelemetry = this._sendTelemetry.bind(this);
this._sendPing = this._sendPing.bind(this);
this.sendUserEventTelemetry = this.sendUserEventTelemetry.bind(this);
this._handleMessageRequest = null;
this._addImpression = null;
this._blockMessageById = null;
this._dispatch = null;
this._sendTelemetry = null;
}
async init(
@ -46,14 +46,14 @@ class _ToolbarBadgeHub {
addImpression,
blockMessageById,
unblockMessageById,
dispatch,
sendTelemetry,
}
) {
this._handleMessageRequest = handleMessageRequest;
this._blockMessageById = blockMessageById;
this._unblockMessageById = unblockMessageById;
this._addImpression = addImpression;
this._dispatch = dispatch;
this._sendTelemetry = sendTelemetry;
// Need to wait for ASRouter to initialize before trying to fetch messages
await waitForInitialized;
this.messageRequest({
@ -267,8 +267,8 @@ class _ToolbarBadgeHub {
}
}
_sendTelemetry(ping) {
this._dispatch({
_sendPing(ping) {
this._sendTelemetry({
type: "TOOLBAR_BADGE_TELEMETRY",
data: { action: "badge_user_event", ...ping },
});
@ -283,7 +283,7 @@ class _ToolbarBadgeHub {
win.ownerGlobal.gBrowser.selectedBrowser
)
) {
this._sendTelemetry({
this._sendPing({
message_id: message.id,
bucket_id: message.id,
event,

Просмотреть файл

@ -59,9 +59,9 @@ class _ToolbarPanelHub {
this.state = {};
}
async init(waitForInitialized, { getMessages, dispatch }) {
async init(waitForInitialized, { getMessages, sendTelemetry }) {
this._getMessages = getMessages;
this._dispatch = dispatch;
this._sendTelemetry = sendTelemetry;
// Wait for ASRouter messages to become available in order to know
// if we can show the What's New panel
await waitForInitialized;
@ -501,8 +501,8 @@ class _ToolbarPanelHub {
}
}
_sendTelemetry(ping) {
this._dispatch({
_sendPing(ping) {
this._sendTelemetry({
type: "TOOLBAR_PANEL_TELEMETRY",
data: { action: "whats-new-panel_user_event", ...ping },
});
@ -516,7 +516,7 @@ class _ToolbarPanelHub {
win.ownerGlobal.gBrowser.selectedBrowser
)
) {
this._sendTelemetry({
this._sendPing({
message_id: message.id,
bucket_id: message.id,
event,
@ -542,10 +542,12 @@ class _ToolbarPanelHub {
const learnMoreLink = doc.querySelector(
"#messaging-system-message-container .text-link"
);
if (learnMoreLink) {
container.toggleAttribute("disabled");
infoButton.toggleAttribute("checked");
panelContainer.toggleAttribute("infoMessageShowing");
learnMoreLink.disabled = !learnMoreLink.disabled;
}
};
if (!container.childElementCount) {
const message = await this._getMessages({
@ -595,8 +597,8 @@ class _ToolbarPanelHub {
* @param {object[]} messages Messages selected from devtools page
*/
forceShowMessage(browser, messages) {
const win = browser.browser.ownerGlobal;
const doc = browser.browser.ownerDocument;
const win = browser.ownerGlobal;
const doc = browser.ownerDocument;
this.removeMessages(win, WHATS_NEW_PANEL_SELECTOR);
this.renderMessages(win, doc, WHATS_NEW_PANEL_SELECTOR, {
force: true,

Просмотреть файл

@ -16,6 +16,11 @@ const SCRIPT_TEMPLATE_RESOURCE_PATH =
let window = self;
window.requestAnimationFrame = () => {};
window.cancelAnimationFrame = () => {};
window.ASRouterMessage = () => {
return Promise.resolve();
};
window.ASRouterAddParentListener = () => {};
window.ASRouterRemoveParentListener = () => {};
importScripts("resource://gre/modules/workers/require.js");

Просмотреть файл

@ -32,6 +32,8 @@ EXTRA_JS_MODULES += [
FINAL_TARGET_FILES.actors += [
'aboutwelcome/AboutWelcomeChild.jsm',
'aboutwelcome/AboutWelcomeParent.jsm',
'actors/ASRouterChild.jsm',
'actors/ASRouterParent.jsm',
]
XPCOM_MANIFESTS += [

Просмотреть файл

@ -18,6 +18,7 @@
},
"devDependencies": {
"@babel/core": "7.8.4",
"@babel/plugin-proposal-class-properties": "7.10.4",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.8.3",
"@babel/plugin-proposal-optional-chaining": "7.9.0",
"@babel/preset-react": "7.8.3",

Просмотреть файл

@ -4,6 +4,8 @@ support-files =
red_page.html
head.js
snippet.json
snippet_below_search_test.json
snippet_simple_test.json
topstories.json
ds_layout.json
prefs =

Просмотреть файл

@ -21,7 +21,7 @@ add_task(async function test_fxa_message_shown() {
BrowserTestUtils.loadURI(browser, testURL);
await BrowserTestUtils.browserLoaded(browser, false, testURL);
const [msg] = PanelTestProvider.getMessages();
const [msg] = await PanelTestProvider.getMessages();
const response = BookmarkPanelHub.onResponse(
msg,
{

Просмотреть файл

@ -200,7 +200,7 @@ function trigger_cfr_panel(
trigger,
recommendation,
// Use the real AS dispatch method to trigger real notifications
ASRouter.dispatch
ASRouter.dispatchCFRAction
);
}
@ -653,10 +653,15 @@ add_task(
"popuphidden"
);
let protectionsTabPromise = BrowserTestUtils.waitForNewTab(gBrowser);
document
.getElementById("contextual-feature-recommendation-notification")
.button.click();
await BrowserTestUtils.removeTab(gBrowser.selectedTab);
let protectionsTab = await protectionsTabPromise;
await BrowserTestUtils.removeTab(protectionsTab);
await hidePanel;
}
);
@ -1000,7 +1005,7 @@ add_task(async function test_providerNames() {
const cfrProviderPrefs = Services.prefs.getChildList(providersBranch);
for (const prefName of cfrProviderPrefs) {
const prefValue = JSON.parse(Services.prefs.getStringPref(prefName));
if (prefValue.id) {
if (prefValue && prefValue.id) {
// Snippets are disabled in tests and value is set to []
Assert.equal(
prefValue.id,
@ -1071,7 +1076,7 @@ add_task(function test_updateCycleForProviders() {
.getChildList("browser.newtabpage.activity-stream.asrouter.providers.")
.forEach(provider => {
const prefValue = JSON.parse(Services.prefs.getStringPref(provider, ""));
if (prefValue.type === "remote-settings") {
if (prefValue && prefValue.type === "remote-settings") {
Assert.ok(prefValue.updateCycleInMs);
}
});
@ -1085,7 +1090,7 @@ add_task(async function test_heartbeat_tactic_2() {
clearNotifications();
});
const msg = CFRMessageProvider.getMessages().find(
const msg = (await CFRMessageProvider.getMessages()).find(
m => m.id === "HEARTBEAT_TACTIC_2"
);
const shown = await CFRPageActions.addRecommendation(
@ -1098,7 +1103,7 @@ add_task(async function test_heartbeat_tactic_2() {
targeting: true,
},
// Use the real AS dispatch method to trigger real notifications
ASRouter.dispatch
ASRouter.dispatchCFRAction
);
Assert.ok(shown, "Heartbeat CFR added");
@ -1109,12 +1114,13 @@ add_task(async function test_heartbeat_tactic_2() {
"Heartbeat button exists"
);
let newTabPromise = BrowserTestUtils.waitForNewTab(
gBrowser,
Services.urlFormatter.formatURL(msg.content.action.url),
true
);
document.getElementById("contextual-feature-recommendation").click();
// This will fail if the URL from the message does not load
await BrowserTestUtils.browserLoaded(
gBrowser.selectedBrowser,
false,
Services.urlFormatter.formatURL(msg.content.action.url)
);
await newTabPromise;
});

Просмотреть файл

@ -16,7 +16,7 @@ const { CFRPageActions } = ChromeUtils.import(
*/
add_task(async function setup() {
const initialMsgCount = ASRouter.state.messages.length;
const heartbeatMsg = CFRMessageProvider.getMessages().find(
const heartbeatMsg = (await CFRMessageProvider.getMessages()).find(
m => m.id === "HEARTBEAT_TACTIC_2"
);
const testMessage = {
@ -129,7 +129,9 @@ add_task(async function test_heartbeat_tactic_2() {
);
await BrowserTestUtils.waitForCondition(
() => ASRouter.state.messageImpressions[msg.id].length === 1,
() =>
ASRouter.state.messageImpressions[msg.id] &&
ASRouter.state.messageImpressions[msg.id].length === 1,
"First impression recorded"
);
@ -145,7 +147,9 @@ add_task(async function test_heartbeat_tactic_2() {
);
await BrowserTestUtils.waitForCondition(
() => ASRouter.state.messageImpressions[msg.id].length === 2,
() =>
ASRouter.state.messageImpressions[msg.id] &&
ASRouter.state.messageImpressions[msg.id].length === 2,
"Second impression recorded"
);
@ -164,6 +168,7 @@ add_task(async function test_heartbeat_tactic_2() {
"Heartbeat button should be hidden"
);
Assert.equal(
ASRouter.state.messageImpressions[msg.id] &&
ASRouter.state.messageImpressions[msg.id].length,
2,
"Number of impressions did not increase"

Просмотреть файл

@ -16,7 +16,7 @@ const { CFRPageActions } = ChromeUtils.import(
*/
add_task(async function setup() {
const initialMsgCount = ASRouter.state.messages.length;
const heartbeatMsg = CFRMessageProvider.getMessages().find(
const heartbeatMsg = (await CFRMessageProvider.getMessages()).find(
m => m.id === "HEARTBEAT_TACTIC_2"
);
const testMessage = {

Просмотреть файл

@ -3,21 +3,19 @@
const { ASRouter } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouter.jsm"
);
const { SnippetsTestMessageProvider } = ChromeUtils.import(
"resource://activity-stream/lib/SnippetsTestMessageProvider.jsm"
);
test_newtab({
async before() {
let data = SnippetsTestMessageProvider.getMessages().find(
m => m.id === "SIMPLE_BELOW_SEARCH_TEST_1"
);
ASRouter.messageChannel.sendAsyncMessage("ASRouter:parent-to-child", {
type: "SET_MESSAGE",
data,
});
add_task(async function some_test() {
ASRouter._validPreviewEndpoint = () => true;
await BrowserTestUtils.withNewTab(
{
gBrowser,
url:
"about:newtab?endpoint=https://example.com/browser/browser/components/newtab/test/browser/snippet_below_search_test.json",
},
test: async function test_simple_below_search_snippet() {
async browser => {
await waitForPreloaded(browser);
const complete = await SpecialPowers.spawn(browser, [], async () => {
// Verify the simple_below_search_snippet renders in container below searchbox
// and nothing is rendered in the footer.
await ContentTaskUtils.waitForCondition(
@ -30,26 +28,31 @@ test_newtab({
is(
0,
content.document.querySelector("#footer-asrouter-container").childNodes
.length,
content.document.querySelector("#footer-asrouter-container")
.childNodes.length,
"Should not find any snippets in the footer container"
);
},
return true;
});
test_newtab({
async before() {
let data = SnippetsTestMessageProvider.getMessages().find(
m => m.id === "SIMPLE_TEST_1"
Assert.ok(complete, "Test complete.");
}
);
ASRouter.messageChannel.sendAsyncMessage("ASRouter:parent-to-child", {
type: "SET_MESSAGE",
data,
});
return data;
add_task(async function some_test() {
await BrowserTestUtils.withNewTab(
{
gBrowser,
url:
"about:newtab?endpoint=https://example.com/browser/browser/components/newtab/test/browser/snippet_simple_test.json",
},
test: async function test_simple_snippet(msg) {
async browser => {
await waitForPreloaded(browser);
const complete = await SpecialPowers.spawn(browser, [], async () => {
const syncLink = "https://www.mozilla.org/en-US/firefox/accounts";
// Verify the simple_snippet renders in the footer and the container below
// searchbox is not rendered.
await ContentTaskUtils.waitForCondition(
@ -69,7 +72,7 @@ test_newtab({
await ContentTaskUtils.waitForCondition(
() =>
content.document.querySelector(
`#footer-asrouter-container .SimpleSnippet a[href='${msg.content.links.syncLink.url}']`
`#footer-asrouter-container .SimpleSnippet a[href='${syncLink}']`
),
"Should render an anchor with the correct href"
);
@ -78,7 +81,13 @@ test_newtab({
!content.document.querySelector(".below-search-snippet"),
"Should not find any snippets below search"
);
},
return true;
});
Assert.ok(complete, "Test complete.");
}
);
});
add_task(async () => {

Просмотреть файл

@ -836,7 +836,7 @@ add_task(async function check_blockedCountByType() {
add_task(async function checkCFRPinnedTabsTargetting() {
const now = Date.now();
const timeMinutesAgo = numMinutes => now - numMinutes * 60 * 1000;
const messages = CFRMessageProvider.getMessages();
const messages = await CFRMessageProvider.getMessages();
const trigger = {
id: "frequentVisits",
context: {
@ -911,7 +911,7 @@ add_task(async function checkPatternMatches() {
});
add_task(async function checkPatternsValid() {
const messages = CFRMessageProvider.getMessages().filter(
const messages = (await CFRMessageProvider.getMessages()).filter(
m => m.trigger.patterns
);
@ -1113,7 +1113,9 @@ add_task(async function check_newTabSettings_webExtension() {
add_task(async function check_openUrlTrigger_context() {
const message = {
...CFRMessageProvider.getMessages().find(m => m.id === "YOUTUBE_ENHANCE_3"),
...(await CFRMessageProvider.getMessages()).find(
m => m.id === "YOUTUBE_ENHANCE_3"
),
targeting: "visitsCount == 3",
};
const trigger = {

Просмотреть файл

@ -0,0 +1,20 @@
{
"messages": [
{
"id": "SIMPLE_BELOW_SEARCH_TEST_1",
"template": "simple_below_search_snippet",
"content": {
"icon": "chrome://branding/content/icon64.png",
"icon_dark_theme": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfjBQ8QDifrKGc/AAABf0lEQVQoz4WRO08UUQCFvztzd1AgG9jRgGwkhEoMIYGSygYt+A00tpZGY0jYxAJKEwkNjX9AK2xACx4dhFiQQCiMMRr2kYXdnQcz7L0z91qAMVac6hTfSU7OgVsk/prtyfSNfRb7ge2cd7dmVucP/wM2lwqVqoyICahRx9Nz71+8AnAAvlTct+dSYDBYcgJ+Fj68XFu/AfamnIoWFoHFYrAUuYMSn55/fAIOxIs1t4MhQpNxRYsUD0ld7r8DCfZph4QecrqkhCREgMLSeISQkAy0UBgE0CYgIkeRA9HdsCQhpEGCxichpItHigEcPH4XJLRbTf8STY0iiiuu60Ifxexx04F0N+aCgJCAhPQmD/cp/RC5A79WvUyhUHSIidAIoESv9VfAhW9n8+XqTCoyMsz1cviMMrGz9BrjAuboYHZajyXCInEocI8yvccbC+0muABanR4/tONjQz3DzgNKtj9sfv66XD9B/3tT9g/akb7h0bJwzxqqmlRHLr4rLPwBlYWoYj77l2AAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDUtMTVUMTY6MTQ6MzkrMDA6MDD5/4XBAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTA1LTE1VDE2OjE0OjM5KzAwOjAwiKI9fQAAAABJRU5ErkJggg==",
"text": "Securely store passwords, bookmarks, and more with a Firefox Account. <syncLink>Sign up</syncLink>",
"links": {
"syncLink": {
"url": "https://www.mozilla.org/en-US/firefox/accounts"
}
},
"block_button_text": "Block"
},
"targeting": "true"
}
]
}

Просмотреть файл

@ -0,0 +1,24 @@
{
"messages": [
{
"id": "SIMPLE_TEST_1",
"template": "simple_snippet",
"campaign": "test_campaign_blocking",
"content": {
"icon": "chrome://branding/content/icon64.png",
"icon_dark_theme": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfjBQ8QDifrKGc/AAABf0lEQVQoz4WRO08UUQCFvztzd1AgG9jRgGwkhEoMIYGSygYt+A00tpZGY0jYxAJKEwkNjX9AK2xACx4dhFiQQCiMMRr2kYXdnQcz7L0z91qAMVac6hTfSU7OgVsk/prtyfSNfRb7ge2cd7dmVucP/wM2lwqVqoyICahRx9Nz71+8AnAAvlTct+dSYDBYcgJ+Fj68XFu/AfamnIoWFoHFYrAUuYMSn55/fAIOxIs1t4MhQpNxRYsUD0ld7r8DCfZph4QecrqkhCREgMLSeISQkAy0UBgE0CYgIkeRA9HdsCQhpEGCxichpItHigEcPH4XJLRbTf8STY0iiiuu60Ifxexx04F0N+aCgJCAhPQmD/cp/RC5A79WvUyhUHSIidAIoESv9VfAhW9n8+XqTCoyMsz1cviMMrGz9BrjAuboYHZajyXCInEocI8yvccbC+0muABanR4/tONjQz3DzgNKtj9sfv66XD9B/3tT9g/akb7h0bJwzxqqmlRHLr4rLPwBlYWoYj77l2AAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDUtMTVUMTY6MTQ6MzkrMDA6MDD5/4XBAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTA1LTE1VDE2OjE0OjM5KzAwOjAwiKI9fQAAAABJRU5ErkJggg==",
"title": "Firefox Account!",
"title_icon": "chrome://branding/content/icon16.png",
"title_icon_dark_theme": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElNRQfjBQ8QDifrKGc/AAABf0lEQVQoz4WRO08UUQCFvztzd1AgG9jRgGwkhEoMIYGSygYt+A00tpZGY0jYxAJKEwkNjX9AK2xACx4dhFiQQCiMMRr2kYXdnQcz7L0z91qAMVac6hTfSU7OgVsk/prtyfSNfRb7ge2cd7dmVucP/wM2lwqVqoyICahRx9Nz71+8AnAAvlTct+dSYDBYcgJ+Fj68XFu/AfamnIoWFoHFYrAUuYMSn55/fAIOxIs1t4MhQpNxRYsUD0ld7r8DCfZph4QecrqkhCREgMLSeISQkAy0UBgE0CYgIkeRA9HdsCQhpEGCxichpItHigEcPH4XJLRbTf8STY0iiiuu60Ifxexx04F0N+aCgJCAhPQmD/cp/RC5A79WvUyhUHSIidAIoESv9VfAhW9n8+XqTCoyMsz1cviMMrGz9BrjAuboYHZajyXCInEocI8yvccbC+0muABanR4/tONjQz3DzgNKtj9sfv66XD9B/3tT9g/akb7h0bJwzxqqmlRHLr4rLPwBlYWoYj77l2AAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDUtMTVUMTY6MTQ6MzkrMDA6MDD5/4XBAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTA1LTE1VDE2OjE0OjM5KzAwOjAwiKI9fQAAAABJRU5ErkJggg==",
"text": "<syncLink>Sync it, link it, take it with you</syncLink>. All this and more with a Firefox Account.",
"links": {
"syncLink": {
"url": "https://www.mozilla.org/en-US/firefox/accounts"
}
},
"block_button_text": "Block"
},
"targeting": "true"
}
]
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,96 @@
/*eslint max-nested-callbacks: ["error", 10]*/
import { ASRouterChild } from "actors/ASRouterChild.jsm";
import { MESSAGE_TYPE_HASH as msg } from "common/ActorConstants.jsm";
import { GlobalOverrider } from "test/unit/utils";
describe("ASRouterChild", () => {
let asRouterChild = null;
let globals = null;
let overrider = null;
let sandbox = null;
beforeEach(() => {
sandbox = sinon.createSandbox();
globals = {
Cu: {
cloneInto: sandbox.stub().returns(Promise.resolve()),
},
};
overrider = new GlobalOverrider();
overrider.set(globals);
asRouterChild = new ASRouterChild();
asRouterChild.telemetry = {
sendTelemetry: sandbox.stub(),
};
sandbox.stub(asRouterChild, "sendAsyncMessage");
sandbox.stub(asRouterChild, "sendQuery").returns(Promise.resolve());
});
afterEach(() => {
sandbox.restore();
overrider.restore();
asRouterChild = null;
});
describe("asRouterMessage", () => {
describe("sends telemetry types to telemetry", () => {
[
msg.AS_ROUTER_TELEMETRY_USER_EVENT,
msg.TOOLBAR_BADGE_TELEMETRY,
msg.TOOLBAR_PANEL_TELEMETRY,
msg.MOMENTS_PAGE_TELEMETRY,
msg.DOORHANGER_TELEMETRY,
].forEach(type => {
it(`type ${type}`, () => {
asRouterChild.asRouterMessage({
type,
data: {
something: 1,
},
});
sandbox.assert.calledOnce(asRouterChild.telemetry.sendTelemetry);
sandbox.assert.calledWith(asRouterChild.telemetry.sendTelemetry, {
something: 1,
});
});
});
});
describe("uses sendAsyncMessage for types that don't need an async response", () => {
[
msg.DISABLE_PROVIDER,
msg.ENABLE_PROVIDER,
msg.EXPIRE_QUERY_CACHE,
msg.FORCE_WHATSNEW_PANEL,
msg.IMPRESSION,
msg.RESET_PROVIDER_PREF,
msg.SET_PROVIDER_USER_PREF,
msg.USER_ACTION,
].forEach(type => {
it(`type ${type}`, () => {
asRouterChild.asRouterMessage({
type,
data: {
something: 1,
},
});
sandbox.assert.calledOnce(asRouterChild.sendAsyncMessage);
sandbox.assert.calledWith(asRouterChild.sendAsyncMessage, type, {
something: 1,
});
});
});
});
describe("use sends messages that need a response using sendQuery", () => {
it("NEWTAB_MESSAGE_REQUEST", () => {
const type = msg.NEWTAB_MESSAGE_REQUEST;
asRouterChild.asRouterMessage({
type,
data: {
something: 1,
},
});
sandbox.assert.calledOnce(asRouterChild.sendQuery);
sandbox.assert.calledWith(asRouterChild.sendQuery, type, {
something: 1,
});
});
});
});
});

Просмотреть файл

@ -1,109 +0,0 @@
import { _ASRouter, ASRouter } from "lib/ASRouter.jsm";
import { FAKE_LOCAL_PROVIDER, FakeRemotePageManager } from "./constants";
import { ASRouterFeed } from "lib/ASRouterFeed.jsm";
import { actionTypes as at } from "common/Actions.jsm";
import { GlobalOverrider } from "test/unit/utils";
import { ASRouterPreferences } from "lib/ASRouterPreferences.jsm";
describe("ASRouterFeed", () => {
let Router;
let feed;
let channel;
let sandbox;
let storage;
let globals;
let FakeBookmarkPanelHub;
let FakeToolbarBadgeHub;
let FakeToolbarPanelHub;
let FakeMomentsPageHub;
beforeEach(() => {
sandbox = sinon.createSandbox();
globals = new GlobalOverrider();
FakeBookmarkPanelHub = {
init: sandbox.stub(),
uninit: sandbox.stub(),
};
FakeToolbarBadgeHub = {
init: sandbox.stub(),
};
FakeToolbarPanelHub = {
init: sandbox.stub(),
uninit: sandbox.stub(),
};
FakeMomentsPageHub = {
init: sandbox.stub(),
uninit: sandbox.stub(),
};
globals.set({
GroupsConfigurationProvider: { getMessages: () => [] },
ASRouterPreferences,
BookmarkPanelHub: FakeBookmarkPanelHub,
ToolbarBadgeHub: FakeToolbarBadgeHub,
ToolbarPanelHub: FakeToolbarPanelHub,
MomentsPageHub: FakeMomentsPageHub,
SpecialMessageActions: {},
});
Router = new _ASRouter({ providers: [FAKE_LOCAL_PROVIDER] });
storage = {
get: sandbox.stub().returns(Promise.resolve([])),
set: sandbox.stub().returns(Promise.resolve()),
};
feed = new ASRouterFeed({ router: Router }, storage);
channel = new FakeRemotePageManager();
feed.store = {
_messageChannel: { channel },
getState: () => ({}),
dbStorage: { getDbTable: sandbox.stub().returns({}) },
};
});
afterEach(() => {
sandbox.restore();
});
it("should set .router to the ASRouter singleton if none is specified in options", () => {
feed = new ASRouterFeed();
assert.equal(feed.router, ASRouter);
feed = new ASRouterFeed({});
assert.equal(feed.router, ASRouter);
});
describe("#onAction: INIT", () => {
it("should initialize the ASRouter if it is not initialized", () => {
sandbox.stub(feed, "enable");
feed.onAction({ type: at.INIT });
assert.calledOnce(feed.enable);
});
it("should initialize ASRouter", async () => {
sandbox.stub(Router, "init").returns(Promise.resolve());
await feed.enable();
assert.calledWith(Router.init, channel);
assert.calledOnce(feed.store.dbStorage.getDbTable);
assert.calledWithExactly(feed.store.dbStorage.getDbTable, "snippets");
});
it("should not re-initialize the ASRouter if it is already initialized", async () => {
// Router starts initialized
await Router.init(new FakeRemotePageManager(), storage, () => {});
sinon.stub(Router, "init");
// call .onAction with INIT
feed.onAction({ type: at.INIT });
assert.notCalled(Router.init);
});
});
describe("#onAction: UNINIT", () => {
it("should uninitialize the ASRouter", async () => {
await Router.init(new FakeRemotePageManager(), storage, () => {});
sinon.stub(Router, "uninit");
feed.onAction({ type: at.UNINIT });
assert.calledOnce(Router.uninit);
});
});
});

Просмотреть файл

@ -0,0 +1,136 @@
/*eslint max-nested-callbacks: ["error", 10]*/
import { ASRouterNewTabHook } from "lib/ASRouterNewTabHook.jsm";
describe("ASRouterNewTabHook", () => {
let sandbox = null;
let initParams = null;
beforeEach(() => {
sandbox = sinon.createSandbox();
initParams = {
router: {
init: sandbox.stub().resolves(),
uninit: sandbox.stub(),
},
messageHandler: {
handleCFRAction: {},
handleTelemetry: {},
},
createStorage: () => Promise.resolve({}),
};
});
afterEach(() => {
sandbox.restore();
});
describe("ASRouterNewTabHook", () => {
describe("getInstance", () => {
it("awaits createInstance and router init before returning instance", async () => {
const getInstanceCall = sandbox.spy();
const waitForInstance = ASRouterNewTabHook.getInstance().then(
getInstanceCall
);
await ASRouterNewTabHook.createInstance(initParams);
await waitForInstance;
assert.callOrder(initParams.router.init, getInstanceCall);
});
});
describe("createInstance", () => {
it("calls router init", async () => {
await ASRouterNewTabHook.createInstance(initParams);
assert.calledOnce(initParams.router.init);
});
it("only calls router init once", async () => {
initParams.router.init.callsFake(() => {
initParams.router.initialized = true;
});
await ASRouterNewTabHook.createInstance(initParams);
await ASRouterNewTabHook.createInstance(initParams);
assert.calledOnce(initParams.router.init);
});
});
describe("destroy", () => {
it("disconnects new tab, uninits ASRouter, and destroys instance", async () => {
await ASRouterNewTabHook.createInstance(initParams);
const instance = await ASRouterNewTabHook.getInstance();
const destroy = instance.destroy.bind(instance);
sandbox.stub(instance, "destroy").callsFake(destroy);
ASRouterNewTabHook.destroy();
assert.calledOnce(initParams.router.uninit);
assert.calledOnce(instance.destroy);
assert.isNotNull(instance);
assert.isNull(instance._newTabMessageHandler);
});
});
describe("instance", () => {
let routerParams = null;
let messageHandler = null;
let instance = null;
beforeEach(async () => {
messageHandler = {
clearChildMessages: sandbox.stub().resolves(),
updateAdminState: sandbox.stub().resolves(),
};
initParams.router.init.callsFake(params => {
routerParams = params;
});
await ASRouterNewTabHook.createInstance(initParams);
instance = await ASRouterNewTabHook.getInstance();
});
describe("connect", () => {
it("before connection messageHandler methods are not called", async () => {
routerParams.clearChildMessages([1]);
routerParams.updateAdminState({ messages: {} });
assert.notCalled(messageHandler.clearChildMessages);
assert.notCalled(messageHandler.updateAdminState);
});
it("after connect updateAdminState and clearChildMessages calls are forwarded to handler", async () => {
instance.connect(messageHandler);
routerParams.clearChildMessages([1]);
routerParams.updateAdminState({ messages: {} });
assert.called(messageHandler.clearChildMessages);
assert.called(messageHandler.updateAdminState);
});
it("calls from before connection are dropped", async () => {
routerParams.clearChildMessages([1]);
routerParams.updateAdminState({ messages: {} });
instance.connect(messageHandler);
routerParams.clearChildMessages([1]);
routerParams.updateAdminState({ messages: {} });
assert.calledOnce(messageHandler.clearChildMessages);
assert.calledOnce(messageHandler.updateAdminState);
});
});
describe("disconnect", () => {
it("calls after disconnect are dropped", async () => {
instance.connect(messageHandler);
instance.disconnect();
routerParams.clearChildMessages([1]);
routerParams.updateAdminState({ messages: {} });
assert.notCalled(messageHandler.clearChildMessages);
assert.notCalled(messageHandler.updateAdminState);
});
it("only calls from when there is a connection are forwarded", async () => {
routerParams.clearChildMessages([1]);
routerParams.updateAdminState({ messages: {} });
instance.connect(messageHandler);
routerParams.clearChildMessages([200]);
routerParams.updateAdminState({
messages: {
data: "accept",
},
});
instance.disconnect();
routerParams.clearChildMessages([1]);
routerParams.updateAdminState({ messages: {} });
assert.calledOnce(messageHandler.clearChildMessages);
assert.calledOnce(messageHandler.updateAdminState);
assert.calledWith(messageHandler.clearChildMessages, [200]);
assert.calledWith(messageHandler.updateAdminState, {
messages: {
data: "accept",
},
});
});
});
});
});
});

Просмотреть файл

@ -0,0 +1,93 @@
import { ASRouterParent } from "actors/ASRouterParent.jsm";
import { MESSAGE_TYPE_HASH as msg } from "common/ActorConstants.jsm";
describe("ASRouterParent", () => {
let asRouterParent = null;
let sandbox = null;
let handleMessage = null;
let tabs = null;
beforeEach(() => {
sandbox = sinon.createSandbox();
handleMessage = sandbox.stub().resolves("handle-message-result");
ASRouterParent.nextTabId = 1;
const methods = {
destroy: sandbox.stub(),
size: 1,
messageAll: sandbox.stub().resolves(),
messagePreloaded: sandbox.stub().resolves(),
registerActor: sandbox.stub(),
unregisterActor: sandbox.stub(),
loadingMessageHandler: Promise.resolve({
handleMessage,
}),
};
tabs = {
methods,
factory: sandbox.stub().returns(methods),
};
asRouterParent = new ASRouterParent({ tabsFactory: tabs.factory });
ASRouterParent.tabs = tabs.methods;
asRouterParent.browsingContext = {
embedderElement: {
getAttribute: () => true,
},
};
});
afterEach(() => {
sandbox.restore();
asRouterParent = null;
});
describe("actorCreated", () => {
it("after ASRouterTabs is instanced", () => {
asRouterParent.actorCreated();
assert.equal(asRouterParent.tabId, 2);
assert.notCalled(tabs.factory);
assert.calledOnce(tabs.methods.registerActor);
});
it("before ASRouterTabs is instanced", () => {
ASRouterParent.tabs = null;
ASRouterParent.nextTabId = 0;
asRouterParent.actorCreated();
assert.calledOnce(tabs.factory);
assert.isNotNull(ASRouterParent.tabs);
assert.equal(asRouterParent.tabId, 1);
});
});
describe("didDestroy", () => {
it("one still remains", () => {
ASRouterParent.tabs.size = 1;
asRouterParent.didDestroy();
assert.isNotNull(ASRouterParent.tabs);
assert.calledOnce(ASRouterParent.tabs.unregisterActor);
assert.notCalled(ASRouterParent.tabs.destroy);
});
it("none remain", () => {
ASRouterParent.tabs.size = 0;
const tabsCopy = ASRouterParent.tabs;
asRouterParent.didDestroy();
assert.isNull(ASRouterParent.tabs);
assert.calledOnce(tabsCopy.unregisterActor);
assert.calledOnce(tabsCopy.destroy);
});
});
describe("receiveMessage", async () => {
it("passes call to parentProcessMessageHandler and returns the result from handler", async () => {
const result = await asRouterParent.receiveMessage({
name: msg.BLOCK_MESSAGE_BY_ID,
data: { id: 1 },
});
assert.calledOnce(handleMessage);
assert.notCalled(ASRouterParent.tabs.messagePreloaded);
assert.equal(result, "handle-message-result");
});
it("passes preloadedOnly BLOCK_MESSAGE_BY_ID calls to messagePreloaded and returns false", async () => {
const result = await asRouterParent.receiveMessage({
name: msg.BLOCK_MESSAGE_BY_ID,
data: { id: 1, preloadedOnly: true },
});
assert.calledOnce(handleMessage);
assert.calledOnce(ASRouterParent.tabs.messagePreloaded);
assert.equal(result, false);
});
});
});

Просмотреть файл

@ -0,0 +1,388 @@
import { ASRouterParentProcessMessageHandler } from "lib/ASRouterParentProcessMessageHandler.jsm";
import { MESSAGE_TYPE_HASH as msg } from "common/ActorConstants.jsm";
describe("ASRouterParentProcessMessageHandler", () => {
let handler = null;
let sandbox = null;
let config = null;
beforeEach(() => {
sandbox = sinon.createSandbox();
const returnValue = { value: 1 };
const router = {
_storage: {
set: sandbox.stub().resolves(),
get: sandbox.stub().resolves(),
},
_updateOnboardingState: sandbox.stub().resolves(),
addImpression: sandbox.stub().resolves(),
addPreviewEndpoint: sandbox.stub().resolves(),
blockMessageById: sandbox.stub().resolves(returnValue),
evaluateExpression: sandbox.stub().resolves(),
forceAttribution: sandbox.stub().resolves(),
forceWNPanel: sandbox.stub().resolves(),
loadMessagesFromAllProviders: sandbox.stub().resolves(returnValue),
sendNewTabMessage: sandbox.stub().resolves(returnValue),
sendTriggerMessage: sandbox.stub().resolves(returnValue),
sendMessage: sandbox.stub().resolves(returnValue),
setMessageById: sandbox.stub().resolves(returnValue),
resetGroupsState: sandbox.stub().resolves(),
setState: sandbox.stub().callsFake(callback => {
if (typeof callback === "function") {
callback({
messageBlockList: [
{
id: 0,
},
{
id: 1,
},
{
id: 2,
},
{
id: 3,
},
{
id: 4,
},
],
});
}
return Promise.resolve(returnValue);
}),
updateTargetingParameters: sandbox.stub().resolves(returnValue),
unblockMessageById: sandbox.stub().resolves(returnValue),
unblockAll: sandbox.stub().resolves(returnValue),
};
const preferences = {
enableOrDisableProvider: sandbox.stub(),
resetProviderPref: sandbox.stub(),
setUserPreference: sandbox.stub(),
};
const specialMessageActions = {
handleAction: sandbox.stub(),
};
const queryCache = {
expireAll: sandbox.stub(),
};
const sendTelemetry = sandbox.stub();
config = {
router,
preferences,
specialMessageActions,
queryCache,
sendTelemetry,
};
handler = new ASRouterParentProcessMessageHandler(config);
});
afterEach(() => {
sandbox.restore();
handler = null;
config = null;
});
describe("constructor", () => {
it("does not throw", () => {
assert.isNotNull(handler);
assert.isNotNull(config);
});
});
describe("handleCFRAction", () => {
it("non-telemetry type isn't sent to telemetry", () => {
handler.handleCFRAction({
type: msg.BLOCK_MESSAGE_BY_ID,
data: { id: 1 },
});
assert.notCalled(config.sendTelemetry);
assert.calledOnce(config.router.blockMessageById);
});
it("passes browser to handleMessage", async () => {
await handler.handleCFRAction(
{
type: msg.USER_ACTION,
data: { id: 1 },
},
{ ownerGlobal: {} }
);
assert.notCalled(config.sendTelemetry);
assert.calledOnce(config.specialMessageActions.handleAction);
assert.calledWith(
config.specialMessageActions.handleAction,
{ id: 1 },
{ ownerGlobal: {} }
);
});
[
msg.AS_ROUTER_TELEMETRY_USER_EVENT,
msg.TOOLBAR_BADGE_TELEMETRY,
msg.TOOLBAR_PANEL_TELEMETRY,
msg.MOMENTS_PAGE_TELEMETRY,
msg.DOORHANGER_TELEMETRY,
].forEach(type => {
it(`telemetry type "${type}" is sent to telemetry`, () => {
handler.handleCFRAction({
type: msg.AS_ROUTER_TELEMETRY_USER_EVENT,
data: { id: 1 },
});
assert.calledOnce(config.sendTelemetry);
assert.notCalled(config.router.blockMessageById);
});
});
});
describe("handleMessage", () => {
describe("BLOCK_MESSAGE_BY_ID action", () => {
it("with preventDismiss returns false", async () => {
const result = await handler.handleMessage(msg.BLOCK_MESSAGE_BY_ID, {
id: 1,
preventDismiss: true,
});
assert.calledOnce(config.router.blockMessageById);
assert.isFalse(result);
});
it("by default returns true", async () => {
const result = await handler.handleMessage(msg.BLOCK_MESSAGE_BY_ID, {
id: 1,
});
assert.calledOnce(config.router.blockMessageById);
assert.isTrue(result);
});
});
describe("USER_ACTION action", () => {
it("with INSTALL_ADDON_FROM_URL calls _updateOnboardingState", () => {
handler.handleMessage(msg.USER_ACTION, {
type: "INSTALL_ADDON_FROM_URL",
});
assert.calledOnce(config.router._updateOnboardingState);
});
it("default calls SpecialMessageActions.handleAction", async () => {
await handler.handleMessage(
msg.USER_ACTION,
{
type: "SOMETHING",
},
{ browser: { ownerGlobal: {} } }
);
assert.notCalled(config.router._updateOnboardingState);
assert.calledOnce(config.specialMessageActions.handleAction);
assert.calledWith(
config.specialMessageActions.handleAction,
{ type: "SOMETHING" },
{ ownerGlobal: {} }
);
});
});
describe("IMPRESSION action", () => {
it("default calls addImpression", () => {
handler.handleMessage(msg.IMPRESSION, {
id: 1,
});
assert.calledOnce(config.router.addImpression);
});
});
describe("TRIGGER action", () => {
it("default calls sendTriggerMessage and returns state", async () => {
const result = await handler.handleMessage(
msg.TRIGGER,
{
trigger: { stuff: {} },
},
{ id: 100, browser: { ownerGlobal: {} } }
);
assert.calledOnce(config.router.sendTriggerMessage);
assert.calledWith(config.router.sendTriggerMessage, {
stuff: {},
tabId: 100,
browser: { ownerGlobal: {} },
});
assert.deepEqual(result, { value: 1 });
});
});
describe("NEWTAB_MESSAGE_REQUEST action", () => {
it("default calls sendNewTabMessage and returns state", async () => {
const result = await handler.handleMessage(
msg.NEWTAB_MESSAGE_REQUEST,
{
stuff: {},
},
{ id: 100, browser: { ownerGlobal: {} } }
);
assert.calledOnce(config.router.sendNewTabMessage);
assert.calledWith(config.router.sendNewTabMessage, {
stuff: {},
tabId: 100,
browser: { ownerGlobal: {} },
});
assert.deepEqual(result, { value: 1 });
});
});
describe("ADMIN_CONNECT_STATE action", () => {
it("with endpoint url calls addPreviewEndpoint, loadMessagesFromAllProviders, and returns state", async () => {
const result = await handler.handleMessage(msg.ADMIN_CONNECT_STATE, {
endpoint: {
url: "test",
},
});
assert.calledOnce(config.router.addPreviewEndpoint);
assert.calledOnce(config.router.loadMessagesFromAllProviders);
assert.deepEqual(result, { value: 1 });
});
it("default returns state", async () => {
const result = await handler.handleMessage(msg.ADMIN_CONNECT_STATE);
assert.calledOnce(config.router.updateTargetingParameters);
assert.deepEqual(result, { value: 1 });
});
});
describe("UNBLOCK_MESSAGE_BY_ID action", () => {
it("default calls unblockMessageById", async () => {
const result = await handler.handleMessage(msg.UNBLOCK_MESSAGE_BY_ID, {
id: 1,
});
assert.calledOnce(config.router.unblockMessageById);
assert.deepEqual(result, { value: 1 });
});
});
describe("UNBLOCK_ALL action", () => {
it("default calls unblockAll", async () => {
const result = await handler.handleMessage(msg.UNBLOCK_ALL);
assert.calledOnce(config.router.unblockAll);
assert.deepEqual(result, { value: 1 });
});
});
describe("BLOCK_BUNDLE action", () => {
it("default calls unblockMessageById", async () => {
const result = await handler.handleMessage(msg.BLOCK_BUNDLE, {
bundle: [
{
id: 8,
},
{
id: 13,
},
],
});
assert.calledOnce(config.router.blockMessageById);
assert.deepEqual(result, { value: 1 });
});
});
describe("UNBLOCK_BUNDLE action", () => {
it("default calls setState", async () => {
const result = await handler.handleMessage(msg.UNBLOCK_BUNDLE, {
bundle: [
{
id: 1,
},
{
id: 3,
},
],
});
assert.calledOnce(config.router.setState);
assert.deepEqual(result, { value: 1 });
});
});
describe("DISABLE_PROVIDER action", () => {
it("default calls ASRouterPreferences.enableOrDisableProvider", () => {
handler.handleMessage(msg.DISABLE_PROVIDER, {});
assert.calledOnce(config.preferences.enableOrDisableProvider);
});
});
describe("ENABLE_PROVIDER action", () => {
it("default calls ASRouterPreferences.enableOrDisableProvider", () => {
handler.handleMessage(msg.ENABLE_PROVIDER, {});
assert.calledOnce(config.preferences.enableOrDisableProvider);
});
});
describe("EVALUATE_JEXL_EXPRESSION action", () => {
it("default calls evaluateExpression", () => {
handler.handleMessage(msg.EVALUATE_JEXL_EXPRESSION, {});
assert.calledOnce(config.router.evaluateExpression);
});
});
describe("EXPIRE_QUERY_CACHE action", () => {
it("default calls QueryCache.expireAll", () => {
handler.handleMessage(msg.EXPIRE_QUERY_CACHE);
assert.calledOnce(config.queryCache.expireAll);
});
});
describe("FORCE_ATTRIBUTION action", () => {
it("default calls forceAttribution", () => {
handler.handleMessage(msg.FORCE_ATTRIBUTION, {});
assert.calledOnce(config.router.forceAttribution);
});
});
describe("FORCE_WHATSNEW_PANEL action", () => {
it("default calls forceWNPanel", () => {
handler.handleMessage(
msg.FORCE_WHATSNEW_PANEL,
{},
{ browser: { ownerGlobal: {} } }
);
assert.calledOnce(config.router.forceWNPanel);
assert.calledWith(config.router.forceWNPanel, { ownerGlobal: {} });
});
});
describe("MODIFY_MESSAGE_JSON action", () => {
it("default calls sendMessage", async () => {
const result = await handler.handleMessage(
msg.MODIFY_MESSAGE_JSON,
{
content: {
text: "something",
},
},
{ id: 100, browser: { ownerGlobal: {} } }
);
assert.calledOnce(config.router.sendMessage);
assert.calledWith(
config.router.sendMessage,
{ text: "something" },
{ content: { text: "something" } },
false,
{ ownerGlobal: {} }
);
assert.deepEqual(result, { value: 1 });
});
});
describe("OVERRIDE_MESSAGE action", () => {
it("default calls sendMessage", async () => {
const result = await handler.handleMessage(
msg.OVERRIDE_MESSAGE,
{
id: 1,
},
{ id: 100, browser: { ownerGlobal: {} } }
);
assert.calledOnce(config.router.setMessageById);
assert.calledWith(config.router.setMessageById, { id: 1 }, true, {
ownerGlobal: {},
});
assert.deepEqual(result, { value: 1 });
});
});
describe("RESET_PROVIDER_PREF action", () => {
it("default calls ASRouterPreferences.resetProviderPref", () => {
handler.handleMessage(msg.RESET_PROVIDER_PREF);
assert.calledOnce(config.preferences.resetProviderPref);
});
});
describe("SET_PROVIDER_USER_PREF action", () => {
it("default calls ASRouterPreferences.setUserPreference", () => {
handler.handleMessage(msg.SET_PROVIDER_USER_PREF, {
id: 1,
value: true,
});
assert.calledOnce(config.preferences.setUserPreference);
assert.calledWith(config.preferences.setUserPreference, 1, true);
});
});
describe("RESET_GROUPS_STATE action", () => {
it("default calls resetGroupsState, loadMessagesFromAllProviders, and returns state", async () => {
const result = await handler.handleMessage(msg.RESET_GROUPS_STATE, {
property: "value",
});
assert.calledOnce(config.router.resetGroupsState);
assert.calledOnce(config.router.loadMessagesFromAllProviders);
assert.deepEqual(result, { value: 1 });
});
});
});
});

Просмотреть файл

@ -0,0 +1,17 @@
import { ASRouterTelemetry } from "lib/ASRouterTelemetry.jsm";
describe("ASRouterTelemetry", () => {
describe("constructor", () => {
it("does not throw", () => {
const t = new ASRouterTelemetry();
assert.isDefined(t);
});
});
describe("sendTelemetry", () => {
it("does not throw", () => {
const t = new ASRouterTelemetry();
t.sendTelemetry({});
assert.isDefined(t);
});
});
});

Просмотреть файл

@ -1,5 +1,4 @@
import { CFRMessageProvider } from "lib/CFRMessageProvider.jsm";
const messages = CFRMessageProvider.getMessages();
const REGULAR_IDS = [
"FACEBOOK_CONTAINER",
@ -11,6 +10,10 @@ const REGULAR_IDS = [
];
describe("CFRMessageProvider", () => {
let messages;
beforeEach(async () => {
messages = await CFRMessageProvider.getMessages();
});
it("should have a total of 13 messages", () => {
assert.lengthOf(messages, 13);
});

Просмотреть файл

@ -1,3 +1,5 @@
/* eslint max-nested-callbacks: ["error", 100] */
import { CFRPageActions, PageAction } from "lib/CFRPageActions.jsm";
import { FAKE_RECOMMENDATION } from "./constants";
import { GlobalOverrider } from "test/unit/utils";
@ -343,20 +345,20 @@ describe("CFRPageActions", () => {
});
describe("#dispatchUserAction", () => {
it("should call ._dispatchToASRouter with the right action", () => {
it("should call ._dispatchCFRAction with the right action", () => {
const fakeAction = {};
pageAction.dispatchUserAction(fakeAction);
assert.calledOnce(dispatchStub);
assert.calledWith(
dispatchStub,
{ type: "USER_ACTION", data: fakeAction },
{ browser: fakeBrowser }
fakeBrowser
);
});
});
describe("#_dispatchImpression", () => {
it("should call ._dispatchToASRouter with the right action", () => {
it("should call ._dispatchCFRAction with the right action", () => {
pageAction._dispatchImpression("fake impression");
assert.calledWith(dispatchStub, {
type: "IMPRESSION",
@ -366,7 +368,7 @@ describe("CFRPageActions", () => {
});
describe("#_sendTelemetry", () => {
it("should call ._dispatchToASRouter with the right action", () => {
it("should call ._dispatchCFRAction with the right action", () => {
const fakePing = { message_id: 42 };
pageAction._sendTelemetry(fakePing);
assert.calledWith(dispatchStub, {
@ -377,7 +379,7 @@ describe("CFRPageActions", () => {
});
describe("#_blockMessage", () => {
it("should call ._dispatchToASRouter with the right action", () => {
it("should call ._dispatchCFRAction with the right action", () => {
pageAction._blockMessage("fake id");
assert.calledOnce(dispatchStub);
assert.calledWith(dispatchStub, {
@ -656,7 +658,7 @@ describe("CFRPageActions", () => {
type: "USER_ACTION",
data: { id: "primary_action", data: { url: "latest-addon.xpi" } },
},
{ browser: fakeBrowser }
fakeBrowser
);
// Should send telemetry
assert.calledWith(dispatchStub, {
@ -831,10 +833,11 @@ describe("CFRPageActions", () => {
});
});
describe("#_cfrUrlbarButtonClick/cfr_urlbar_chiclet", () => {
const heartbeatRecommendation = CFRMessageProvider.getMessages().find(
let heartbeatRecommendation;
beforeEach(async () => {
heartbeatRecommendation = (await CFRMessageProvider.getMessages()).find(
m => m.template === "cfr_urlbar_chiclet"
);
beforeEach(async () => {
CFRPageActions.PageActionMap.set(fakeBrowser.ownerGlobal, pageAction);
await CFRPageActions.addRecommendation(
fakeBrowser,
@ -985,7 +988,7 @@ describe("CFRPageActions", () => {
it("should succeed and add an element to the RecommendationMap", async () => {
assert.isTrue(
await CFRPageActions.forceRecommendation(
{ browser: fakeBrowser },
fakeBrowser,
fakeRecommendation,
dispatchStub
)
@ -999,13 +1002,13 @@ describe("CFRPageActions", () => {
const win = fakeBrowser.ownerGlobal;
assert.isFalse(CFRPageActions.PageActionMap.has(win));
await CFRPageActions.forceRecommendation(
{ browser: fakeBrowser },
fakeBrowser,
fakeRecommendation,
dispatchStub
);
const pageAction = CFRPageActions.PageActionMap.get(win);
assert.equal(win, pageAction.window);
assert.equal(dispatchStub, pageAction._dispatchToASRouter);
assert.equal(dispatchStub, pageAction._dispatchCFRAction);
assert.calledOnce(PageAction.prototype.showAddressBarNotifier);
});
});
@ -1071,7 +1074,7 @@ describe("CFRPageActions", () => {
);
const pageAction = CFRPageActions.PageActionMap.get(win);
assert.equal(win, pageAction.window);
assert.equal(dispatchStub, pageAction._dispatchToASRouter);
assert.equal(dispatchStub, pageAction._dispatchCFRAction);
assert.calledOnce(PageAction.prototype.showAddressBarNotifier);
});
it("should add the right url if we fetched and addon install URL", async () => {

Просмотреть файл

@ -2,9 +2,12 @@ import { PanelTestProvider } from "lib/PanelTestProvider.jsm";
import schema from "content-src/asrouter/schemas/panel/cfr-fxa-bookmark.schema.json";
import update_schema from "content-src/asrouter/templates/OnboardingMessage/UpdateAction.schema.json";
import whats_new_schema from "content-src/asrouter/templates/OnboardingMessage/WhatsNewMessage.schema.json";
const messages = PanelTestProvider.getMessages();
describe("PanelTestProvider", () => {
let messages;
beforeEach(async () => {
messages = await PanelTestProvider.getMessages();
});
it("should have a message", () => {
// Careful: when changing this number make sure that new messages also go
// through schema verifications.

Просмотреть файл

@ -15,8 +15,8 @@ const schemas = {
simple_below_search_snippet: SimpleBelowSearchSnippetSchema,
};
describe("SnippetsTestMessageProvider", () => {
let messages = SnippetsTestMessageProvider.getMessages();
describe("SnippetsTestMessageProvider", async () => {
let messages = await SnippetsTestMessageProvider.getMessages();
it("should return an array of messages", () => {
assert.isArray(messages);

Просмотреть файл

@ -1,9 +1,6 @@
import {
ASRouterUISurface,
ASRouterUtils,
} from "content-src/asrouter/asrouter-content";
import { ASRouterUISurface } from "content-src/asrouter/asrouter-content";
import { ASRouterUtils } from "content-src/asrouter/asrouter-utils";
import { GlobalOverrider } from "test/unit/utils";
import { OUTGOING_MESSAGE_NAME as AS_GENERAL_OUTGOING_MESSAGE_NAME } from "content-src/lib/init-store";
import { FAKE_LOCAL_MESSAGES } from "./constants";
import React from "react";
import { mount } from "enzyme";
@ -21,39 +18,45 @@ const FAKE_BELOW_SEARCH_SNIPPET = FAKE_LOCAL_MESSAGES.find(
FAKE_MESSAGE = Object.assign({}, FAKE_MESSAGE, { provider: "fakeprovider" });
describe("ASRouterUtils", () => {
let global;
let globalOverrider;
let sandbox;
let fakeSendAsyncMessage;
let globals;
beforeEach(() => {
global = new GlobalOverrider();
globalOverrider = new GlobalOverrider();
sandbox = sinon.createSandbox();
fakeSendAsyncMessage = sandbox.stub();
global.set({ RPMSendAsyncMessage: fakeSendAsyncMessage });
globals = {
ASRouterMessage: sandbox.stub(),
};
globalOverrider.set(globals);
});
afterEach(() => {
sandbox.restore();
global.restore();
globalOverrider.restore();
});
it("should send a message with the right payload data", () => {
ASRouterUtils.sendTelemetry({ id: 1, event: "CLICK" });
assert.calledOnce(fakeSendAsyncMessage);
assert.calledWith(fakeSendAsyncMessage, AS_GENERAL_OUTGOING_MESSAGE_NAME);
const [, payload] = fakeSendAsyncMessage.firstCall.args;
assert.propertyVal(payload.data, "id", 1);
assert.propertyVal(payload.data, "event", "CLICK");
assert.calledOnce(globals.ASRouterMessage);
assert.calledWith(globals.ASRouterMessage, {
type: "AS_ROUTER_TELEMETRY_USER_EVENT",
meta: { from: "ActivityStream:Content", to: "ActivityStream:Main" },
data: {
id: 1,
event: "CLICK",
},
});
});
});
describe("ASRouterUISurface", () => {
let wrapper;
let globalO;
let globalOverrider;
let sandbox;
let headerPortal;
let footerPortal;
let root;
let fakeDocument;
let fetchStub;
let globals;
beforeEach(() => {
sandbox = sinon.createSandbox();
@ -61,11 +64,6 @@ describe("ASRouterUISurface", () => {
footerPortal = document.createElement("div");
root = document.createElement("div");
sandbox.stub(footerPortal, "querySelector").returns(footerPortal);
fetchStub = sandbox.stub(global, "fetch").resolves({
ok: true,
status: 200,
json: () => Promise.resolve({}),
});
fakeDocument = {
location: { href: "" },
_listeners: new Set(),
@ -108,13 +106,18 @@ describe("ASRouterUISurface", () => {
return document.createElement(tag);
},
};
globalO = new GlobalOverrider();
globalO.set({
RPMAddMessageListener: sandbox.stub(),
RPMRemoveMessageListener: sandbox.stub(),
RPMSendAsyncMessage: sandbox.stub(),
});
globals = {
ASRouterMessage: sandbox.stub().resolves(),
ASRouterAddParentListener: sandbox.stub(),
ASRouterRemoveParentListener: sandbox.stub(),
fetch: sandbox.stub().resolves({
ok: true,
status: 200,
json: () => Promise.resolve({}),
}),
};
globalOverrider = new GlobalOverrider();
globalOverrider.set(globals);
sandbox.stub(ASRouterUtils, "sendTelemetry");
wrapper = mount(<ASRouterUISurface document={fakeDocument} />);
@ -122,7 +125,7 @@ describe("ASRouterUISurface", () => {
afterEach(() => {
sandbox.restore();
globalO.restore();
globalOverrider.restore();
});
it("should render the component if a message id is defined", () => {
@ -224,7 +227,7 @@ describe("ASRouterUISurface", () => {
describe("Triplet bundle Card", () => {
it("should send NEW_TAB_MESSAGE_REQUEST if a bundle card id is blocked or cleared", async () => {
sandbox.stub(ASRouterUtils, "sendMessage");
sandbox.stub(ASRouterUtils, "sendMessage").resolves();
const FAKE_TRIPLETS_BUNDLE_1 = [
{
id: "CARD_1",
@ -262,11 +265,11 @@ describe("ASRouterUISurface", () => {
it("should call blockById after CTA link is clicked", () => {
wrapper.setState({ message: FAKE_MESSAGE });
sandbox.stub(ASRouterUtils, "blockById");
sandbox.stub(ASRouterUtils, "blockById").resolves();
wrapper.instance().sendClick({ target: { dataset: { metric: "" } } });
assert.calledOnce(ASRouterUtils.blockById);
assert.calledWithExactly(ASRouterUtils.blockById, FAKE_MESSAGE.id);
assert.calledWith(ASRouterUtils.blockById, FAKE_MESSAGE.id);
});
it("should executeAction if defined on the anchor", () => {
@ -444,7 +447,7 @@ describe("ASRouterUISurface", () => {
describe(".fetchFlowParams", () => {
let dispatchStub;
const assertCalledWithURL = url =>
assert.calledWith(fetchStub, new URL(url).toString(), {
assert.calledWith(globals.fetch, new URL(url).toString(), {
credentials: "omit",
});
beforeEach(() => {
@ -478,7 +481,9 @@ describe("ASRouterUISurface", () => {
});
it("should return flowId, flowBeginTime, deviceId on a 200 response", async () => {
const flowInfo = { flowId: "foo", flowBeginTime: 123, deviceId: "bar" };
fetchStub.withArgs("https://accounts.firefox.com/metrics-flow").resolves({
globals.fetch
.withArgs("https://accounts.firefox.com/metrics-flow")
.resolves({
ok: true,
status: 200,
json: () => Promise.resolve(flowInfo),
@ -488,7 +493,9 @@ describe("ASRouterUISurface", () => {
assert.deepEqual(result, flowInfo);
});
it("should return {} and dispatch a TELEMETRY_UNDESIRED_EVENT on a non-200 response", async () => {
fetchStub.withArgs("https://accounts.firefox.com/metrics-flow").resolves({
globals.fetch
.withArgs("https://accounts.firefox.com/metrics-flow")
.resolves({
ok: false,
status: 400,
statusText: "Client error",
@ -509,7 +516,9 @@ describe("ASRouterUISurface", () => {
);
});
it("should return {} and dispatch a TELEMETRY_UNDESIRED_EVENT on a parsing erorr", async () => {
fetchStub.withArgs("https://accounts.firefox.com/metrics-flow").resolves({
globals.fetch
.withArgs("https://accounts.firefox.com/metrics-flow")
.resolves({
ok: false,
status: 200,
// No json to parse, throws an error
@ -529,7 +538,7 @@ describe("ASRouterUISurface", () => {
describe(".onUserAction", () => {
it("if the action.type is ENABLE_FIREFOX_MONITOR, it should generate the right monitor URL given some flowParams", async () => {
const flowInfo = { flowId: "foo", flowBeginTime: 123, deviceId: "bar" };
fetchStub
globals.fetch
.withArgs(
"https://accounts.firefox.com/metrics-flow?utm_term=avocado"
)

Просмотреть файл

@ -0,0 +1,109 @@
import { ASRouterUtils } from "content-src/asrouter/asrouter-utils";
import { GlobalOverrider } from "test/unit/utils";
describe("ASRouterUtils", () => {
let globals = null;
let overrider = null;
let sandbox = null;
beforeEach(() => {
sandbox = sinon.createSandbox();
globals = {
ASRouterMessage: sandbox.stub().resolves({}),
};
overrider = new GlobalOverrider();
overrider.set(globals);
});
afterEach(() => {
sandbox.restore();
overrider.restore();
});
describe("sendMessage", () => {
it("default", () => {
ASRouterUtils.sendMessage({ foo: "bar" });
assert.calledOnce(globals.ASRouterMessage);
assert.calledWith(globals.ASRouterMessage, { foo: "bar" });
});
it("throws if ASRouterMessage is not defined", () => {
overrider.set("ASRouterMessage", undefined);
assert.throws(() => ASRouterUtils.sendMessage({ foo: "bar" }));
});
});
describe("blockById", () => {
it("default", () => {
ASRouterUtils.blockById(1, { foo: "bar" });
assert.calledWith(
globals.ASRouterMessage,
sinon.match({ data: { foo: "bar", id: 1 } })
);
});
});
describe("modifyMessageJson", () => {
it("default", () => {
ASRouterUtils.modifyMessageJson({ foo: "bar" });
assert.calledWith(
globals.ASRouterMessage,
sinon.match({ data: { content: { foo: "bar" } } })
);
});
});
describe("dismissById", () => {
it("default", () => {
ASRouterUtils.dismissById(1);
assert.calledWith(
globals.ASRouterMessage,
sinon.match({ data: { id: 1 } })
);
});
});
describe("executeAction", () => {
it("default", () => {
ASRouterUtils.executeAction({ foo: "bar" });
assert.calledWith(
globals.ASRouterMessage,
sinon.match({ data: { foo: "bar" } })
);
});
});
describe("unblockById", () => {
it("default", () => {
ASRouterUtils.unblockById(2);
assert.calledWith(
globals.ASRouterMessage,
sinon.match({ data: { id: 2 } })
);
});
});
describe("blockBundle", () => {
it("default", () => {
ASRouterUtils.blockBundle(2);
assert.calledWith(
globals.ASRouterMessage,
sinon.match({ data: { bundle: 2 } })
);
});
});
describe("unblockBundle", () => {
it("default", () => {
ASRouterUtils.unblockBundle(2);
assert.calledWith(
globals.ASRouterMessage,
sinon.match({ data: { bundle: 2 } })
);
});
});
describe("overrideMessage", () => {
it("default", () => {
ASRouterUtils.overrideMessage(12);
assert.calledWith(
globals.ASRouterMessage,
sinon.match({ data: { id: 12 } })
);
});
});
describe("sendTelemetry", () => {
it("default", () => {
ASRouterUtils.sendTelemetry({ foo: "bar" });
assert.calledOnce(globals.ASRouterMessage);
});
});
});

Просмотреть файл

@ -57,7 +57,9 @@ export const FAKE_LOCAL_PROVIDER = {
cohort: 0,
};
export const FAKE_LOCAL_PROVIDERS = {
FAKE_LOCAL_PROVIDER: { getMessages: () => FAKE_LOCAL_MESSAGES },
FAKE_LOCAL_PROVIDER: {
getMessages: () => Promise.resolve(FAKE_LOCAL_MESSAGES),
},
};
export const FAKE_REMOTE_MESSAGES = [

Просмотреть файл

@ -87,8 +87,8 @@ describe("ExtensionDoorhanger", () => {
it("should validate L10N_CONTENT", () => {
assert.jsonSchema(L10N_CONTENT, CFRDoorhangerSchema);
});
it("should validate all messages from CFRMessageProvider", () => {
const messages = CFRMessageProvider.getMessages();
it("should validate all messages from CFRMessageProvider", async () => {
const messages = await CFRMessageProvider.getMessages();
messages.forEach(msg =>
assert.jsonSchema(msg.content, SCHEMAS[msg.template])
);

Просмотреть файл

@ -4,11 +4,8 @@ import React from "react";
import schema from "content-src/asrouter/templates/FXASignupSnippet/FXASignupSnippet.schema.json";
import { SnippetsTestMessageProvider } from "lib/SnippetsTestMessageProvider.jsm";
const DEFAULT_CONTENT = SnippetsTestMessageProvider.getMessages().find(
msg => msg.template === "fxa_signup_snippet"
).content;
describe("FXASignupSnippet", () => {
let DEFAULT_CONTENT;
let sandbox;
function mountAndCheckProps(content = {}) {
@ -30,8 +27,11 @@ describe("FXASignupSnippet", () => {
return comp;
}
beforeEach(() => {
beforeEach(async () => {
sandbox = sinon.createSandbox();
DEFAULT_CONTENT = (await SnippetsTestMessageProvider.getMessages()).find(
msg => msg.template === "fxa_signup_snippet"
).content;
});
afterEach(() => {
sandbox.restore();

Просмотреть файл

@ -4,12 +4,9 @@ import React from "react";
import schema from "content-src/asrouter/templates/NewsletterSnippet/NewsletterSnippet.schema.json";
import { SnippetsTestMessageProvider } from "lib/SnippetsTestMessageProvider.jsm";
const DEFAULT_CONTENT = SnippetsTestMessageProvider.getMessages().find(
msg => msg.template === "newsletter_snippet"
).content;
describe("NewsletterSnippet", () => {
let sandbox;
let DEFAULT_CONTENT;
function mountAndCheckProps(content = {}) {
const props = {
@ -26,8 +23,11 @@ describe("NewsletterSnippet", () => {
return comp;
}
beforeEach(() => {
beforeEach(async () => {
sandbox = sinon.createSandbox();
DEFAULT_CONTENT = (await SnippetsTestMessageProvider.getMessages()).find(
msg => msg.template === "newsletter_snippet"
).content;
});
afterEach(() => {
sandbox.restore();

Просмотреть файл

@ -7,13 +7,6 @@ import {
} from "content-src/asrouter/templates/SendToDeviceSnippet/SendToDeviceSnippet";
import { SnippetsTestMessageProvider } from "lib/SnippetsTestMessageProvider.jsm";
const DEFAULT_CONTENT = SnippetsTestMessageProvider.getMessages().find(
msg => msg.template === "send_to_device_snippet"
).content;
const DEFAULT_SCENE2_CONTENT = SnippetsTestMessageProvider.getMessages().find(
msg => msg.template === "send_to_device_scene2_snippet"
).content;
async function testBodyContains(body, key, value) {
const regex = new RegExp(
`Content-Disposition: form-data; name="${key}"${value}`
@ -42,6 +35,8 @@ describe("SendToDeviceSnippet", () => {
let sandbox;
let fetchStub;
let jsonResponse;
let DEFAULT_CONTENT;
let DEFAULT_SCENE2_CONTENT;
function mountAndCheckProps(content = {}) {
const props = {
@ -58,7 +53,13 @@ describe("SendToDeviceSnippet", () => {
return comp;
}
beforeEach(() => {
beforeEach(async () => {
DEFAULT_CONTENT = (await SnippetsTestMessageProvider.getMessages()).find(
msg => msg.template === "send_to_device_snippet"
).content;
DEFAULT_SCENE2_CONTENT = (
await SnippetsTestMessageProvider.getMessages()
).find(msg => msg.template === "send_to_device_scene2_snippet").content;
sandbox = sinon.createSandbox();
jsonResponse = { status: "ok" };
fetchStub = sandbox

Просмотреть файл

@ -6,17 +6,16 @@ import {
Personalization,
ToggleStoryButton,
} from "content-src/components/ASRouterAdmin/ASRouterAdmin";
import { ASRouterUtils } from "content-src/asrouter/asrouter-utils";
import { GlobalOverrider } from "test/unit/utils";
import React from "react";
import { shallow } from "enzyme";
describe("ASRouterAdmin", () => {
let globals;
let globalOverrider;
let sandbox;
let sendMessageStub;
let addListenerStub;
let removeListenerStub;
let wrapper;
let globals;
let FAKE_PROVIDER_PREF = [
{
enabled: true,
@ -35,46 +34,32 @@ describe("ASRouterAdmin", () => {
},
];
beforeEach(() => {
globals = new GlobalOverrider();
globalOverrider = new GlobalOverrider();
sandbox = sinon.createSandbox();
sendMessageStub = sandbox.stub();
addListenerStub = sandbox.stub();
removeListenerStub = sandbox.stub();
globals.set("RPMSendAsyncMessage", sendMessageStub);
globals.set("RPMAddMessageListener", addListenerStub);
globals.set("RPMRemoveMessageListener", removeListenerStub);
sandbox.stub(ASRouterUtils, "getPreviewEndpoint").returns("foo");
globals = {
ASRouterMessage: sandbox.stub().resolves(),
ASRouterAddParentListener: sandbox.stub(),
ASRouterRemoveParentListener: sandbox.stub(),
};
globalOverrider.set(globals);
wrapper = shallow(
<ASRouterAdminInner collapsed={false} location={{ routes: [""] }} />
);
});
afterEach(() => {
sandbox.restore();
globals.restore();
globalOverrider.restore();
});
it("should render ASRouterAdmin component", () => {
assert.ok(wrapper.exists());
});
it("should send ADMIN_CONNECT_STATE on mount", () => {
assert.calledOnce(sendMessageStub);
assert.propertyVal(
sendMessageStub.firstCall.args[1],
"type",
"ADMIN_CONNECT_STATE"
);
assert.calledOnce(globals.ASRouterMessage);
assert.calledWith(globals.ASRouterMessage, {
type: "ADMIN_CONNECT_STATE",
data: { endpoint: "foo" },
});
it("should set a listener on mount", () => {
assert.calledOnce(addListenerStub);
assert.calledWithExactly(
addListenerStub,
sinon.match.string,
wrapper.instance().onMessage
);
});
it("should remove listener on unmount", () => {
wrapper.unmount();
assert.calledOnce(removeListenerStub);
});
it("should set a .collapsed class on the outer div if props.collapsed is true", () => {
wrapper.setProps({ collapsed: true });
@ -181,6 +166,10 @@ describe("ASRouterAdmin", () => {
});
describe("#renderMessages", () => {
beforeEach(() => {
sandbox.stub(ASRouterUtils, "blockById").resolves();
sandbox.stub(ASRouterUtils, "unblockById").resolves();
sandbox.stub(ASRouterUtils, "overrideMessage").resolves({ foo: "bar" });
sandbox.stub(ASRouterUtils, "sendMessage").resolves();
wrapper.setState({
messageFilter: "all",
messageBlockList: [],
@ -202,17 +191,8 @@ describe("ASRouterAdmin", () => {
assert.lengthOf(wrapper.find(".message-id"), 1);
wrapper.find(".message-item button.primary").simulate("click");
// first call is ADMIN_CONNECT_STATE
assert.propertyVal(
sendMessageStub.secondCall.args[1],
"type",
"BLOCK_MESSAGE_BY_ID"
);
assert.propertyVal(
sendMessageStub.secondCall.args[1].data,
"id",
"foo"
);
assert.calledOnce(ASRouterUtils.blockById);
assert.calledWith(ASRouterUtils.blockById, "foo");
});
it("should render a blocked message", () => {
wrapper.setState({
@ -227,17 +207,8 @@ describe("ASRouterAdmin", () => {
});
assert.lengthOf(wrapper.find(".message-item.blocked"), 1);
wrapper.find(".message-item.blocked button").simulate("click");
// first call is ADMIN_CONNECT_STATE
assert.propertyVal(
sendMessageStub.secondCall.args[1],
"type",
"UNBLOCK_MESSAGE_BY_ID"
);
assert.propertyVal(
sendMessageStub.secondCall.args[1].data,
"id",
"foo"
);
assert.calledOnce(ASRouterUtils.unblockById);
assert.calledWith(ASRouterUtils.unblockById, "foo");
});
it("should render a message if provider matches filter", () => {
wrapper.setState({
@ -253,7 +224,7 @@ describe("ASRouterAdmin", () => {
assert.lengthOf(wrapper.find(".message-id"), 1);
});
it("should override with the selected message", () => {
it("should override with the selected message", async () => {
wrapper.setState({
messageFilter: "messageProvider",
messages: [
@ -267,17 +238,10 @@ describe("ASRouterAdmin", () => {
assert.lengthOf(wrapper.find(".message-id"), 1);
wrapper.find(".message-item button.show").simulate("click");
// first call is ADMIN_CONNECT_STATE
assert.propertyVal(
sendMessageStub.secondCall.args[1],
"type",
"OVERRIDE_MESSAGE"
);
assert.propertyVal(
sendMessageStub.secondCall.args[1].data,
"id",
"foo"
);
assert.calledOnce(ASRouterUtils.overrideMessage);
assert.calledWith(ASRouterUtils.overrideMessage, "foo");
await ASRouterUtils.overrideMessage();
assert.equal(wrapper.state().foo, "bar");
});
it("should hide message if provider filter changes", () => {
wrapper.setState({
@ -336,17 +300,15 @@ describe("ASRouterAdmin", () => {
],
});
wrapper.find(".messages-reset").simulate("click");
assert.propertyVal(
sendMessageStub.secondCall.args[1],
"type",
"DISABLE_PROVIDER"
);
assert.propertyVal(
sendMessageStub.thirdCall.args[1],
"type",
"ENABLE_PROVIDER"
);
assert.calledTwice(ASRouterUtils.sendMessage);
assert.calledWith(ASRouterUtils.sendMessage, {
type: "DISABLE_PROVIDER",
data: "messageProvider",
});
assert.calledWith(ASRouterUtils.sendMessage, {
type: "ENABLE_PROVIDER",
data: "messageProvider",
});
});
});
});

Просмотреть файл

@ -13,6 +13,9 @@ describe("<Base>", () => {
Sections: [],
DiscoveryStream: { config: { enabled: false } },
dispatch: () => {},
adminContent: {
message: {},
},
};
it("should render Base component", () => {
@ -22,8 +25,12 @@ describe("<Base>", () => {
it("should render the BaseContent component, passing through all props", () => {
const wrapper = shallow(<Base {...DEFAULT_PROPS} />);
assert.deepEqual(wrapper.find(BaseContent).props(), DEFAULT_PROPS);
const props = wrapper.find(BaseContent).props();
assert.deepEqual(
props,
DEFAULT_PROPS,
JSON.stringify([props, DEFAULT_PROPS], null, 3)
);
});
it("should render an ErrorBoundary with class base-content-fallback", () => {

Просмотреть файл

@ -146,10 +146,6 @@ describe("ActivityStream", () => {
const feed = as.feeds.get("feeds.favicon")();
assert.ok(feed, "feed should exist");
});
it("should create a ASRouter feed", () => {
const feed = as.feeds.get("feeds.asrouterfeed")();
assert.ok(feed, "feed should exist");
});
it("should create a RecommendationProviderSwitcher feed", () => {
const feed = as.feeds.get("feeds.recommendationproviderswitcher")();
assert.ok(feed, "feed should exist");

Просмотреть файл

@ -13,10 +13,10 @@ describe("BookmarkPanelHub", () => {
let fakeMessageFluent;
let fakeTarget;
let fakeContainer;
let fakeDispatch;
let fakeSendTelemetry;
let fakeWindow;
let isBrowserPrivateStub;
beforeEach(() => {
beforeEach(async () => {
sandbox = sinon.createSandbox();
globals = new GlobalOverrider();
@ -41,7 +41,7 @@ describe("BookmarkPanelHub", () => {
[
{ content: fakeMessageFluent },
{ content: fakeMessage },
] = PanelTestProvider.getMessages();
] = await PanelTestProvider.getMessages();
fakeContainer = {
addEventListener: sandbox.stub(),
setAttribute: sandbox.stub(),
@ -85,7 +85,7 @@ describe("BookmarkPanelHub", () => {
},
},
};
fakeDispatch = sandbox.stub();
fakeSendTelemetry = sandbox.stub();
});
afterEach(() => {
instance.uninit();
@ -103,11 +103,15 @@ describe("BookmarkPanelHub", () => {
assert.isNull(instance._handleMessageRequest);
});
it("should instantiate handleMessageRequest and addImpression and l10n", () => {
instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch);
instance.init(
fakeHandleMessageRequest,
fakeAddImpression,
fakeSendTelemetry
);
assert.equal(instance._addImpression, fakeAddImpression);
assert.equal(instance._handleMessageRequest, fakeHandleMessageRequest);
assert.equal(instance._dispatch, fakeDispatch);
assert.equal(instance._sendTelemetry, fakeSendTelemetry);
assert.ok(instance._l10n);
assert.isTrue(instance._initialized);
});
@ -117,7 +121,11 @@ describe("BookmarkPanelHub", () => {
describe("#messageRequest", () => {
beforeEach(() => {
sandbox.stub(instance, "onResponse");
instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch);
instance.init(
fakeHandleMessageRequest,
fakeAddImpression,
fakeSendTelemetry
);
});
afterEach(() => {
sandbox.restore();
@ -158,7 +166,11 @@ describe("BookmarkPanelHub", () => {
});
describe("#onResponse", () => {
beforeEach(() => {
instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch);
instance.init(
fakeHandleMessageRequest,
fakeAddImpression,
fakeSendTelemetry
);
sandbox.stub(instance, "showMessage");
sandbox.stub(instance, "sendImpression");
sandbox.stub(instance, "hideMessage");
@ -200,9 +212,9 @@ describe("BookmarkPanelHub", () => {
"IMPRESSION",
fakeWindow
);
assert.calledOnce(fakeDispatch);
assert.calledOnce(fakeSendTelemetry);
const [ping] = fakeDispatch.firstCall.args;
const [ping] = fakeSendTelemetry.firstCall.args;
assert.equal(ping.type, "DOORHANGER_TELEMETRY");
assert.equal(ping.data.event, "IMPRESSION");
@ -219,7 +231,7 @@ describe("BookmarkPanelHub", () => {
"IMPRESSION",
fakeWindow
);
assert.notCalled(fakeDispatch);
assert.notCalled(fakeSendTelemetry);
});
it("should hide existing messages if no response is provided", () => {
instance.onResponse(null, fakeTarget);
@ -230,7 +242,11 @@ describe("BookmarkPanelHub", () => {
});
describe("#showMessage.collapsed=false", () => {
beforeEach(() => {
instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch);
instance.init(
fakeHandleMessageRequest,
fakeAddImpression,
fakeSendTelemetry
);
sandbox.stub(instance, "toggleRecommendation");
sandbox.stub(instance, "_response").value({ collapsed: false });
});
@ -438,12 +454,12 @@ describe("BookmarkPanelHub", () => {
assert.calledOnce(target.container.removeAttribute);
});
});
describe("#_forceShowMessage", () => {
describe("#forceShowMessage", () => {
it("should call showMessage with the correct args", () => {
sandbox.spy(instance, "showMessage");
sandbox.stub(instance, "hideMessage");
instance._forceShowMessage(fakeTarget, { content: fakeMessage });
instance.forceShowMessage(fakeTarget.browser, { content: fakeMessage });
assert.calledOnce(instance.showMessage);
assert.calledOnce(instance.hideMessage);
@ -457,7 +473,7 @@ describe("BookmarkPanelHub", () => {
it("should insert required fluent files", () => {
sandbox.stub(instance, "showMessage");
instance._forceShowMessage(fakeTarget, { content: fakeMessage });
instance.forceShowMessage(fakeTarget.browser, { content: fakeMessage });
assert.calledTwice(fakeWindow.MozXULElement.insertFTLIfNeeded);
});
@ -466,7 +482,7 @@ describe("BookmarkPanelHub", () => {
sandbox.stub(instance, "toggleRecommendation");
sandbox.stub(instance, "sendUserEventTelemetry");
instance._forceShowMessage(fakeTarget, { content: fakeMessage });
instance.forceShowMessage(fakeTarget.browser, { content: fakeMessage });
const [
,
@ -481,7 +497,11 @@ describe("BookmarkPanelHub", () => {
});
describe("#sendImpression", () => {
beforeEach(() => {
instance.init(fakeHandleMessageRequest, fakeAddImpression, fakeDispatch);
instance.init(
fakeHandleMessageRequest,
fakeAddImpression,
fakeSendTelemetry
);
instance._response = "foo";
});
it("should dispatch an impression", () => {

Просмотреть файл

@ -10,7 +10,7 @@ describe("MomentsPageHub", () => {
let handleMessageRequestStub;
let addImpressionStub;
let blockMessageByIdStub;
let dispatchStub;
let sendTelemetryStub;
let getStringPrefStub;
let setStringPrefStub;
let setIntervalStub;
@ -30,7 +30,7 @@ describe("MomentsPageHub", () => {
setStringPrefStub = sandbox.stub();
setIntervalStub = sandbox.stub();
clearIntervalStub = sandbox.stub();
dispatchStub = sandbox.stub();
sendTelemetryStub = sandbox.stub();
globals.set({
setInterval: setIntervalStub,
clearInterval: clearIntervalStub,
@ -88,7 +88,7 @@ describe("MomentsPageHub", () => {
handleMessageRequest: handleMessageRequestStub,
addImpression: addImpressionStub,
blockMessageById: blockMessageByIdStub,
dispatch: dispatchStub,
sendTelemetry: sendTelemetryStub,
});
});
afterEach(() => {
@ -200,7 +200,7 @@ describe("MomentsPageHub", () => {
await instance.init(sandbox.stub().resolves(), {
addImpression: addImpressionStub,
blockMessageById: blockMessageByIdStub,
dispatch: dispatchStub,
sendTelemetry: sendTelemetryStub,
});
});
it("should set HOMEPAGE_OVERRIDE_PREF on `moments-wnp` action", async () => {
@ -276,9 +276,9 @@ describe("MomentsPageHub", () => {
);
instance.executeAction(msg);
assert.calledOnce(dispatchStub);
assert.calledOnce(sendTelemetryStub);
assert.calledWithExactly(sendUserEventTelemetrySpy, msg);
assert.calledWithExactly(dispatchStub, {
assert.calledWithExactly(sendTelemetryStub, {
type: "MOMENTS_PAGE_TELEMETRY",
data: {
action: "moments_user_event",

Просмотреть файл

@ -17,6 +17,7 @@ import {
import { FakePrefs, GlobalOverrider } from "test/unit/utils";
import { ASRouterPreferences } from "lib/ASRouterPreferences.jsm";
import injector from "inject!lib/TelemetryFeed.jsm";
import { MESSAGE_TYPE_HASH as msg } from "common/ActorConstants.jsm";
const FAKE_UUID = "{foo-123-foo}";
const FAKE_ROUTER_MESSAGE_PROVIDER = [{ id: "cfr", enabled: true }];
@ -101,6 +102,18 @@ describe("TelemetryFeed", () => {
ASRouterPreferences.uninit();
});
describe("#init", () => {
it("should set preferences in parent process", () => {
const testInstance = new TelemetryFeed({ isParentProcess: true });
// unfortuntely testing gUUIDGenerator.generateUUID is not possible here
// but we still need test coverage
assert.isDefined(testInstance);
});
it("should not set preferences in content process", () => {
const testInstance = new TelemetryFeed({ isParentProcess: false });
// unfortuntely testing gUUIDGenerator.generateUUID is not possible here
// but we still need test coverage
assert.isDefined(testInstance);
});
it("should add .pingCentre, a PingCentre instance", () => {
assert.instanceOf(instance.pingCentre, PingCentre);
});
@ -1459,14 +1472,23 @@ describe("TelemetryFeed", () => {
assert.calledWith(sendEvent, eventCreator.returnValue);
assert.calledWith(utSendUserEvent, eventCreator.returnValue);
});
it("should call handleASRouterUserEvent on TELEMETRY_USER_EVENT action", () => {
describe("should call handleASRouterUserEvent on x action", () => {
const actions = [
at.AS_ROUTER_TELEMETRY_USER_EVENT,
msg.TOOLBAR_BADGE_TELEMETRY,
msg.TOOLBAR_PANEL_TELEMETRY,
msg.MOMENTS_PAGE_TELEMETRY,
msg.DOORHANGER_TELEMETRY,
];
actions.forEach(type => {
it(`${type} action`, () => {
FakePrefs.prototype.prefs[TELEMETRY_PREF] = true;
FakePrefs.prototype.prefs[EVENTS_TELEMETRY_PREF] = true;
instance = new TelemetryFeed();
const eventHandler = sandbox.spy(instance, "handleASRouterUserEvent");
const action = {
type: at.AS_ROUTER_TELEMETRY_USER_EVENT,
type,
data: { event: "CLICK" },
};
@ -1474,6 +1496,8 @@ describe("TelemetryFeed", () => {
assert.calledWith(eventHandler, action);
});
});
});
it("should send an event on a TELEMETRY_PERFORMANCE_EVENT action", () => {
const sendEvent = sandbox.stub(instance, "sendEvent");
const eventCreator = sandbox.stub(instance, "createPerformanceEvent");

Просмотреть файл

@ -7,7 +7,7 @@ describe("ToolbarBadgeHub", () => {
let sandbox;
let instance;
let fakeAddImpression;
let fakeDispatch;
let fakeSendTelemetry;
let isBrowserPrivateStub;
let fxaMessage;
let whatsnewMessage;
@ -28,7 +28,7 @@ describe("ToolbarBadgeHub", () => {
sandbox = sinon.createSandbox();
instance = new _ToolbarBadgeHub();
fakeAddImpression = sandbox.stub();
fakeDispatch = sandbox.stub();
fakeSendTelemetry = sandbox.stub();
isBrowserPrivateStub = sandbox.stub();
const onboardingMsgs = await OnboardingMessageProvider.getUntranslatedMessages();
fxaMessage = onboardingMsgs.find(({ id }) => id === "FXA_ACCOUNTS_BADGE");
@ -233,7 +233,7 @@ describe("ToolbarBadgeHub", () => {
beforeEach(async () => {
await instance.init(sandbox.stub().resolves(), {
addImpression: fakeAddImpression,
dispatch: fakeDispatch,
sendTelemetry: fakeSendTelemetry,
});
fakeDocument = {
getElementById: sandbox.stub().returns(fakeElement),
@ -348,7 +348,7 @@ describe("ToolbarBadgeHub", () => {
beforeEach(async () => {
await instance.init(sandbox.stub().resolves(), {
addImpression: fakeAddImpression,
dispatch: fakeDispatch,
sendTelemetry: fakeSendTelemetry,
});
sandbox.stub(instance, "addToolbarNotification").returns(fakeElement);
sandbox.stub(instance, "removeToolbarNotification");
@ -459,7 +459,7 @@ describe("ToolbarBadgeHub", () => {
let fakeEvent;
beforeEach(async () => {
await instance.init(sandbox.stub().resolves(), {
dispatch: fakeDispatch,
sendTelemetry: fakeSendTelemetry,
});
blockMessageByIdStub = sandbox.stub();
sandbox.stub(instance, "_blockMessageById").value(blockMessageByIdStub);
@ -600,7 +600,7 @@ describe("ToolbarBadgeHub", () => {
describe("#sendUserEventTelemetry", () => {
beforeEach(async () => {
await instance.init(sandbox.stub().resolves(), {
dispatch: fakeDispatch,
sendTelemetry: fakeSendTelemetry,
});
});
it("should check for private window and not send", () => {
@ -608,15 +608,15 @@ describe("ToolbarBadgeHub", () => {
instance.sendUserEventTelemetry("CLICK", { id: fxaMessage });
assert.notCalled(instance._dispatch);
assert.notCalled(instance._sendTelemetry);
});
it("should check for private window and send", () => {
isBrowserPrivateStub.returns(false);
instance.sendUserEventTelemetry("CLICK", { id: fxaMessage });
assert.calledOnce(fakeDispatch);
const [ping] = instance._dispatch.firstCall.args;
assert.calledOnce(fakeSendTelemetry);
const [ping] = instance._sendTelemetry.firstCall.args;
assert.propertyVal(ping, "type", "TOOLBAR_BADGE_TELEMETRY");
assert.propertyVal(ping.data, "event", "CLICK");
});

Просмотреть файл

@ -21,7 +21,7 @@ describe("ToolbarPanelHub", () => {
let setBoolPrefStub;
let waitForInitializedStub;
let isBrowserPrivateStub;
let fakeDispatch;
let fakeSendTelemetry;
let getEarliestRecordedDateStub;
let getEventsByDateRangeStub;
let defaultSearchStub;
@ -119,7 +119,7 @@ describe("ToolbarPanelHub", () => {
removeObserverStub = sandbox.stub();
getBoolPrefStub = sandbox.stub();
setBoolPrefStub = sandbox.stub();
fakeDispatch = sandbox.stub();
fakeSendTelemetry = sandbox.stub();
isBrowserPrivateStub = sandbox.stub();
getEarliestRecordedDateStub = sandbox.stub().returns(
// A random date that's not the current timestamp
@ -336,7 +336,7 @@ describe("ToolbarPanelHub", () => {
getMessagesStub = sandbox.stub();
instance.init(waitForInitializedStub, {
getMessages: getMessagesStub,
dispatch: fakeDispatch,
sendTelemetry: fakeSendTelemetry,
});
});
it("should have correct state", async () => {
@ -582,7 +582,7 @@ describe("ToolbarPanelHub", () => {
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.calledOnce(spy);
assert.calledOnce(fakeDispatch);
assert.calledOnce(fakeSendTelemetry);
assert.propertyVal(
spy.firstCall.args[2],
"id",
@ -606,7 +606,7 @@ describe("ToolbarPanelHub", () => {
await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
assert.calledOnce(spy);
assert.calledOnce(fakeDispatch);
assert.calledOnce(fakeSendTelemetry);
spy.resetHistory();
@ -645,10 +645,10 @@ describe("ToolbarPanelHub", () => {
},
}
);
assert.calledOnce(fakeDispatch);
assert.calledOnce(fakeSendTelemetry);
const {
args: [dispatchPayload],
} = fakeDispatch.lastCall;
} = fakeSendTelemetry.lastCall;
assert.propertyVal(dispatchPayload, "type", "TOOLBAR_PANEL_TELEMETRY");
assert.propertyVal(dispatchPayload.data, "message_id", panelPingId);
assert.deepEqual(dispatchPayload.data.event_context, {
@ -684,10 +684,10 @@ describe("ToolbarPanelHub", () => {
},
}
);
assert.calledOnce(fakeDispatch);
assert.calledOnce(fakeSendTelemetry);
const {
args: [dispatchPayload],
} = fakeDispatch.lastCall;
} = fakeSendTelemetry.lastCall;
assert.propertyVal(dispatchPayload, "type", "TOOLBAR_PANEL_TELEMETRY");
assert.propertyVal(dispatchPayload.data, "message_id", panelPingId);
assert.deepEqual(dispatchPayload.data.event_context, {
@ -709,9 +709,7 @@ describe("ToolbarPanelHub", () => {
removeMessagesSpy = sandbox.spy(instance, "removeMessages");
renderMessagesStub = sandbox.spy(instance, "renderMessages");
addEventListenerStub = fakeElementById.addEventListener;
browser = {
browser: { ownerGlobal: fakeWindow, ownerDocument: fakeDocument },
};
browser = { ownerGlobal: fakeWindow, ownerDocument: fakeDocument };
fakeElementById.querySelectorAll.returns([fakeElementById]);
});
it("should call removeMessages when forcing a message to show", () => {
@ -768,7 +766,7 @@ describe("ToolbarPanelHub", () => {
onboardingMsgs.find(msg => msg.template === "protections_panel")
);
await instance.init(waitForInitializedStub, {
dispatch: fakeDispatch,
sendTelemetry: fakeSendTelemetry,
getMessages: getMessagesStub,
});
});

Просмотреть файл

@ -44,7 +44,31 @@ const RemoteSettings = name => ({
});
RemoteSettings.pollChanges = () => {};
class JSWindowActorParent {
sendAsyncMessage(name, data) {
return { name, data };
}
}
class JSWindowActorChild {
sendAsyncMessage(name, data) {
return { name, data };
}
sendQuery(name, data) {
return Promise.resolve({ name, data });
}
get contentWindow() {
return {
Promise,
};
}
}
const TEST_GLOBAL = {
JSWindowActorParent,
JSWindowActorChild,
AboutReaderParent: {
addMessageListener: (messageName, listener) => {},
removeMessageListener: (messageName, listener) => {},
@ -408,7 +432,12 @@ const TEST_GLOBAL = {
FX_MONITOR_OAUTH_CLIENT_ID: "fake_client_id",
TelemetryEnvironment: {
setExperimentActive() {},
currentEnvironment: { profile: { creationDate: 16587 } },
currentEnvironment: {
profile: {
creationDate: 16587,
},
settings: {},
},
},
TelemetryStopwatch: {
start: () => {},

Просмотреть файл

@ -5,13 +5,13 @@ user_pref("app.update.disabledForTesting", true);
user_pref("browser.chrome.guess_favicon", false);
user_pref("browser.dom.window.dump.enabled", true);
user_pref("devtools.console.stdout.chrome", true);
// Use a python-eval-able empty JSON array even though asrouter expects plain object
user_pref("browser.newtabpage.activity-stream.asrouter.providers.cfr", "[]");
user_pref("browser.newtabpage.activity-stream.asrouter.providers.cfr-fxa", "[]");
user_pref("browser.newtabpage.activity-stream.asrouter.providers.snippets", "[]");
user_pref("browser.newtabpage.activity-stream.asrouter.providers.message-groups", "[]");
user_pref("browser.newtabpage.activity-stream.asrouter.providers.whats-new-panel", "[]");
user_pref("browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments", "[]");
// asrouter expects a plain object or null
user_pref("browser.newtabpage.activity-stream.asrouter.providers.cfr", "null");
user_pref("browser.newtabpage.activity-stream.asrouter.providers.cfr-fxa", "null");
user_pref("browser.newtabpage.activity-stream.asrouter.providers.snippets", "null");
user_pref("browser.newtabpage.activity-stream.asrouter.providers.message-groups", "null");
user_pref("browser.newtabpage.activity-stream.asrouter.providers.whats-new-panel", "null");
user_pref("browser.newtabpage.activity-stream.asrouter.providers.messaging-experiments", "null");
user_pref("browser.newtabpage.activity-stream.feeds.system.topstories", false);
user_pref("browser.newtabpage.activity-stream.feeds.snippets", false);
user_pref("browser.newtabpage.activity-stream.tippyTop.service.endpoint", "");

Просмотреть файл

@ -8,7 +8,7 @@ const { CFRMessageProvider } = ChromeUtils.import(
);
add_task(async function test_all_test_messages() {
let messagesWithButtons = CFRMessageProvider.getMessages().filter(
let messagesWithButtons = (await CFRMessageProvider.getMessages()).filter(
m => m.content.buttons
);

Просмотреть файл

@ -59,7 +59,7 @@ add_task(async function test_trigger_docs() {
});
add_task(async function test_message_triggers() {
const messages = CFRMessageProvider.getMessages();
const messages = await CFRMessageProvider.getMessages();
for (let message of messages) {
await validateTrigger(message.trigger);
}

Просмотреть файл

@ -99,7 +99,7 @@ add_task(async function check_openURL_listener() {
});
// Initialise listener
await openURLListener.init(triggerHandler, ["example.com"]);
openURLListener.init(triggerHandler, ["example.com"]);
await openURLInWindow(normalWindow, TEST_URL);
await BrowserTestUtils.waitForCondition(

Просмотреть файл

@ -208,6 +208,10 @@ LRESULT StatusBarEntry::OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
return TRUE;
}
double scale = GetDesktopToDeviceScale().scale;
int32_t x = NSToIntRound(GET_X_LPARAM(wp) * scale);
int32_t y = NSToIntRound(GET_Y_LPARAM(wp) * scale);
// The menu that is being opened is a Gecko <xul:menu>, and the popup code
// that manages it expects that the window that the <xul:menu> belongs to
// will be in the foreground when it opens. If we don't do this, then if the
@ -217,8 +221,7 @@ LRESULT StatusBarEntry::OnMessage(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
// focuses any window in the parent process).
::SetForegroundWindow(win);
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
pm->ShowPopupAtScreen(popupFrame->GetContent(), GET_X_LPARAM(wp),
GET_Y_LPARAM(wp), false, nullptr);
pm->ShowPopupAtScreen(popupFrame->GetContent(), x, y, false, nullptr);
}
return DefWindowProc(hWnd, msg, wp, lp);