Backed out changeset 2dd9568a9044 (bug 1636463) for perma failures on browser_asrouter_cfr.js. CLOSED TREE
authorRazvan Maries <rmaries@mozilla.com>
Wed, 20 May 2020 18:13:24 +0300
changeset 531252 8ee537e6b4b8807fe8b7992b2586951690f47a0e
parent 531251 4b9b959c1ef084147376f21f5514e400ffceb80d
child 531253 0a89ee7a4082dffa7f2a5382871fb932bb6b3f45
push id37436
push userncsoregi@mozilla.com
push dateWed, 20 May 2020 21:30:50 +0000
treeherdermozilla-central@6c10970490f3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1636463
milestone78.0a1
backs out2dd9568a90442602f449ffdc1bce6926de7765d1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset 2dd9568a9044 (bug 1636463) for perma failures on browser_asrouter_cfr.js. CLOSED TREE
browser/components/newtab/common/Actions.jsm
browser/components/newtab/content-src/asrouter/asrouter-content.jsx
browser/components/newtab/data/content/activity-stream.bundle.js
browser/components/newtab/lib/ASRouter.jsm
browser/components/newtab/lib/CFRPageActions.jsm
browser/components/newtab/lib/ToolbarPanelHub.jsm
browser/components/newtab/test/browser/browser_asrouter_cfr.js
browser/components/newtab/test/unit/asrouter/ASRouter.test.js
browser/components/newtab/test/unit/asrouter/ASRouterFeed.test.js
browser/components/newtab/test/unit/asrouter/MessageLoaderUtils.test.js
browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js
toolkit/components/messaging-system/lib/SpecialMessageActions.jsm
toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas.js
toolkit/components/messaging-system/test/browser/specialMessageActions/browser_sma_cancel.js
--- a/browser/components/newtab/common/Actions.jsm
+++ b/browser/components/newtab/common/Actions.jsm
@@ -156,16 +156,38 @@ for (const type of [
   "UPDATE_SEARCH_SHORTCUTS",
   "UPDATE_SECTION_PREFS",
   "WEBEXT_CLICK",
   "WEBEXT_DISMISS",
 ]) {
   actionTypes[type] = type;
 }
 
+// These are acceptable actions for AS Router messages to have. They can show up
+// as call-to-action buttons in snippets, onboarding tour, etc.
+const ASRouterActions = {};
+for (const type of [
+  "HIGHLIGHT_FEATURE",
+  "INSTALL_ADDON_FROM_URL",
+  "OPEN_APPLICATIONS_MENU",
+  "OPEN_PRIVATE_BROWSER_WINDOW",
+  "OPEN_URL",
+  "OPEN_ABOUT_PAGE",
+  "OPEN_PREFERENCES_PAGE",
+  "SHOW_FIREFOX_ACCOUNTS",
+  "PIN_CURRENT_TAB",
+  "ENABLE_FIREFOX_MONITOR",
+  "OPEN_PROTECTION_PANEL",
+  "OPEN_PROTECTION_REPORT",
+  "DISABLE_STP_DOORHANGERS",
+  "SHOW_MIGRATION_WIZARD",
+]) {
+  ASRouterActions[type] = type;
+}
+
 // Helper function for creating routed actions between content and main
 // Not intended to be used by consumers
 function _RouteMessage(action, options) {
   const meta = action.meta ? { ...action.meta } : {};
   if (!options || !options.from || !options.to) {
     throw new Error(
       "Routed Messages must have options as the second parameter, and must at least include a .from and .to property."
     );
@@ -410,16 +432,17 @@ function WebExtEvent(type, data, importC
       'WebExtEvent actions should include a property "source", the id of the webextension that should receive the event.'
     );
   }
   const action = { type, data };
   return importContext === UI_CODE ? AlsoToMain(action) : action;
 }
 
 this.actionTypes = actionTypes;
+this.ASRouterActions = ASRouterActions;
 
 this.actionCreators = {
   BroadcastToContent,
   UserEvent,
   ASRouterUserEvent,
   UndesiredEvent,
   PerfEvent,
   ImpressionStats,
@@ -487,15 +510,16 @@ this.actionUtils = {
   },
   _RouteMessage,
 };
 
 const EXPORTED_SYMBOLS = [
   "actionTypes",
   "actionCreators",
   "actionUtils",
+  "ASRouterActions",
   "globalImportContext",
   "UI_CODE",
   "BACKGROUND_PROCESS",
   "MAIN_MESSAGE_TYPE",
   "CONTENT_MESSAGE_TYPE",
   "PRELOAD_MESSAGE_TYPE",
 ];
--- a/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
+++ b/browser/components/newtab/content-src/asrouter/asrouter-content.jsx
@@ -1,13 +1,17 @@
 /* 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 { actionCreators as ac, actionTypes as at } from "common/Actions.jsm";
+import {
+  actionCreators as ac,
+  actionTypes as at,
+  ASRouterActions as ra,
+} from "common/Actions.jsm";
 import { OUTGOING_MESSAGE_NAME as AS_GENERAL_OUTGOING_MESSAGE_NAME } from "content-src/lib/init-store";
 import { generateBundles } from "./rich-text-strings";
 import { ImpressionsWrapper } from "./components/ImpressionsWrapper/ImpressionsWrapper";
 import { LocalizationProvider } from "fluent-react";
 import { NEWTAB_DARK_THEME } from "content-src/lib/constants";
 import React from "react";
 import ReactDOM from "react-dom";
 import { SnippetsTemplates } from "./templates/template-manifest";
@@ -340,19 +344,19 @@ export class ASRouterUISurface extends R
     });
 
     return urlObj.toString();
   }
 
   async onUserAction(action) {
     switch (action.type) {
       // This needs to be handled locally because its
-      case "ENABLE_FIREFOX_MONITOR":
+      case ra.ENABLE_FIREFOX_MONITOR:
         const url = await this.getMonitorUrl(action.data.args);
-        ASRouterUtils.executeAction({ type: "OPEN_URL", data: { args: url } });
+        ASRouterUtils.executeAction({ type: ra.OPEN_URL, data: { args: url } });
         break;
       default:
         ASRouterUtils.executeAction(action);
     }
   }
 
   renderSnippets() {
     const { message } = this.state;
--- a/browser/components/newtab/data/content/activity-stream.bundle.js
+++ b/browser/components/newtab/data/content/activity-stream.bundle.js
@@ -205,16 +205,17 @@ module.exports = g;
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MAIN_MESSAGE_TYPE", function() { return MAIN_MESSAGE_TYPE; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CONTENT_MESSAGE_TYPE", function() { return CONTENT_MESSAGE_TYPE; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PRELOAD_MESSAGE_TYPE", function() { return PRELOAD_MESSAGE_TYPE; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "UI_CODE", function() { return UI_CODE; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BACKGROUND_PROCESS", function() { return BACKGROUND_PROCESS; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "globalImportContext", function() { return globalImportContext; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "actionTypes", function() { return actionTypes; });
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ASRouterActions", function() { return ASRouterActions; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "actionCreators", function() { return actionCreators; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "actionUtils", function() { return actionUtils; });
 /* 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/. */
 
 
 var MAIN_MESSAGE_TYPE = "ActivityStream:Main";
@@ -234,16 +235,24 @@ const globalImportContext = typeof Windo
 // {
 //   INIT: "INIT",
 //   UNINIT: "UNINIT"
 // }
 const actionTypes = {};
 
 for (const type of ["ADDONS_INFO_REQUEST", "ADDONS_INFO_RESPONSE", "ARCHIVE_FROM_POCKET", "AS_ROUTER_INITIALIZED", "AS_ROUTER_PREF_CHANGED", "AS_ROUTER_TARGETING_UPDATE", "AS_ROUTER_TELEMETRY_USER_EVENT", "BLOCK_URL", "BOOKMARK_URL", "CLEAR_PREF", "COPY_DOWNLOAD_LINK", "DELETE_BOOKMARK_BY_ID", "DELETE_FROM_POCKET", "DELETE_HISTORY_URL", "DIALOG_CANCEL", "DIALOG_OPEN", "DISCOVERY_STREAM_COLLECTION_DISMISSIBLE_TOGGLE", "DISCOVERY_STREAM_CONFIG_CHANGE", "DISCOVERY_STREAM_CONFIG_RESET", "DISCOVERY_STREAM_CONFIG_RESET_DEFAULTS", "DISCOVERY_STREAM_CONFIG_SETUP", "DISCOVERY_STREAM_CONFIG_SET_VALUE", "DISCOVERY_STREAM_DEV_EXPIRE_CACHE", "DISCOVERY_STREAM_DEV_IDLE_DAILY", "DISCOVERY_STREAM_DEV_SYNC_RS", "DISCOVERY_STREAM_DEV_SYSTEM_TICK", "DISCOVERY_STREAM_FEEDS_UPDATE", "DISCOVERY_STREAM_FEED_UPDATE", "DISCOVERY_STREAM_IMPRESSION_STATS", "DISCOVERY_STREAM_LAYOUT_RESET", "DISCOVERY_STREAM_LAYOUT_UPDATE", "DISCOVERY_STREAM_LINK_BLOCKED", "DISCOVERY_STREAM_LOADED_CONTENT", "DISCOVERY_STREAM_PERSONALIZATION_INIT", "DISCOVERY_STREAM_PERSONALIZATION_LAST_UPDATED", "DISCOVERY_STREAM_PERSONALIZATION_VERSION", "DISCOVERY_STREAM_PERSONALIZATION_VERSION_TOGGLE", "DISCOVERY_STREAM_RETRY_FEED", "DISCOVERY_STREAM_SPOCS_CAPS", "DISCOVERY_STREAM_SPOCS_ENDPOINT", "DISCOVERY_STREAM_SPOCS_FILL", "DISCOVERY_STREAM_SPOCS_PLACEMENTS", "DISCOVERY_STREAM_SPOCS_UPDATE", "DISCOVERY_STREAM_SPOC_BLOCKED", "DISCOVERY_STREAM_SPOC_IMPRESSION", "DOWNLOAD_CHANGED", "FAKE_FOCUS_SEARCH", "FILL_SEARCH_TERM", "HANDOFF_SEARCH_TO_AWESOMEBAR", "HIDE_PRIVACY_INFO", "HIDE_SEARCH", "INIT", "NEW_TAB_INIT", "NEW_TAB_INITIAL_STATE", "NEW_TAB_LOAD", "NEW_TAB_REHYDRATED", "NEW_TAB_STATE_REQUEST", "NEW_TAB_UNLOAD", "OPEN_DOWNLOAD_FILE", "OPEN_LINK", "OPEN_NEW_WINDOW", "OPEN_PRIVATE_WINDOW", "OPEN_WEBEXT_SETTINGS", "PLACES_BOOKMARK_ADDED", "PLACES_BOOKMARK_REMOVED", "PLACES_HISTORY_CLEARED", "PLACES_LINKS_CHANGED", "PLACES_LINK_BLOCKED", "PLACES_LINK_DELETED", "PLACES_SAVED_TO_POCKET", "POCKET_CTA", "POCKET_LINK_DELETED_OR_ARCHIVED", "POCKET_LOGGED_IN", "POCKET_WAITING_FOR_SPOC", "PREFS_INITIAL_VALUES", "PREF_CHANGED", "PREVIEW_REQUEST", "PREVIEW_REQUEST_CANCEL", "PREVIEW_RESPONSE", "REMOVE_DOWNLOAD_FILE", "RICH_ICON_MISSING", "SAVE_SESSION_PERF_DATA", "SAVE_TO_POCKET", "SCREENSHOT_UPDATED", "SECTION_DEREGISTER", "SECTION_DISABLE", "SECTION_ENABLE", "SECTION_MOVE", "SECTION_OPTIONS_CHANGED", "SECTION_REGISTER", "SECTION_UPDATE", "SECTION_UPDATE_CARD", "SETTINGS_CLOSE", "SETTINGS_OPEN", "SET_PREF", "SHOW_DOWNLOAD_FILE", "SHOW_FIREFOX_ACCOUNTS", "SHOW_PRIVACY_INFO", "SHOW_SEARCH", "SKIPPED_SIGNIN", "SNIPPETS_BLOCKLIST_CLEARED", "SNIPPETS_BLOCKLIST_UPDATED", "SNIPPETS_DATA", "SNIPPETS_PREVIEW_MODE", "SNIPPETS_RESET", "SNIPPET_BLOCKED", "SUBMIT_EMAIL", "SUBMIT_SIGNIN", "SYSTEM_TICK", "TELEMETRY_IMPRESSION_STATS", "TELEMETRY_PERFORMANCE_EVENT", "TELEMETRY_UNDESIRED_EVENT", "TELEMETRY_USER_EVENT", "TOP_SITES_CANCEL_EDIT", "TOP_SITES_CLOSE_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_EDIT", "TOP_SITES_INSERT", "TOP_SITES_OPEN_SEARCH_SHORTCUTS_MODAL", "TOP_SITES_PIN", "TOP_SITES_PREFS_UPDATED", "TOP_SITES_UNPIN", "TOP_SITES_UPDATED", "TOTAL_BOOKMARKS_REQUEST", "TOTAL_BOOKMARKS_RESPONSE", "TRAILHEAD_ENROLL_EVENT", "UNINIT", "UPDATE_PINNED_SEARCH_SHORTCUTS", "UPDATE_SEARCH_SHORTCUTS", "UPDATE_SECTION_PREFS", "WEBEXT_CLICK", "WEBEXT_DISMISS"]) {
   actionTypes[type] = type;
+} // These are acceptable actions for AS Router messages to have. They can show up
+// as call-to-action buttons in snippets, onboarding tour, etc.
+
+
+const ASRouterActions = {};
+
+for (const type of ["HIGHLIGHT_FEATURE", "INSTALL_ADDON_FROM_URL", "OPEN_APPLICATIONS_MENU", "OPEN_PRIVATE_BROWSER_WINDOW", "OPEN_URL", "OPEN_ABOUT_PAGE", "OPEN_PREFERENCES_PAGE", "SHOW_FIREFOX_ACCOUNTS", "PIN_CURRENT_TAB", "ENABLE_FIREFOX_MONITOR", "OPEN_PROTECTION_PANEL", "OPEN_PROTECTION_REPORT", "DISABLE_STP_DOORHANGERS", "SHOW_MIGRATION_WIZARD"]) {
+  ASRouterActions[type] = type;
 } // Helper function for creating routed actions between content and main
 // Not intended to be used by consumers
 
 
 function _RouteMessage(action, options) {
   const meta = action.meta ? { ...action.meta
   } : {};
 
@@ -2571,20 +2580,20 @@ class ASRouterUISurface extends react__W
       }
     });
     return urlObj.toString();
   }
 
   async onUserAction(action) {
     switch (action.type) {
       // This needs to be handled locally because its
-      case "ENABLE_FIREFOX_MONITOR":
+      case common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["ASRouterActions"].ENABLE_FIREFOX_MONITOR:
         const url = await this.getMonitorUrl(action.data.args);
         ASRouterUtils.executeAction({
-          type: "OPEN_URL",
+          type: common_Actions_jsm__WEBPACK_IMPORTED_MODULE_0__["ASRouterActions"].OPEN_URL,
           data: {
             args: url
           }
         });
         break;
 
       default:
         ASRouterUtils.executeAction(action);
--- a/browser/components/newtab/lib/ASRouter.jsm
+++ b/browser/components/newtab/lib/ASRouter.jsm
@@ -4,16 +4,19 @@
 "use strict";
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
 XPCOMUtils.defineLazyModuleGetters(this, {
+  AddonManager: "resource://gre/modules/AddonManager.jsm",
+  UITour: "resource:///modules/UITour.jsm",
+  FxAccounts: "resource://gre/modules/FxAccounts.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   BookmarkPanelHub: "resource://activity-stream/lib/BookmarkPanelHub.jsm",
   SnippetsTestMessageProvider:
     "resource://activity-stream/lib/SnippetsTestMessageProvider.jsm",
   PanelTestProvider: "resource://activity-stream/lib/PanelTestProvider.jsm",
   ToolbarBadgeHub: "resource://activity-stream/lib/ToolbarBadgeHub.jsm",
   ToolbarPanelHub: "resource://activity-stream/lib/ToolbarPanelHub.jsm",
@@ -26,26 +29,27 @@ XPCOMUtils.defineLazyModuleGetters(this,
   ASRouterTriggerListeners:
     "resource://activity-stream/lib/ASRouterTriggerListeners.jsm",
   CFRMessageProvider: "resource://activity-stream/lib/CFRMessageProvider.jsm",
   GroupsConfigurationProvider:
     "resource://activity-stream/lib/GroupsConfigurationProvider.jsm",
   KintoHttpClient: "resource://services-common/kinto-http-client.js",
   Downloader: "resource://services-settings/Attachments.jsm",
   RemoteL10n: "resource://activity-stream/lib/RemoteL10n.jsm",
+  MigrationUtils: "resource:///modules/MigrationUtils.jsm",
   ExperimentAPI: "resource://messaging-system/experiments/ExperimentAPI.jsm",
-  SpecialMessageActions:
-    "resource://messaging-system/lib/SpecialMessageActions.jsm",
 });
 XPCOMUtils.defineLazyServiceGetters(this, {
   BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"],
 });
-const { actionTypes: at, actionCreators: ac } = ChromeUtils.import(
-  "resource://activity-stream/common/Actions.jsm"
-);
+const {
+  ASRouterActions: ra,
+  actionTypes: at,
+  actionCreators: ac,
+} = ChromeUtils.import("resource://activity-stream/common/Actions.jsm");
 
 const { CFRMessageProvider } = ChromeUtils.import(
   "resource://activity-stream/lib/CFRMessageProvider.jsm"
 );
 const { OnboardingMessageProvider } = ChromeUtils.import(
   "resource://activity-stream/lib/OnboardingMessageProvider.jsm"
 );
 const { RemoteSettings } = ChromeUtils.import(
@@ -467,16 +471,64 @@ const MessageLoaderUtils = {
         })
         .filter(message => message.weight > 0),
       lastUpdated,
       errors: MessageLoaderUtils.errors,
     };
   },
 
   /**
+   * _loadAddonIconInURLBar - load addons-notification icon by displaying
+   * box containing addons icon in urlbar. See Bug 1513882
+   *
+   * @param  {XULElement} Target browser element for showing addons icon
+   */
+  _loadAddonIconInURLBar(browser) {
+    if (!browser) {
+      return;
+    }
+    const chromeDoc = browser.ownerDocument;
+    let notificationPopupBox = chromeDoc.getElementById(
+      "notification-popup-box"
+    );
+    if (!notificationPopupBox) {
+      return;
+    }
+    if (
+      notificationPopupBox.style.display === "none" ||
+      notificationPopupBox.style.display === ""
+    ) {
+      notificationPopupBox.style.display = "block";
+    }
+  },
+
+  async installAddonFromURL(browser, url, telemetrySource = "amo") {
+    try {
+      MessageLoaderUtils._loadAddonIconInURLBar(browser);
+      const aUri = Services.io.newURI(url);
+      const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+
+      // AddonManager installation source associated to the addons installed from activitystream's CFR
+      // and RTAMO (source is going to be "amo" if not configured explicitly in the message provider).
+      const telemetryInfo = { source: telemetrySource };
+      const install = await AddonManager.getInstallForURL(aUri.spec, {
+        telemetryInfo,
+      });
+      await AddonManager.installAddonFromWebpage(
+        "application/x-xpinstall",
+        browser,
+        systemPrincipal,
+        install
+      );
+    } catch (e) {
+      Cu.reportError(e);
+    }
+  },
+
+  /**
    * cleanupCache - Removes cached data of removed providers.
    *
    * @param {Array} providers A list of activer AS Router providers
    */
   async cleanupCache(providers, storage) {
     const ids = providers.filter(p => p.type === "remote").map(p => p.id);
     const cache = await MessageLoaderUtils._remoteLoaderCache(storage);
     let dirty = false;
@@ -905,16 +957,17 @@ class _ASRouter {
       addImpression: this.addImpression,
       blockMessageById: this.blockMessageById,
       unblockMessageById: this.unblockMessageById,
       dispatch: this.dispatch,
     });
     ToolbarPanelHub.init(this.waitForInitialized, {
       getMessages: this.handleMessageRequest,
       dispatch: this.dispatch,
+      handleUserAction: this.handleUserAction,
     });
     MomentsPageHub.init(this.waitForInitialized, {
       handleMessageRequest: this.handleMessageRequest,
       addImpression: this.addImpression,
       blockMessageById: this.blockMessageById,
       dispatch: this.dispatch,
     });
 
@@ -961,17 +1014,16 @@ class _ASRouter {
     // set necessary state in the rest of AS
     this.dispatchToAS(
       ac.BroadcastToContent({
         type: at.AS_ROUTER_INITIALIZED,
         data: ASRouterPreferences.specialConditions,
       })
     );
 
-    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();
   }
 
   uninit() {
     this._storage.set("previousSessionEnd", Date.now());
@@ -1868,16 +1920,120 @@ class _ASRouter {
 
     // Clear and refresh Attribution, and then fetch the messages again to update
     AttributionCode._clearCache();
     await AttributionCode.getAttrDataAsync();
     this._updateMessageProviders();
     await this.loadMessagesFromAllProviders();
   }
 
+  async handleUserAction({ data: action, target }) {
+    switch (action.type) {
+      case ra.SHOW_MIGRATION_WIZARD:
+        MigrationUtils.showMigrationWizard(target.browser.ownerGlobal, [
+          MigrationUtils.MIGRATION_ENTRYPOINT_NEWTAB,
+        ]);
+        break;
+      case ra.OPEN_PRIVATE_BROWSER_WINDOW:
+        // Forcefully open about:privatebrowsing
+        target.browser.ownerGlobal.OpenBrowserWindow({ private: true });
+        break;
+      case ra.OPEN_URL:
+        target.browser.ownerGlobal.openLinkIn(
+          Services.urlFormatter.formatURL(action.data.args),
+          action.data.where || "current",
+          {
+            private: false,
+            triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
+              {}
+            ),
+            csp: null,
+          }
+        );
+        break;
+      case ra.OPEN_ABOUT_PAGE:
+        let aboutPageURL = new URL(`about:${action.data.args}`);
+        if (action.data.entrypoint) {
+          aboutPageURL.search = action.data.entrypoint;
+        }
+        target.browser.ownerGlobal.openTrustedLinkIn(
+          aboutPageURL.toString(),
+          "tab"
+        );
+        break;
+      case ra.OPEN_PREFERENCES_PAGE:
+        target.browser.ownerGlobal.openPreferences(
+          action.data.category || action.data.args,
+          action.data.entrypoint && {
+            urlParams: { entrypoint: action.data.entrypoint },
+          }
+        );
+        break;
+      case ra.OPEN_APPLICATIONS_MENU:
+        UITour.showMenu(target.browser.ownerGlobal, action.data.args);
+        break;
+      case ra.HIGHLIGHT_FEATURE:
+        const highlight = await UITour.getTarget(
+          target.browser.ownerGlobal,
+          action.data.args
+        );
+        if (highlight) {
+          await UITour.showHighlight(
+            target.browser.ownerGlobal,
+            highlight,
+            "none",
+            { autohide: true }
+          );
+        }
+        break;
+      case ra.INSTALL_ADDON_FROM_URL:
+        this._updateOnboardingState();
+        await MessageLoaderUtils.installAddonFromURL(
+          target.browser,
+          action.data.url,
+          action.data.telemetrySource
+        );
+        break;
+      case ra.PIN_CURRENT_TAB:
+        let tab = target.browser.ownerGlobal.gBrowser.selectedTab;
+        target.browser.ownerGlobal.gBrowser.pinTab(tab);
+        target.browser.ownerGlobal.ConfirmationHint.show(tab, "pinTab", {
+          showDescription: true,
+        });
+        break;
+      case ra.SHOW_FIREFOX_ACCOUNTS:
+        const url = await FxAccounts.config.promiseConnectAccountURI(
+          "snippets"
+        );
+        // We want to replace the current tab.
+        target.browser.ownerGlobal.openLinkIn(url, "current", {
+          private: false,
+          triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
+            {}
+          ),
+          csp: null,
+        });
+        break;
+      case ra.OPEN_PROTECTION_PANEL:
+        let { gProtectionsHandler } = target.browser.ownerGlobal;
+        gProtectionsHandler.showProtectionsPopup({});
+        break;
+      case ra.OPEN_PROTECTION_REPORT:
+        target.browser.ownerGlobal.gProtectionsHandler.openProtections();
+        break;
+      case ra.DISABLE_STP_DOORHANGERS:
+        await this.blockMessageById([
+          "SOCIAL_TRACKING_PROTECTION",
+          "FINGERPRINTERS_PROTECTION",
+          "CRYPTOMINERS_PROTECTION",
+        ]);
+        break;
+    }
+  }
+
   /**
    * 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) {
@@ -1996,21 +2152,19 @@ class _ASRouter {
       browserWindow.document.getElementById("whats-new-menu-button")
     );
   }
 
   /* eslint-disable complexity */
   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();
+        if (action.data.type in ra) {
+          await this.handleUserAction({ data: action.data, target });
         }
-        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(
--- a/browser/components/newtab/lib/CFRPageActions.jsm
+++ b/browser/components/newtab/lib/CFRPageActions.jsm
@@ -2,16 +2,19 @@
  * 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 { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const { ASRouterActions: ra } = ChromeUtils.import(
+  "resource://activity-stream/common/Actions.jsm"
+);
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["fetch"]);
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   RemoteL10n: "resource://activity-stream/lib/RemoteL10n.jsm",
 });
 
@@ -867,17 +870,17 @@ class PageAction {
 
   _executeNotifierAction(browser, message) {
     switch (message.content.layout) {
       case "chiclet_open_url":
         this._dispatchToASRouter(
           {
             type: "USER_ACTION",
             data: {
-              type: "OPEN_URL",
+              type: ra.OPEN_URL,
               data: {
                 args: message.content.action.url,
                 where: message.content.action.where,
               },
             },
           },
           this.window
         );
--- a/browser/components/newtab/lib/ToolbarPanelHub.jsm
+++ b/browser/components/newtab/lib/ToolbarPanelHub.jsm
@@ -6,18 +6,16 @@
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 XPCOMUtils.defineLazyModuleGetters(this, {
   Services: "resource://gre/modules/Services.jsm",
   EveryWindow: "resource:///modules/EveryWindow.jsm",
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   Preferences: "resource://gre/modules/Preferences.jsm",
-  SpecialMessageActions:
-    "resource://messaging-system/lib/SpecialMessageActions.jsm",
 });
 XPCOMUtils.defineLazyServiceGetter(
   this,
   "TrackingDBService",
   "@mozilla.org/tracking-db-service;1",
   "nsITrackingDBService"
 );
 
@@ -48,19 +46,20 @@ class _ToolbarPanelHub {
     this._hideToolbarButton = this._hideToolbarButton.bind(this);
     this.insertProtectionPanelMessage = this.insertProtectionPanelMessage.bind(
       this
     );
 
     this.state = {};
   }
 
-  async init(waitForInitialized, { getMessages, dispatch }) {
+  async init(waitForInitialized, { getMessages, dispatch, handleUserAction }) {
     this._getMessages = getMessages;
     this._dispatch = dispatch;
+    this._handleUserAction = handleUserAction;
     // Wait for ASRouter messages to become available in order to know
     // if we can show the What's New panel
     await waitForInitialized;
     // Enable the application menu button so that the user can access
     // the panel outside of the toolbar button
     await this.enableAppmenuButton();
 
     this.state = {
@@ -235,26 +234,26 @@ class _ToolbarPanelHub {
     let url;
     try {
       // Set platform specific path variables for SUMO articles
       url = Services.urlFormatter.formatURL(message.content.cta_url);
     } catch (e) {
       Cu.reportError(e);
       url = message.content.cta_url;
     }
-    SpecialMessageActions.handleAction(
-      {
+    this._handleUserAction({
+      target: win,
+      data: {
         type: message.content.cta_type,
         data: {
           args: url,
           where: "tabshifted",
         },
       },
-      win.browser
-    );
+    });
 
     this.sendUserEventTelemetry(win, "CLICK", message);
   }
 
   /**
    * Attach event listener to dispatch message defined action.
    */
   _attachCommandListener(win, element, message) {
--- a/browser/components/newtab/test/browser/browser_asrouter_cfr.js
+++ b/browser/components/newtab/test/browser/browser_asrouter_cfr.js
@@ -56,17 +56,19 @@ const createDummyRecommendation = ({
           },
         },
         secondary: [
           {
             label: {
               value: "Cancel",
               attributes: { accesskey: "C" },
             },
-            action: { type: "CANCEL" },
+            action: {
+              type: "CANCEL",
+            },
           },
           {
             label: {
               value: "Cancel 1",
               attributes: { accesskey: "A" },
             },
           },
           {
@@ -144,32 +146,33 @@ function checkCFRTrackingProtectionMiles
   Assert.ok(notification.hidden === false, "Panel should be visible");
   Assert.ok(
     notification.getAttribute("data-notification-category") === "short_message",
     "Panel have correct data attribute"
   );
 }
 
 function clearNotifications() {
-  CFRPageActions.clearRecommendations();
   for (let notification of PopupNotifications._currentNotifications) {
     notification.remove();
   }
+
+  // Clicking the primary action also removes the notification
   Assert.equal(
     PopupNotifications._currentNotifications.length,
     0,
     "Should have removed the notification"
   );
 }
 
 function trigger_cfr_panel(
   browser,
   trigger,
   {
-    action = { type: "OPEN_APPLICATIONS_MENU", data: { args: "fake" } },
+    action = { type: "FOO" },
     heading_text,
     category = "cfrAddons",
     layout,
     skip_address_bar_notifier = false,
     use_single_secondary_button = false,
     template = "cfr_doorhanger",
   } = {}
 ) {
@@ -186,16 +189,17 @@ function trigger_cfr_panel(
     delete recommendation.content.addon;
   }
   if (use_single_secondary_button) {
     recommendation.content.buttons.secondary = [
       recommendation.content.buttons.secondary[0],
     ];
   }
 
+  clearNotifications();
   if (recommendation.template === "milestone_message") {
     return CFRPageActions.showMilestone(
       browser,
       recommendation,
       // Use the real AS dispatch method to trigger real notifications
       ASRouter.dispatch
     );
   }
@@ -212,16 +216,17 @@ add_task(async function setup() {
   // Store it in order to restore to the original value
   const { _fetchLatestAddonVersion } = CFRPageActions;
   // Prevent fetching the real addon url and making a network request
   CFRPageActions._fetchLatestAddonVersion = x => "http://example.com";
 
   registerCleanupFunction(() => {
     CFRPageActions._fetchLatestAddonVersion = _fetchLatestAddonVersion;
     clearNotifications();
+    CFRPageActions.clearRecommendations();
   });
 });
 
 add_task(async function test_cfr_notification_show() {
   // addRecommendation checks that scheme starts with http and host matches
   let browser = gBrowser.selectedBrowser;
   await BrowserTestUtils.loadURI(browser, "http://example.com/");
   await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
@@ -322,23 +327,27 @@ add_task(async function test_cfr_notific
   Assert.ok(
     document.getElementById("contextual-feature-recommendation-notification")
       .button
   );
   let hidePanel = BrowserTestUtils.waitForEvent(
     PopupNotifications.panel,
     "popuphidden"
   );
-  // Clicking the primary action also removes the notification
   document
     .getElementById("contextual-feature-recommendation-notification")
     .button.click();
   await hidePanel;
 
-  clearNotifications();
+  // Clicking the primary action also removes the notification
+  Assert.equal(
+    PopupNotifications._currentNotifications.length,
+    0,
+    "Should have removed the notification"
+  );
 });
 
 add_task(async function test_cfr_notification_minimize() {
   // addRecommendation checks that scheme starts with http and host matches
   let browser = gBrowser.selectedBrowser;
   await BrowserTestUtils.loadURI(browser, "http://example.com/");
   await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
 
@@ -376,17 +385,16 @@ add_task(async function test_cfr_notific
   let hidePanel = BrowserTestUtils.waitForEvent(
     PopupNotifications.panel,
     "popuphidden"
   );
   document
     .getElementById("contextual-feature-recommendation-notification")
     .button.click();
   await hidePanel;
-  clearNotifications();
 });
 
 add_task(async function test_cfr_notification_minimize_2() {
   // addRecommendation checks that scheme starts with http and host matches
   let browser = gBrowser.selectedBrowser;
   await BrowserTestUtils.loadURI(browser, "http://example.com/");
   await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
 
@@ -438,16 +446,17 @@ add_task(async function test_cfr_notific
   );
 
   await BrowserTestUtils.waitForCondition(
     () => gURLBar.getAttribute("cfr-recommendation-state") === "collapsed",
     "Clicking the secondary button should collapse the notification"
   );
 
   clearNotifications();
+  CFRPageActions.clearRecommendations();
 });
 
 add_task(async function test_cfr_addon_install() {
   // addRecommendation checks that scheme starts with http and host matches
   const browser = gBrowser.selectedBrowser;
   await BrowserTestUtils.loadURI(browser, "http://example.com/");
   await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
 
@@ -501,35 +510,25 @@ add_task(async function test_cfr_addon_i
     "Should try to install the addon"
   );
 
   clearNotifications();
 });
 
 add_task(async function test_cfr_pin_tab_notification_show() {
   // addRecommendation checks that scheme starts with http and host matches
-  await BrowserTestUtils.loadURI(
-    gBrowser.selectedBrowser,
-    "http://example.com/"
-  );
-  await BrowserTestUtils.browserLoaded(
-    gBrowser.selectedBrowser,
-    false,
-    "http://example.com/"
-  );
+  let browser = gBrowser.selectedBrowser;
+  await BrowserTestUtils.loadURI(browser, "http://example.com/");
+  await BrowserTestUtils.browserLoaded(browser, false, "http://example.com/");
 
-  const response = await trigger_cfr_panel(
-    gBrowser.selectedBrowser,
-    "example.com",
-    {
-      action: { type: "PIN_CURRENT_TAB" },
-      category: "cfrFeatures",
-      layout: "message_and_animation",
-    }
-  );
+  const response = await trigger_cfr_panel(browser, "example.com", {
+    action: { type: "PIN_CURRENT_TAB" },
+    category: "cfrFeatures",
+    layout: "message_and_animation",
+  });
   Assert.ok(
     response,
     "Should return true if addRecommendation checks were successful"
   );
 
   const showPanel = BrowserTestUtils.waitForEvent(
     PopupNotifications.panel,
     "popupshown"
--- a/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouter.test.js
@@ -205,19 +205,16 @@ describe("ASRouter", () => {
         download() {
           return Promise.resolve("/path/to/download");
         }
       },
       ExperimentAPI: {
         getExperiment: sandbox.stub().returns({ branch: { value: [] } }),
         ready: sandbox.stub().resolves(),
       },
-      SpecialMessageActions: {
-        handleAction: sandbox.stub(),
-      },
     });
     await createRouterAndInit();
   });
   afterEach(() => {
     ASRouterPreferences.uninit();
     sandbox.restore();
     globals.restore();
   });
@@ -281,16 +278,17 @@ describe("ASRouter", () => {
       );
 
       assert.calledWithExactly(
         FakeToolbarPanelHub.init,
         Router.waitForInitialized,
         {
           getMessages: Router.handleMessageRequest,
           dispatch: Router.dispatch,
+          handleUserAction: Router.handleUserAction,
         }
       );
 
       assert.calledWithExactly(
         FakeMomentsPageHub.init,
         Router.waitForInitialized,
         {
           handleMessageRequest: Router.handleMessageRequest,
@@ -2596,16 +2594,274 @@ describe("ASRouter", () => {
         assert.calledWith(
           msg.target.sendAsyncMessage,
           PARENT_TO_CHILD_MESSAGE_NAME,
           { type: "CLEAR_ALL" }
         );
       });
     });
 
+    describe("#onMessage: Onboarding actions", () => {
+      it("should call OpenBrowserWindow with a private window on OPEN_PRIVATE_BROWSER_WINDOW", async () => {
+        let [testMessage] = Router.state.messages;
+        const msg = fakeExecuteUserAction({
+          type: "OPEN_PRIVATE_BROWSER_WINDOW",
+          data: testMessage,
+        });
+        await Router.onMessage(msg);
+
+        assert.calledWith(msg.target.browser.ownerGlobal.OpenBrowserWindow, {
+          private: true,
+        });
+      });
+      it("should call openLinkIn with the correct params on OPEN_URL", async () => {
+        let [testMessage] = Router.state.messages;
+        testMessage.button_action = {
+          type: "OPEN_URL",
+          data: { args: "some/url.com", where: "tabshifted" },
+        };
+        const msg = fakeExecuteUserAction(testMessage.button_action);
+        await Router.onMessage(msg);
+
+        assert.calledOnce(msg.target.browser.ownerGlobal.openLinkIn);
+        assert.calledWith(
+          msg.target.browser.ownerGlobal.openLinkIn,
+          "some/url.com",
+          "tabshifted",
+          { private: false, triggeringPrincipal: undefined, csp: null }
+        );
+      });
+      it("should call openLinkIn with the correct params on OPEN_ABOUT_PAGE", async () => {
+        let [testMessage] = Router.state.messages;
+        testMessage.button_action = {
+          type: "OPEN_ABOUT_PAGE",
+          data: { args: "something" },
+        };
+        const msg = fakeExecuteUserAction(testMessage.button_action);
+        await Router.onMessage(msg);
+
+        assert.calledOnce(msg.target.browser.ownerGlobal.openTrustedLinkIn);
+        assert.calledWith(
+          msg.target.browser.ownerGlobal.openTrustedLinkIn,
+          "about:something",
+          "tab"
+        );
+      });
+      it("should call openLinkIn with the entrypoint params on OPEN_ABOUT_PAGE", async () => {
+        let [testMessage] = Router.state.messages;
+        testMessage.button_action = {
+          type: "OPEN_ABOUT_PAGE",
+          data: { args: "something", entrypoint: "entryPoint=foo" },
+        };
+        const msg = fakeExecuteUserAction(testMessage.button_action);
+        await Router.onMessage(msg);
+
+        assert.calledOnce(msg.target.browser.ownerGlobal.openTrustedLinkIn);
+        assert.calledWith(
+          msg.target.browser.ownerGlobal.openTrustedLinkIn,
+          "about:something?entryPoint=foo",
+          "tab"
+        );
+      });
+      it("should call MigrationUtils.showMigrationWizard on SHOW_MIGRATION_WIZARD", async () => {
+        let [testMessage] = Router.state.messages;
+        testMessage.button_action = {
+          type: "SHOW_MIGRATION_WIZARD",
+        };
+        const msg = fakeExecuteUserAction(testMessage.button_action);
+        globals.set("MigrationUtils", {
+          showMigrationWizard: sandbox
+            .stub()
+            .withArgs(msg.target.browser.ownerGlobal, ["test"]),
+          MIGRATION_ENTRYPOINT_NEWTAB: "test",
+        });
+        await Router.onMessage(msg);
+
+        assert.calledOnce(MigrationUtils.showMigrationWizard);
+        assert.calledWith(
+          MigrationUtils.showMigrationWizard,
+          msg.target.browser.ownerGlobal,
+          [MigrationUtils.MIGRATION_ENTRYPOINT_NEWTAB]
+        );
+      });
+    });
+
+    describe("#onMessage: SHOW_FIREFOX_ACCOUNTS", () => {
+      beforeEach(() => {
+        globals.set("FxAccounts", {
+          config: {
+            promiseConnectAccountURI: sandbox.stub().resolves("some/url"),
+          },
+        });
+      });
+      it("should call openLinkIn with the correct params on OPEN_URL", async () => {
+        let [testMessage] = Router.state.messages;
+        testMessage.button_action = { type: "SHOW_FIREFOX_ACCOUNTS" };
+        const msg = fakeExecuteUserAction(testMessage.button_action);
+        await Router.onMessage(msg);
+
+        assert.calledOnce(msg.target.browser.ownerGlobal.openLinkIn);
+        assert.calledWith(
+          msg.target.browser.ownerGlobal.openLinkIn,
+          "some/url",
+          "current",
+          { private: false, triggeringPrincipal: undefined, csp: null }
+        );
+      });
+    });
+
+    describe("#onMessage: OPEN_PREFERENCES_PAGE", () => {
+      it("should call openPreferences with the correct params on OPEN_PREFERENCES_PAGE", async () => {
+        let [testMessage] = Router.state.messages;
+        testMessage.button_action = {
+          type: "OPEN_PREFERENCES_PAGE",
+          data: { category: "something" },
+        };
+        const msg = fakeExecuteUserAction(testMessage.button_action);
+        await Router.onMessage(msg);
+
+        assert.calledOnce(msg.target.browser.ownerGlobal.openPreferences);
+        assert.calledWith(
+          msg.target.browser.ownerGlobal.openPreferences,
+          "something"
+        );
+      });
+      it("should call openPreferences with the correct params on OPEN_PREFERENCES_PAGE (snippets payload)", async () => {
+        let [testMessage] = Router.state.messages;
+        testMessage.button_action = {
+          type: "OPEN_PREFERENCES_PAGE",
+          data: { args: "arg_from_snippets" },
+        };
+        const msg = fakeExecuteUserAction(testMessage.button_action);
+        await Router.onMessage(msg);
+
+        assert.calledOnce(msg.target.browser.ownerGlobal.openPreferences);
+        assert.calledWith(
+          msg.target.browser.ownerGlobal.openPreferences,
+          "arg_from_snippets"
+        );
+      });
+      it("should call openPreferences with the correct entrypoint if defined", async () => {
+        let [testMessage] = Router.state.messages;
+        testMessage.button_action = {
+          type: "OPEN_PREFERENCES_PAGE",
+          data: { category: "something", entrypoint: "unittest" },
+        };
+        const msg = fakeExecuteUserAction(testMessage.button_action);
+        await Router.onMessage(msg);
+
+        assert.calledOnce(msg.target.browser.ownerGlobal.openPreferences);
+        assert.calledWithExactly(
+          msg.target.browser.ownerGlobal.openPreferences,
+          "something",
+          { urlParams: { entrypoint: "unittest" } }
+        );
+      });
+    });
+
+    describe("#onMessage: INSTALL_ADDON_FROM_URL", () => {
+      it("should call installAddonFromURL with correct arguments", async () => {
+        sandbox.stub(MessageLoaderUtils, "installAddonFromURL").resolves(null);
+        const msg = fakeExecuteUserAction({
+          type: "INSTALL_ADDON_FROM_URL",
+          data: { url: "foo.com", telemetrySource: "foo" },
+        });
+
+        await Router.onMessage(msg);
+
+        assert.calledOnce(MessageLoaderUtils.installAddonFromURL);
+        assert.calledWithExactly(
+          MessageLoaderUtils.installAddonFromURL,
+          msg.target.browser,
+          "foo.com",
+          "foo"
+        );
+      });
+
+      it("should add/remove observers for `webextension-install-notify`", async () => {
+        sandbox.spy(global.Services.obs, "addObserver");
+        sandbox.spy(global.Services.obs, "removeObserver");
+
+        sandbox.stub(MessageLoaderUtils, "installAddonFromURL").resolves(null);
+        const msg = fakeExecuteUserAction({
+          type: "INSTALL_ADDON_FROM_URL",
+          data: { url: "foo.com" },
+        });
+
+        await Router.onMessage(msg);
+
+        assert.calledOnce(global.Services.obs.addObserver);
+
+        const [cb] = global.Services.obs.addObserver.firstCall.args;
+
+        cb();
+
+        assert.calledOnce(global.Services.obs.removeObserver);
+        assert.calledOnce(channel.sendAsyncMessage);
+      });
+    });
+
+    describe("#onMessage: PIN_CURRENT_TAB", () => {
+      it("should call pin tab with the selectedTab", async () => {
+        const msg = fakeExecuteUserAction({ type: "PIN_CURRENT_TAB" });
+        const { gBrowser, ConfirmationHint } = msg.target.browser.ownerGlobal;
+
+        await Router.onMessage(msg);
+
+        assert.calledOnce(gBrowser.pinTab);
+        assert.calledWithExactly(gBrowser.pinTab, gBrowser.selectedTab);
+        assert.calledOnce(ConfirmationHint.show);
+        assert.calledWithExactly(
+          ConfirmationHint.show,
+          gBrowser.selectedTab,
+          "pinTab",
+          { showDescription: true }
+        );
+      });
+    });
+
+    describe("#onMessage: OPEN_PROTECTION_PANEL", () => {
+      it("should open protection panel", async () => {
+        const msg = fakeExecuteUserAction({ type: "OPEN_PROTECTION_PANEL" });
+        let { gProtectionsHandler } = msg.target.browser.ownerGlobal;
+
+        await Router.onMessage(msg);
+
+        assert.calledOnce(gProtectionsHandler.showProtectionsPopup);
+        assert.calledWithExactly(gProtectionsHandler.showProtectionsPopup, {});
+      });
+    });
+
+    describe("#onMessage: OPEN_PROTECTION_REPORT", () => {
+      it("should open protection report", async () => {
+        const msg = fakeExecuteUserAction({ type: "OPEN_PROTECTION_REPORT" });
+        let { gProtectionsHandler } = msg.target.browser.ownerGlobal;
+
+        await Router.onMessage(msg);
+
+        assert.calledOnce(gProtectionsHandler.openProtections);
+      });
+    });
+
+    describe("#onMessage: DISABLE_STP_DOORHANGERS", () => {
+      it("should block STP related messages", async () => {
+        const msg = fakeExecuteUserAction({ type: "DISABLE_STP_DOORHANGERS" });
+
+        assert.deepEqual(Router.state.messageBlockList, []);
+
+        await Router.onMessage(msg);
+
+        assert.deepEqual(Router.state.messageBlockList, [
+          "SOCIAL_TRACKING_PROTECTION",
+          "FINGERPRINTERS_PROTECTION",
+          "CRYPTOMINERS_PROTECTION",
+        ]);
+      });
+    });
+
     describe("#dispatch(action, target)", () => {
       it("should an action and target to onMessage", async () => {
         // use the IMPRESSION action to make sure actions are actually getting processed
         sandbox.stub(Router, "addImpression");
         sandbox.spy(Router, "onMessage");
         const target = {};
         const action = { type: "IMPRESSION" };
 
@@ -2897,60 +3153,16 @@ describe("ASRouter", () => {
         const msg = fakeAsyncMessage({ type: "FOO" });
         sandbox.stub(Cu, "reportError");
 
         Router.onMessage(msg);
 
         assert.calledOnce(Cu.reportError);
       });
     });
-    describe("#onMessage: USER_ACTION", () => {
-      it("should dispatch to SpecialMessageActions", async () => {
-        const msg = fakeExecuteUserAction({
-          type: "OPEN_URL",
-          data: {
-            args: "foo",
-          },
-        });
-
-        await Router.onMessage(msg);
-
-        assert.calledOnce(global.SpecialMessageActions.handleAction);
-        assert.calledWithExactly(
-          global.SpecialMessageActions.handleAction,
-          msg.data.data,
-          msg.target.browser
-        );
-      });
-      it("should call update onboarding state on INSTALL_ADDON_FROM_URL", async () => {
-        const spy = sandbox.spy(global.Services.obs, "addObserver");
-        const msg = fakeExecuteUserAction({
-          type: "INSTALL_ADDON_FROM_URL",
-        });
-
-        await Router.onMessage(msg);
-
-        assert.calledOnce(spy);
-        assert.calledWithExactly(
-          spy,
-          sinon.match.func,
-          "webextension-install-notify"
-        );
-
-        const [eventCb] = spy.firstCall.args;
-        // Call the observer cb
-        eventCb();
-        assert.calledOnce(Router.messageChannel.sendAsyncMessage);
-        assert.calledWithExactly(
-          Router.messageChannel.sendAsyncMessage,
-          OUTGOING_MESSAGE_NAME,
-          { type: "CLEAR_INTERRUPT" }
-        );
-      });
-    });
   });
 
   describe("_triggerHandler", () => {
     it("should call #onMessage with the correct trigger", () => {
       const getter = sandbox.stub();
       getter.returns(false);
       sandbox.stub(global.BrowserHandler, "kiosk").get(getter);
       sinon.spy(Router, "onMessage");
@@ -2973,16 +3185,61 @@ describe("ASRouter", () => {
       sinon.spy(Router, "onMessage");
       const target = {};
       const trigger = { id: "FAKE_TRIGGER", param: "some fake param" };
       Router._triggerHandler(target, trigger);
       assert.notCalled(Router.onMessage);
     });
   });
 
+  describe("#UITour", () => {
+    let showMenuStub;
+    const highlightTarget = { target: "target" };
+    beforeEach(() => {
+      showMenuStub = sandbox.stub();
+      globals.set("UITour", {
+        showMenu: showMenuStub,
+        getTarget: sandbox
+          .stub()
+          .withArgs(sinon.match.object, "pageAaction-sendToDevice")
+          .resolves(highlightTarget),
+        showHighlight: sandbox.stub(),
+      });
+    });
+    it("should call UITour.showMenu with the correct params on OPEN_APPLICATIONS_MENU", async () => {
+      const msg = fakeExecuteUserAction({
+        type: "OPEN_APPLICATIONS_MENU",
+        data: { args: "appMenu" },
+      });
+      await Router.onMessage(msg);
+
+      assert.calledOnce(showMenuStub);
+      assert.calledWith(
+        showMenuStub,
+        msg.target.browser.ownerGlobal,
+        "appMenu"
+      );
+    });
+    it("should call UITour.showHighlight with the correct params on HIGHLIGHT_FEATURE", async () => {
+      const msg = fakeExecuteUserAction({
+        type: "HIGHLIGHT_FEATURE",
+        data: { args: "pageAction-sendToDevice" },
+      });
+      await Router.onMessage(msg);
+
+      assert.calledOnce(UITour.getTarget);
+      assert.calledOnce(UITour.showHighlight);
+      assert.calledWith(
+        UITour.showHighlight,
+        msg.target.browser.ownerGlobal,
+        highlightTarget
+      );
+    });
+  });
+
   describe("valid preview endpoint", () => {
     it("should report an error if url protocol is not https", () => {
       sandbox.stub(Cu, "reportError");
 
       assert.equal(false, Router._validPreviewEndpoint("http://foo.com"));
       assert.calledTwice(Cu.reportError);
     });
   });
--- a/browser/components/newtab/test/unit/asrouter/ASRouterFeed.test.js
+++ b/browser/components/newtab/test/unit/asrouter/ASRouterFeed.test.js
@@ -36,17 +36,16 @@ describe("ASRouterFeed", () => {
     };
     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()),
     };
--- a/browser/components/newtab/test/unit/asrouter/MessageLoaderUtils.test.js
+++ b/browser/components/newtab/test/unit/asrouter/MessageLoaderUtils.test.js
@@ -1,8 +1,9 @@
+import { GlobalOverrider } from "test/unit/utils";
 import { MessageLoaderUtils } from "lib/ASRouter.jsm";
 const { STARTPAGE_VERSION } = MessageLoaderUtils;
 
 const FAKE_OPTIONS = {
   storage: {
     set() {
       return Promise.resolve();
     },
@@ -424,16 +425,121 @@ describe("MessageLoaderUtils", () => {
           id: "foo",
           lastUpdated: 0,
           updateCycleInMs: 300,
         })
       );
     });
   });
 
+  describe("#_loadAddonIconInURLBar", () => {
+    let notificationContainerEl;
+    let browser;
+    let getContainerStub;
+    beforeEach(() => {
+      notificationContainerEl = { style: {} };
+      browser = {
+        ownerDocument: {
+          getElementById() {
+            return {};
+          },
+        },
+      };
+      getContainerStub = sandbox.stub(browser.ownerDocument, "getElementById");
+    });
+    it("should return for empty args", () => {
+      MessageLoaderUtils._loadAddonIconInURLBar();
+      assert.notCalled(getContainerStub);
+    });
+    it("should return if notification popup box not found", () => {
+      getContainerStub.returns(null);
+      MessageLoaderUtils._loadAddonIconInURLBar(browser);
+      assert.calledOnce(getContainerStub);
+    });
+    it("should unhide notification popup box with display style as none", () => {
+      getContainerStub.returns(notificationContainerEl);
+      notificationContainerEl.style.display = "none";
+      MessageLoaderUtils._loadAddonIconInURLBar(browser);
+      assert.calledWith(
+        browser.ownerDocument.getElementById,
+        "notification-popup-box"
+      );
+      assert.equal(notificationContainerEl.style.display, "block");
+    });
+    it("should unhide notification popup box with display style empty", () => {
+      getContainerStub.returns(notificationContainerEl);
+      notificationContainerEl.style.display = "";
+      MessageLoaderUtils._loadAddonIconInURLBar(browser);
+      assert.calledWith(
+        browser.ownerDocument.getElementById,
+        "notification-popup-box"
+      );
+      assert.equal(notificationContainerEl.style.display, "block");
+    });
+  });
+
+  describe("#installAddonFromURL", () => {
+    let globals;
+    let getInstallStub;
+    let installAddonStub;
+    beforeEach(() => {
+      globals = new GlobalOverrider();
+      getInstallStub = sandbox.stub();
+      installAddonStub = sandbox.stub();
+      sandbox.stub(MessageLoaderUtils, "_loadAddonIconInURLBar").returns(null);
+      globals.set("AddonManager", {
+        getInstallForURL: getInstallStub,
+        installAddonFromWebpage: installAddonStub,
+      });
+    });
+    afterEach(() => {
+      globals.restore();
+    });
+    it("should call the Addons API when passed a valid URL", async () => {
+      getInstallStub.resolves(null);
+      installAddonStub.resolves(null);
+
+      await MessageLoaderUtils.installAddonFromURL({}, "foo.com");
+
+      assert.calledOnce(getInstallStub);
+      assert.calledOnce(installAddonStub);
+
+      // Verify that the expected installation source has been passed to the getInstallForURL
+      // method (See Bug 1496167 for a rationale).
+      assert.calledWithExactly(getInstallStub, "foo.com", {
+        telemetryInfo: { source: "amo" },
+      });
+    });
+    it("should optionally pass a custom telemetrySource to the Addons API if specified", async () => {
+      getInstallStub.resolves(null);
+      installAddonStub.resolves(null);
+
+      await MessageLoaderUtils.installAddonFromURL({}, "foo.com", "foo");
+
+      assert.calledOnce(getInstallStub);
+      assert.calledOnce(installAddonStub);
+
+      // Verify that a custom installation source can be passed to the getInstallForURL
+      // method (See Bug 1549770 for a rationale).
+      assert.calledWithExactly(getInstallStub, "foo.com", {
+        telemetryInfo: { source: "foo" },
+      });
+    });
+    it("should not call the Addons API on invalid URLs", async () => {
+      sandbox
+        .stub(global.Services.scriptSecurityManager, "getSystemPrincipal")
+        .throws();
+
+      await MessageLoaderUtils.installAddonFromURL({}, "https://foo.com");
+
+      assert.notCalled(getInstallStub);
+      assert.notCalled(installAddonStub);
+    });
+  });
+
   describe("#cleanupCache", () => {
     it("should remove data for providers no longer active", async () => {
       const fakeStorage = {
         get: sinon.stub().returns(
           Promise.resolve({
             "id-1": {},
             "id-2": {},
             "id-3": {},
--- a/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js
+++ b/browser/components/newtab/test/unit/lib/ToolbarPanelHub.test.js
@@ -20,16 +20,17 @@ describe("ToolbarPanelHub", () => {
   let removeObserverStub;
   let getBoolPrefStub;
   let setBoolPrefStub;
   let waitForInitializedStub;
   let isBrowserPrivateStub;
   let fakeDispatch;
   let getEarliestRecordedDateStub;
   let getEventsByDateRangeStub;
+  let handleUserActionStub;
   let defaultSearchStub;
   let scriptloaderStub;
 
   beforeEach(async () => {
     sandbox = sinon.createSandbox();
     globals = new GlobalOverrider();
     instance = new _ToolbarPanelHub();
     waitForInitializedStub = sandbox.stub().resolves();
@@ -136,16 +137,17 @@ describe("ToolbarPanelHub", () => {
     setBoolPrefStub = sandbox.stub();
     fakeDispatch = sandbox.stub();
     isBrowserPrivateStub = sandbox.stub();
     getEarliestRecordedDateStub = sandbox.stub().returns(
       // A random date that's not the current timestamp
       new Date() - 500
     );
     getEventsByDateRangeStub = sandbox.stub().returns([]);
+    handleUserActionStub = sandbox.stub();
     defaultSearchStub = { defaultEngine: { name: "DDG" } };
     globals.set({
       EveryWindow: everyWindowStub,
       Services: {
         ...Services,
         prefs: {
           addObserver: addObserverStub,
           removeObserver: removeObserverStub,
@@ -158,19 +160,16 @@ describe("ToolbarPanelHub", () => {
       PrivateBrowsingUtils: {
         isBrowserPrivate: isBrowserPrivateStub,
       },
       Preferences: preferencesStub,
       TrackingDBService: {
         getEarliestRecordedDate: getEarliestRecordedDateStub,
         getEventsByDateRange: getEventsByDateRangeStub,
       },
-      SpecialMessageActions: {
-        handleAction: sandbox.stub(),
-      },
     });
   });
   afterEach(() => {
     instance.uninit();
     sandbox.restore();
     globals.restore();
     eventListeners = {};
     createdElements = [];
@@ -350,16 +349,17 @@ describe("ToolbarPanelHub", () => {
   });
   describe("#renderMessages", () => {
     let getMessagesStub;
     beforeEach(() => {
       getMessagesStub = sandbox.stub();
       instance.init(waitForInitializedStub, {
         getMessages: getMessagesStub,
         dispatch: fakeDispatch,
+        handleUserAction: handleUserActionStub,
       });
     });
     it("should have correct state", async () => {
       const messages = (await PanelTestProvider.getMessages()).filter(
         m => m.template === "whatsnew_panel_message"
       );
 
       getMessagesStub.returns(messages);
@@ -426,17 +426,17 @@ describe("ToolbarPanelHub", () => {
         assert.ok(
           createdCustomElements.find(el =>
             el.classList.includes("whatsNew-message-content")
           )
         );
       }
       // Call the click handler to make coverage happy.
       eventListeners.mouseup();
-      assert.calledOnce(global.SpecialMessageActions.handleAction);
+      assert.calledOnce(handleUserActionStub);
     });
     it("should clear previous messages on 2nd renderMessages()", async () => {
       const messages = (await PanelTestProvider.getMessages()).filter(
         m => m.template === "whatsnew_panel_message"
       );
       const removeStub = sandbox.stub();
       fakeElementById.querySelectorAll.onCall(0).returns([]);
       fakeElementById.querySelectorAll
@@ -544,22 +544,22 @@ describe("ToolbarPanelHub", () => {
       );
       getMessagesStub.returns(messages);
 
       await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
 
       const buttonEl = createdElements.find(el => el.tagName === "button");
       const anchorEl = createdElements.find(el => el.tagName === "a");
 
-      assert.notCalled(global.SpecialMessageActions.handleAction);
+      assert.notCalled(handleUserActionStub);
 
       buttonEl.doCommand();
       anchorEl.doCommand();
 
-      assert.calledTwice(global.SpecialMessageActions.handleAction);
+      assert.calledTwice(handleUserActionStub);
     });
     it("should listen for panelhidden and remove the toolbar button", async () => {
       getMessagesStub.returns([]);
 
       await instance.renderMessages(fakeWindow, fakeDocument, "container-id");
 
       assert.calledOnce(fakeElementById.addEventListener);
       assert.calledWithExactly(
@@ -778,16 +778,17 @@ describe("ToolbarPanelHub", () => {
       getMessagesStub = sandbox
         .stub()
         .resolves(
           onboardingMsgs.find(msg => msg.template === "protections_panel")
         );
       await instance.init(waitForInitializedStub, {
         dispatch: fakeDispatch,
         getMessages: getMessagesStub,
+        handleUserAction: handleUserActionStub,
       });
     });
     it("should remember it showed", async () => {
       await fakeInsert();
 
       assert.calledWithExactly(
         setBoolPrefStub,
         "browser.protections_panel.infoMessage.seen",
@@ -823,108 +824,104 @@ describe("ToolbarPanelHub", () => {
         sendTelemetryStub,
         fakeWindow,
         "IMPRESSION",
         msg
       );
 
       eventListeners.mouseup();
 
-      assert.calledOnce(global.SpecialMessageActions.handleAction);
-      assert.calledWithExactly(
-        global.SpecialMessageActions.handleAction,
-        {
+      assert.calledOnce(handleUserActionStub);
+      assert.calledWithExactly(handleUserActionStub, {
+        target: fakeWindow,
+        data: {
           type: "OPEN_URL",
           data: {
             args: sinon.match.string,
             where: "tabshifted",
           },
         },
-        fakeWindow.browser
-      );
+      });
     });
     it("should format the url", async () => {
       const stub = sandbox
         .stub(global.Services.urlFormatter, "formatURL")
         .returns("formattedURL");
       const onboardingMsgs = await OnboardingMessageProvider.getUntranslatedMessages();
       const msg = onboardingMsgs.find(m => m.template === "protections_panel");
 
       await fakeInsert();
 
       eventListeners.mouseup();
 
       assert.calledOnce(stub);
       assert.calledWithExactly(stub, msg.content.cta_url);
-      assert.calledOnce(global.SpecialMessageActions.handleAction);
-      assert.calledWithExactly(
-        global.SpecialMessageActions.handleAction,
-        {
+      assert.calledOnce(handleUserActionStub);
+      assert.calledWithExactly(handleUserActionStub, {
+        target: fakeWindow,
+        data: {
           type: "OPEN_URL",
           data: {
             args: "formattedURL",
             where: "tabshifted",
           },
         },
-        fakeWindow.browser
-      );
+      });
     });
     it("should report format url errors", async () => {
       const stub = sandbox
         .stub(global.Services.urlFormatter, "formatURL")
         .throws();
       const onboardingMsgs = await OnboardingMessageProvider.getUntranslatedMessages();
       const msg = onboardingMsgs.find(m => m.template === "protections_panel");
       sandbox.spy(global.Cu, "reportError");
 
       await fakeInsert();
 
       eventListeners.mouseup();
 
       assert.calledOnce(stub);
       assert.calledOnce(global.Cu.reportError);
-      assert.calledOnce(global.SpecialMessageActions.handleAction);
-      assert.calledWithExactly(
-        global.SpecialMessageActions.handleAction,
-        {
+      assert.calledOnce(handleUserActionStub);
+      assert.calledWithExactly(handleUserActionStub, {
+        target: fakeWindow,
+        data: {
           type: "OPEN_URL",
           data: {
             args: msg.content.cta_url,
             where: "tabshifted",
           },
         },
-        fakeWindow.browser
-      );
+      });
     });
     it("should open link on click (directly attached to the message)", async () => {
       const onboardingMsgs = await OnboardingMessageProvider.getUntranslatedMessages();
       const msg = onboardingMsgs.find(m => m.template === "protections_panel");
       getMessagesStub.resolves({
         ...msg,
         content: { ...msg.content, link_text: null },
       });
       await fakeInsert();
 
       eventListeners.mouseup();
 
-      assert.calledOnce(global.SpecialMessageActions.handleAction);
-      assert.calledWithExactly(
-        global.SpecialMessageActions.handleAction,
-        {
+      assert.calledOnce(handleUserActionStub);
+      assert.calledWithExactly(handleUserActionStub, {
+        target: fakeWindow,
+        data: {
           type: "OPEN_URL",
           data: {
             args: sinon.match.string,
             where: "tabshifted",
           },
         },
-        fakeWindow.browser
-      );
+      });
     });
-    it("should handle user actions from mouseup and keyup", async () => {
+    it("should handleUserAction from mouseup and keyup", async () => {
       await fakeInsert();
 
       eventListeners.mouseup();
       eventListeners.keyup({ key: "Enter" });
       eventListeners.keyup({ key: " " });
-      assert.calledThrice(global.SpecialMessageActions.handleAction);
+      assert.calledThrice(handleUserActionStub);
     });
   });
 });
--- a/toolkit/components/messaging-system/lib/SpecialMessageActions.jsm
+++ b/toolkit/components/messaging-system/lib/SpecialMessageActions.jsm
@@ -13,21 +13,16 @@ const { XPCOMUtils } = ChromeUtils.impor
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonManager: "resource://gre/modules/AddonManager.jsm",
   UITour: "resource:///modules/UITour.jsm",
   FxAccounts: "resource://gre/modules/FxAccounts.jsm",
   MigrationUtils: "resource:///modules/MigrationUtils.jsm",
 });
 
 const SpecialMessageActions = {
-  // This is overridden by ASRouter.init
-  blockMessageById() {
-    throw new Error("ASRouter not intialized yet");
-  },
-
   /**
    * loadAddonIconInURLBar - load addons-notification icon by displaying
    * box containing addons icon in urlbar. See Bug 1513882
    *
    * @param  {Browser} browser browser element for showing addons icon
    */
   loadAddonIconInURLBar(browser) {
     if (!browser) {
@@ -142,18 +137,18 @@ const SpecialMessageActions = {
       case "INSTALL_ADDON_FROM_URL":
         await this.installAddonFromURL(
           browser,
           action.data.url,
           action.data.telemetrySource
         );
         break;
       case "PIN_CURRENT_TAB":
-        let tab = window.gBrowser.selectedTab;
-        window.gBrowser.pinTab(tab);
+        let tab = browser.selectedTab;
+        browser.pinTab(tab);
         window.ConfirmationHint.show(tab, "pinTab", {
           showDescription: true,
         });
         break;
       case "SHOW_FIREFOX_ACCOUNTS":
         const url = await FxAccounts.config.promiseConnectAccountURI(
           (action.data && action.data.entrypoint) || "snippets"
         );
@@ -171,26 +166,15 @@ const SpecialMessageActions = {
         gProtectionsHandler.showProtectionsPopup({});
         break;
       case "OPEN_PROTECTION_REPORT":
         window.gProtectionsHandler.openProtections();
         break;
       case "OPEN_AWESOME_BAR":
         window.gURLBar.search("");
         break;
-      case "DISABLE_STP_DOORHANGERS":
-        await this.blockMessageById([
-          "SOCIAL_TRACKING_PROTECTION",
-          "FINGERPRINTERS_PROTECTION",
-          "CRYPTOMINERS_PROTECTION",
-        ]);
-        break;
-      case "CANCEL":
-        // A no-op used by CFRs that minimizes the notification but does not
-        // trigger a dismiss or block (it keeps the notification around)
-        break;
       default:
         throw new Error(
           `Special message action with type ${action.type} is unsupported.`
         );
     }
   },
 };
--- a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas.js
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas.js
@@ -228,19 +228,9 @@ const SpecialMessageActionSchemas = {
     properties: {
       type: {
         enum: ["SHOW_MIGRATION_WIZARD"],
         type: "string",
       },
     },
     type: "object",
   },
-  CANCEL: {
-    description: "Cancel (dismiss) the CFR doorhanger.",
-    properties: {
-      type: {
-        enum: ["CANCEL"],
-        type: "string",
-      },
-    },
-    type: "object",
-  },
 };
deleted file mode 100644
--- a/toolkit/components/messaging-system/test/browser/specialMessageActions/browser_sma_cancel.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-add_task(async function test_cancel_event() {
-  let error = null;
-  try {
-    await SMATestUtils.executeAndValidateAction({ type: "CANCEL" });
-  } catch (e) {
-    error = e;
-  }
-  ok(!error, "should not throw for CANCEL");
-});