author | Norisz Fay <nfay@mozilla.com> |
Mon, 18 Apr 2022 00:24:46 +0300 | |
changeset 684935 | fb7973567fac46ba2768713f6344d65c9c371ead |
parent 684932 | 08918da0d247f66ca5ff7955fe7078e3d37991b4 (current diff) |
parent 684931 | 0b44605d104918d1a53c700742721fbabbf6543c (diff) |
child 684937 | 4b300c60e0350dd3376a133a3ec2868fd677673d |
child 684945 | 53dbcc3cbe3c88fa9de5cedc9fc382eda6e33c82 |
push id | 16598 |
push user | ffxbld-merge |
push date | Mon, 02 May 2022 14:23:32 +0000 |
treeherder | mozilla-beta@de86a81c7a63 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 101.0a1 |
first release with | nightly linux32
fb7973567fac
/
101.0a1
/
20220417212536
/
files
nightly linux64
fb7973567fac
/
101.0a1
/
20220417212536
/
files
nightly mac
fb7973567fac
/
101.0a1
/
20220417212536
/
files
nightly win32
fb7973567fac
/
101.0a1
/
20220417212536
/
files
nightly win64
fb7973567fac
/
101.0a1
/
20220417212536
/
files
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
releases | nightly linux32
101.0a1
/
20220417212536
/
pushlog to previous
nightly linux64
101.0a1
/
20220417212536
/
pushlog to previous
nightly mac
101.0a1
/
20220417212536
/
pushlog to previous
nightly win32
101.0a1
/
20220417212536
/
pushlog to previous
nightly win64
101.0a1
/
20220417212536
/
pushlog to previous
|
--- a/browser/components/extensions/parent/ext-browser.js +++ b/browser/components/extensions/parent/ext-browser.js @@ -834,20 +834,16 @@ class Tab extends TabBase { return this.nativeTab.selected; } get highlighted() { let { selected, multiselected } = this.nativeTab; return selected || multiselected; } - get selected() { - return this.nativeTab.selected; - } - get status() { if (this.nativeTab.getAttribute("busy") === "true") { return "loading"; } return "complete"; } get width() {
--- a/browser/components/extensions/schemas/tabs.json +++ b/browser/components/extensions/schemas/tabs.json @@ -84,17 +84,16 @@ { "id": "Tab", "type": "object", "properties": { "id": {"type": "integer", "minimum": -1, "optional": true, "description": "The ID of the tab. Tab IDs are unique within a browser session. Under some circumstances a Tab may not be assigned an ID, for example when querying foreign tabs using the $(ref:sessions) API, in which case a session ID may be present. Tab ID can also be set to $(ref:tabs.TAB_ID_NONE) for apps and devtools windows."}, "index": {"type": "integer", "minimum": -1, "description": "The zero-based index of the tab within its window."}, "windowId": {"type": "integer", "optional": true, "minimum": 0, "description": "The ID of the window the tab is contained within."}, "openerTabId": {"type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."}, - "selected": {"type": "boolean", "description": "Whether the tab is selected.", "deprecated": "Please use $(ref:tabs.Tab.highlighted).", "unsupported": true}, "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted. Works as an alias of active"}, "active": {"type": "boolean", "description": "Whether the tab is active in its window. (Does not necessarily mean the window is focused.)"}, "pinned": {"type": "boolean", "description": "Whether the tab is pinned."}, "lastAccessed": {"type": "integer", "optional": true, "description": "The last time the tab was accessed as the number of milliseconds since epoch."}, "audible": {"type": "boolean", "optional": true, "description": "Whether the tab has produced sound over the past couple of seconds (but it might not be heard if also muted). Equivalent to whether the speaker audio indicator is showing."}, "mutedInfo": {"$ref": "MutedInfo", "optional": true, "description": "Current tab muted state and the reason for the last state change."}, "url": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL the tab is displaying. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."}, "title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."}, @@ -419,46 +418,16 @@ } ], "returns": { "$ref": "runtime.Port", "description": "A port that can be used to communicate with the content scripts running in the specified tab. The port's $(ref:runtime.Port) event is fired if the tab closes or does not exist. " } }, { - "name": "sendRequest", - "deprecated": "Please use $(ref:runtime.sendMessage).", - "unsupported": true, - "type": "function", - "description": "Sends a single request to the content script(s) in the specified tab, with an optional callback to run when a response is sent back. The $(ref:extension.onRequest) event is fired in each content script running in the specified tab for the current extension.", - "parameters": [ - { - "type": "integer", - "name": "tabId", - "minimum": 0 - }, - { - "type": "any", - "name": "request" - }, - { - "type": "function", - "name": "responseCallback", - "optional": true, - "parameters": [ - { - "name": "response", - "type": "any", - "description": "The JSON response object sent by the handler of the request. If an error occurs while connecting to the specified tab, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message." - } - ] - } - ] - }, - { "name": "sendMessage", "type": "function", "description": "Sends a single message to the content script(s) in the specified tab, with an optional callback to run when a response is sent back. The $(ref:runtime.onMessage) event is fired in each content script running in the specified tab for the current extension.", "async": "responseCallback", "parameters": [ { "type": "integer", "name": "tabId", @@ -491,64 +460,16 @@ "type": "any", "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the specified tab, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message." } ] } ] }, { - "name": "getSelected", - "deprecated": "Please use $(ref:tabs.query) <code>{active: true}</code>.", - "unsupported": true, - "type": "function", - "description": "Gets the tab that is selected in the specified window.", - "async": "callback", - "parameters": [ - { - "type": "integer", - "name": "windowId", - "minimum": -2, - "optional": true, - "description": "Defaults to the $(topic:current-window)[current window]." - }, - { - "type": "function", - "name": "callback", - "parameters": [ - {"name": "tab", "$ref": "Tab"} - ] - } - ] - }, - { - "name": "getAllInWindow", - "deprecated": "Please use $(ref:tabs.query) <code>{windowId: windowId}</code>.", - "unsupported": true, - "type": "function", - "description": "Gets details about all tabs in the specified window.", - "async": "callback", - "parameters": [ - { - "type": "integer", - "name": "windowId", - "minimum": -2, - "optional": true, - "description": "Defaults to the $(topic:current-window)[current window]." - }, - { - "type": "function", - "name": "callback", - "parameters": [ - {"name": "tabs", "type": "array", "items": { "$ref": "Tab" } } - ] - } - ] - }, - { "name": "create", "type": "function", "description": "Creates a new tab.", "async": "callback", "parameters": [ { "type": "object", "name": "createProperties", @@ -570,23 +491,16 @@ "optional": true, "description": "The URL to navigate the tab to initially. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Defaults to the New Tab Page." }, "active": { "type": "boolean", "optional": true, "description": "Whether the tab should become the active tab in the window. Does not affect whether the window is focused (see $(ref:windows.update)). Defaults to <var>true</var>." }, - "selected": { - "deprecated": "Please use <em>active</em>.", - "unsupported": true, - "type": "boolean", - "optional": true, - "description": "Whether the tab should become the selected tab in the window. Defaults to <var>true</var>" - }, "pinned": { "type": "boolean", "optional": true, "description": "Whether the tab should be pinned. Defaults to <var>false</var>" }, "openerTabId": { "type": "integer", "minimum": 0, @@ -900,23 +814,16 @@ "optional": true, "description": "Whether the tab should be active. Does not affect whether the window is focused (see $(ref:windows.update))." }, "highlighted": { "type": "boolean", "optional": true, "description": "Adds or removes the tab from the current selection." }, - "selected": { - "unsupported": true, - "deprecated": "Please use <em>highlighted</em>.", - "type": "boolean", - "optional": true, - "description": "Whether the tab should be selected." - }, "pinned": { "type": "boolean", "optional": true, "description": "Whether the tab should be pinned." }, "muted": { "type": "boolean", "optional": true, @@ -1182,16 +1089,17 @@ } ] } ] }, { "name": "executeScript", "type": "function", + "max_manifest_version": 2, "description": "Injects JavaScript code into a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.", "async": "callback", "parameters": [ { "type": "integer", "name": "tabId", "minimum": 0, "optional": true, @@ -1217,16 +1125,17 @@ } ] } ] }, { "name": "insertCSS", "type": "function", + "max_manifest_version": 2, "description": "Injects CSS into a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.", "async": "callback", "parameters": [ { "type": "integer", "name": "tabId", "minimum": 0, "optional": true, @@ -1244,16 +1153,17 @@ "description": "Called when all the CSS has been inserted.", "parameters": [] } ] }, { "name": "removeCSS", "type": "function", + "max_manifest_version": 2, "description": "Removes injected CSS from a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.", "async": "callback", "parameters": [ { "type": "integer", "name": "tabId", "minimum": 0, "optional": true, @@ -1664,68 +1574,16 @@ "windowId": {"type": "integer", "minimum": 0}, "fromIndex": {"type": "integer", "minimum": 0}, "toIndex": {"type": "integer", "minimum": 0} } } ] }, { - "name": "onSelectionChanged", - "deprecated": "Please use $(ref:tabs.onActivated).", - "unsupported": true, - "type": "function", - "description": "Fires when the selected tab in a window changes.", - "parameters": [ - { - "type": "integer", - "name": "tabId", - "minimum": 0, - "description": "The ID of the tab that has become active." - }, - { - "type": "object", - "name": "selectInfo", - "properties": { - "windowId": { - "type": "integer", - "minimum": 0, - "description": "The ID of the window the selected tab changed inside of." - } - } - } - ] - }, - { - "name": "onActiveChanged", - "deprecated": "Please use $(ref:tabs.onActivated).", - "unsupported": true, - "type": "function", - "description": "Fires when the selected tab in a window changes. Note that the tab's URL may not be set at the time this event fired, but you can listen to $(ref:tabs.onUpdated) events to be notified when a URL is set.", - "parameters": [ - { - "type": "integer", - "name": "tabId", - "minimum": 0, - "description": "The ID of the tab that has become active." - }, - { - "type": "object", - "name": "selectInfo", - "properties": { - "windowId": { - "type": "integer", - "minimum": 0, - "description": "The ID of the window the selected tab changed inside of." - } - } - } - ] - }, - { "name": "onActivated", "type": "function", "description": "Fires when the active tab in a window changes. Note that the tab's URL may not be set at the time this event fired, but you can listen to onUpdated events to be notified when a URL is set.", "parameters": [ { "type": "object", "name": "activeInfo", "properties": { @@ -1745,41 +1603,16 @@ "minimum": 0, "description": "The ID of the window the active tab changed inside of." } } } ] }, { - "name": "onHighlightChanged", - "deprecated": "Please use $(ref:tabs.onHighlighted).", - "unsupported": true, - "type": "function", - "description": "Fired when the highlighted or selected tabs in a window changes.", - "parameters": [ - { - "type": "object", - "name": "selectInfo", - "properties": { - "windowId": { - "type": "integer", - "minimum": 0, - "description": "The window whose tabs changed." - }, - "tabIds": { - "type": "array", - "items": {"type": "integer", "minimum": 0}, - "description": "All highlighted tabs in the window." - } - } - } - ] - }, - { "name": "onHighlighted", "type": "function", "description": "Fired when the highlighted or selected tabs in a window changes.", "parameters": [ { "type": "object", "name": "highlightInfo", "properties": {
--- a/browser/components/extensions/test/xpcshell/test_ext_manifest.js +++ b/browser/components/extensions/test/xpcshell/test_ext_manifest.js @@ -72,48 +72,43 @@ add_task(async function test_manifest() await testManifest(manifest); } } }); add_task(async function test_action_version() { // The above test validates these work with the correct version, // here we verify they fail with the incorrect version for MV3. - testManifest( - { - manifest_version: 3, - browser_action: { - default_panel: "foo.html", - }, - }, - /Property "browser_action" is unsupported in Manifest Version 3/ - ); - - // But we still allow previously ignored keys in MV2, just warn about them. ExtensionTestUtils.failOnSchemaWarnings(false); let warnings = await testManifest({ + manifest_version: 3, + browser_action: { + default_panel: "foo.html", + }, + }); + Assert.deepEqual( + warnings, + [`Property "browser_action" is unsupported in Manifest Version 3`], + `Manifest v3 with "browser_action" key logs an error.` + ); + + warnings = await testManifest({ manifest_version: 2, action: { default_icon: "", default_panel: "foo.html", }, }); - equal(warnings.length, 2, "Got exactly two warnings"); - equal( - warnings[0], - `Property "action" is unsupported in Manifest Version 2`, + Assert.deepEqual( + warnings, + [`Property "action" is unsupported in Manifest Version 2`], `Manifest v2 with "action" key first warning is clear.` ); - equal( - warnings[1], - "Warning processing action: An unexpected property was found in the WebExtension manifest.", - `Manifest v2 with "action" key second warning has more details.` - ); ExtensionTestUtils.failOnSchemaWarnings(true); }); add_task(async function test_scripting_permission() { ExtensionTestUtils.failOnSchemaWarnings(false); // The "scripting" permission is only available in MV3.
--- a/mobile/android/components/extensions/ext-android.js +++ b/mobile/android/components/extensions/ext-android.js @@ -358,20 +358,16 @@ class Tab extends TabBase { get active() { return this.nativeTab.getActive(); } get highlighted() { return this.active; } - get selected() { - return this.nativeTab.getActive(); - } - get status() { if (this.browser.webProgress.isLoadingDocument) { return "loading"; } return "complete"; } get successorTabId() {
--- a/mobile/android/components/extensions/schemas/tabs.json +++ b/mobile/android/components/extensions/schemas/tabs.json @@ -83,17 +83,16 @@ { "id": "Tab", "type": "object", "properties": { "id": {"type": "integer", "minimum": -1, "optional": true, "description": "The ID of the tab. Tab IDs are unique within a browser session. Under some circumstances a Tab may not be assigned an ID, for example when querying foreign tabs using the $(ref:sessions) API, in which case a session ID may be present. Tab ID can also be set to $(ref:tabs.TAB_ID_NONE) for apps and devtools windows."}, "index": {"type": "integer", "minimum": -1, "description": "The zero-based index of the tab within its window."}, "windowId": {"type": "integer", "optional": true, "minimum": 0, "description": "The ID of the window the tab is contained within."}, "openerTabId": {"unsupported": true, "type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."}, - "selected": {"type": "boolean", "description": "Whether the tab is selected.", "deprecated": "Please use $(ref:tabs.Tab.highlighted).", "unsupported": true}, "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted. Works as an alias of active."}, "active": {"type": "boolean", "description": "Whether the tab is active in its window. (Does not necessarily mean the window is focused.)"}, "pinned": {"type": "boolean", "description": "Whether the tab is pinned."}, "lastAccessed": {"type": "integer", "optional": true, "description": "The last time the tab was accessed as the number of milliseconds since epoch."}, "audible": {"type": "boolean", "optional": true, "description": "Whether the tab has produced sound over the past couple of seconds (but it might not be heard if also muted). Equivalent to whether the speaker audio indicator is showing."}, "mutedInfo": {"$ref": "MutedInfo", "optional": true, "description": "Current tab muted state and the reason for the last state change."}, "url": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL the tab is displaying. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."}, "title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."}, @@ -254,46 +253,16 @@ } ], "returns": { "$ref": "runtime.Port", "description": "A port that can be used to communicate with the content scripts running in the specified tab. The port's $(ref:runtime.Port) event is fired if the tab closes or does not exist. " } }, { - "name": "sendRequest", - "deprecated": "Please use $(ref:runtime.sendMessage).", - "unsupported": true, - "type": "function", - "description": "Sends a single request to the content script(s) in the specified tab, with an optional callback to run when a response is sent back. The $(ref:extension.onRequest) event is fired in each content script running in the specified tab for the current extension.", - "parameters": [ - { - "type": "integer", - "name": "tabId", - "minimum": 0 - }, - { - "type": "any", - "name": "request" - }, - { - "type": "function", - "name": "responseCallback", - "optional": true, - "parameters": [ - { - "name": "response", - "type": "any", - "description": "The JSON response object sent by the handler of the request. If an error occurs while connecting to the specified tab, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message." - } - ] - } - ] - }, - { "name": "sendMessage", "type": "function", "description": "Sends a single message to the content script(s) in the specified tab, with an optional callback to run when a response is sent back. The $(ref:runtime.onMessage) event is fired in each content script running in the specified tab for the current extension.", "async": "responseCallback", "parameters": [ { "type": "integer", "name": "tabId", @@ -326,64 +295,16 @@ "type": "any", "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the specified tab, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message." } ] } ] }, { - "name": "getSelected", - "deprecated": "Please use $(ref:tabs.query) <code>{active: true}</code>.", - "unsupported": true, - "type": "function", - "description": "Gets the tab that is selected in the specified window.", - "async": "callback", - "parameters": [ - { - "type": "integer", - "name": "windowId", - "minimum": -2, - "optional": true, - "description": "Defaults to the $(topic:current-window)[current window]." - }, - { - "type": "function", - "name": "callback", - "parameters": [ - {"name": "tab", "$ref": "Tab"} - ] - } - ] - }, - { - "name": "getAllInWindow", - "deprecated": "Please use $(ref:tabs.query) <code>{windowId: windowId}</code>.", - "unsupported": true, - "type": "function", - "description": "Gets details about all tabs in the specified window.", - "async": "callback", - "parameters": [ - { - "type": "integer", - "name": "windowId", - "minimum": -2, - "optional": true, - "description": "Defaults to the $(topic:current-window)[current window]." - }, - { - "type": "function", - "name": "callback", - "parameters": [ - {"name": "tabs", "type": "array", "items": { "$ref": "Tab" } } - ] - } - ] - }, - { "name": "create", "type": "function", "description": "Creates a new tab.", "async": "callback", "parameters": [ { "type": "object", "name": "createProperties", @@ -405,23 +326,16 @@ "optional": true, "description": "The URL to navigate the tab to initially. Fully-qualified URLs must include a scheme (i.e. 'http://www.google.com', not 'www.google.com'). Relative URLs will be relative to the current page within the extension. Defaults to the New Tab Page." }, "active": { "type": "boolean", "optional": true, "description": "Whether the tab should become the active tab in the window. Does not affect whether the window is focused (see $(ref:windows.update)). Defaults to <var>true</var>." }, - "selected": { - "deprecated": "Please use <em>active</em>.", - "unsupported": true, - "type": "boolean", - "optional": true, - "description": "Whether the tab should become the selected tab in the window. Defaults to <var>true</var>" - }, "pinned": { "type": "boolean", "optional": true, "description": "Whether the tab should be pinned. Defaults to <var>false</var>" }, "openerTabId": { "unsupported": true, "type": "integer", @@ -662,23 +576,16 @@ "description": "Whether the tab should be active. Does not affect whether the window is focused (see $(ref:windows.update))." }, "highlighted": { "unsupported": true, "type": "boolean", "optional": true, "description": "Adds or removes the tab from the current selection." }, - "selected": { - "unsupported": true, - "deprecated": "Please use <em>highlighted</em>.", - "type": "boolean", - "optional": true, - "description": "Whether the tab should be selected." - }, "pinned": { "type": "boolean", "unsupported": true, "optional": true, "description": "Whether the tab should be pinned." }, "muted": { "type": "boolean", @@ -892,16 +799,17 @@ } ] } ] }, { "name": "executeScript", "type": "function", + "max_manifest_version": 2, "description": "Injects JavaScript code into a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.", "async": "callback", "parameters": [ { "type": "integer", "name": "tabId", "minimum": 0, "optional": true, @@ -927,16 +835,17 @@ } ] } ] }, { "name": "insertCSS", "type": "function", + "max_manifest_version": 2, "description": "Injects CSS into a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.", "async": "callback", "parameters": [ { "type": "integer", "name": "tabId", "minimum": 0, "optional": true, @@ -954,16 +863,17 @@ "description": "Called when all the CSS has been inserted.", "parameters": [] } ] }, { "name": "removeCSS", "type": "function", + "max_manifest_version": 2, "description": "Removes injected CSS from a page. For details, see the $(topic:content_scripts)[programmatic injection] section of the content scripts doc.", "async": "callback", "parameters": [ { "type": "integer", "name": "tabId", "minimum": 0, "optional": true, @@ -1219,68 +1129,16 @@ "windowId": {"type": "integer", "minimum": 0}, "fromIndex": {"type": "integer", "minimum": 0}, "toIndex": {"type": "integer", "minimum": 0} } } ] }, { - "name": "onSelectionChanged", - "deprecated": "Please use $(ref:tabs.onActivated).", - "unsupported": true, - "type": "function", - "description": "Fires when the selected tab in a window changes.", - "parameters": [ - { - "type": "integer", - "name": "tabId", - "minimum": 0, - "description": "The ID of the tab that has become active." - }, - { - "type": "object", - "name": "selectInfo", - "properties": { - "windowId": { - "type": "integer", - "minimum": 0, - "description": "The ID of the window the selected tab changed inside of." - } - } - } - ] - }, - { - "name": "onActiveChanged", - "deprecated": "Please use $(ref:tabs.onActivated).", - "unsupported": true, - "type": "function", - "description": "Fires when the selected tab in a window changes. Note that the tab's URL may not be set at the time this event fired, but you can listen to $(ref:tabs.onUpdated) events to be notified when a URL is set.", - "parameters": [ - { - "type": "integer", - "name": "tabId", - "minimum": 0, - "description": "The ID of the tab that has become active." - }, - { - "type": "object", - "name": "selectInfo", - "properties": { - "windowId": { - "type": "integer", - "minimum": 0, - "description": "The ID of the window the selected tab changed inside of." - } - } - } - ] - }, - { "name": "onActivated", "type": "function", "description": "Fires when the active tab in a window changes. Note that the tab's URL may not be set at the time this event fired, but you can listen to onUpdated events to be notified when a URL is set.", "parameters": [ { "type": "object", "name": "activeInfo", "properties": { @@ -1300,41 +1158,16 @@ "minimum": 0, "description": "The ID of the window the active tab changed inside of." } } } ] }, { - "name": "onHighlightChanged", - "deprecated": "Please use $(ref:tabs.onHighlighted).", - "unsupported": true, - "type": "function", - "description": "Fired when the highlighted or selected tabs in a window changes.", - "parameters": [ - { - "type": "object", - "name": "selectInfo", - "properties": { - "windowId": { - "type": "integer", - "minimum": 0, - "description": "The window whose tabs changed." - }, - "tabIds": { - "type": "array", - "items": {"type": "integer", "minimum": 0}, - "description": "All highlighted tabs in the window." - } - } - } - ] - }, - { "name": "onHighlighted", "type": "function", "description": "Fired when the highlighted or selected tabs in a window changes.", "parameters": [ { "type": "object", "name": "highlightInfo", "properties": {
--- a/testing/mochitest/tests/SimpleTest/ExtensionTestUtils.js +++ b/testing/mochitest/tests/SimpleTest/ExtensionTestUtils.js @@ -161,19 +161,21 @@ ExtensionTestUtils.loadExtension = funct }; SimpleTest.info(`Extension loaded`); return extension; }; ExtensionTestUtils.failOnSchemaWarnings = (warningsAsErrors = true) => { let prefName = "extensions.webextensions.warnings-as-errors"; - SpecialPowers.setBoolPref(prefName, warningsAsErrors); + let prefPromise = SpecialPowers.setBoolPref(prefName, warningsAsErrors); if (!warningsAsErrors) { let registerCleanup; if (typeof registerCleanupFunction != "undefined") { registerCleanup = registerCleanupFunction; } else { registerCleanup = SimpleTest.registerCleanupFunction.bind(SimpleTest); } registerCleanup(() => SpecialPowers.setBoolPref(prefName, true)); } + // In mochitests, setBoolPref is async. + return prefPromise.then(() => {}); };
--- a/toolkit/components/extensions/Schemas.jsm +++ b/toolkit/components/extensions/Schemas.jsm @@ -2008,22 +2008,31 @@ class ObjectType extends Type { let error = null; if (!context.matchManifestVersion(type)) { if (prop in properties) { error = context.error( `Property "${prop}" is unsupported in Manifest Version ${context.manifestVersion}`, `not contain an unsupported "${prop}" property` ); - if (context.manifestVersion === 2) { - // Existing MV2 extensions might have some of the new MV3 properties. - // Since we've ignored them till now, we should just warn and bail. - this.logWarning(context, forceString(error.error)); - return; + + this.logWarning(context, forceString(error.error)); + if (this.additionalProperties) { + // When `additionalProperties` is set to UnrecognizedProperty, the + // caller (i.e. ObjectType's normalize method) assigns the original + // value to `result[prop]`. Erase the property now to prevent + // `result[prop]` from becoming anything other than `undefined. + // + // A warning was already logged above, so we do not need to also log + // "An unexpected property was found in the WebExtension manifest." + remainingProps.delete(prop); } + // When `additionalProperties` is not set, ObjectType's normalize method + // will return an error because prop is still in remainingProps. + return; } } else if (unsupported) { if (prop in properties) { error = context.error( `Property "${prop}" is unsupported by Firefox`, `not contain an unsupported "${prop}" property` ); }
--- a/toolkit/components/extensions/schemas/content_scripts.json +++ b/toolkit/components/extensions/schemas/content_scripts.json @@ -1,15 +1,16 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ [ { "namespace": "contentScripts", + "max_manifest_version": 2, "types": [ { "id": "RegisteredContentScriptOptions", "type": "object", "description": "Details of a content script registered programmatically", "properties": { "matches": { "type": "array",
--- a/toolkit/components/extensions/schemas/extension.json +++ b/toolkit/components/extensions/schemas/extension.json @@ -93,17 +93,16 @@ "isInstanceOf": "Window", "additionalProperties": { "type": "any" } } } }, { "name": "getBackgroundPage", "type": "function", - "max_manifest_version": 2, "description": "Returns the JavaScript 'window' object for the background page running inside the current extension. Returns null if the extension has no background page.", "parameters": [], "returns": { "type": "object", "optional": true, "isInstanceOf": "Window", "additionalProperties": { "type": "any" } }
--- a/toolkit/components/extensions/schemas/user_scripts.json +++ b/toolkit/components/extensions/schemas/user_scripts.json @@ -6,31 +6,33 @@ { "namespace": "manifest", "types": [ { "$extend": "WebExtensionManifest", "properties": { "user_scripts": { "type": "object", + "max_manifest_version": 2, "optional": true, "properties": { "api_script": { "optional": true, "$ref": "manifest.ExtensionURL" } }, "additionalProperties": { "$ref": "UnrecognizedProperty" } } } } ] }, { "namespace": "userScripts", + "max_manifest_version": 2, "permissions": ["manifest:user_scripts"], "types": [ { "id": "UserScriptOptions", "type": "object", "description": "Details of a user script", "properties": { "js": {
--- a/toolkit/components/extensions/schemas/user_scripts_content.json +++ b/toolkit/components/extensions/schemas/user_scripts_content.json @@ -1,15 +1,16 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ [ { "namespace": "userScripts", + "max_manifest_version": 2, "permissions": ["manifest:user_scripts"], "allowedContexts": ["content"], "events": [ { "name": "onBeforeScript", "permissions": ["manifest:user_scripts.api_script"], "allowedContexts": ["content", "content_only"], "type": "function",
--- a/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js +++ b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js @@ -102,16 +102,29 @@ let expectedBackgroundApis = [ "runtime.reload", "runtime.setUninstallURL", "theme.getCurrent", "theme.onUpdated", "types.LevelOfControl", "types.SettingScope", ]; +// APIs that are exposed to MV2 by default, but not to MV3. +const mv2onlyBackgroundApis = new Set([ + "extension.getURL", + "extension.lastError", + "contentScripts.register", + "tabs.executeScript", + "tabs.insertCSS", + "tabs.removeCSS", +]); +let expectedBackgroundApisMV3 = expectedBackgroundApis.filter( + path => !mv2onlyBackgroundApis.has(path) +); + function sendAllApis() { function isEvent(key, val) { if (!/^on[A-Z]/.test(key)) { return false; } let eventKeys = []; for (let prop in val) { eventKeys.push(prop); @@ -138,54 +151,79 @@ function sendAllApis() { } } } diveDeeper("browser", browser); diveDeeper("chrome", chrome); browser.test.sendMessage("allApis", results.sort()); } +add_task(async function setup() { + // This test enumerates all APIs and may access a deprecated API. Just log a + // warning instead of throwing. + await ExtensionTestUtils.failOnSchemaWarnings(false); +}); + add_task(async function test_enumerate_content_script_apis() { let extensionData = { manifest: { content_scripts: [ { matches: ["http://mochi.test/*/file_sample.html"], js: ["contentscript.js"], run_at: "document_start", }, ], }, files: { "contentscript.js": sendAllApis, }, }; - // Turn off warning as errors to pass for deprecated APIs - ExtensionTestUtils.failOnSchemaWarnings(false); let extension = ExtensionTestUtils.loadExtension(extensionData); await extension.startup(); let win = window.open("file_sample.html"); let actualApis = await extension.awaitMessage("allApis"); win.close(); let expectedApis = generateExpectations(expectedContentApis); isDeeply(actualApis, expectedApis, "content script APIs"); await extension.unload(); - ExtensionTestUtils.failOnSchemaWarnings(true); }); add_task(async function test_enumerate_background_script_apis() { let extensionData = { background: sendAllApis, }; - // Turn off warning as errors to pass for deprecated APIs - ExtensionTestUtils.failOnSchemaWarnings(false); let extension = ExtensionTestUtils.loadExtension(extensionData); await extension.startup(); let actualApis = await extension.awaitMessage("allApis"); let expectedApis = generateExpectations(expectedBackgroundApis); isDeeply(actualApis, expectedApis, "background script APIs"); await extension.unload(); - ExtensionTestUtils.failOnSchemaWarnings(true); }); + +add_task(async function test_enumerate_background_script_apis_mv3() { + await SpecialPowers.pushPrefEnv({ + set: [["extensions.manifestV3.enabled", true]], + }); + let extensionData = { + background: sendAllApis, + manifest: { + manifest_version: 3, + + // Features that expose APIs in MV2, but should not do anything with MV3. + browser_action: {}, + user_scripts: {}, + }, + }; + let extension = ExtensionTestUtils.loadExtension(extensionData); + await extension.startup(); + + let actualApis = await extension.awaitMessage("allApis"); + let expectedApis = generateExpectations(expectedBackgroundApisMV3); + isDeeply(actualApis, expectedApis, "background script APIs in MV3"); + + await extension.unload(); + await SpecialPowers.popPrefEnv(); +});
--- a/toolkit/components/extensions/test/xpcshell/test_ext_schemas_versioned.js +++ b/toolkit/components/extensions/test/xpcshell/test_ext_schemas_versioned.js @@ -63,16 +63,45 @@ let json = [ type: "object", properties: { value: { type: "boolean" }, }, }, }, ], }, + accepting_unrecognized_props: { + optional: true, + type: "object", + properties: { + mv2_only_prop: { + type: "string", + optional: true, + max_manifest_version: 2, + }, + mv3_only_prop: { + type: "string", + optional: true, + min_manifest_version: 3, + }, + mv2_only_prop_with_default: { + type: "string", + optional: true, + default: "only in MV2", + max_manifest_version: 2, + }, + mv3_only_prop_with_default: { + type: "string", + optional: true, + default: "only in MV3", + min_manifest_version: 3, + }, + }, + additionalProperties: { $ref: "UnrecognizedProperty" }, + }, }, }, { id: "submodule", type: "object", min_manifest_version: 3, functions: [ { @@ -245,22 +274,22 @@ let json = [ ], }, ]; add_task(async function setup() { let url = "data:," + JSON.stringify(json); Schemas._rootSchema = null; await Schemas.load(url); + + // We want the actual errors thrown here, and not warnings recast as errors. + ExtensionTestUtils.failOnSchemaWarnings(false); }); add_task(async function test_inject_V2() { - // We want the actual errors thrown here, and not warnings recast as errors. - ExtensionTestUtils.failOnSchemaWarnings(false); - // Test injecting into a V2 context. let wrapper = getContextWrapper(2); let root = {}; Schemas.inject(root, wrapper); // Test elements available to both Assert.equal(root.mixed.type_any.VALUE1, "value1", "type_any exists"); @@ -331,24 +360,25 @@ add_task(async function test_inject_V2() `Property "prop_mv3" is unsupported in Manifest Version 2`, ]); Assert.throws( () => root.mixed.fun_no_valid_param("anything"), /Incorrect argument types for mixed.fun_no_valid_param/, "fun_no_valid_param should throw for versioned type" ); - - ExtensionTestUtils.failOnSchemaWarnings(true); }); function normalizeTest(manifest, test, wrapper) { let normalized = Schemas.normalize(manifest, "mixed.manifestTest", wrapper); test(normalized); - return normalized; + // The test function should call wrapper.checkErrors if it expected errors. + // Here we call checkErrors again to ensure that there are not any unexpected + // errors left. + wrapper.checkErrors([]); } add_task(async function test_normalize_V2() { let wrapper = getContextWrapper(2); // Test normalize additions to the manifest structure normalizeTest( { @@ -431,16 +461,43 @@ add_task(async function test_normalize_V normalized => { Assert.ok( normalized.value.multiple_choice[0].value, "resources normalized" ); }, wrapper ); + + // Tests that object definitions including additionalProperties can + // successfully accept objects from another manifest version, while ignoring + // the actual value from the non-matching manifest value. + normalizeTest( + { + accepting_unrecognized_props: { + mv2_only_prop: "mv2 here", + mv3_only_prop: "mv3 here", + }, + }, + normalized => { + equal(normalized.error, undefined, "no normalization error"); + Assert.deepEqual( + normalized.value.accepting_unrecognized_props, + { + mv2_only_prop: "mv2 here", + mv2_only_prop_with_default: "only in MV2", + }, + "Normalized object for MV2, without MV3-specific props" + ); + wrapper.checkErrors([ + `Property "mv3_only_prop" is unsupported in Manifest Version 2`, + ]); + }, + wrapper + ); }); add_task(async function test_inject_V3() { // Test injecting into a V3 context. let wrapper = getContextWrapper(3); let root = {}; Schemas.inject(root, wrapper); @@ -502,19 +559,22 @@ add_task(async function test_inject_V3() "should throw for invalid type" ); let propObj = { prop_any: "prop_any", prop_mv3: "prop_mv3" }; root.mixed.fun_param_change(propObj); wrapper.verify("call", "mixed", "fun_param_change", [propObj]); Assert.throws( () => root.mixed.fun_param_change({ prop_mv2: "prop_mv2", ...propObj }), - /Property "prop_mv2" is unsupported in Manifest Version 3/, + /Unexpected property "prop_mv2"/, "should throw for versioned type" ); + wrapper.checkErrors([ + `Property "prop_mv2" is unsupported in Manifest Version 3`, + ]); root.mixed.PROP_mv3.sub_foo(); wrapper.verify("call", "mixed.PROP_mv3", "sub_foo", []); Assert.throws( () => root.mixed.PROP_mv3.sub_no_match(), /TypeError: root.mixed.PROP_mv3.sub_no_match is not a function/, "sub_no_match should throw" ); @@ -524,22 +584,24 @@ add_task(async function test_normalize_V let wrapper = getContextWrapper(3); // Test normalize additions to the manifest structure normalizeTest( { versioned_extend: "test", }, normalized => { - Assert.ok( - normalized.error.startsWith( - `Property "versioned_extend" is unsupported in Manifest Version 3` - ), - "manifest error" + Assert.equal( + normalized.error, + `Unexpected property "versioned_extend"`, + "expected manifest error" ); + wrapper.checkErrors([ + `Property "versioned_extend" is unsupported in Manifest Version 3`, + ]); }, wrapper ); // Test normalizing baseType normalizeTest( { permissions: ["base"], @@ -617,9 +679,36 @@ add_task(async function test_normalize_V normalizeTest( {}, normalized => { ok(!normalized.error, "manifest normalized"); }, wrapper ); + + // Tests that object definitions including additionalProperties can + // successfully accept objects from another manifest version, while ignoring + // the actual value from the non-matching manifest value. + normalizeTest( + { + accepting_unrecognized_props: { + mv2_only_prop: "mv2 here", + mv3_only_prop: "mv3 here", + }, + }, + normalized => { + equal(normalized.error, undefined, "no normalization error"); + Assert.deepEqual( + normalized.value.accepting_unrecognized_props, + { + mv3_only_prop: "mv3 here", + mv3_only_prop_with_default: "only in MV3", + }, + "Normalized object for MV3, without MV2-specific props" + ); + wrapper.checkErrors([ + `Property "mv2_only_prop" is unsupported in Manifest Version 3`, + ]); + }, + wrapper + ); });