diff options
Diffstat (limited to 'toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas')
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); + }, +}; |