Merge autoland to mozilla-central. a=merge
authorNorisz 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 id16598
push userffxbld-merge
push dateMon, 02 May 2022 14:23:32 +0000
treeherdermozilla-beta@de86a81c7a63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone101.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
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
--- 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
+  );
 });