summaryrefslogtreecommitdiffstats
path: root/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas')
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/SpecialMessageActionSchemas.json600
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/index.md341
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser.toml54
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma.js21
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_accept_doh.js17
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_block_message.js23
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_cancel.js14
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_cfrmessageprovider.js31
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_configure_homepage.js103
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_default_browser.js22
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_default_pdf_handler.js110
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_disable_doh.js28
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_docs.js32
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_handle_multiaction.js72
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_about_page.js34
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_awesome_bar.js9
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_private_browser_window.js17
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_protection_panel.js22
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_protection_report.js29
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_spotlight_dialog.js38
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_url.js33
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_current_tab.js14
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_firefox.js72
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_private_firefox.js78
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_set_prefs.js167
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_show_firefox_accounts.js66
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_show_migration_wizard.js39
-rw-r--r--toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/head.js62
28 files changed, 2148 insertions, 0 deletions
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/SpecialMessageActionSchemas.json b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/SpecialMessageActionSchemas.json
new file mode 100644
index 0000000000..4c9bccf8a6
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/SpecialMessageActionSchemas.json
@@ -0,0 +1,600 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "$ref": "#/definitions/SpecialMessageActionSchemas",
+ "definitions": {
+ "SpecialMessageActionSchemas": {
+ "anyOf": [
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["DISABLE_STP_DOORHANGERS"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Disables all STP doorhangers."
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "args": {
+ "type": "string",
+ "description": "The element to highlight"
+ }
+ },
+ "required": ["args"],
+ "additionalProperties": false
+ },
+ "type": {
+ "type": "string",
+ "enum": ["HIGHLIGHT_FEATURE"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Highlights an element, such as a menu item"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "telemetrySource": {
+ "type": "string"
+ },
+ "url": {
+ "type": "string"
+ }
+ },
+ "required": ["telemetrySource", "url"],
+ "additionalProperties": false
+ },
+ "type": {
+ "type": "string",
+ "enum": ["INSTALL_ADDON_FROM_URL"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Install an add-on from AMO"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "args": {
+ "type": "string",
+ "description": "The about page. E.g. \"welcome\" for about:welcome'"
+ },
+ "where": {
+ "type": "string",
+ "enum": ["current", "save", "tab", "tabshifted", "window"],
+ "description": "Where the URL is opened",
+ "default": "tab"
+ },
+ "entrypoint": {
+ "type": "string",
+ "description": "Any optional entrypoint value that will be added to the search. E.g. \"foo=bar\" would result in about:welcome?foo=bar'"
+ }
+ },
+ "required": ["args", "where", "entrypoint"],
+ "additionalProperties": false
+ },
+ "type": {
+ "type": "string",
+ "enum": ["OPEN_ABOUT_PAGE"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Opens an about: page in Firefox"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["OPEN_FIREFOX_VIEW"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Opens the Firefox View pseudo-pinned-tab"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "args": {
+ "type": "string",
+ "description": "The menu name, e.g. \"appMenu\""
+ }
+ },
+ "required": ["args"],
+ "additionalProperties": false
+ },
+ "type": {
+ "type": "string",
+ "enum": ["OPEN_APPLICATIONS_MENU"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Opens an application menu"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["OPEN_AWESOME_BAR"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Focuses and expands the awesome bar"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "category": {
+ "type": "string",
+ "description": "Section of about:preferences, e.g. \"privacy-reports\""
+ },
+ "entrypoint": {
+ "type": "string",
+ "description": "Add a queryparam for metrics"
+ }
+ },
+ "required": ["category"],
+ "additionalProperties": false
+ },
+ "type": {
+ "type": "string",
+ "enum": ["OPEN_PREFERENCES_PAGE"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Opens a preference page"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["OPEN_PRIVATE_BROWSER_WINDOW"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Opens a private browsing window."
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["OPEN_PROTECTION_PANEL"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Opens the protections panel"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["OPEN_PROTECTION_REPORT"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Opens the protections panel report"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "args": {
+ "type": "string",
+ "description": "URL to open"
+ },
+ "where": {
+ "type": "string",
+ "enum": ["current", "save", "tab", "tabshifted", "window"],
+ "description": "Where the URL is opened",
+ "default": "tab"
+ }
+ },
+ "required": ["args", "where"],
+ "additionalProperties": false
+ },
+ "type": {
+ "type": "string",
+ "enum": ["OPEN_URL"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Opens given URL"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["PIN_CURRENT_TAB"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Pin the current tab"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["SHOW_FIREFOX_ACCOUNTS"]
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "entrypoint": {
+ "type": "string",
+ "description": "Adds entrypoint={your value} to the FXA URL"
+ },
+ "extraParams": {
+ "type": "object",
+ "description": "Any extra parameter that will be added to the FXA URL. E.g. {foo: bar} would result in <FXA_url>?foo=bar'"
+ }
+ },
+ "required": ["entrypoint"],
+ "additionalProperties": false
+ }
+ },
+ "required": ["type", "data"],
+ "additionalProperties": false,
+ "description": "Show Firefox Accounts"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["SHOW_MIGRATION_WIZARD"]
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "source": {
+ "type": "string",
+ "description": "Identitifer of the browser that should be pre-selected in the import migration wizard popup (e.g. 'chrome'), See https://searchfox.org/mozilla-central/rev/8dae1cc76a6b45e05198bc0d5d4edb7bf1003265/browser/components/migration/MigrationUtils.jsm#917"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Shows the Migration Wizard to import data from another Browser. See https://support.mozilla.org/en-US/kb/import-data-another-browser\""
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["CANCEL"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Minimize the CFR doorhanger back into the URLbar"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["ACCEPT_DOH"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Accept DOH doorhanger notification"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["DISABLE_DOH"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Dismiss DOH doorhanger notification"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["PIN_FIREFOX_TO_TASKBAR"]
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "privatePin": {
+ "type": "boolean",
+ "description": "Whether or not to pin private browsing mode"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Pin the app to taskbar"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["SET_DEFAULT_BROWSER"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Message action to set Firefox as default browser"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["SET_DEFAULT_PDF_HANDLER"]
+ },
+ "data": {
+ "type": "object",
+ "properties": {
+ "onlyIfKnownBrowser": {
+ "type": "boolean",
+ "description": "Only set Firefox as the default PDF handler if the current PDF handler is a known browser."
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Message action to set Firefox as the default PDF handler"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["DECLINE_DEFAULT_PDF_HANDLER"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Message action to decline setting Firefox as the default PDF handler"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "homePage": {
+ "type": "string",
+ "description": "Should reset homepage pref",
+ "enum": ["default"]
+ },
+ "newtab": {
+ "type": "string",
+ "enum": ["default"],
+ "description": "Should reset newtab pref"
+ },
+ "layout": {
+ "type": "object",
+ "description": "Section name and boolean value that specifies if the section should be on or off.",
+ "properties": {
+ "search": {
+ "type": "boolean"
+ },
+ "topsites": {
+ "type": "boolean"
+ },
+ "highlights": {
+ "type": "boolean"
+ },
+ "topstories": {
+ "type": "boolean"
+ }
+ },
+ "required": [
+ "search",
+ "topsites",
+ "highlights",
+ "topstories"
+ ],
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ },
+ "type": {
+ "type": "string",
+ "enum": ["CONFIGURE_HOMEPAGE"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Resets homepage pref and sections layout"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "content": {
+ "type": "object",
+ "description": "Object containing content rendered inside spotlight dialog"
+ }
+ },
+ "required": ["content"],
+ "additionalProperties": false
+ },
+ "type": {
+ "type": "string",
+ "enum": ["SHOW_SPOTLIGHT"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Opens a spotlight dialog"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "id": {
+ "type": "string",
+ "description": "Message id to block"
+ }
+ },
+ "required": ["id"],
+ "additionalProperties": false
+ },
+ "type": {
+ "type": "string",
+ "enum": ["BLOCK_MESSAGE"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Add message to an indexedDb list of blocked messages"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "type": "object",
+ "properties": {
+ "pref": {
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string"
+ },
+ "value": {
+ "type": ["boolean", "string", "number", "null"]
+ }
+ },
+ "description": "An object representing a pref containing a name and a value."
+ }
+ },
+ "required": ["pref"],
+ "additionalProperties": false
+ },
+ "type": {
+ "type": "string",
+ "enum": ["SET_PREF"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Sets prefs from special message actions"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "actions": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "description": "A special message action definition"
+ }
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": ["MULTI_ACTION"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Runs multiple actions"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "data": {
+ "selector": {
+ "type": "string",
+ "description": "A CSS selector for the HTML element to be clicked"
+ }
+ },
+ "type": {
+ "type": "string",
+ "enum": ["CLICK_ELEMENT"]
+ }
+ },
+ "required": ["data", "type"],
+ "additionalProperties": false,
+ "description": "Selects an element in the current Window's document and triggers a click action"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["RELOAD_BROWSER"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Message action that reloads the current browser"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["FOCUS_URLBAR"]
+ }
+ },
+ "required": ["type"],
+ "additionalProperties": false,
+ "description": "Focuses the urlbar in the window the message was displayed in"
+ }
+ ]
+ }
+ }
+}
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/index.md b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/index.md
new file mode 100644
index 0000000000..8b431a66fe
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/index.md
@@ -0,0 +1,341 @@
+# User Actions
+
+A subset of actions are available to messages via fields like `action` on buttons for CFRs.
+
+## Usage
+
+For CFRs, you should add the action `type` in `action` and any additional parameters in `data`. For example:
+
+```json
+"action": {
+ "type": "OPEN_PREFERENCES_PAGE",
+ "data": { "category": "sync" },
+}
+```
+
+## Available Actions
+
+### `OPEN_APPLICATIONS_MENU`
+
+* args: (none)
+
+Opens the applications menu.
+
+### `OPEN_FIREFOX_VIEW`
+
+* args: (none)
+
+Opens the Firefox View pseudo-tab.
+
+### `OPEN_PRIVATE_BROWSER_WINDOW`
+
+* args: (none)
+
+Opens a new private browsing window.
+
+
+### `OPEN_URL`
+
+* args: `string` (a url)
+
+Opens a given url.
+
+Example:
+
+```json
+"action": {
+ "type": "OPEN_URL",
+ "data": { "args": "https://foo.com" },
+}
+```
+
+### `OPEN_ABOUT_PAGE`
+
+* args:
+```ts
+{
+ args: string, // (a valid about page without the `about:` prefix)
+ entrypoint?: string, // URL search param used for referrals
+}
+```
+
+Opens a given about page
+
+Example:
+
+```json
+"action": {
+ "type": "OPEN_ABOUT_PAGE",
+ "data": { "args": "privatebrowsing" },
+}
+```
+
+### `OPEN_PREFERENCES_PAGE`
+
+* args:
+```
+{
+ args?: string, // (a category accessible via a `#`)
+ entrypoint?: string // URL search param used to referrals
+
+Opens `about:preferences` with an optional category accessible via a `#` in the URL (e.g. `about:preferences#home`).
+
+Example:
+
+```json
+"action": {
+ "type": "OPEN_PREFERENCES_PAGE",
+ "data": { "category": "general-cfrfeatures" },
+}
+```
+
+### `SHOW_FIREFOX_ACCOUNTS`
+
+* args: (none)
+
+Opens Firefox accounts sign-up page. Encodes some information that the origin was from snippets by default.
+
+### `FXA_SIGNIN_FLOW`
+
+* args:
+
+```ts
+{
+ // a valid `where` value for `openUILinkIn`. Only `tab` and `window` have been tested, and `tabshifted`
+ // is unlikely to do anything different from `tab`.
+ where?: "tab" | "window" = "tab",
+
+ entrypoint?: string // URL search params string to pass along to FxA. Defaults to "activity-stream-firstrun".
+ extraParams?: object // Extra parameters to pass along to FxA. See FxAccountsConfig.promiseConnectAccountURI.
+}
+```
+
+Opens a Firefox accounts sign-up or sign-in page, and does the work of closing the resulting tab or window once
+sign-in completes. Returns a Promise that resolves to `true` if sign-in succeeded, or to `false` if the sign-in
+window or tab closed before sign-in could be completed.
+
+Encodes some information that the origin was from about:welcome by default.
+
+
+### `SHOW_MIGRATION_WIZARD`
+
+* args: (none)
+
+Opens import wizard to bring in settings and data from another browser.
+
+### `PIN_CURRENT_TAB`
+
+* args: (none)
+
+Pins the currently focused tab.
+
+### `HIGHLIGHT_FEATURE`
+
+Can be used to highlight (show a light blue overlay) a certain button or part of the browser UI.
+
+* args: `string` a [valid targeting defined in the UITour](https://searchfox.org/mozilla-central/rev/7fd1c1c34923ece7ad8c822bee062dd0491d64dc/browser/components/uitour/UITour.jsm#108)
+
+### `INSTALL_ADDON_FROM_URL`
+
+Can be used to install an addon from addons.mozilla.org.
+
+* args:
+```ts
+{
+ url: string,
+ telemetrySource?: string
+};
+```
+
+### `OPEN_PROTECTION_REPORT`
+
+Opens `about:protections`
+
+### `OPEN_PROTECTION_PANEL`
+
+Opens the protection panel behind on the lock icon of the awesomebar
+
+### `DISABLE_STP_DOORHANGERS`
+
+Disables all Social Tracking Protection messages
+
+* args: (none)
+
+### `OPEN_AWESOME_BAR`
+
+Focuses and expands the awesome bar.
+
+* args: (none)
+
+### `CANCEL`
+
+No-op action used to dismiss CFR notifications (but not remove or block them)
+
+* args: (none)
+
+### `DISABLE_DOH`
+
+User action for turning off the DoH feature
+
+* args: (none)
+
+### `ACCEPT_DOH`
+
+User action for continuing to use the DoH feature
+
+* args: (none)
+
+### `CONFIGURE_HOMEPAGE`
+
+Action for configuring the user homepage and restoring defaults.
+
+* args:
+```ts
+{
+ homePage: "default" | null;
+ newtab: "default" | null;
+ layout: {
+ search: boolean;
+ topsites: boolean;
+ highlights: boolean;
+ topstories: boolean;
+ }
+}
+```
+
+### `PIN_FIREFOX_TO_TASKBAR`
+
+Action for pinning Firefox to the user's taskbar.
+
+* args: (none)
+
+### `SET_DEFAULT_BROWSER`
+
+Action for setting the default browser to Firefox on the user's system.
+
+- args: (none)
+
+### `SET_DEFAULT_PDF_HANDLER`
+
+Action for setting the default PDF handler to Firefox on the user's system.
+
+Windows only.
+
+- args:
+```ts
+{
+ // Only set Firefox as the default PDF handler if the current PDF handler is a
+ // known browser.
+ onlyIfKnownBrowser?: boolean;
+}
+```
+
+### `DECLINE_DEFAULT_PDF_HANDLER`
+
+Action for declining to set the default PDF handler to Firefox on the user's
+system. Prevents the user from being asked again about this.
+
+Windows only.
+
+- args: (none)
+
+### `SHOW_SPOTLIGHT`
+
+Action for opening a spotlight tab or window modal using the content passed to the dialog.
+
+### `BLOCK_MESSAGE`
+
+Disable a message by adding to an indexedDb list of blocked messages
+
+* args: `string` id of the message
+
+### `SET_PREF`
+
+Action for setting various browser prefs
+
+Prefs that can be changed with this action are:
+
+- `browser.dataFeatureRecommendations.enabled`
+- `browser.migrate.content-modal.about-welcome-behavior`
+- `browser.migrate.content-modal.import-all.enabled`
+- `browser.migrate.preferences-entrypoint.enabled`
+- `browser.shopping.experience2023.active`
+- `browser.shopping.experience2023.optedIn`
+- `browser.shopping.experience2023.survey.optedInTime`
+- `browser.shopping.experience2023.survey.hasSeen`
+- `browser.shopping.experience2023.survey.pdpVisits`
+- `browser.startup.homepage`
+- `browser.startup.windowsLaunchOnLogin.disableLaunchOnLoginPrompt`
+- `browser.privateWindowSeparation.enabled`
+- `browser.firefox-view.feature-tour`
+- `browser.pdfjs.feature-tour`
+- `browser.newtab.feature-tour`
+- `cookiebanners.service.mode`
+- `cookiebanners.service.mode.privateBrowsing`
+- `cookiebanners.service.detectOnly`
+- `messaging-system.askForFeedback`
+
+Any pref that begins with `messaging-system-action.` is also allowed.
+Alternatively, if the pref is not present in the list above and does not begin
+with `messaging-system-action.`, it will be created and prepended with
+`messaging-system-action.`. For example, `example.pref` will be created as
+`messaging-system-action.example.pref`.
+
+* args:
+```ts
+{
+ pref: {
+ name: string;
+ value: string | boolean | number;
+ }
+}
+```
+
+### `MULTI_ACTION`
+
+Action for running multiple actions. Actions should be included in an array of actions.
+
+* args:
+```ts
+{
+ actions: Array<UserAction>
+}
+```
+
+* example:
+```json
+"action": {
+ "type": "MULTI_ACTION",
+ "data": {
+ "actions": [
+ {
+ "type": "OPEN_URL",
+ "args": "https://www.example.com"
+ },
+ {
+ "type": "OPEN_AWESOME_BAR"
+ }
+ ]
+ }
+}
+```
+
+### `CLICK_ELEMENT`
+
+* args: `string` A CSS selector for the HTML element to be clicked
+
+Selects an element in the current Window's document and triggers a click action
+
+
+### `RELOAD_BROWSER`
+
+* args: (none)
+
+Action for reloading the current browser.
+
+
+### `FOCUS_URLBAR`
+
+Focuses the urlbar in the window the message was displayed in
+
+* args: (none)
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser.toml b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser.toml
new file mode 100644
index 0000000000..fc5b6b6550
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser.toml
@@ -0,0 +1,54 @@
+[DEFAULT]
+prefs = ["identity.fxaccounts.remote.root=https://example.com/"]
+support-files = [
+ "head.js",
+ "../../index.md",
+]
+
+["browser_sma.js"]
+
+["browser_sma_accept_doh.js"]
+
+["browser_sma_block_message.js"]
+
+["browser_sma_cfrmessageprovider.js"]
+
+["browser_sma_configure_homepage.js"]
+
+["browser_sma_default_browser.js"]
+
+["browser_sma_default_pdf_handler.js"]
+
+["browser_sma_disable_doh.js"]
+
+["browser_sma_docs.js"]
+
+["browser_sma_handle_multiaction.js"]
+
+["browser_sma_open_about_page.js"]
+
+["browser_sma_open_awesome_bar.js"]
+
+["browser_sma_open_private_browser_window.js"]
+
+["browser_sma_open_protection_panel.js"]
+
+["browser_sma_open_protection_report.js"]
+
+["browser_sma_open_spotlight_dialog.js"]
+
+["browser_sma_open_url.js"]
+
+["browser_sma_pin_current_tab.js"]
+
+["browser_sma_pin_firefox.js"]
+
+["browser_sma_pin_private_firefox.js"]
+skip-if = ["os != 'win'"]
+
+["browser_sma_set_prefs.js"]
+
+["browser_sma_show_firefox_accounts.js"]
+
+["browser_sma_show_migration_wizard.js"]
+skip-if = ["apple_catalina && debug"] # Bug 1837646
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma.js
new file mode 100644
index 0000000000..b46b3730e9
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_unknown_event() {
+ let error;
+ try {
+ await SpecialMessageActions.handleAction(
+ { type: "UNKNOWN_EVENT_123" },
+ gBrowser
+ );
+ } catch (e) {
+ error = e;
+ }
+ ok(error, "should throw if an unexpected event is handled");
+ Assert.equal(
+ error.message,
+ "Special message action with type UNKNOWN_EVENT_123 is unsupported."
+ );
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_accept_doh.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_accept_doh.js
new file mode 100644
index 0000000000..f9255b17ec
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_accept_doh.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+const DOH_DOORHANGER_DECISION_PREF = "doh-rollout.doorhanger-decision";
+
+add_task(async function test_disable_doh() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[DOH_DOORHANGER_DECISION_PREF, ""]],
+ });
+ await SMATestUtils.executeAndValidateAction({ type: "ACCEPT_DOH" });
+ Assert.equal(
+ Services.prefs.getStringPref(DOH_DOORHANGER_DECISION_PREF, ""),
+ "UIOk",
+ "Pref should be set on accept"
+ );
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_block_message.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_block_message.js
new file mode 100644
index 0000000000..0671253b43
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_block_message.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_block_message() {
+ let blockStub = sinon.stub(SpecialMessageActions, "blockMessageById");
+
+ await SMATestUtils.executeAndValidateAction({
+ type: "BLOCK_MESSAGE",
+ data: {
+ id: "TEST_MESSAGE_ID",
+ },
+ });
+
+ Assert.equal(blockStub.callCount, 1, "blockMessageById called by the action");
+ Assert.equal(
+ blockStub.firstCall.args[0],
+ "TEST_MESSAGE_ID",
+ "Argument is message id"
+ );
+ blockStub.restore();
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_cancel.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_cancel.js
new file mode 100644
index 0000000000..ca42dac563
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_cancel.js
@@ -0,0 +1,14 @@
+/* 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");
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_cfrmessageprovider.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_cfrmessageprovider.js
new file mode 100644
index 0000000000..a01b8c8bbf
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_cfrmessageprovider.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { CFRMessageProvider } = ChromeUtils.importESModule(
+ "resource:///modules/asrouter/CFRMessageProvider.sys.mjs"
+);
+
+add_task(async function test_all_test_messages() {
+ let messagesWithButtons = (await CFRMessageProvider.getMessages()).filter(
+ m => m.content.buttons
+ );
+
+ for (let message of messagesWithButtons) {
+ info(`Testing ${message.id}`);
+ if (message.template === "infobar") {
+ for (let button of message.content.buttons) {
+ await SMATestUtils.validateAction(button.action);
+ }
+ } else {
+ let { primary, secondary } = message.content.buttons;
+ await SMATestUtils.validateAction(primary.action);
+ for (let secondaryBtn of secondary) {
+ if (secondaryBtn.action) {
+ await SMATestUtils.validateAction(secondaryBtn.action);
+ }
+ }
+ }
+ }
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_configure_homepage.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_configure_homepage.js
new file mode 100644
index 0000000000..d05ffb7b1f
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_configure_homepage.js
@@ -0,0 +1,103 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const HOMEPAGE_PREF = "browser.startup.homepage";
+const NEWTAB_PREF = "browser.newtabpage.enabled";
+const HIGHLIGHTS_PREF =
+ "browser.newtabpage.activity-stream.feeds.section.highlights";
+const HIGHLIGHTS_ROWS_PREF =
+ "browser.newtabpage.activity-stream.section.highlights.rows";
+const SEARCH_PREF = "browser.newtabpage.activity-stream.showSearch";
+const TOPSITES_PREF = "browser.newtabpage.activity-stream.feeds.topsites";
+const TOPSTORIES_PREF =
+ "browser.newtabpage.activity-stream.feeds.system.topstories";
+
+add_setup(async function () {
+ await SpecialPowers.pushPrefEnv({
+ // Highlights are preffed off by default.
+ set: [
+ [HIGHLIGHTS_PREF, true],
+ [
+ "browser.newtabpage.activity-stream.discoverystream.endpointSpocsClear",
+ "",
+ ],
+ ],
+ });
+
+ registerCleanupFunction(async () => {
+ await SpecialPowers.popPrefEnv();
+ [
+ HOMEPAGE_PREF,
+ NEWTAB_PREF,
+ HIGHLIGHTS_PREF,
+ HIGHLIGHTS_ROWS_PREF,
+ SEARCH_PREF,
+ TOPSITES_PREF,
+ ].forEach(prefName => Services.prefs.clearUserPref(prefName));
+ });
+});
+
+add_task(async function test_CONFIGURE_HOMEPAGE_newtab_home_prefs() {
+ const action = {
+ type: "CONFIGURE_HOMEPAGE",
+ data: { homePage: "default", newtab: "default" },
+ };
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [HOMEPAGE_PREF, "about:blank"],
+ [NEWTAB_PREF, false],
+ ],
+ });
+
+ Assert.ok(Services.prefs.prefHasUserValue(HOMEPAGE_PREF), "Test setup ok");
+ Assert.ok(Services.prefs.prefHasUserValue(NEWTAB_PREF), "Test setup ok");
+
+ await SMATestUtils.executeAndValidateAction(action);
+
+ Assert.ok(
+ !Services.prefs.prefHasUserValue(HOMEPAGE_PREF),
+ "Homepage pref should be back to default"
+ );
+ Assert.ok(
+ !Services.prefs.prefHasUserValue(NEWTAB_PREF),
+ "Newtab pref should be back to default"
+ );
+});
+
+add_task(async function test_CONFIGURE_HOMEPAGE_layout_prefs() {
+ const action = {
+ type: "CONFIGURE_HOMEPAGE",
+ data: {
+ layout: {
+ search: true,
+ topsites: false,
+ highlights: false,
+ topstories: false,
+ },
+ },
+ };
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [HIGHLIGHTS_ROWS_PREF, 3],
+ [SEARCH_PREF, false],
+ ],
+ });
+
+ await SMATestUtils.executeAndValidateAction(action);
+
+ Assert.ok(Services.prefs.getBoolPref(SEARCH_PREF), "Search is turned on");
+ Assert.ok(
+ !Services.prefs.getBoolPref(TOPSITES_PREF),
+ "Topsites are turned off"
+ );
+ Assert.ok(
+ Services.prefs.getBoolPref(HIGHLIGHTS_PREF),
+ "HIGHLIGHTS_PREF are on because they have been customized"
+ );
+ Assert.ok(
+ !Services.prefs.getBoolPref(TOPSTORIES_PREF),
+ "Topstories are turned off"
+ );
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_default_browser.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_default_browser.js
new file mode 100644
index 0000000000..2d919456cd
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_default_browser.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_set_default_browser() {
+ const sandbox = sinon.createSandbox();
+ const stub = sandbox.stub();
+
+ await SMATestUtils.executeAndValidateAction(
+ { type: "SET_DEFAULT_BROWSER" },
+ {
+ ownerGlobal: {
+ getShellService: () => ({
+ setAsDefault: stub,
+ }),
+ },
+ }
+ );
+
+ Assert.equal(stub.callCount, 1, "setAsDefault was called by the action");
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_default_pdf_handler.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_default_pdf_handler.js
new file mode 100644
index 0000000000..30c2229380
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_default_pdf_handler.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_set_default_pdf_handler_no_data() {
+ const sandbox = sinon.createSandbox();
+ const stub = sandbox.stub();
+
+ await SMATestUtils.executeAndValidateAction(
+ { type: "SET_DEFAULT_PDF_HANDLER" },
+ {
+ ownerGlobal: {
+ getShellService: () => ({
+ setAsDefaultPDFHandler: stub,
+ }),
+ },
+ }
+ );
+
+ Assert.equal(
+ stub.callCount,
+ 1,
+ "setAsDefaultPDFHandler was called by the action"
+ );
+ Assert.ok(
+ stub.calledWithExactly(false),
+ "setAsDefaultPDFHandler called with onlyIfKnownBrowser = false"
+ );
+});
+
+add_task(async function test_set_default_pdf_handler_data_false() {
+ const sandbox = sinon.createSandbox();
+ const stub = sandbox.stub();
+
+ await SMATestUtils.executeAndValidateAction(
+ {
+ type: "SET_DEFAULT_PDF_HANDLER",
+ data: {
+ onlyIfKnownBrowser: false,
+ },
+ },
+ {
+ ownerGlobal: {
+ getShellService: () => ({
+ setAsDefaultPDFHandler: stub,
+ }),
+ },
+ }
+ );
+
+ Assert.equal(
+ stub.callCount,
+ 1,
+ "setAsDefaultPDFHandler was called by the action"
+ );
+ Assert.ok(
+ stub.calledWithExactly(false),
+ "setAsDefaultPDFHandler called with onlyIfKnownBrowser = false"
+ );
+});
+
+add_task(async function test_set_default_pdf_handler_data_true() {
+ const sandbox = sinon.createSandbox();
+ const stub = sandbox.stub();
+
+ await SMATestUtils.executeAndValidateAction(
+ {
+ type: "SET_DEFAULT_PDF_HANDLER",
+ data: {
+ onlyIfKnownBrowser: true,
+ },
+ },
+ {
+ ownerGlobal: {
+ getShellService: () => ({
+ setAsDefaultPDFHandler: stub,
+ }),
+ },
+ }
+ );
+
+ Assert.equal(
+ stub.callCount,
+ 1,
+ "setAsDefaultPDFHandler was called by the action"
+ );
+ Assert.ok(
+ stub.calledWithExactly(true),
+ "setAsDefaultPDFHandler called with onlyIfKnownBrowser = true"
+ );
+});
+
+add_task(async function test_decline_default_pdf_handler() {
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(
+ "browser.shell.checkDefaultPDF.silencedByUser"
+ );
+ });
+
+ await SMATestUtils.executeAndValidateAction({
+ type: "DECLINE_DEFAULT_PDF_HANDLER",
+ });
+
+ Assert.equal(
+ Services.prefs.getBoolPref("browser.shell.checkDefaultPDF.silencedByUser"),
+ true,
+ "DECLINE_DEFAULT_PDF_HANDLER ought to set pref properly."
+ );
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_disable_doh.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_disable_doh.js
new file mode 100644
index 0000000000..aa61214360
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_disable_doh.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+const DOH_DOORHANGER_DECISION_PREF = "doh-rollout.doorhanger-decision";
+const NETWORK_TRR_MODE_PREF = "network.trr.mode";
+
+add_task(async function test_disable_doh() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [DOH_DOORHANGER_DECISION_PREF, "mochitest"],
+ [NETWORK_TRR_MODE_PREF, 0],
+ ],
+ });
+
+ await SMATestUtils.executeAndValidateAction({ type: "DISABLE_DOH" });
+
+ Assert.equal(
+ Services.prefs.getStringPref(DOH_DOORHANGER_DECISION_PREF, ""),
+ "UIDisabled",
+ "Pref should be set on disabled"
+ );
+ Assert.equal(
+ Services.prefs.getIntPref(NETWORK_TRR_MODE_PREF, 0),
+ 5,
+ "Pref should be set on disabled"
+ );
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_docs.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_docs.js
new file mode 100644
index 0000000000..cf2ec2c305
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_docs.js
@@ -0,0 +1,32 @@
+const TEST_URL =
+ "https://example.com/browser/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/index.md";
+
+function getHeadingsFromDocs(docs) {
+ const re = /### `(\w+)`/g;
+ const found = [];
+ let match = 1;
+ while (match) {
+ match = re.exec(docs);
+ if (match) {
+ found.push(match[1]);
+ }
+ }
+ return found;
+}
+
+add_task(async function test_sma_docs() {
+ let request = await fetch(TEST_URL);
+ let docs = await request.text();
+ let headings = getHeadingsFromDocs(docs);
+ const schemaTypes = (
+ await fetchSMASchema
+ ).definitions.SpecialMessageActionSchemas.anyOf.map(
+ s => s.properties.type.enum[0]
+ );
+ for (let schemaType of schemaTypes) {
+ Assert.ok(
+ headings.includes(schemaType),
+ `${schemaType} not found in SpecialMessageActionSchemas/index.md`
+ );
+ }
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_handle_multiaction.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_handle_multiaction.js
new file mode 100644
index 0000000000..709f4515ad
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_handle_multiaction.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_handle_multi_action() {
+ const action = {
+ type: "MULTI_ACTION",
+ data: {
+ actions: [
+ {
+ type: "DISABLE_DOH",
+ },
+ {
+ type: "OPEN_AWESOME_BAR",
+ },
+ ],
+ },
+ };
+ const DOH_DOORHANGER_DECISION_PREF = "doh-rollout.doorhanger-decision";
+ const NETWORK_TRR_MODE_PREF = "network.trr.mode";
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [DOH_DOORHANGER_DECISION_PREF, "mochitest"],
+ [NETWORK_TRR_MODE_PREF, 0],
+ ],
+ });
+
+ await SMATestUtils.executeAndValidateAction(action);
+
+ Assert.equal(
+ Services.prefs.getStringPref(DOH_DOORHANGER_DECISION_PREF, ""),
+ "UIDisabled",
+ "Pref should be set on disabled"
+ );
+ Assert.equal(
+ Services.prefs.getIntPref(NETWORK_TRR_MODE_PREF, 0),
+ 5,
+ "Pref should be set on disabled"
+ );
+
+ Assert.ok(gURLBar.focused, "Focus should be on awesome bar");
+});
+
+add_task(async function test_handle_multi_action_with_invalid_action() {
+ const action = {
+ type: "MULTI_ACTION",
+ data: {
+ actions: [
+ {
+ type: "NONSENSE",
+ },
+ ],
+ },
+ };
+
+ await SMATestUtils.validateAction(action);
+
+ let error;
+ try {
+ await SpecialMessageActions.handleAction(action, gBrowser);
+ } catch (e) {
+ error = e;
+ }
+
+ ok(error, "should throw if an unexpected event is handled");
+ Assert.equal(
+ error.message,
+ "Error in MULTI_ACTION event: Special message action with type NONSENSE is unsupported."
+ );
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_about_page.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_about_page.js
new file mode 100644
index 0000000000..264646bd0e
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_about_page.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_OPEN_ABOUT_PAGE() {
+ const tabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "about:logins?foo=bar"
+ );
+ await SMATestUtils.executeAndValidateAction({
+ type: "OPEN_ABOUT_PAGE",
+ data: { args: "logins", entrypoint: "foo=bar", where: "tabshifted" },
+ });
+
+ const tab = await tabPromise;
+ ok(tab, "should open about page with entrypoint in a new tab by default");
+ BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_OPEN_ABOUT_PAGE_NEW_WINDOW() {
+ const newWindowPromise = BrowserTestUtils.waitForNewWindow(
+ gBrowser,
+ "about:robots?foo=bar"
+ );
+ await SMATestUtils.executeAndValidateAction({
+ type: "OPEN_ABOUT_PAGE",
+ data: { args: "robots", entrypoint: "foo=bar", where: "window" },
+ });
+
+ const win = await newWindowPromise;
+ ok(win, "should open about page in a new window");
+ BrowserTestUtils.closeWindow(win);
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_awesome_bar.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_awesome_bar.js
new file mode 100644
index 0000000000..62f7d8bb68
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_awesome_bar.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_OPEN_AWESOME_BAR() {
+ await SMATestUtils.executeAndValidateAction({ type: "OPEN_AWESOME_BAR" });
+ Assert.ok(gURLBar.focused, "Focus should be on awesome bar");
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_private_browser_window.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_private_browser_window.js
new file mode 100644
index 0000000000..b6c933fbcf
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_private_browser_window.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_OPEN_PRIVATE_BROWSER_WINDOW() {
+ const newWindowPromise = BrowserTestUtils.waitForNewWindow();
+ await SMATestUtils.executeAndValidateAction({
+ type: "OPEN_PRIVATE_BROWSER_WINDOW",
+ });
+ const win = await newWindowPromise;
+ ok(
+ PrivateBrowsingUtils.isWindowPrivate(win),
+ "should open a private browsing window"
+ );
+ await BrowserTestUtils.closeWindow(win);
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_protection_panel.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_protection_panel.js
new file mode 100644
index 0000000000..c9522426a2
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_protection_panel.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_OPEN_PROTECTION_PANEL() {
+ await BrowserTestUtils.withNewTab(EXAMPLE_URL, async browser => {
+ const popupshown = BrowserTestUtils.waitForEvent(
+ window,
+ "popupshown",
+ true,
+ e => e.target.id == "protections-popup"
+ );
+
+ await SMATestUtils.executeAndValidateAction({
+ type: "OPEN_PROTECTION_PANEL",
+ });
+
+ let { target: popupEl } = await popupshown;
+ Assert.equal(popupEl.state, "open", "Protections popup is open.");
+ });
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_protection_report.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_protection_report.js
new file mode 100644
index 0000000000..3960075cd9
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_protection_report.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_OPEN_PROTECTION_REPORT() {
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ let loaded = BrowserTestUtils.browserLoaded(
+ browser,
+ false,
+ "about:protections"
+ );
+
+ await SMATestUtils.executeAndValidateAction({
+ type: "OPEN_PROTECTION_REPORT",
+ });
+
+ await loaded;
+
+ // When the graph is built it means any messaging has finished,
+ // we can close the tab.
+ await SpecialPowers.spawn(browser, [], async function () {
+ await ContentTaskUtils.waitForCondition(() => {
+ let bars = content.document.querySelectorAll(".graph-bar");
+ return bars.length;
+ }, "The graph has been built");
+ });
+ });
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_spotlight_dialog.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_spotlight_dialog.js
new file mode 100644
index 0000000000..f3274c8aa5
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_spotlight_dialog.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { OnboardingMessageProvider } = ChromeUtils.importESModule(
+ "resource:///modules/asrouter/OnboardingMessageProvider.sys.mjs"
+);
+
+const { Spotlight } = ChromeUtils.importESModule(
+ "resource:///modules/asrouter/Spotlight.sys.mjs"
+);
+
+add_task(async function test_OPEN_SPOTLIGHT_DIALOG() {
+ let pbNewTabMessage = (
+ await OnboardingMessageProvider.getUntranslatedMessages()
+ ).filter(m => m.id === "PB_NEWTAB_FOCUS_PROMO");
+ info(`Testing ${pbNewTabMessage[0].id}`);
+ let showSpotlightStub = sinon.stub(Spotlight, "showSpotlightDialog");
+ await SMATestUtils.executeAndValidateAction({
+ type: "SHOW_SPOTLIGHT",
+ data: { ...pbNewTabMessage[0].content.promoButton.action.data },
+ });
+
+ Assert.equal(
+ showSpotlightStub.callCount,
+ 1,
+ "Should call showSpotlightDialog"
+ );
+
+ Assert.deepEqual(
+ showSpotlightStub.firstCall.args[1],
+ pbNewTabMessage[0].content.promoButton.action.data,
+ "Should be called with action.data"
+ );
+
+ showSpotlightStub.restore();
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_url.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_url.js
new file mode 100644
index 0000000000..876193b7ad
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_open_url.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_OPEN_URL() {
+ const action = {
+ type: "OPEN_URL",
+ data: { args: EXAMPLE_URL, where: "current" },
+ };
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ const loaded = BrowserTestUtils.browserLoaded(browser);
+ await SMATestUtils.executeAndValidateAction(action);
+ const url = await loaded;
+ Assert.equal(
+ url,
+ "https://example.com/",
+ "should open URL in the same tab"
+ );
+ });
+});
+
+add_task(async function test_OPEN_URL_new_tab() {
+ const action = {
+ type: "OPEN_URL",
+ data: { args: EXAMPLE_URL, where: "tab" },
+ };
+ const tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, EXAMPLE_URL);
+ await SpecialMessageActions.handleAction(action, gBrowser);
+ const browser = await tabPromise;
+ ok(browser, "should open URL in a new tab");
+ BrowserTestUtils.removeTab(browser);
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_current_tab.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_current_tab.js
new file mode 100644
index 0000000000..4425325526
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_current_tab.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_PIN_CURRENT_TAB() {
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ await SMATestUtils.executeAndValidateAction({ type: "PIN_CURRENT_TAB" });
+
+ ok(gBrowser.selectedTab.pinned, "should pin current tab");
+
+ gBrowser.unpinTab(gBrowser.selectedTab);
+ });
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_firefox.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_firefox.js
new file mode 100644
index 0000000000..09714e6703
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_firefox.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const isWin = AppConstants.platform == "win";
+const isMac = AppConstants.platform == "macosx";
+
+add_task(async function test_PIN_FIREFOX_TO_TASKBAR() {
+ const sandbox = sinon.createSandbox();
+ let shell = {
+ async checkPinCurrentAppToTaskbarAsync() {},
+ QueryInterface: () => shell,
+ get macDockSupport() {
+ return this;
+ },
+ get shellService() {
+ return this;
+ },
+
+ ensureAppIsPinnedToDock: sandbox.stub(),
+ isCurrentAppPinnedToTaskbarAsync: sandbox.stub(),
+ pinCurrentAppToTaskbarAsync: sandbox.stub().resolves(undefined),
+ isAppInDock: false,
+ };
+
+ // Prefer the mocked implementation and fall back to the original version,
+ // which can call back into the mocked version (via this.shellService).
+ shell = new Proxy(shell, {
+ get(target, prop) {
+ return (prop in target ? target : ShellService)[prop];
+ },
+ });
+
+ const test = () =>
+ SMATestUtils.executeAndValidateAction(
+ { type: "PIN_FIREFOX_TO_TASKBAR" },
+ {
+ ownerGlobal: {
+ getShellService: () => shell,
+ },
+ }
+ );
+
+ await test();
+
+ function check(count, message) {
+ Assert.equal(
+ shell.pinCurrentAppToTaskbarAsync.callCount,
+ count * isWin,
+ `pinCurrentAppToTaskbarAsync was ${message} by the action for windows`
+ );
+ Assert.equal(
+ shell.ensureAppIsPinnedToDock.callCount,
+ count * isMac,
+ `ensureAppIsPinnedToDock was ${message} by the action for not windows`
+ );
+ }
+ check(1, "called");
+
+ // Pretend the app is already pinned.
+ shell.isCurrentAppPinnedToTaskbarAsync.resolves(true);
+ shell.isAppInDock = true;
+ await test();
+ check(1, "not called");
+
+ // Pretend the app became unpinned.
+ shell.isCurrentAppPinnedToTaskbarAsync.resolves(false);
+ shell.isAppInDock = false;
+ await test();
+ check(2, "called again");
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_private_firefox.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_private_firefox.js
new file mode 100644
index 0000000000..90880edd27
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_pin_private_firefox.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_PIN_PRIVATE_FIREFOX_TO_TASKBAR() {
+ const sandbox = sinon.createSandbox();
+ let shell = {
+ async checkPinCurrentAppToTaskbarAsync() {},
+ QueryInterface: () => shell,
+ get macDockSupport() {
+ return this;
+ },
+ get shellService() {
+ return this;
+ },
+
+ ensureAppIsPinnedToDock: sandbox.stub(),
+ isCurrentAppPinnedToTaskbarAsync: sandbox.stub(),
+ pinCurrentAppToTaskbarAsync: sandbox.stub().resolves(undefined),
+ isAppInDock: false,
+ };
+
+ // Prefer the mocked implementation and fall back to the original version,
+ // which can call back into the mocked version (via this.shellService).
+ shell = new Proxy(shell, {
+ get(target, prop) {
+ return (Object.hasOwn(target, prop) ? target : ShellService)[prop];
+ },
+ });
+
+ const test = () =>
+ SMATestUtils.executeAndValidateAction(
+ {
+ type: "PIN_FIREFOX_TO_TASKBAR",
+ data: {
+ privatePin: true,
+ },
+ },
+ {
+ ownerGlobal: {
+ getShellService: () => shell,
+ },
+ }
+ );
+
+ await test();
+
+ function check(count, message, arg) {
+ Assert.equal(
+ shell.pinCurrentAppToTaskbarAsync.callCount,
+ count,
+ `pinCurrentAppToTaskbarAsync was ${message} by the action for windows`
+ );
+ if (arg) {
+ Assert.equal(
+ shell.pinCurrentAppToTaskbarAsync.calledWith(arg),
+ true,
+ `pinCurrentAppToTaskbarAsync was ${message} with the arg: ${JSON.stringify(
+ arg
+ )}`
+ );
+ }
+ }
+ check(1, "called", true);
+
+ // Pretend the app is already pinned.
+ shell.isCurrentAppPinnedToTaskbarAsync.resolves(true);
+ shell.isAppInDock = true;
+ await test();
+ check(1, "not called");
+
+ // Pretend the app became unpinned.
+ shell.isCurrentAppPinnedToTaskbarAsync.resolves(false);
+ shell.isAppInDock = false;
+ await test();
+ check(2, "called again", true);
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_set_prefs.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_set_prefs.js
new file mode 100644
index 0000000000..3d408192b6
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_set_prefs.js
@@ -0,0 +1,167 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const HOMEPAGE_PREF = "browser.startup.homepage";
+const PRIVACY_SEGMENTATION_PREF = "browser.dataFeatureRecommendations.enabled";
+const MESSAGING_ACTION_PREF = "special-message-testpref";
+
+const PREFS_TO_CLEAR = [
+ HOMEPAGE_PREF,
+ PRIVACY_SEGMENTATION_PREF,
+ `messaging-system-action.${MESSAGING_ACTION_PREF}`,
+];
+
+add_setup(async function () {
+ registerCleanupFunction(async () => {
+ PREFS_TO_CLEAR.forEach(pref => Services.prefs.clearUserPref(pref));
+ });
+});
+
+add_task(async function test_set_privacy_segmentation_pref() {
+ const action = {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: PRIVACY_SEGMENTATION_PREF,
+ value: true,
+ },
+ },
+ };
+
+ Assert.ok(
+ !Services.prefs.prefHasUserValue(PRIVACY_SEGMENTATION_PREF),
+ "Test setup ok"
+ );
+
+ await SMATestUtils.executeAndValidateAction(action);
+
+ Assert.ok(
+ Services.prefs.getBoolPref(PRIVACY_SEGMENTATION_PREF),
+ `${PRIVACY_SEGMENTATION_PREF} pref successfully updated`
+ );
+});
+
+add_task(async function test_clear_privacy_segmentation_pref() {
+ Services.prefs.setBoolPref(PRIVACY_SEGMENTATION_PREF, true);
+ Assert.ok(
+ Services.prefs.prefHasUserValue(PRIVACY_SEGMENTATION_PREF),
+ "Test setup ok"
+ );
+
+ const action = {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: PRIVACY_SEGMENTATION_PREF,
+ },
+ },
+ };
+
+ await SMATestUtils.executeAndValidateAction(action);
+
+ Assert.ok(
+ !Services.prefs.prefHasUserValue(PRIVACY_SEGMENTATION_PREF),
+ `${PRIVACY_SEGMENTATION_PREF} pref successfully cleared`
+ );
+});
+
+add_task(async function test_set_homepage_pref() {
+ const action = {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: HOMEPAGE_PREF,
+ value: "https://foo.example.com",
+ },
+ },
+ };
+
+ Assert.ok(!Services.prefs.prefHasUserValue(HOMEPAGE_PREF), "Test setup ok");
+
+ await SMATestUtils.executeAndValidateAction(action);
+
+ Assert.equal(
+ Services.prefs.getStringPref(HOMEPAGE_PREF),
+ "https://foo.example.com",
+ `${HOMEPAGE_PREF} pref successfully updated`
+ );
+});
+
+add_task(async function test_clear_homepage_pref() {
+ Services.prefs.setStringPref(HOMEPAGE_PREF, "https://www.example.com");
+ Assert.ok(Services.prefs.prefHasUserValue(HOMEPAGE_PREF), "Test setup ok");
+
+ const action = {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: HOMEPAGE_PREF,
+ },
+ },
+ };
+
+ await SMATestUtils.executeAndValidateAction(action);
+
+ Assert.ok(
+ !Services.prefs.prefHasUserValue(HOMEPAGE_PREF),
+ `${HOMEPAGE_PREF} pref successfully updated`
+ );
+});
+
+// Set a pref not listed in "allowed prefs"
+add_task(async function test_set_messaging_system_pref() {
+ const action = {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: MESSAGING_ACTION_PREF,
+ value: true,
+ },
+ },
+ };
+
+ await SMATestUtils.executeAndValidateAction(action);
+
+ Assert.equal(
+ Services.prefs.getBoolPref(
+ `messaging-system-action.${MESSAGING_ACTION_PREF}`
+ ),
+ true,
+ `messaging-system-action.${MESSAGING_ACTION_PREF} pref successfully updated to correct value`
+ );
+});
+
+// Clear a pref not listed in "allowed prefs" that was initially set by
+// the SET_PREF special messaging action
+add_task(async function test_clear_messaging_system_pref() {
+ Services.prefs.setBoolPref(
+ `messaging-system-action.${MESSAGING_ACTION_PREF}`,
+ true
+ );
+ Assert.ok(
+ Services.prefs.prefHasUserValue(
+ `messaging-system-action.${MESSAGING_ACTION_PREF}`
+ ),
+ "Test setup ok"
+ );
+
+ const action = {
+ type: "SET_PREF",
+ data: {
+ pref: {
+ name: MESSAGING_ACTION_PREF,
+ },
+ },
+ };
+
+ await SMATestUtils.executeAndValidateAction(action);
+
+ Assert.ok(
+ !Services.prefs.prefHasUserValue(
+ `messaging-system-action.${MESSAGING_ACTION_PREF}`
+ ),
+ `messaging-system-action.${MESSAGING_ACTION_PREF} pref successfully cleared`
+ );
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_show_firefox_accounts.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_show_firefox_accounts.js
new file mode 100644
index 0000000000..82bba359a3
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_show_firefox_accounts.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Note: "identity.fxaccounts.remote.root" is set to https://example.com in browser.ini
+add_task(async function test_SHOW_FIREFOX_ACCOUNTS() {
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ let loaded = BrowserTestUtils.browserLoaded(browser);
+ await SMATestUtils.executeAndValidateAction({
+ type: "SHOW_FIREFOX_ACCOUNTS",
+ data: { entrypoint: "snippets" },
+ });
+ Assert.equal(
+ await loaded,
+ "https://example.com/?context=fx_desktop_v3&entrypoint=snippets&action=email&service=sync",
+ "should load fxa with endpoint=snippets"
+ );
+
+ // Open a URL
+ loaded = BrowserTestUtils.browserLoaded(browser);
+ await SMATestUtils.executeAndValidateAction({
+ type: "SHOW_FIREFOX_ACCOUNTS",
+ data: { entrypoint: "aboutwelcome" },
+ });
+
+ Assert.equal(
+ await loaded,
+ "https://example.com/?context=fx_desktop_v3&entrypoint=aboutwelcome&action=email&service=sync",
+ "should load fxa with a custom endpoint"
+ );
+
+ // Open a URL with extra parameters
+ loaded = BrowserTestUtils.browserLoaded(browser);
+ await SMATestUtils.executeAndValidateAction({
+ type: "SHOW_FIREFOX_ACCOUNTS",
+ data: { entrypoint: "test", extraParams: { foo: "bar" } },
+ });
+
+ Assert.equal(
+ await loaded,
+ "https://example.com/?context=fx_desktop_v3&entrypoint=test&action=email&service=sync&foo=bar",
+ "should load fxa with a custom endpoint and extra parameters in url"
+ );
+ });
+
+ add_task(async function test_SHOW_FIREFOX_ACCOUNTS_where() {
+ // Open FXA with a 'where' prop
+ const action = {
+ type: "SHOW_FIREFOX_ACCOUNTS",
+ data: {
+ entrypoint: "activity-stream-firstrun",
+ where: "tab",
+ },
+ };
+ const tabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ "https://example.com/?context=fx_desktop_v3&entrypoint=activity-stream-firstrun&action=email&service=sync"
+ );
+
+ await SpecialMessageActions.handleAction(action, gBrowser);
+ const browser = await tabPromise;
+ ok(browser, "should open FXA in a new tab");
+ BrowserTestUtils.removeTab(browser);
+ });
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_show_migration_wizard.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_show_migration_wizard.js
new file mode 100644
index 0000000000..4efbabfe3c
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_show_migration_wizard.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async () => {
+ // Load the initial tab at example.com. This makes it so that if
+ // when loading the migration wizard in about:preferences, we'll
+ // load the about:preferences page in a new tab rather than overtaking
+ // the initial one. This makes cleanup of that opened tab more explicit.
+ let browser = gBrowser.selectedBrowser;
+ BrowserTestUtils.startLoadingURIString(browser, "https://example.com");
+ await BrowserTestUtils.browserLoaded(browser);
+});
+
+add_task(async function test_SHOW_MIGRATION_WIZARD() {
+ let wizardOpened = BrowserTestUtils.waitForMigrationWizard(window);
+
+ await SMATestUtils.executeAndValidateAction({
+ type: "SHOW_MIGRATION_WIZARD",
+ });
+
+ let wizard = await wizardOpened;
+ ok(wizard, "Migration wizard opened");
+ await BrowserTestUtils.removeTab(wizard);
+});
+
+add_task(async function test_SHOW_MIGRATION_WIZARD_WITH_SOURCE() {
+ let wizardOpened = BrowserTestUtils.waitForMigrationWizard(window);
+
+ await SMATestUtils.executeAndValidateAction({
+ type: "SHOW_MIGRATION_WIZARD",
+ data: { source: "chrome" },
+ });
+
+ let wizard = await wizardOpened;
+ ok(wizard, "Migrator window opened when source param specified");
+ await BrowserTestUtils.removeTab(wizard);
+});
diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/head.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/head.js
new file mode 100644
index 0000000000..7d77a80ee0
--- /dev/null
+++ b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/head.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+const { JsonSchema } = ChromeUtils.importESModule(
+ "resource://gre/modules/JsonSchema.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ SpecialMessageActions:
+ "resource://messaging-system/lib/SpecialMessageActions.sys.mjs",
+});
+
+ChromeUtils.defineLazyGetter(this, "fetchSMASchema", async () => {
+ const response = await fetch(
+ "resource://testing-common/SpecialMessageActionSchemas.json"
+ );
+ const schema = await response.json();
+ if (!schema) {
+ throw new Error("Failed to load SpecialMessageActionSchemas");
+ }
+ return schema;
+});
+
+const EXAMPLE_URL = "https://example.com/";
+
+const SMATestUtils = {
+ /**
+ * Checks if an action is valid acording to existing schemas
+ * @param {SpecialMessageAction} action
+ */
+ async validateAction(action) {
+ const schema = await fetchSMASchema;
+ const result = JsonSchema.validate(action, schema);
+ if (result.errors.length) {
+ throw new Error(
+ `Action with type ${
+ action.type
+ } was not valid. Errors: ${JSON.stringify(result.errors, undefined, 2)}`
+ );
+ }
+ is(
+ result.errors.length,
+ 0,
+ `Should be a valid action of type ${action.type}`
+ );
+ },
+
+ /**
+ * Executes a Special Message Action after validating it
+ * @param {SpecialMessageAction} action
+ * @param {Browser} browser
+ */
+ async executeAndValidateAction(action, browser = gBrowser) {
+ await SMATestUtils.validateAction(action);
+ await SpecialMessageActions.handleAction(action, browser);
+ },
+};