Merge mozilla-central to inbound a=merge
authorCoroiu Cristina <ccoroiu@mozilla.com>
Wed, 01 May 2019 00:58:36 +0300
changeset 530918 4755a19e0826d2e3beee0559440387e84bc60444
parent 530917 0259eb7967f41e2476320a899efa85e59a3fe837 (current diff)
parent 530798 11b4d70a2b5d6373f5ec8fa01ca48a1c7bc1829e (diff)
child 530919 628585931d03d6c71294f6ba559e506414959f10
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound a=merge
browser/components/enterprisepolicies/EnterprisePolicies.js
browser/components/enterprisepolicies/EnterprisePolicies.manifest
browser/components/enterprisepolicies/EnterprisePoliciesContent.js
browser/components/enterprisepolicies/WindowsGPOParser.jsm
browser/components/enterprisepolicies/macOSPoliciesParser.jsm
browser/components/enterprisepolicies/tests/EnterprisePolicyTesting.jsm
browser/components/enterprisepolicies/tests/browser/browser_policies_basic_tests.js
browser/components/enterprisepolicies/tests/browser/browser_policies_broken_json.js
browser/components/enterprisepolicies/tests/browser/browser_policies_enterprise_only.js
browser/components/enterprisepolicies/tests/browser/browser_policies_mistyped_json.js
browser/components/enterprisepolicies/tests/browser/config_broken_json.json
devtools/server/tests/unit/test_longstringgrips-02.js
devtools/shared/client/array-buffer-client.js
dom/base/Document.cpp
extensions/cookie/components.conf
extensions/cookie/moz.build
extensions/cookie/nsPermission.cpp
extensions/cookie/nsPermission.h
extensions/cookie/nsPermissionManager.cpp
extensions/cookie/nsPermissionManager.h
extensions/cookie/test/.eslintrc.js
extensions/cookie/test/beltzner.jpg
extensions/cookie/test/beltzner.jpg^headers^
extensions/cookie/test/browser.ini
extensions/cookie/test/browser_permmgr_sync.js
extensions/cookie/test/browser_permmgr_viewsrc.js
extensions/cookie/test/browser_test_favicon.js
extensions/cookie/test/damonbowling.jpg
extensions/cookie/test/damonbowling.jpg^headers^
extensions/cookie/test/file_chromecommon.js
extensions/cookie/test/file_domain_hierarchy_inner.html
extensions/cookie/test/file_domain_hierarchy_inner_inner.html
extensions/cookie/test/file_domain_hierarchy_inner_inner_inner.html
extensions/cookie/test/file_domain_inner.html
extensions/cookie/test/file_domain_inner_inner.html
extensions/cookie/test/file_favicon.html
extensions/cookie/test/file_image_inner.html
extensions/cookie/test/file_image_inner_inner.html
extensions/cookie/test/file_loadflags_inner.html
extensions/cookie/test/file_localhost_inner.html
extensions/cookie/test/file_loopback_inner.html
extensions/cookie/test/file_subdomain_inner.html
extensions/cookie/test/file_testcommon.js
extensions/cookie/test/file_testloadflags.js
extensions/cookie/test/file_testloadflags_chromescript.js
extensions/cookie/test/gtest/PermissionManagerTest.cpp
extensions/cookie/test/gtest/moz.build
extensions/cookie/test/image1.png
extensions/cookie/test/image1.png^headers^
extensions/cookie/test/image2.png
extensions/cookie/test/image2.png^headers^
extensions/cookie/test/mochitest.ini
extensions/cookie/test/moz.build
extensions/cookie/test/test1.css
extensions/cookie/test/test1.css^headers^
extensions/cookie/test/test2.css
extensions/cookie/test/test2.css^headers^
extensions/cookie/test/test_different_domain_in_hierarchy.html
extensions/cookie/test/test_differentdomain.html
extensions/cookie/test/test_image.html
extensions/cookie/test/test_loadflags.html
extensions/cookie/test/test_same_base_domain.html
extensions/cookie/test/test_same_base_domain_2.html
extensions/cookie/test/test_same_base_domain_3.html
extensions/cookie/test/test_same_base_domain_4.html
extensions/cookie/test/test_same_base_domain_5.html
extensions/cookie/test/test_same_base_domain_6.html
extensions/cookie/test/test_samedomain.html
extensions/cookie/test/unit/head_cookies.js
extensions/cookie/test/unit/test_bug526789.js
extensions/cookie/test/unit/test_bug650522.js
extensions/cookie/test/unit/test_bug667087.js
extensions/cookie/test/unit/test_cookies_async_failure.js
extensions/cookie/test/unit/test_cookies_persistence.js
extensions/cookie/test/unit/test_cookies_privatebrowsing.js
extensions/cookie/test/unit/test_cookies_profile_close.js
extensions/cookie/test/unit/test_cookies_read.js
extensions/cookie/test/unit/test_cookies_sync_failure.js
extensions/cookie/test/unit/test_cookies_thirdparty.js
extensions/cookie/test/unit/test_cookies_thirdparty_nonsecure_session.js
extensions/cookie/test/unit/test_cookies_thirdparty_session.js
extensions/cookie/test/unit/test_domain_eviction.js
extensions/cookie/test/unit/test_eviction.js
extensions/cookie/test/unit/test_permmanager_cleardata.js
extensions/cookie/test/unit/test_permmanager_default_pref.js
extensions/cookie/test/unit/test_permmanager_defaults.js
extensions/cookie/test/unit/test_permmanager_expiration.js
extensions/cookie/test/unit/test_permmanager_getAllForURI.js
extensions/cookie/test/unit/test_permmanager_getAllWithTypePrefix.js
extensions/cookie/test/unit/test_permmanager_getPermissionObject.js
extensions/cookie/test/unit/test_permmanager_idn.js
extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js
extensions/cookie/test/unit/test_permmanager_local_files.js
extensions/cookie/test/unit/test_permmanager_matches.js
extensions/cookie/test/unit/test_permmanager_matchesuri.js
extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js
extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
extensions/cookie/test/unit/test_permmanager_notifications.js
extensions/cookie/test/unit/test_permmanager_removeall.js
extensions/cookie/test/unit/test_permmanager_removebytype.js
extensions/cookie/test/unit/test_permmanager_removebytypesince.js
extensions/cookie/test/unit/test_permmanager_removeforapp.js
extensions/cookie/test/unit/test_permmanager_removepermission.js
extensions/cookie/test/unit/test_permmanager_removesince.js
extensions/cookie/test/unit/test_permmanager_subdomains.js
extensions/cookie/test/unit/test_schema_2_migration.js
extensions/cookie/test/unit/test_schema_3_migration.js
extensions/cookie/test/unit/xpcshell.ini
testing/web-platform/meta/mediacapture-streams/MediaDevices-SecureContext.html.ini
testing/web-platform/meta/mediacapture-streams/MediaDevices-getSupportedConstraints.html.ini
testing/web-platform/meta/webrtc/RTCIceConnectionState-candidate-pair.https.html.ini
testing/web-platform/meta/webrtc/RTCPeerConnection-generateCertificate.html.ini
testing/web-platform/meta/webrtc/RTCTrackEvent-fire.html.ini
testing/web-platform/tests/mediacapture-streams/MediaDevices-getSupportedConstraints.html
toolkit/mozapps/update/tests/unit_aus_update/downloadCompleteAfterPartialFailure.js
toolkit/mozapps/update/tests/unit_aus_update/downloadInvalidSizeMar.js
toolkit/mozapps/update/tests/unit_aus_update/downloadMissingMar.js
tools/profiler/tests/.eslintrc.js
tools/profiler/tests/head_profiler.js
tools/profiler/tests/test_asm.js
tools/profiler/tests/test_enterjit_osr.js
tools/profiler/tests/test_enterjit_osr_disabling.js
tools/profiler/tests/test_enterjit_osr_enabling.js
tools/profiler/tests/test_feature_mainthreadio.js
tools/profiler/tests/test_get_features.js
tools/profiler/tests/test_pause.js
tools/profiler/tests/test_run.js
tools/profiler/tests/test_shared_library.js
tools/profiler/tests/test_start.js
tools/profiler/tests/xpcshell.ini
--- a/.eslintignore
+++ b/.eslintignore
@@ -9,17 +9,17 @@
 **/reftests/**
 
 # Exclude expected objdirs.
 obj*/**
 
 # We ignore all these directories by default, until we get them enabled.
 # If you are enabling a directory, please add directory specific exclusions
 # below.
-extensions/cookie/**
+extensions/permissions/**
 extensions/spellcheck/**
 extensions/universalchardet/**
 image/**
 layout/**
 netwerk/cookie/test/browser/**
 netwerk/test/browser/**
 netwerk/test/mochitests/**
 netwerk/test/unit*/**
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -48,8 +48,71 @@ 06c3d29113e60ab1a316fbd5a16c667202d012fb
 d25e4032d4d15e732a78082505d62b6d6ffa1445 # cinnabar
 e5e885ae3128a6878d08df4ff6baaf3fc4a4b9e8 # gecko-dev
 
 # Bug 1513205 - Ride along, update some code to match the Google coding style
 # https://hg.mozilla.org/integration/autoland/rev/039a7c0c18fb
 1ce955dcb2e09fd9ce19c1ec6c470f75db486bd2 # cinnabar
 7cf43b9bc01d650f87deceb65f336cdac7c0e78f # gecko-dev
 
+# Bug 1519636 - clang-format-8: Reformat recent changes to the Google coding style
+# and its various backouts.
+# https://hg.mozilla.org/integration/autoland/rev/d091a005f031
+a7f51c0bd925f88cd4f7c90e53eee9bc8a699958 # cinnabar
+96da5036adba9b6cf7339388ef20fc81fbc8bf8f # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/44928a7f5745
+85e14a8329a4f70eff94454c20fb30d068afdd7e # cinnabar
+a1dce6440a18b10feed1479fd9e1bb76498c5297 # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/f95dc32944ac
+e4401f52c313cdbd1f6a21d2d158a39aea08629a # cinnabar
+7f60810d8613c150c5efd762229bbfaea7c95b34 # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/662b776a02e6
+a45536a2ffe94764488656e0cff7f792da61d208 # cinnabar
+03c8e8c2ddd618ee28fd1fe42126cc7e7c903871 # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/6d386a2162b9
+5ff1b801dc84e045ebe1c10ffa0008f1551baa07 # cinnabar
+ba58e936bd475793a3c74f9355498554ea02c341 # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/4ad80127f89f
+51f8a2a90bddca7d6b72b4170b10caae45dcac7b # cinnabar
+d1c1878603873095a41a7d0f45022d469eb6baa8 # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/9e48fefcf1ac
+64a824984811080bc44ad5364ff70e652396c29c # cinnabar
+24dbe577a5e1f238defc1cfd800e6258a72632df # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/389b6bbd76db
+8780f9e6d11598df632eb1ac1a9fa2cb11a3e76a # cinnabar
+399dbd28fe9d399571a92347957ae4712333441c # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/7de6e431f1ae
+48ca7974405930abc6f87d89704fe111444e6f86 # cinnabar
+ef0bfc3822e1ebcc4af26fa1d604393f504333c0 # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/fa3daa92292d
+7c9505d11132c1713ae20d3de68086f7dee06454 # cinnabar
+3d0316a9027c4ae6ec84d454a3b4454b7fd84e1f # gecko-dev
+# https://hg.mozilla.org/integration/mozilla-inbound/rev/d55401632cea
+4ea8582800a843ae3348a03ca619b0109d1c1537 # cinnabar
+4aa92e3091227fe771d64424fe62d3e133633be2 # gecko-dev
+# https://hg.mozilla.org/integration/mozilla-inbound/rev/9adeff2c423f
+dafa8b4d1a427e98492d391d8692d555e716711c # cinnabar
+e0c61dafa502cf337a3ace00834c6138a5a8ba11 # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/a031c9dd9dd5
+d2f46ba4a543531b6c87ebf54d57a8be1999038e # cinnabar
+41d1d7909496a3f672eb9aeed1efec042c9568b0 # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/3924aed041cf
+20f6da83f406ebbe5367f6df3b8719428c022d92 # cinnabar
+14486004b69a5342e075d784430b741a259c9ac7 # gecko-dev
+# https://hg.mozilla.org/integration/mozilla-inbound/rev/07f6f62a24f5
+9595b32aaa2c87be8168ad20ba8d277dc5f474c9 # cinnabar
+ee2016670c850d7ec19c6d6c56fca0a38e591d9d # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/08b686c04a01
+0f53aeedad4bcfe93916273a00b4577747c3914c # cinnabar
+b61d90492bef978d04e9a12e7081837825e55973 # gecko-dev
+# https://hg.mozilla.org/integration/mozilla-inbound/rev/7d46c1872ba7
+140cb38f7666d3d48c527f8ce9ca13f0b060ab03 # cinnabar
+c4a4488fb459de2180f7d39bfc9be97bfa59bbf8 # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/2307fb1cfa80
+d9145bdd58d06b0550ff76d6cd449fb0aa065787 # cinnabar
+9c0e9e7467ba1697326a8f056cc11e0153e8217d # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/d536f45f5b16
+cfc0d52d902cd58293f32a97f286a5e2a3683386 # cinnabar
+755a1a7c2f77bf360808d8470d92a8b48dd2469c # gecko-dev
+# https://hg.mozilla.org/integration/autoland/rev/d54846d01280
+abc28896717209d5f0ed2a6b0a46f8e03880ad19 # cinnabar
+47a5dd1fb80fa5bbe608c32db426f1ed29b7fd79 # gecko-dev
+
--- a/browser/base/content/test/webrtc/browser_devices_get_user_media.js
+++ b/browser/base/content/test/webrtc/browser_devices_get_user_media.js
@@ -525,16 +525,20 @@ var gTests = [
     await closeStream();
   },
 },
 
 {
   desc: "'Always Allow' disabled on http pages",
   run: async function checkNoAlwaysOnHttp() {
     // Load an http page instead of the https version.
+    await SpecialPowers.pushPrefEnv({ set: [
+        ["media.devices.insecure.enabled", true],
+        ["media.getusermedia.insecure.enabled", true],
+    ]});
     let browser = gBrowser.selectedBrowser;
     BrowserTestUtils.loadURI(browser, browser.documentURI.spec.replace("https://", "http://"));
     await BrowserTestUtils.browserLoaded(browser);
 
     // Initially set both permissions to 'allow'.
     let Perms = Services.perms;
     let uri = browser.documentURI;
     Perms.add(uri, "microphone", Perms.ALLOW_ACTION);
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -398,16 +398,17 @@
                        key="key_quitApplication"
                        command="cmd_quitApplication"/>
 #endif
       </vbox>
     </panelview>
     <panelview id="PanelUI-history" flex="1">
       <vbox class="panel-subview-body">
         <toolbarbutton id="appMenuViewHistorySidebar"
+                       label="&appMenuHistory.viewSidebar.label;"
                        label-checked="&appMenuHistory.hideSidebar.label;"
                        label-unchecked="&appMenuHistory.viewSidebar.label;"                      
                        type="checkbox"
                        class="subviewbutton subviewbutton-iconic"
                        key="key_gotoHistory"
                        oncommand="SidebarUI.toggle('viewHistorySidebar');">
                        <observes element="sidebar-box" attribute="positionend"/>
         </toolbarbutton>
@@ -450,16 +451,17 @@
                descriptionheightworkaround="true">
       <vbox class="panel-subview-body">
         <!-- this widget has 3 boxes in the body, but only 1 is ever visible -->
         <!-- When Sync is ready to sync -->
         <vbox id="PanelUI-remotetabs-main" hidden="true">
           <vbox id="PanelUI-remotetabs-buttons">
             <toolbarbutton id="PanelUI-remotetabs-view-sidebar"
                            class="subviewbutton subviewbutton-iconic"
+                           label="&appMenuRemoteTabs.sidebar.label;"
                            label-checked="&appMenuRemoteTabs.hidesidebar.label;"
                            label-unchecked="&appMenuRemoteTabs.sidebar.label;"
                            oncommand="SidebarUI.toggle('viewTabsSidebar', this);"/>
             <toolbarbutton id="PanelUI-remotetabs-view-managedevices"
                            class="subviewbutton subviewbutton-iconic"
                            label="&appMenuRemoteTabs.managedevices.label;"
                            oncommand="gSync.openDevicesManagementPage('syncedtabs-menupanel');">
                            <observes element="sidebar-box" attribute="positionend"/>
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -83,17 +83,16 @@ skip-if = os == "linux" # Intermittent f
 [browser_947914_button_zoomReset.js]
 skip-if = os == "linux" # Intermittent failures
 [browser_947987_removable_default.js]
 [browser_948985_non_removable_defaultArea.js]
 [browser_952963_areaType_getter_no_area.js]
 skip-if = verify
 [browser_956602_remove_special_widget.js]
 [browser_962069_drag_to_overflow_chevron.js]
-skip-if = verify || os == "linux" || os == "mac" && debug # bug 1515204
 [browser_963639_customizing_attribute_non_customizable_toolbar.js]
 [browser_967000_button_charEncoding.js]
 [browser_968565_insert_before_hidden_items.js]
 [browser_969427_recreate_destroyed_widget_after_reset.js]
 [browser_969661_character_encoding_navbar_disabled.js]
 [browser_970511_undo_restore_default.js]
 skip-if = verify
 [browser_972267_customizationchange_events.js]
--- a/browser/components/enterprisepolicies/moz.build
+++ b/browser/components/enterprisepolicies/moz.build
@@ -11,31 +11,15 @@ DIRS += [
     'helpers',
     'schemas',
 ]
 
 TEST_DIRS += [
 	'tests'
 ]
 
-EXTRA_COMPONENTS += [
-    'EnterprisePolicies.js',
-    'EnterprisePolicies.manifest',
-    'EnterprisePoliciesContent.js',
-]
-
 EXTRA_JS_MODULES.policies += [
     'Policies.jsm',
 ]
 
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
-    EXTRA_JS_MODULES.policies += [
-        'WindowsGPOParser.jsm',
-]
-
-if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
-    EXTRA_JS_MODULES.policies += [
-        'macOSPoliciesParser.jsm',
-]
-
 FINAL_LIBRARY = 'browsercomps'
 
 JAR_MANIFESTS += ['jar.mn']
--- a/browser/components/enterprisepolicies/tests/browser/browser.ini
+++ b/browser/components/enterprisepolicies/tests/browser/browser.ini
@@ -1,29 +1,24 @@
 [DEFAULT]
 support-files =
   head.js
   config_popups_cookies_addons_flash.json
-  config_broken_json.json
   opensearch.html
   opensearchEngine.xml
   policytest_v0.1.xpi
   policytest_v0.2.xpi
   policy_websitefilter_block.html
   policy_websitefilter_exception.html
   ../../../../../toolkit/components/antitracking/test/browser/page.html
   ../../../../../toolkit/components/antitracking/test/browser/subResources.sjs
 
-[browser_policies_basic_tests.js]
-[browser_policies_broken_json.js]
-[browser_policies_enterprise_only.js]
 [browser_policies_getActivePolicies.js]
 [browser_policies_macosparser_unflatten.js]
 skip-if = os != 'mac'
-[browser_policies_mistyped_json.js]
 [browser_policies_notice_in_aboutpreferences.js]
 [browser_policies_popups_cookies_addons_flash.js]
 [browser_policies_runOnce_helper.js]
 [browser_policies_setAndLockPref_API.js]
 [browser_policies_simple_pref_policies.js]
 [browser_policies_sorted_alphabetically.js]
 [browser_policy_3rdparty.js]
 [browser_policy_app_update.js]
--- a/browser/components/enterprisepolicies/tests/browser/browser_policies_macosparser_unflatten.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_macosparser_unflatten.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-let { macOSPoliciesParser } = ChromeUtils.import("resource:///modules/policies/macOSPoliciesParser.jsm");
+let { macOSPoliciesParser } = ChromeUtils.import("resource://gre/modules/policies/macOSPoliciesParser.jsm");
 
 add_task(async function test_object_unflatten() {
   // Note: these policies are just examples and they won't actually
   // run through the policy engine on this test. We're just testing
   // that the unflattening algorithm produces the correct output.
   let input = {
     "DisplayBookmarksToolbar": true,
 
--- a/browser/components/enterprisepolicies/tests/moz.build
+++ b/browser/components/enterprisepolicies/tests/moz.build
@@ -8,12 +8,8 @@ BROWSER_CHROME_MANIFESTS += [
     'browser/browser.ini',
     'browser/disable_app_update/browser.ini',
     'browser/disable_default_bookmarks/browser.ini',
     'browser/disable_developer_tools/browser.ini',
     'browser/disable_forget_button/browser.ini',
     'browser/disable_fxscreenshots/browser.ini',
     'browser/hardware_acceleration/browser.ini',
 ]
-
-TESTING_JS_MODULES += [
-    'EnterprisePolicyTesting.jsm',
-]
--- a/browser/components/urlbar/UrlbarView.jsm
+++ b/browser/components/urlbar/UrlbarView.jsm
@@ -7,16 +7,17 @@
 var EXPORTED_SYMBOLS = ["UrlbarView"];
 
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetters(this, {
   Services: "resource://gre/modules/Services.jsm",
   UrlbarPrefs: "resource:///modules/UrlbarPrefs.jsm",
   UrlbarTokenizer: "resource:///modules/UrlbarTokenizer.jsm",
   UrlbarUtils: "resource:///modules/UrlbarUtils.jsm",
+  AppConstants: "resource://gre/modules/AppConstants.jsm",
 });
 
 XPCOMUtils.defineLazyGetter(this, "bundle", function() {
   return Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
 });
 
 /**
  * Receives and displays address bar autocomplete results.
@@ -366,20 +367,26 @@ class UrlbarView {
     } else {
       this.panel.style.removeProperty("--item-padding-start");
       this.panel.style.removeProperty("--item-padding-end");
     }
 
     // Align the panel with the input's parent toolbar.
     let toolbarRect =
       this._getBoundsWithoutFlushing(this.input.textbox.closest("toolbar"));
-    this.panel.style.marginInlineStart = px(this.window.RTL_UI ?
+    let horizontalOffset = this.window.RTL_UI ?
       inputRect.right - documentRect.right :
-      documentRect.left - inputRect.left);
-    this.panel.style.marginTop = px(inputRect.top - toolbarRect.top);
+      documentRect.left - inputRect.left;
+    let verticalOffset = inputRect.top - toolbarRect.top;
+    if (AppConstants.platform == "macosx") {
+      // Adjust vertical offset to account for the popup's native outer border.
+      verticalOffset++;
+    }
+    this.panel.style.marginInlineStart = px(horizontalOffset);
+    this.panel.style.marginTop = px(verticalOffset);
 
     this.panel.openPopup(this.input.textbox, "after_start");
   }
 
   _updateResults(queryContext) {
     let results = queryContext.results;
     let i = 0;
     for (let row of this._rows.children) {
--- a/browser/extensions/fxmonitor/moz.build
+++ b/browser/extensions/fxmonitor/moz.build
@@ -20,10 +20,12 @@ FINAL_TARGET_FILES.features['fxmonitor@m
 ]
 
 FINAL_TARGET_FILES.features['fxmonitor@mozilla.org']['privileged'] += [
   'privileged/api.js',
   'privileged/FirefoxMonitor.css',
   'privileged/schema.json'
 ]
 
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+
 with Files('**'):
   BUG_COMPONENT = ('Firefox', 'Firefox Monitor')
--- a/browser/extensions/fxmonitor/privileged/api.js
+++ b/browser/extensions/fxmonitor/privileged/api.js
@@ -36,23 +36,16 @@ this.fxmonitor = class extends Extension
     FirefoxMonitor.stopObserving();
   }
 };
 
 this.FirefoxMonitor = {
   // Map of breached site host -> breach metadata.
   domainMap: new Map(),
 
-  // Set of hosts for which the user has already been shown,
-  // and interacted with, the popup.
-  warnedHostsSet: new Set(),
-
-  // The above set is persisted as a JSON string in this pref.
-  kWarnedHostsPref: "extensions.fxmonitor.warnedHosts",
-
   // Reference to the extension object from the WebExtension context.
   // Used for getting URIs for resources packaged in the extension.
   extension: null,
 
   // Whether we've started observing for the user visiting a breached site.
   observerAdded: false,
 
   // This is here for documentation, will be redefined to a lazy getter
@@ -97,16 +90,41 @@ this.FirefoxMonitor = {
   getString(aKey) {
     return this.strings.GetStringFromName(aKey);
   },
 
   getFormattedString(aKey, args) {
     return this.strings.formatStringFromName(aKey, args, args.length);
   },
 
+  // We used to persist the list of hosts we've already warned the
+  // user for in this pref. Now, we check the pref at init and
+  // if it has a value, migrate the remembered hosts to content prefs
+  // and clear this one.
+  kWarnedHostsPref: "extensions.fxmonitor.warnedHosts",
+  migrateWarnedHostsIfNeeded() {
+    if (!Preferences.isSet(this.kWarnedHostsPref)) {
+      return;
+    }
+
+    let hosts = [];
+    try {
+      hosts = JSON.parse(Preferences.get(this.kWarnedHostsPref));
+    } catch (ex) {
+      // Invalid JSON, nothing to be done.
+    }
+
+    let loadContext = Cu.createLoadContext();
+    for (let host of hosts) {
+      this.rememberWarnedHost(loadContext, host);
+    }
+
+    Preferences.reset(this.kWarnedHostsPref);
+  },
+
   init(aExtension) {
     this.extension = aExtension;
 
     XPCOMUtils.defineLazyPreferenceGetter(
       this, "enabled", this.kEnabledPref, true,
       (pref, oldVal, newVal) => {
         if (newVal) {
           this.startObserving();
@@ -126,16 +144,22 @@ this.FirefoxMonitor = {
   _delayedInited: false,
   async delayedInit() {
     if (this._delayedInited) {
       return;
     }
 
     this._delayedInited = true;
 
+    XPCOMUtils.defineLazyServiceGetter(this, "_contentPrefService",
+      "@mozilla.org/content-pref/service;1",
+      "nsIContentPrefService2");
+
+    this.migrateWarnedHostsIfNeeded();
+
     // Expire our telemetry on November 1, at which time
     // we should redo data-review.
     let telemetryExpiryDate = new Date(2019, 10, 1); // Month is zero-index
     let today = new Date();
     let expired = today.getTime() > telemetryExpiryDate.getTime();
 
     Services.telemetry.registerEvents("fxmonitor", {
       "interaction": {
@@ -150,27 +174,16 @@ this.FirefoxMonitor = {
         record_on_release: true,
         expired,
       },
     });
 
     let telemetryEnabled = !Preferences.get(this.kTelemetryDisabledPref);
     Services.telemetry.setEventRecordingEnabled("fxmonitor", telemetryEnabled);
 
-    let warnedHostsJSON = Preferences.get(this.kWarnedHostsPref, "");
-    if (warnedHostsJSON) {
-      try {
-        let json = JSON.parse(warnedHostsJSON);
-        this.warnedHostsSet = new Set(json);
-      } catch (ex) {
-        // Invalid JSON, invalidate the pref.
-        Preferences.reset(this.kWarnedHostsPref);
-      }
-    }
-
     XPCOMUtils.defineLazyPreferenceGetter(this, "FirefoxMonitorURL",
       this.kFirefoxMonitorURLPref, this.kDefaultFirefoxMonitorURL);
 
     XPCOMUtils.defineLazyPreferenceGetter(this, "firstAlertShown",
       this.kFirstAlertShownPref, false);
 
     await this.loadStrings();
     await this.loadBreaches();
@@ -283,25 +296,29 @@ this.FirefoxMonitor = {
         if (closing) {
           return;
         }
 
         let DOMWindowUtils = win.windowUtils;
         DOMWindowUtils.removeSheetUsingURIString(this.getURL("privileged/FirefoxMonitor.css"),
                                                  DOMWindowUtils.AUTHOR_SHEET);
 
-        this.notificationsByWindow.get(win).forEach(n => {
-          n.remove();
-        });
-        this.notificationsByWindow.delete(win);
+        if (this.notificationsByWindow.has(win)) {
+          this.notificationsByWindow.get(win).forEach(n => {
+            n.remove();
+          });
+          this.notificationsByWindow.delete(win);
+        }
 
-        let doc = win.document;
-        doc.getElementById(`${this.kNotificationID}-notification-anchor`).remove();
-        doc.getElementById(`${this.kNotificationID}-notification`).remove();
-        this.panelUIsByWindow.delete(win);
+        if (this.panelUIsByWindow.has(win)) {
+          let doc = win.document;
+          doc.getElementById(`${this.kNotificationID}-notification-anchor`).remove();
+          doc.getElementById(`${this.kNotificationID}-notification`).remove();
+          this.panelUIsByWindow.delete(win);
+        }
 
         win.gBrowser.removeTabsProgressListener(this);
       },
     );
 
     this.observerAdded = true;
   },
 
@@ -350,18 +367,40 @@ this.FirefoxMonitor = {
       return;
     }
 
     EveryWindow.unregisterCallback(this.kNotificationID);
 
     this.observerAdded = false;
   },
 
-  warnIfNeeded(browser, host) {
-    if (!this.enabled || this.warnedHostsSet.has(host) || !this.domainMap.has(host)) {
+  async hostAlreadyWarned(loadContext, host) {
+    return new Promise((resolve, reject) => {
+      this._contentPrefService.getByDomainAndName(
+        host,
+        "extensions.fxmonitor.hostAlreadyWarned",
+        loadContext,
+        {
+          handleCompletion: () => resolve(false),
+          handleResult: (result) => resolve(result.value),
+        });
+    });
+  },
+
+  rememberWarnedHost(loadContext, host) {
+    this._contentPrefService.set(
+      host,
+      "extensions.fxmonitor.hostAlreadyWarned",
+      true,
+      loadContext);
+  },
+
+  async warnIfNeeded(browser, host) {
+    if (!this.enabled || !this.domainMap.has(host) ||
+        await this.hostAlreadyWarned(browser.loadContext, host)) {
       return;
     }
 
     let site = this.domainMap.get(host);
 
     // We only alert for breaches that were found up to 2 months ago,
     // except for the very first alert we show the user - in which case,
     // we include breaches found in the last three years.
@@ -373,18 +412,17 @@ this.FirefoxMonitor = {
     }
 
     if (new Date(site.AddedDate).getTime() < breachDateThreshold.getTime()) {
       return;
     } else if (!this.firstAlertShown) {
       Preferences.set(this.kFirstAlertShownPref, true);
     }
 
-    this.warnedHostsSet.add(host);
-    Preferences.set(this.kWarnedHostsPref, JSON.stringify([...this.warnedHostsSet]));
+    this.rememberWarnedHost(browser.loadContext, host);
 
     let doc = browser.ownerDocument;
     let win = doc.defaultView;
     let panelUI = this.panelUIsByWindow.get(win);
     if (!panelUI) {
       panelUI = this.setupPanelUI(win);
     }
 
new file mode 100644
--- /dev/null
+++ b/browser/extensions/fxmonitor/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/browser-test"
+  ]
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/fxmonitor/test/browser/browser.ini
@@ -0,0 +1,2 @@
+[browser_fxmonitor_doorhanger.js]
+skip-if = debug # bug 1547517
new file mode 100644
--- /dev/null
+++ b/browser/extensions/fxmonitor/test/browser/browser_fxmonitor_doorhanger.js
@@ -0,0 +1,270 @@
+"use strict";
+
+ChromeUtils.defineModuleGetter(this, "AddonManager",
+                               "resource://gre/modules/AddonManager.jsm");
+ChromeUtils.defineModuleGetter(this, "RemoteSettings",
+                               "resource://services-settings/remote-settings.js");
+
+const kNotificationId = "fxmonitor";
+const kRemoteSettingsKey = "fxmonitor-breaches";
+
+async function fxmonitorNotificationShown() {
+  await TestUtils.waitForCondition(() => {
+    return PopupNotifications.getNotification(kNotificationId)
+      && PopupNotifications.panel.state == "open";
+  }, "Waiting for fxmonitor notification to be shown");
+  ok(true, "Firefox Monitor PopupNotification was added.");
+}
+
+async function fxmonitorNotificationGone() {
+  await TestUtils.waitForCondition(() => {
+    return !PopupNotifications.getNotification(kNotificationId)
+      && PopupNotifications.panel.state == "closed";
+  }, "Waiting for fxmonitor notification to go away");
+  ok(true, "Firefox Monitor PopupNotification was removed.");
+}
+
+let cps2 = Cc["@mozilla.org/content-pref/service;1"]
+             .getService(Ci.nsIContentPrefService2);
+
+async function clearWarnedHosts() {
+  return new Promise((resolve, reject) => {
+    cps2.removeByName("extensions.fxmonitor.hostAlreadyWarned", Cu.createLoadContext(), {
+      handleCompletion: resolve,
+    });
+  });
+}
+
+add_task(async function test_warnedHosts_migration() {
+  info("Test that we correctly migrate the warnedHosts pref to content prefs.");
+
+  // Pre-set the warnedHosts pref to include example.com.
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.fxmonitor.warnedHosts", "[\"example.com\"]"]],
+  });
+
+  // Pre-populate the Remote Settings collection with a breach.
+  let collection = await RemoteSettings(kRemoteSettingsKey).openCollection();
+  let BreachDate = new Date();
+  let AddedDate = new Date();
+  await collection.create({
+    Domain: "example.com",
+    Name: "Example Site",
+    BreachDate: `${BreachDate.getFullYear()}-${BreachDate.getMonth() + 1}-${BreachDate.getDate()}`,
+    AddedDate: `${AddedDate.getFullYear()}-${AddedDate.getMonth() + 1}-${AddedDate.getDate()}`,
+    PwnCount: 1000000,
+  });
+  await collection.db.saveLastModified(1234567);
+
+  // Finally, reload the extension.
+  let addon = await AddonManager.getAddonByID("fxmonitor@mozilla.org");
+  await addon.reload();
+
+  await TestUtils.waitForCondition(() => {
+    return !Services.prefs.prefHasUserValue("extensions.fxmonitor.warnedHosts");
+  }, "Waiting for the warnedHosts pref to be cleared");
+  ok(true, "The warnedHosts pref was cleared");
+
+  // Open a tab and ensure the alert isn't shown.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+  await fxmonitorNotificationGone();
+
+  // Clean up.
+  BrowserTestUtils.removeTab(tab);
+  await collection.clear();
+  await collection.db.saveLastModified(1234567);
+  // Trigger a sync to clear.
+  await RemoteSettings(kRemoteSettingsKey).emit("sync", {
+    data: {
+      current: [],
+    },
+  });
+  await clearWarnedHosts();
+  await SpecialPowers.pushPrefEnv({
+    clear: [["extensions.fxmonitor.enabled"],
+            ["extensions.fxmonitor.firstAlertShown"]],
+  });
+});
+
+add_task(async function test_main_flow() {
+  info("Test that we show the first alert correctly for a recent breach.");
+
+  // Pre-populate the Remote Settings collection with a breach.
+  let collection = await RemoteSettings(kRemoteSettingsKey).openCollection();
+  let BreachDate = new Date();
+  let AddedDate = new Date();
+  await collection.create({
+    Domain: "example.com",
+    Name: "Example Site",
+    BreachDate: `${BreachDate.getFullYear()}-${BreachDate.getMonth() + 1}-${BreachDate.getDate()}`,
+    AddedDate: `${AddedDate.getFullYear()}-${AddedDate.getMonth() + 1}-${AddedDate.getDate()}`,
+    PwnCount: 1000000,
+  });
+  await collection.db.saveLastModified(1234567);
+
+  // Trigger a sync.
+  await RemoteSettings(kRemoteSettingsKey).emit("sync", {
+    data: {
+      current: await RemoteSettings(kRemoteSettingsKey).get(),
+    },
+  });
+
+  // Enable the extension.
+  await SpecialPowers.pushPrefEnv({
+    set: [["extensions.fxmonitor.FirefoxMonitorURL", "http://example.org"]],
+  });
+
+  // Open a tab and wait for the alert.
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com");
+  await fxmonitorNotificationShown();
+
+  // Test that dismissing works.
+  let notification = Array.prototype.find.call(PopupNotifications.panel.children,
+    elt => elt.getAttribute("popupid") == kNotificationId);
+  EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
+  await fxmonitorNotificationGone();
+
+  // Reload and make sure the alert isn't shown again.
+  let promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await promise;
+  await fxmonitorNotificationGone();
+
+  // Reset state.
+  await collection.clear();
+  await collection.db.saveLastModified(1234567);
+  await clearWarnedHosts();
+  await SpecialPowers.pushPrefEnv({
+    clear: [["extensions.fxmonitor.firstAlertShown"]],
+  });
+
+  // Reload and wait for the alert.
+  promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await promise;
+  await fxmonitorNotificationShown();
+
+  // Test that the primary button opens Firefox Monitor in a new tab.
+  notification = Array.prototype.find.call(PopupNotifications.panel.children,
+    elt => elt.getAttribute("popupid") == kNotificationId);
+  let url =
+    `http://example.org/?breach=${encodeURIComponent("Example Site")}&utm_source=firefox&utm_medium=popup`;
+  promise = BrowserTestUtils.waitForNewTab(gBrowser, url);
+  EventUtils.synthesizeMouseAtCenter(notification.button, {});
+  let newtab = await promise;
+
+  // Close the new tab and check that the alert is gone.
+  BrowserTestUtils.removeTab(newtab);
+  await fxmonitorNotificationGone();
+
+  // Reset state (but not firstAlertShown).
+  await collection.clear();
+  await collection.db.saveLastModified(1234567);
+  await clearWarnedHosts();
+
+  info("Test that we do not show the second alert for a breach added over two months ago.");
+
+  // Add a new "old" breach - added over 2 months ago.
+  AddedDate.setMonth(AddedDate.getMonth() - 3);
+  await collection.create({
+    Domain: "example.com",
+    Name: "Example Site",
+    BreachDate: `${BreachDate.getFullYear()}-${BreachDate.getMonth() + 1}-${BreachDate.getDate()}`,
+    AddedDate: `${AddedDate.getFullYear()}-${AddedDate.getMonth() + 1}-${AddedDate.getDate()}`,
+    PwnCount: 1000000,
+  });
+  await collection.db.saveLastModified(1234567);
+
+  // Trigger a sync.
+  await RemoteSettings(kRemoteSettingsKey).emit("sync", {
+    data: {
+      current: await RemoteSettings(kRemoteSettingsKey).get(),
+    },
+  });
+
+  // Check that there's no alert for the old breach.
+  promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await promise;
+  await fxmonitorNotificationGone();
+
+  // Reset state (but not firstAlertShown).
+  AddedDate.setMonth(AddedDate.getMonth() + 3);
+  await collection.clear();
+  await collection.db.saveLastModified(1234567);
+  await clearWarnedHosts();
+
+  info("Test that we do show the second alert for a recent breach.");
+
+  // Add a new "recent" breach.
+  await collection.create({
+    Domain: "example.com",
+    Name: "Example Site",
+    BreachDate: `${BreachDate.getFullYear()}-${BreachDate.getMonth() + 1}-${BreachDate.getDate()}`,
+    AddedDate: `${AddedDate.getFullYear()}-${AddedDate.getMonth() + 1}-${AddedDate.getDate()}`,
+    PwnCount: 1000000,
+  });
+  await collection.db.saveLastModified(1234567);
+
+  // Trigger a sync.
+  await RemoteSettings(kRemoteSettingsKey).emit("sync", {
+    data: {
+      current: await RemoteSettings(kRemoteSettingsKey).get(),
+    },
+  });
+
+  // Check that there's an alert for the new breach.
+  promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await promise;
+  await fxmonitorNotificationShown();
+
+  // Reset state (including firstAlertShown)
+  await collection.clear();
+  await collection.db.saveLastModified(1234567);
+  await clearWarnedHosts();
+  await SpecialPowers.pushPrefEnv({
+    clear: [["extensions.fxmonitor.firstAlertShown"]],
+  });
+
+  info("Test that we do not show the first alert for a breach added over a year ago.");
+
+  // Add a new "old" breach - added over a year ago.
+  AddedDate.setFullYear(AddedDate.getFullYear() - 2);
+  await collection.create({
+    Domain: "example.com",
+    Name: "Example Site",
+    BreachDate: `${BreachDate.getFullYear()}-${BreachDate.getMonth() + 1}-${BreachDate.getDate()}`,
+    AddedDate: `${AddedDate.getFullYear()}-${AddedDate.getMonth() + 1}-${AddedDate.getDate()}`,
+    PwnCount: 1000000,
+  });
+  await collection.db.saveLastModified(1234567);
+
+  // Trigger a sync.
+  await RemoteSettings(kRemoteSettingsKey).emit("sync", {
+    data: {
+      current: await RemoteSettings(kRemoteSettingsKey).get(),
+    },
+  });
+
+  // Check that there's no alert for the old breach.
+  promise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  tab.linkedBrowser.reload();
+  await promise;
+  await fxmonitorNotificationGone();
+
+  // Clean up.
+  BrowserTestUtils.removeTab(tab);
+  await collection.clear();
+  await collection.db.saveLastModified(1234567);
+  // Trigger a sync to clear.
+  await RemoteSettings(kRemoteSettingsKey).emit("sync", {
+    data: {
+      current: [],
+    },
+  });
+  await clearWarnedHosts();
+  await SpecialPowers.pushPrefEnv({
+    clear: [["extensions.fxmonitor.firstAlertShown"]],
+  });
+});
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -160,19 +160,19 @@
 @BINPATH@/AccessibleHandler.dll
 @BINPATH@/AccessibleMarshal.dll
 @BINPATH@/IA2Marshal.dll
 #endif
 #endif
 
 ; JavaScript components
 @RESPATH@/browser/components/BrowserComponents.manifest
-@RESPATH@/browser/components/EnterprisePolicies.js
-@RESPATH@/browser/components/EnterprisePoliciesContent.js
-@RESPATH@/browser/components/EnterprisePolicies.manifest
+@RESPATH@/components/EnterprisePolicies.js
+@RESPATH@/components/EnterprisePoliciesContent.js
+@RESPATH@/components/EnterprisePolicies.manifest
 @RESPATH@/components/toolkitsearch.manifest
 @RESPATH@/components/extensions.manifest
 #ifdef MOZ_UPDATER
 @RESPATH@/components/nsUpdateService.manifest
 #endif
 @RESPATH@/components/ProcessSingleton.manifest
 @RESPATH@/components/HandlerService.manifest
 @RESPATH@/components/HandlerService.js
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -331,16 +331,22 @@ html|input.urlbar-input {
 %include ../shared/autocomplete.inc.css
 %include ../shared/urlbar-autocomplete.inc.css
 
 :root {
   --urlbar-popup-url-color: hsl(210, 77%, 47%);
   --urlbar-popup-action-color: hsl(178, 100%, 28%);
 }
 
+/* Remove our custom border since we already get a native one that we can't
+ * seem to override. */
+#urlbar-results {
+  border-style: none;
+}
+
 /* Give an extra margin top to align the top of the awesomebar with the
  * bottom of the nav bar, OSX calculates the panel position with an missing
  * 1px - https://bugzilla.mozilla.org/show_bug.cgi?id=1406353
  */
 #PopupAutoCompleteRichResult {
   margin-top: 1px;
 }
 
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -156,17 +156,17 @@ button > hbox > label {
 
 /**
  * The first subcategory title for each category should not have margin-top.
  */
 
 .subcategory:not([hidden]) ~ .subcategory {
   margin-top: 16px;
   padding-top: 16px;
-  border-top: 1px solid rgba(12, 12, 13, 0.15);
+  border-top: 1px solid var(--in-content-border-color);
 }
 
 /* Category List */
 
 #categories {
   overflow: visible !important; /* Cancel scrollbar and do not clip overflow content when window size goes very small */
   padding: 1px; /* Adding padding around richlistitem in order to make entire keyboard focusing outline visible */
 }
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -1183,20 +1183,21 @@ def msvs_version(info):
 set_config('MSVS_VERSION', msvs_version)
 
 include('compile-checks.configure')
 include('arm.configure', when=depends(target.cpu)(lambda cpu: cpu == 'arm'))
 
 
 @depends(host,
          host_os_kernel_major_version,
+         target,
          cxx_compiler.try_run(header='#include_next <inttypes.h>'))
-def check_have_mac_10_14_sdk(host, version, value):
+def check_have_mac_10_14_sdk(host, version, target, value):
     # Only an issue on Mac OS X 10.14 (and probably above).
-    if host.kernel != 'Darwin' or version < '18' or value:
+    if host.kernel != 'Darwin' or target.kernel !='Darwin' or version < '18' or value:
         return
 
     die('System inttypes.h not found. Please try running '
          '`open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg` '
          'and following the instructions to install the necessary headers')
 
 
 @depends(have_64_bit,
--- a/devtools/client/aboutdebugging-new/index.html
+++ b/devtools/client/aboutdebugging-new/index.html
@@ -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/. -->
 <!DOCTYPE html>
 <html>
   <head>
     <meta charset="utf-8" />
     <title>Debugging</title>
+    <link rel="icon" type="image/png" href="chrome://browser/skin/developer.svg">
     <link rel="stylesheet" href="chrome://devtools/content/aboutdebugging-new/aboutdebugging.css"/>
     <script src="resource://devtools/client/aboutdebugging-new/initializer.js"></script>
   </head>
   <body>
     <div id="mount"></div>
   </body>
 </html>
--- a/devtools/client/aboutdebugging-new/src/components/App.css
+++ b/devtools/client/aboutdebugging-new/src/components/App.css
@@ -12,17 +12,17 @@
  *  +-------------+-------------------------------+
  *
  * Some of the values (font sizes, widths, etc.) are the same as
  * about:preferences, which uses the shared common.css
  */
 
 .app {
   /* from common */
-  --sidebar-width: 320px;
+  --sidebar-width: 260px;
   --app-top-padding: 70px;
   --app-bottom-padding: 40px;
   --app-left-padding: 34px;
 
   box-sizing: border-box;
   width: 100vw;
   height: 100vh;
   overflow: hidden; /* we don't want the sidebar to scroll, only the main content */
--- a/devtools/client/aboutdebugging-new/test/browser/browser.ini
+++ b/devtools/client/aboutdebugging-new/test/browser/browser.ini
@@ -60,16 +60,17 @@ skip-if = (os == 'linux' && bits == 32) 
 [browser_aboutdebugging_devtoolstoolbox_focus.js]
 [browser_aboutdebugging_devtoolstoolbox_menubar.js]
 [browser_aboutdebugging_devtoolstoolbox_performance.js]
 skip-if = os == 'linux' && e10s && (asan || debug) # Same skip-if as old perf panel test suite. Bug 1254821
 [browser_aboutdebugging_devtoolstoolbox_reload.js]
 skip-if = verify || ccov # test loads the toolbox 2 times for each panel, might timeout or OOM
 [browser_aboutdebugging_devtoolstoolbox_shortcuts.js]
 skip-if = (os == "win" && ccov) # Bug 1521349
+[browser_aboutdebugging_devtoolstoolbox_splitconsole_key.js]
 [browser_aboutdebugging_devtoolstoolbox_target_destroyed.js]
 skip-if = debug || asan # This test leaks. See bug 1529005
 [browser_aboutdebugging_devtoolstoolbox_tooltip_markupview.js]
 [browser_aboutdebugging_message_close.js]
 [browser_aboutdebugging_navigate.js]
 [browser_aboutdebugging_persist_connection.js]
 [browser_aboutdebugging_process_category.js]
 [browser_aboutdebugging_process_main.js]
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_devtoolstoolbox_focus.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_devtoolstoolbox_focus.js
@@ -48,18 +48,17 @@ add_task(async function() {
   });
   clickInspectButton(inspectionTarget, document);
   assertDevtoolsToolboxTabState(devtoolsURL);
 
   info("Close new navigator and wait until the debug target disappears");
   const onToolboxDestroyed = gDevTools.once("toolbox-destroyed");
   newNavigator.close();
   await onToolboxDestroyed;
-  await waitUntil(() =>
-    !findDebugTargetByText("about:devtools-toolbox?", document));
+  await waitUntil(() => !findDebugTargetByText("Toolbox - ", document));
 
   await removeTab(tab);
 });
 
 function clickInspectButton(inspectionTarget, doc) {
   const target = findDebugTargetByText(inspectionTarget, doc);
   const button = target.querySelector(".js-debug-target-inspect-button");
   button.click();
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_devtoolstoolbox_menubar.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_devtoolstoolbox_menubar.js
@@ -10,44 +10,59 @@ Services.scriptloader.loadSubScript(CHRO
  * Test the status of menu items when open about:devtools-toolbox.
  */
 add_task(async function() {
   info("Force all debug target panes to be expanded");
   prepareCollapsibilitiesTest();
 
   const { document, tab, window } = await openAboutDebugging();
   await selectThisFirefoxPage(document, window.AboutDebugging.store);
-  const { devtoolsTab } = await openAboutDevtoolsToolbox(document, tab, window);
+  const { devtoolsTab, devtoolsWindow } =
+    await openAboutDevtoolsToolbox(document, tab, window);
 
   info("Check whether the menu items are disabled");
   const rootDocument = devtoolsTab.ownerDocument;
   await assertMenusItems(rootDocument, false);
 
+  info("Select the inspector");
+  const toolbox = getToolbox(devtoolsWindow);
+  await toolbox.selectTool("inspector");
+
+  info("Click on the console item");
+  const onConsoleLoaded = toolbox.once("webconsole-ready");
+  const webconsoleMenuItem = rootDocument.getElementById("menuitem_webconsole");
+  webconsoleMenuItem.click();
+
+  info("Wait until about:devtools-toolbox switches to the console");
+  await onConsoleLoaded;
+
   info("Force to select about:debugging page");
   gBrowser.selectedTab = tab;
   info("Check whether the menu items are enabled");
   await assertMenusItems(rootDocument, true);
 
   await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
   await removeTab(tab);
 });
 
 async function assertMenusItems(rootDocument, shouldBeEnabled) {
+  info("Wait for the Toggle Tools menu-item hidden attribute to change");
   const menuItem = rootDocument.getElementById("menu_devToolbox");
-  // Wait for hidden attribute changed since the menu items will update asynchronously.
   await waitUntil(() => menuItem.hidden === !shouldBeEnabled);
 
+  info("Check that the state of the Toggle Tools menu-item depends on the page");
   assertMenuItem(rootDocument, "menu_devToolbox", shouldBeEnabled);
 
+  info("Check that the tools menu-items are always enabled regardless of the page");
   for (const toolDefinition of gDevTools.getToolDefinitionArray()) {
     if (!toolDefinition.inMenu) {
       continue;
     }
 
-    assertMenuItem(rootDocument, "menuitem_" + toolDefinition.id, shouldBeEnabled);
+    assertMenuItem(rootDocument, "menuitem_" + toolDefinition.id, true);
   }
 }
 
 function assertMenuItem(rootDocument, menuItemId, shouldBeEnabled) {
   const menuItem = rootDocument.getElementById(menuItemId);
   is(menuItem.hidden, !shouldBeEnabled,
      `"hidden" attribute of menu item(${ menuItemId }) should be correct`);
 }
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_devtoolstoolbox_shortcuts.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_devtoolstoolbox_shortcuts.js
@@ -10,22 +10,35 @@ Services.scriptloader.loadSubScript(CHRO
  * Test shortcut keys on about:devtools-toolbox page.
  */
 add_task(async function() {
   info("Force all debug target panes to be expanded");
   prepareCollapsibilitiesTest();
 
   const { document, tab, window } = await openAboutDebugging();
   await selectThisFirefoxPage(document, window.AboutDebugging.store);
-  const { devtoolsBrowser, devtoolsTab } =
+  const { devtoolsBrowser, devtoolsTab, devtoolsWindow } =
     await openAboutDevtoolsToolbox(document, tab, window);
 
   info("Check whether the shortcut keys which opens devtools is disabled");
   await assertShortcutKeys(devtoolsBrowser, false);
 
+  info("Switch to the inspector programmatically");
+  const toolbox = getToolbox(devtoolsWindow);
+  await toolbox.selectTool("inspector");
+
+  info("Use the Webconsole keyboard shortcut and wait for the panel to be selected");
+  const onToolReady = toolbox.once("webconsole-ready");
+  EventUtils.synthesizeKey("K", {
+    accelKey: true,
+    shiftKey: !navigator.userAgent.match(/Mac/),
+    altKey: navigator.userAgent.match(/Mac/),
+  }, devtoolsWindow);
+  await onToolReady;
+
   info("Force to select about:debugging page");
   gBrowser.selectedTab = tab;
   info("Check whether the shortcut keys which opens devtools is enabled");
   await assertShortcutKeys(tab.linkedBrowser, true);
 
   await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
   await removeTab(tab);
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_devtoolstoolbox_splitconsole_key.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/* import-globals-from helper-collapsibilities.js */
+Services.scriptloader.loadSubScript(CHROME_URL_ROOT + "helper-collapsibilities.js", this);
+
+/**
+ * Test that the split console key shortcut works on about:devtools-toolbox.
+ */
+add_task(async function() {
+  info("Force all debug target panes to be expanded");
+  prepareCollapsibilitiesTest();
+
+  const { document, tab, window } = await openAboutDebugging();
+  await selectThisFirefoxPage(document, window.AboutDebugging.store);
+  const { devtoolsTab, devtoolsWindow } =
+    await openAboutDevtoolsToolbox(document, tab, window);
+
+  // Select any tool that is not the Webconsole, since we will assert the split-console.
+  info("Select inspector tool");
+  const toolbox = getToolbox(devtoolsWindow);
+  await toolbox.selectTool("inspector");
+
+  info("Press Escape and wait for the split console to be opened");
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, devtoolsWindow);
+  await waitUntil(() => toolbox.isSplitConsoleFocused());
+  ok(true, "Split console is opened and focused");
+
+  info("Press Escape again and wait for the split console to be closed");
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, devtoolsWindow);
+  await waitUntil(() => !toolbox.isSplitConsoleFocused());
+  ok(true, "Split console is closed and no longer focused");
+
+  await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
+  await removeTab(tab);
+});
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_process_main.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_process_main.js
@@ -49,11 +49,11 @@ add_task(async function() {
   is(remoteID, `${ RUNTIME_ID }-usb`, "Correct remote runtime id");
 
   info("Remove USB runtime");
   mocks.removeUSBRuntime(RUNTIME_ID);
   mocks.emitUSBUpdate();
   await waitUntilUsbDeviceIsUnplugged(RUNTIME_DEVICE_NAME, document);
 
   await removeTab(devtoolsTab);
-  await waitUntil(() => !findDebugTargetByText("about:devtools-toolbox?", document));
+  await waitUntil(() => !findDebugTargetByText("Toolbox - ", document));
   await removeTab(tab);
 });
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_serviceworker_timeout.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_serviceworker_timeout.js
@@ -52,29 +52,30 @@ add_task(async function() {
   await onServiceWorkerRunning;
 
   const inspectButton = getInspectButton(SW_URL, document);
   ok(!!inspectButton, "Service worker target has an inspect button");
 
   info("Click on inspect and wait for the toolbox to open");
   const onToolboxReady = gDevTools.once("toolbox-ready");
   inspectButton.click();
-  const toolbox = await onToolboxReady;
+  await onToolboxReady;
 
   // Wait for more 10 times the service worker timeout to check that the toolbox prevents
   // the worker from being destroyed.
   await wait(SW_TIMEOUT * 10);
 
   // Check that the service worker is still running, even after waiting 10 times the
   // service worker timeout.
   const hasInspectButton = !!getInspectButton(SW_URL, document);
   ok(hasInspectButton, "Service worker target still has an inspect button");
 
   info("Destroy the toolbox");
-  await toolbox.destroy();
+  const devtoolsTab = gBrowser.selectedTab;
+  await closeAboutDevtoolsToolbox(document, devtoolsTab, window);
 
   // After stopping the toolbox, the service worker instance should be released and the
   // service worker registration should be displayed as stopped again.
   info("Wait until the service worker stops after closing the toolbox");
   await waitForServiceWorkerStopped(SW_URL, document);
 
   info("Unregister service worker");
   await unregisterServiceWorker(swTab);
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_thisfirefox_worker_inspection.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_thisfirefox_worker_inspection.js
@@ -53,11 +53,11 @@ add_task(async function() {
 
   info("Check whether the correct actor front will be opened in worker toolbox");
   const url = new window.URL(devtoolsWindow.location.href);
   const workerID = url.searchParams.get("id");
   is(workerID, testWorker.id,
      "Correct actor front will be opened in worker toolbox");
 
   await removeTab(devtoolsTab);
-  await waitUntil(() => !findDebugTargetByText("about:devtools-toolbox?", document));
+  await waitUntil(() => !findDebugTargetByText("Toolbox - ", document));
   await removeTab(tab);
 });
--- a/devtools/client/aboutdebugging-new/test/browser/head.js
+++ b/devtools/client/aboutdebugging-new/test/browser/head.js
@@ -114,18 +114,17 @@ async function closeAboutDevtoolsToolbox
 
   // Changing the tab will also trigger a request to list tabs, so wait until the selected
   // tab has changed to wait for requests to settle.
   info("Wait until aboutdebugging is selected");
   await waitUntil(() => gBrowser.selectedTab !== devtoolsTab);
 
   // Wait for removing about:devtools-toolbox tab info from about:debugging.
   info("Wait until about:devtools-toolbox is removed from debug targets");
-  await waitUntil(() =>
-    !findDebugTargetByText("about:devtools-toolbox?", aboutDebuggingDocument));
+  await waitUntil(() => !findDebugTargetByText("Toolbox - ", aboutDebuggingDocument));
 
   await waitForRequestsToSettle(win.AboutDebugging.store);
 }
 
 async function reloadAboutDebugging(tab) {
   info("reload about:debugging");
 
   await refreshTab(tab);
--- a/devtools/client/framework/browser-menus.js
+++ b/devtools/client/framework/browser-menus.js
@@ -76,17 +76,17 @@ function createToolMenuElements(toolDefi
   // Prevent multiple entries for the same tool.
   if (doc.getElementById(menuId)) {
     return;
   }
 
   const oncommand = (async function(id, event) {
     try {
       const window = event.target.ownerDocument.defaultView;
-      await gDevToolsBrowser.selectToolCommand(window.gBrowser, id, Cu.now());
+      await gDevToolsBrowser.selectToolCommand(window, id, Cu.now());
       sendEntryPointTelemetry(window);
     } catch (e) {
       console.error(`Exception while opening ${id}: ${e}\n${e.stack}`);
     }
   }).bind(null, id);
 
   const menuitem = createMenuItem({
     doc,
@@ -294,56 +294,8 @@ exports.addMenus = function(doc) {
  * @param {XULDocument} doc
  *        The document to which menus are to be removed.
  */
 exports.removeMenus = function(doc) {
   // We only remove top level entries. Per-tool entries are removed while
   // unregistering each tool.
   removeTopLevelItems(doc);
 };
-
-/**
- * This is used for about:devtools-toolbox and that we are hiding the main toolbox toggle
- * menu item, as well as all the tool items displayed on the menu. But we keep the
- * non-toolbox menu items such as Scratchpad, Browser Console etc.
- *
- * @param {XULDocument} doc
- * @param {boolean} isEnabled
- */
-function setDevtoolsMenuItemsEnabled(doc, isEnabled) {
-  setMenuItemEnabled(doc, "menu_devToolbox", isEnabled);
-
-  for (const toolDefinition of gDevTools.getToolDefinitionArray()) {
-    if (!toolDefinition.inMenu) {
-      continue;
-    }
-    setMenuItemEnabled(doc, "menuitem_" + toolDefinition.id, isEnabled);
-  }
-}
-
-function setMenuItemEnabled(doc, menuItemId, isEnabled) {
-  const menuItem = doc.getElementById(menuItemId);
-  if (menuItem) {
-    if (isEnabled) {
-      menuItem.removeAttribute("hidden");
-    } else {
-      menuItem.setAttribute("hidden", true);
-    }
-  }
-}
-
-/**
- * Enable all devtools menu items.
- *
- * @param {XULDocument} doc
- */
-exports.enableDevtoolsMenuItems = function(doc) {
-  setDevtoolsMenuItemsEnabled(doc, true);
-};
-
-/**
- * Disable all devtools menu items.
- *
- * @param {XULDocument} doc
- */
-exports.disableDevtoolsMenuItems = function(doc) {
-  setDevtoolsMenuItemsEnabled(doc, false);
-};
--- a/devtools/client/framework/components/DebugTargetInfo.js
+++ b/devtools/client/framework/components/DebugTargetInfo.js
@@ -26,16 +26,41 @@ class DebugTargetInfo extends PureCompon
         }).isRequired,
         targetType: PropTypes.oneOf(Object.values(DEBUG_TARGET_TYPES)).isRequired,
       }).isRequired,
       L10N: PropTypes.object.isRequired,
       toolbox: PropTypes.object.isRequired,
     };
   }
 
+  componentDidMount() {
+    this.updateTitle();
+  }
+
+  updateTitle() {
+    const { L10N, debugTargetData, toolbox } = this.props;
+    const title = toolbox.target.name;
+    const targetTypeStr = L10N.getStr(this.getAssetsForDebugTargetType().l10nId);
+
+    const { connectionType } = debugTargetData;
+    if (connectionType === CONNECTION_TYPES.THIS_FIREFOX) {
+      toolbox.doc.title = L10N.getFormatStr("toolbox.debugTargetInfo.tabTitleLocal",
+        targetTypeStr,
+        title
+      );
+    } else {
+      const connectionTypeStr = L10N.getStr(this.getAssetsForConnectionType().l10nId);
+      toolbox.doc.title = L10N.getFormatStr("toolbox.debugTargetInfo.tabTitleRemote",
+        connectionTypeStr,
+        targetTypeStr,
+        title
+      );
+    }
+  }
+
   getRuntimeText() {
     const { debugTargetData, L10N } = this.props;
     const { brandName, version } = debugTargetData.deviceDescription;
     const { connectionType } = debugTargetData;
 
     return (connectionType === CONNECTION_TYPES.THIS_FIREFOX)
       ? L10N.getFormatStr("toolbox.debugTargetInfo.runtimeLabel.thisFirefox", version)
       : L10N.getFormatStr("toolbox.debugTargetInfo.runtimeLabel", brandName, version);
@@ -50,16 +75,18 @@ class DebugTargetInfo extends PureCompon
           image: "chrome://devtools/skin/images/aboutdebugging-usb-icon.svg",
           l10nId: "toolbox.debugTargetInfo.connection.usb",
         };
       case CONNECTION_TYPES.NETWORK:
         return {
           image: "chrome://devtools/skin/images/aboutdebugging-globe-icon.svg",
           l10nId: "toolbox.debugTargetInfo.connection.network",
         };
+      default:
+        return {};
     }
   }
 
   getAssetsForDebugTargetType() {
     const { targetType } = this.props.debugTargetData;
 
     // TODO: https://bugzilla.mozilla.org/show_bug.cgi?id=1520723
     //       Show actual favicon (currently toolbox.target.activeTab.favicon
@@ -82,16 +109,18 @@ class DebugTargetInfo extends PureCompon
           image: favicon,
           l10nId: "toolbox.debugTargetInfo.targetType.tab",
         };
       case DEBUG_TARGET_TYPES.WORKER:
         return {
           image: "chrome://devtools/skin/images/debugging-workers.svg",
           l10nId: "toolbox.debugTargetInfo.targetType.worker",
         };
+      default:
+        return {};
     }
   }
 
   shallRenderConnection() {
     const { connectionType } = this.props.debugTargetData;
     const renderableTypes = [
       CONNECTION_TYPES.USB,
       CONNECTION_TYPES.NETWORK,
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -184,29 +184,38 @@ var gDevToolsBrowser = exports.gDevTools
     }
   },
 
   /**
    * This function is for the benefit of Tools:{toolId} commands,
    * triggered from the WebDeveloper menu and keyboard shortcuts.
    *
    * selectToolCommand's behavior:
+   * - if the current page is about:devtools-toolbox
+   *   we select the targeted tool
    * - if the toolbox is closed,
    *   we open the toolbox and select the tool
    * - if the toolbox is open, and the targeted tool is not selected,
    *   we select it
    * - if the toolbox is open, and the targeted tool is selected,
    *   and the host is NOT a window, we close the toolbox
    * - if the toolbox is open, and the targeted tool is selected,
    *   and the host is a window, we raise the toolbox window
+   *
+   * Used when: - registering a new tool
+   *            - new xul window, to add menu items
    */
-  // Used when: - registering a new tool
-  //            - new xul window, to add menu items
-  async selectToolCommand(gBrowser, toolId, startTime) {
-    const target = await TargetFactory.forTab(gBrowser.selectedTab);
+  async selectToolCommand(win, toolId, startTime) {
+    if (gDevToolsBrowser._isAboutDevtoolsToolbox(win)) {
+      const toolbox = gDevToolsBrowser._getAboutDevtoolsToolbox(win);
+      toolbox.selectTool(toolId, "key_shortcut");
+      return;
+    }
+
+    const target = await TargetFactory.forTab(win.gBrowser.selectedTab);
     const toolbox = gDevTools.getToolbox(target);
     const toolDefinition = gDevTools.getToolDefinition(toolId);
 
     if (toolbox &&
         (toolbox.currentToolId == toolId ||
           (toolId == "webconsole" && toolbox.splitConsole))) {
       toolbox.fireCustomKey(toolId);
 
@@ -240,26 +249,24 @@ var gDevToolsBrowser = exports.gDevTools
    * @param {Number} startTime
    *        Optional, indicates the time at which the key event fired. This is a
    *        `Cu.now()` timing.
    */
   async onKeyShortcut(window, key, startTime) {
     // Avoid to open devtools when the about:devtools-toolbox page is showing
     // on the window now.
     if (gDevToolsBrowser._isAboutDevtoolsToolbox(window) &&
-        (key.toolId ||
-         key.id === "toggleToolbox" ||
-         key.id === "toggleToolboxF12" ||
-         key.id === "inspectorMac")) {
+        (key.id === "toggleToolbox" ||
+         key.id === "toggleToolboxF12")) {
       return;
     }
 
     // If this is a toolbox's panel key shortcut, delegate to selectToolCommand
     if (key.toolId) {
-      await gDevToolsBrowser.selectToolCommand(window.gBrowser, key.toolId, startTime);
+      await gDevToolsBrowser.selectToolCommand(window, key.toolId, startTime);
       return;
     }
     // Otherwise implement all other key shortcuts individually here
     switch (key.id) {
       case "toggleToolbox":
       case "toggleToolboxF12":
         await gDevToolsBrowser.toggleToolboxCommand(window.gBrowser, startTime);
         break;
@@ -277,17 +284,17 @@ var gDevToolsBrowser = exports.gDevTools
         ResponsiveUIManager.toggle(window, window.gBrowser.selectedTab, {
           trigger: "shortcut",
         });
         break;
       case "scratchpad":
         ScratchpadManager.openScratchpad();
         break;
       case "inspectorMac":
-        await gDevToolsBrowser.selectToolCommand(window.gBrowser, "inspector", startTime);
+        await gDevToolsBrowser.selectToolCommand(window, "inspector", startTime);
         break;
     }
   },
 
   /**
    * Open a tab on "about:debugging", optionally pre-select a given tab.
    */
    // Used by browser-sets.inc, command
@@ -623,26 +630,28 @@ var gDevToolsBrowser = exports.gDevTools
   },
 
   /**
    * Update developer tools menu items and the "Toggle Tools" checkbox of XULWindow.
    *
    * @param {XULWindow} win
    */
   _updateMenuItems(win) {
-    if (gDevToolsBrowser._isAboutDevtoolsToolbox(win)) {
-      BrowserMenus.disableDevtoolsMenuItems(win.document);
-      return;
+    const menu = win.document.getElementById("menu_devToolbox");
+
+    // Hide the "Toggle Tools" menu item if we are on about:devtools-toolbox.
+    const isAboutDevtoolsToolbox = gDevToolsBrowser._isAboutDevtoolsToolbox(win);
+    if (isAboutDevtoolsToolbox) {
+      menu.setAttribute("hidden", "true");
+    } else {
+      menu.removeAttribute("hidden");
     }
 
-    BrowserMenus.enableDevtoolsMenuItems(win.document);
-
+    // Add a checkmark for the "Toggle Tools" menu item if a toolbox is already opened.
     const hasToolbox = gDevToolsBrowser.hasToolboxOpened(win);
-
-    const menu = win.document.getElementById("menu_devToolbox");
     if (hasToolbox) {
       menu.setAttribute("checked", "true");
     } else {
       menu.removeAttribute("checked");
     }
   },
 
   /**
@@ -653,16 +662,33 @@ var gDevToolsBrowser = exports.gDevTools
    *                   false: otherwise
    */
   _isAboutDevtoolsToolbox(win) {
     const currentURI = win.gBrowser.currentURI;
     return currentURI.scheme === "about" && currentURI.filePath === "devtools-toolbox";
   },
 
   /**
+   * Retrieve the Toolbox instance loaded in the current page if the page is
+   * about:devtools-toolbox, null otherwise.
+   *
+   * @param {XULWindow} win
+   *        The chrome window containing about:devtools-toolbox. Will match
+   *        toolbox.topWindow.
+   * @return {Toolbox} The toolbox instance loaded in about:devtools-toolbox
+   *
+   */
+  _getAboutDevtoolsToolbox(win) {
+    if (!gDevToolsBrowser._isAboutDevtoolsToolbox(win)) {
+      return null;
+    }
+    return gDevTools.getToolboxes().find(toolbox => toolbox.topWindow === win);
+  },
+
+  /**
    * Remove the menuitem for a tool to all open browser windows.
    *
    * @param {string} toolId
    *        id of the tool to remove
    */
   _removeToolFromWindows(toolId) {
     for (const win of gDevToolsBrowser._trackedBrowserWindows) {
       BrowserMenus.removeToolFromMenu(toolId, win.document);
--- a/devtools/client/framework/toolbox-init.js
+++ b/devtools/client/framework/toolbox-init.js
@@ -50,16 +50,20 @@ async function showErrorPage(doc, errorM
   const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
   const DebugTargetErrorPage = React.createFactory(
     require("devtools/client/framework/components/DebugTargetErrorPage"));
   const { LocalizationHelper } = browserRequire("devtools/shared/l10n");
   const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
 
   // mount the React component into our XUL container once the DOM is ready
   await onLoad;
+
+  // Update the tab title.
+  document.title = L10N.getStr("toolbox.debugTargetInfo.tabTitleError");
+
   const mountEl = doc.querySelector("#toolbox-error-mount");
   const element = DebugTargetErrorPage({
     errorMessage,
     L10N,
   });
   ReactDOM.render(element, mountEl);
 
   // make sure we unmount the component when the page is destroyed
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -1096,19 +1096,20 @@ Toolbox.prototype = {
     EventEmitter.decorate(button);
 
     return button;
   },
 
   _splitConsoleOnKeypress: function(e) {
     if (e.keyCode === KeyCodes.DOM_VK_ESCAPE) {
       this.toggleSplitConsole();
-      // If the debugger is paused, don't let the ESC key stop any pending
-      // navigation.
-      if (this._threadClient.state == "paused") {
+      // If the debugger is paused, don't let the ESC key stop any pending navigation.
+      // If the host is page, don't let the ESC stop the load of the webconsole frame.
+      if (this._threadClient.state == "paused" ||
+          this.hostType === Toolbox.HostType.PAGE) {
         e.preventDefault();
       }
     }
   },
 
   /**
    * Add a shortcut key that should work when a split console
    * has focus to the toolbox.
--- a/devtools/client/framework/toolbox.xul
+++ b/devtools/client/framework/toolbox.xul
@@ -12,16 +12,17 @@
 %toolboxDTD;
 <!ENTITY % globalKeysDTD SYSTEM "chrome://global/locale/globalKeys.dtd">
 %globalKeysDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml">
 
+  <html:link href="chrome://browser/skin/window.svg" rel="shortcut icon"/>
   <script src="chrome://devtools/content/shared/theme-switching.js"/>
   <script src="chrome://global/content/viewSourceUtils.js"/>
 
   <script src="chrome://global/content/globalOverlay.js"/>
   <script src="chrome://devtools/content/framework/toolbox-init.js"/>
 
   <vbox id="toolbox-container" flex="1">
     <div xmlns="http://www.w3.org/1999/xhtml" id="toolbox-error-mount"/>
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -1,25 +1,28 @@
 /* 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/. */
 
 "use strict";
 
+const Services = require("Services");
 const promise = require("promise");
 const Rule = require("devtools/client/inspector/rules/models/rule");
 const UserProperties = require("devtools/client/inspector/rules/models/user-properties");
 const { ELEMENT_STYLE } = require("devtools/shared/specs/styles");
 
 loader.lazyRequireGetter(this, "promiseWarn", "devtools/client/inspector/shared/utils", true);
 loader.lazyRequireGetter(this, "parseDeclarations", "devtools/shared/css/parsing-utils", true);
 loader.lazyRequireGetter(this, "parseNamedDeclarations", "devtools/shared/css/parsing-utils", true);
 loader.lazyRequireGetter(this, "parseSingleValue", "devtools/shared/css/parsing-utils", true);
 loader.lazyRequireGetter(this, "isCssVariable", "devtools/shared/fronts/css-properties", true);
 
+const PREF_INACTIVE_CSS_ENABLED = "devtools.inspector.inactive.css.enabled";
+
 /**
  * ElementStyle is responsible for the following:
  *   Keeps track of which properties are overridden.
  *   Maintains a list of Rule objects for a given element.
  */
 class ElementStyle {
   /**
    * @param  {Element} element
@@ -60,16 +63,23 @@ class ElementStyle {
 
     if (this.ruleView.isNewRulesView) {
       this.pageStyle.on("stylesheet-updated", this.onRefresh);
       this.ruleView.inspector.styleChangeTracker.on("style-changed", this.onRefresh);
       this.ruleView.selection.on("pseudoclass", this.onRefresh);
     }
   }
 
+  get unusedCssEnabled() {
+    if (!this._unusedCssEnabled) {
+      this._unusedCssEnabled = Services.prefs.getBoolPref(PREF_INACTIVE_CSS_ENABLED);
+    }
+    return this._unusedCssEnabled;
+  }
+
   destroy() {
     if (this.destroyed) {
       return;
     }
 
     this.destroyed = true;
 
     for (const rule of this.rules) {
@@ -120,17 +130,17 @@ class ElementStyle {
 
       this.rules = [];
 
       for (const entry of entries) {
         this._maybeAddRule(entry, existingRules);
       }
 
       // Mark overridden computed styles.
-      this.markOverriddenAll();
+      this.onRuleUpdated();
 
       this._sortRulesForPseudoElement();
 
       if (this.ruleView.isNewRulesView) {
         this.subscribeRulesToLocationChange();
       }
 
       // We're done with the previous list of rules.
@@ -237,36 +247,38 @@ class ElementStyle {
       return false;
     }
 
     this.rules.push(rule);
     return true;
   }
 
   /**
-   * Calls markOverridden with all supported pseudo elements
+   * Calls updateDeclarations with all supported pseudo elements
    */
-  markOverriddenAll() {
+  onRuleUpdated() {
     this.variables.clear();
-    this.markOverridden();
+    this.updateDeclarations();
 
     for (const pseudo of this.cssProperties.pseudoElements) {
-      this.markOverridden(pseudo);
+      this.updateDeclarations(pseudo);
     }
   }
 
   /**
-   * Mark the properties listed in this.rules for a given pseudo element
-   * with an overridden flag if an earlier property overrides it.
+   * Mark the declarations for a given pseudo element with an overridden flag if
+   * an earlier property overrides it and update the editor to show it in the
+   * UI. If there is any inactive CSS we also update the editors state to show
+   * the inactive CSS icon.
    *
    * @param  {String} pseudo
    *         Which pseudo element to flag as overridden.
    *         Empty string or undefined will default to no pseudo element.
    */
-  markOverridden(pseudo = "") {
+  updateDeclarations(pseudo = "") {
     // Gather all the text properties applied by these rules, ordered
     // from more- to less-specific. Text properties from keyframes rule are
     // excluded from being marked as overridden since a number of criteria such
     // as time, and animation overlay are required to be check in order to
     // determine if the property is overridden.
     const textProps = [];
     for (const rule of this.rules) {
       if ((rule.matchedSelectors.length > 0 ||
@@ -340,26 +352,32 @@ class ElementStyle {
         taken[computedProp.name] = computedProp;
 
         if (isCssVariable(computedProp.name)) {
           this.variables.set(computedProp.name, computedProp.value);
         }
       }
     }
 
-    // For each TextProperty, mark it overridden if all of its
-    // computed properties are marked overridden. Update the text
-    // property's associated editor, if any. This will clear the
-    // _overriddenDirty state on all computed properties.
+    // For each TextProperty, mark it overridden if all of its computed
+    // properties are marked overridden. Update the text property's associated
+    // editor, if any. This will clear the _overriddenDirty state on all
+    // computed properties. For each editor we also show or hide the inactive
+    // CSS icon as needed.
     for (const textProp of textProps) {
       // _updatePropertyOverridden will return true if the
       // overridden state has changed for the text property.
       if (this._updatePropertyOverridden(textProp)) {
         textProp.updateEditor();
       }
+
+      // For each editor show or hide the inactive CSS icon as needed.
+      if (textProp.editor && this.unusedCssEnabled) {
+        textProp.editor.updatePropertyState();
+      }
     }
   }
 
   /**
    * Adds a new declaration to the rule.
    *
    * @param  {String} ruleId
    *         The id of the Rule to be modified.
@@ -576,30 +594,30 @@ class ElementStyle {
 
       // Remove the old rule and insert the new rule according to where it appears
       // in the list of applied styles.
       this.rules.splice(oldIndex, 1);
       // If the selector no longer matches, then we leave the rule in
       // the same relative position.
       this.rules.splice(newIndex === -1 ? oldIndex : newIndex, 0, newRule);
 
-      // Mark any properties that are overridden according to the new list of rules.
-      this.markOverriddenAll();
+      // Recompute, mark and update the UI for any properties that are
+      // overridden or contain inactive CSS according to the new list of rules.
+      this.onRuleUpdated();
 
       // In order to keep the new rule in place of the old in the rules view, we need
       // to remove the rule again if the rule was inserted to its new index according
       // to the list of applied styles.
       // Note: you might think we would replicate the list-modification logic above,
       // but that is complicated due to the way the UI installs pseudo-element rules
       // and the like.
       if (newIndex !== -1) {
         this.rules.splice(newIndex, 1);
         this.rules.splice(oldIndex, 0, newRule);
       }
-
       this._changed();
     } catch (e) {
       console.error(e);
     }
   }
 
   /**
    * Subscribes all the rules to location changes.
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -303,17 +303,17 @@ class Rule {
   }
 
   /**
    * Helper function for applyProperties that is called when the actor
    * does not support as-authored styles.  Store disabled properties
    * in the element style's store.
    */
   _applyPropertiesNoAuthored(modifications) {
-    this.elementStyle.markOverriddenAll();
+    this.elementStyle.onRuleUpdated();
 
     const disabledProps = [];
 
     for (const prop of this.textProps) {
       if (prop.invisible) {
         continue;
       }
       if (!prop.enabled) {
@@ -414,17 +414,17 @@ class Rule {
           const modifications = this.domRule.startModifyingProperties(
             this.cssProperties);
           modifier(modifications);
           if (this.domRule.canSetRuleText) {
             return this._applyPropertiesAuthored(modifications);
           }
           return this._applyPropertiesNoAuthored(modifications);
         }).then(() => {
-          this.elementStyle.markOverriddenAll();
+          this.elementStyle.onRuleUpdated();
 
           if (resultPromise === this._applyingModifications) {
             this._applyingModifications = null;
             this.elementStyle._changed();
           }
         }).catch(promiseWarn);
 
     this._applyingModifications = resultPromise;
--- a/devtools/client/inspector/rules/models/text-property.js
+++ b/devtools/client/inspector/rules/models/text-property.js
@@ -227,16 +227,29 @@ class TextProperty {
     // true.
     if (!this.rule.domRule.declarations[selfIndex]) {
       return true;
     }
 
     return this.rule.domRule.declarations[selfIndex].isValid;
   }
 
+  isUsed() {
+    const selfIndex = this.rule.textProps.indexOf(this);
+    const declarations = this.rule.domRule.declarations;
+
+    // StyleRuleActor's declarations may have a isUsed flag (if the server is the right
+    // version). Just return true if the information is missing.
+    if (!declarations || !declarations[selfIndex] || !declarations[selfIndex].isUsed) {
+      return { used: true };
+    }
+
+    return declarations[selfIndex].isUsed;
+  }
+
   /**
    * Validate the name of this property.
    *
    * @return {Boolean} true if the property name is valid, false otherwise.
    */
   isNameValid() {
     const selfIndex = this.rule.textProps.indexOf(this);
 
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -190,16 +190,18 @@ skip-if = (os == "win" && debug) # bug 9
 [browser_rules_grid-toggle_03.js]
 [browser_rules_grid-toggle_04.js]
 [browser_rules_grid-toggle_05.js]
 [browser_rules_gridline-names-autocomplete.js]
 [browser_rules_guessIndentation.js]
 [browser_rules_highlight-element-rule.js]
 [browser_rules_highlight-property.js]
 [browser_rules_highlight-used-fonts.js]
+[browser_rules_inactive_css_flexbox.js]
+[browser_rules_inactive_css_grid.js]
 [browser_rules_inherited-properties_01.js]
 [browser_rules_inherited-properties_02.js]
 [browser_rules_inherited-properties_03.js]
 [browser_rules_inherited-properties_04.js]
 [browser_rules_inline-source-map.js]
 [browser_rules_inline-style-order.js]
 [browser_rules_invalid.js]
 [browser_rules_invalid-source-map.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_inactive_css_flexbox.js
@@ -0,0 +1,170 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test inactive flex properties.
+
+const TEST_URI = `
+<head>
+  <style>
+    #container {
+      width: 200px;
+      height: 100px;
+      border: 1px solid #000;
+      align-content: space-between;
+      order: 1;
+    }
+
+    .flex-item {
+      flex-basis: auto;
+      flex-grow: 1;
+      flex-shrink: 1;
+      flex-direction: row;
+    }
+
+    #self-aligned {
+      align-self: stretch;
+    }
+  </style>
+</head>
+<body>
+    <h1>browser_rules_inactive_css_flexbox.js</h1>
+    <div id="container" style="display:flex">
+      <div class="flex-item item-1" style="order:1">1</div>
+      <div class="flex-item item-2" style="order:2">2</div>
+      <div class="flex-item item-3" style="order:3">3</div>
+    </div>
+    <div id="self-aligned"></div>
+</body>`;
+
+const BEFORE = [
+  {
+    selector: "#self-aligned",
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.grid.or.flex.item",
+        declaration: {
+          "align-self": "stretch",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+  {
+    selector: ".item-2",
+    activeDeclarations: [
+      {
+        declarations: {
+          "order": "2",
+        },
+        ruleIndex: 0,
+      },
+      {
+        declarations: {
+          "flex-basis": "auto",
+          "flex-grow": "1",
+          "flex-shrink": "1",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.flex.container",
+        declaration: {
+          "flex-direction": "row",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+  {
+    selector: "#container",
+    activeDeclarations: [
+      {
+        declarations: {
+          "display": "flex",
+        },
+        ruleIndex: 0,
+      },
+      {
+        declarations: {
+          width: "200px",
+          height: "100px",
+          border: "1px solid #000",
+          "align-content": "space-between",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.flex.item",
+        declaration: {
+          "order": "1",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    waitFor: "inspector-updated",
+  },
+];
+
+const AFTER = [
+  {
+    selector: ".item-2",
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.flex.item",
+        declaration: {
+          "order": "2",
+        },
+        ruleIndex: 0,
+      },
+      {
+        l10n: "rule.inactive.css.not.flex.item",
+        declaration: {
+          "flex-basis": "auto",
+        },
+        ruleIndex: 1,
+      },
+      {
+        l10n: "rule.inactive.css.not.flex.item",
+        declaration: {
+          "flex-grow": "1",
+        },
+        ruleIndex: 1,
+      },
+      {
+        l10n: "rule.inactive.css.not.flex.item",
+        declaration: {
+          "flex-shrink": "1",
+        },
+        ruleIndex: 1,
+      },
+      {
+        l10n: "rule.inactive.css.not.flex.container",
+        declaration: {
+          "flex-direction": "row",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+];
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const {inspector, view} = await openRuleView();
+
+  await runInactiveCSSTests(view, inspector, BEFORE);
+
+  // Toggle `display:flex` to disabled.
+  await toggleDeclaration(inspector, view, 0, {
+    display: "flex",
+  });
+  await view.once("ruleview-refreshed");
+  await runInactiveCSSTests(view, inspector, AFTER);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_inactive_css_grid.js
@@ -0,0 +1,170 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test inactive grid properties.
+
+const TEST_URI = `
+<head>
+  <style>
+    #container {
+      width: 200px;
+      height: 100px;
+      border: 1px solid #000;
+      column-gap: 10px;
+      row-gap: 10px;
+      align-self: start;
+    }
+
+    .item-1 {
+      grid-column-start: 1;
+      grid-column-end: 3;
+      grid-row-start: 1;
+      grid-row-end: auto;
+      flex-direction: row
+    }
+
+    #self-aligned {
+      align-self: stretch;
+    }
+  </style>
+</head>
+<body>
+    <h1>browser_rules_inactive_css_grid.js</h1>
+    <div id="container" style="display:grid">
+      <div class="grid-item item-1">1</div>
+      <div class="grid-item item-2">2</div>
+      <div class="grid-item item-3">3</div>
+      <div class="grid-item item-4">4</div>
+    </div>
+    <div id="self-aligned"></div>
+</body>`;
+
+const BEFORE = [
+  {
+    selector: "#self-aligned",
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.grid.or.flex.item",
+        declaration: {
+          "align-self": "stretch",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+  {
+    selector: ".item-1",
+    activeDeclarations: [
+      {
+        declarations: {
+          "grid-column-start": "1",
+          "grid-column-end": "3",
+          "grid-row-start": "1",
+          "grid-row-end": "auto",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.flex.container",
+        declaration: {
+          "flex-direction": "row",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+  {
+    selector: "#container",
+    activeDeclarations: [
+      {
+        declarations: {
+          display: "grid",
+        },
+        ruleIndex: 0,
+      },
+      {
+        declarations: {
+          width: "200px",
+          height: "100px",
+          border: "1px solid #000",
+          "column-gap": "10px",
+          "row-gap": "10px",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.grid.or.flex.item",
+        declaration: {
+          "align-self": "start",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    waitFor: "inspector-updated",
+  },
+];
+
+const AFTER = [
+  {
+    activeDeclarations: [
+      {
+        declarations: {
+          display: "grid",
+        },
+        ruleIndex: 0,
+      },
+      {
+        declarations: {
+          width: "200px",
+          height: "100px",
+          border: "1px solid #000",
+        },
+        ruleIndex: 1,
+      },
+    ],
+    inactiveDeclarations: [
+      {
+        l10n: "rule.inactive.css.not.grid.container",
+        declaration: {
+          "column-gap": "10px",
+        },
+        ruleIndex: 1,
+      },
+      {
+        l10n: "rule.inactive.css.not.grid.container",
+        declaration: {
+          "row-gap": "10px",
+        },
+        ruleIndex: 1,
+      },
+      {
+        l10n: "rule.inactive.css.not.grid.or.flex.item",
+        declaration: {
+          "align-self": "start",
+        },
+        ruleIndex: 1,
+      },
+    ],
+  },
+];
+
+add_task(async function() {
+  await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  const {inspector, view} = await openRuleView();
+
+  await runInactiveCSSTests(view, inspector, BEFORE);
+
+  // Toggle `display:grid` to disabled.
+  await toggleDeclaration(inspector, view, 0, {
+    display: "grid",
+  });
+  await view.once("ruleview-refreshed");
+  await runInactiveCSSTests(view, inspector, AFTER);
+});
--- a/devtools/client/inspector/rules/test/head.js
+++ b/devtools/client/inspector/rules/test/head.js
@@ -563,17 +563,17 @@ async function toggleClassPanelCheckBox(
   const onMutation = view.inspector.once("markupmutation");
   checkBox.click();
   info("Waiting for a markupmutation as a result of toggling this class");
   await onMutation;
 }
 
 /**
  * Verify the content of the class-panel.
- * @param {CssRuleView} view The rule-view isntance
+ * @param {CssRuleView} view The rule-view instance
  * @param {Array} classes The list of expected classes. Each item in this array is an
  * object with the following properties: {name: {String}, state: {Boolean}}
  */
 function checkClassPanelContent(view, classes) {
   const checkBoxNodeList = view.classPanel.querySelectorAll("[type=checkbox]");
   is(checkBoxNodeList.length, classes.length,
      "The panel contains the expected number of checkboxes");
 
@@ -601,8 +601,209 @@ async function openEyedropper(view, swat
 
   const dropperButton = tooltip.container.querySelector("#eyedropper-button");
 
   info("Click on the eyedropper icon");
   const onOpened = tooltip.once("eyedropper-opened");
   dropperButton.click();
   await onOpened;
 }
+
+/**
+ * Gets a set of declarations for a rule index.
+ *
+ * @param {ruleView} view
+ *        The rule-view instance.
+ * @param {Number} ruleIndex
+ *        The index we expect the rule to have in the rule-view.
+ *
+ * @returns A map containing stringified property declarations e.g.
+ *          [
+ *            {
+ *              "color:red":
+ *                {
+ *                  propertyName: "color",
+ *                  propertyValue: "red",
+ *                  warnings: "This won't work",
+ *                  used: true,
+ *                }
+ *            },
+ *            ...
+ *          ]
+ */
+function getPropertiesForRuleIndex(view, ruleIndex) {
+  const declaration = new Map();
+  const ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+
+  for (const currProp of ruleEditor.rule.textProps) {
+    const icon = currProp.editor.unusedState;
+
+    declaration.set(`${currProp.name}:${currProp.value}`, {
+      propertyName: currProp.name,
+      propertyValue: currProp.value,
+      warnings: icon.title ? icon.title.split("\n") : [],
+      used: !currProp.editor.element.classList.contains("unused"),
+    });
+  }
+
+  return declaration;
+}
+
+/**
+ * Toggle a declaration disabled or enabled.
+ *
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox.
+ * @param {ruleView} view
+ *        The rule-view instance
+ * @param {Number} ruleIndex
+ *        The index of the CSS rule where we can find the declaration to be
+ *        toggled.
+ * @param {Object} declaration
+ *        An object representing the declaration e.g. { color: "red" }.
+ */
+async function toggleDeclaration(inspector, view, ruleIndex, declaration) {
+  const ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+  const [[ name, value ]] = Object.entries(declaration);
+
+  let textProp = null;
+  for (const currProp of ruleEditor.rule.textProps) {
+    if (currProp.name === name && currProp.value === value) {
+      textProp = currProp;
+      break;
+    }
+  }
+
+  const dec = `${name}:${value}`;
+  ok(textProp, `Declaration "${dec}" found`);
+
+  const newStatus = textProp.enabled ? "disabled" : "enabled";
+  info(`Toggling declaration "${dec}" of rule ${ruleIndex} to ${newStatus}`);
+
+  await togglePropStatus(view, textProp);
+}
+
+/**
+ * Check that a declaration is marked inactive and that it has the expected
+ * warning.
+ *
+ * @param {ruleView} view
+ *        The rule-view instance.
+ * @param {Number} ruleIndex
+ *        The index we expect the rule to have in the rule-view.
+ * @param {Object} declaration
+ *        An object representing the declaration e.g. { color: "red" }.
+ * @param {String} warningL10nString
+ *        l10n string representing an expected warning.
+ */
+function checkDeclarationIsInactive(view, ruleIndex, declaration, warningL10nString) {
+  const declarations = getPropertiesForRuleIndex(view, ruleIndex);
+  const [[ name, value ]] = Object.entries(declaration);
+  const dec = `${name}:${value}`;
+  const { used, warnings } = declarations.get(dec);
+
+  ok(!used, `"${dec}" is inactive`);
+  is(warnings.length, 1, `"${dec}" has a warning`);
+
+  const warning = INSPECTOR_L10N.getFormatStr(warningL10nString, name);
+  is(warnings[0], warning, `The warning on "${dec}" is correct`);
+}
+
+/**
+ * Check that a declaration is marked active.
+ *
+ * @param {ruleView} view
+ *        The rule-view instance.
+ * @param {Number} ruleIndex
+ *        The index we expect the rule to have in the rule-view.
+ * @param {Object} declaration
+ *        An object representing the declaration e.g. { color: "red" }.
+ */
+function checkDeclarationIsActive(view, ruleIndex, declaration) {
+  const declarations = getPropertiesForRuleIndex(view, ruleIndex);
+  const [[ name, value ]] = Object.entries(declaration);
+  const dec = `${name}:${value}`;
+  const { used, warnings } = declarations.get(dec);
+
+  ok(used, `${dec} is active`);
+  is(warnings.length, 0, `${dec} has no warnings`);
+}
+
+/**
+ * Inactive CSS test runner.
+ *
+ * @param {ruleView} view
+ *        The rule-view instance.
+ * @param {InspectorPanel} inspector
+ *        The instance of InspectorPanel currently loaded in the toolbox.
+ * @param {Array} tests
+ *        An array of test object for this method to consume e.g.
+ *          [
+ *            {
+ *              selector: "#flex-item",
+ *              activeDeclarations: [
+ *                {
+ *                  declarations: {
+ *                    "order": "2",
+ *                  },
+ *                  ruleIndex: 0,
+ *                },
+ *                {
+ *                  declarations: {
+ *                    "flex-basis": "auto",
+ *                    "flex-grow": "1",
+ *                    "flex-shrink": "1",
+ *                  },
+ *                  ruleIndex: 1,
+ *                },
+ *              ],
+ *              inactiveDeclarations: [
+ *                {
+ *                  l10n: "rule.inactive.css.not.flex.container",
+ *                  declaration: {
+ *                    "flex-direction": "row",
+ *                  },
+ *                  ruleIndex: 1,
+ *                },
+ *              ],
+ *              waitFor: "markupmutation",
+ *            },
+ *            ...
+ *          ]
+ */
+async function runInactiveCSSTests(view, inspector, tests) {
+  for (const test of tests) {
+    let event = null;
+
+    if (test.waitFor) {
+      event = inspector.once(test.waitFor);
+    }
+
+    if (test.selector) {
+      await selectNode(test.selector, inspector);
+    }
+
+    if (test.waitFor) {
+      await event;
+    }
+
+    if (test.activeDeclarations) {
+      // Check whether declarations are marked as used.
+      for (const activeDeclarations of test.activeDeclarations) {
+        for (const [name, value] of Object.entries(activeDeclarations.declarations)) {
+          checkDeclarationIsActive(view, activeDeclarations.ruleIndex, {
+            [name]: value,
+          });
+        }
+      }
+    }
+
+    if (test.inactiveDeclarations) {
+      for (const inactiveDeclaration of test.inactiveDeclarations) {
+        // Check that declaration is unused and has a warning.
+        checkDeclarationIsInactive(view,
+                                   inactiveDeclaration.ruleIndex,
+                                   inactiveDeclaration.declaration,
+                                   inactiveDeclaration.l10n);
+      }
+    }
+  }
+}
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -696,17 +696,17 @@ RuleEditor.prototype = {
       if (newRuleIndex === -1) {
         newRuleIndex = oldIndex;
       }
 
       // Remove the old rule and insert the new rule.
       rules.splice(oldIndex, 1);
       rules.splice(newRuleIndex, 0, newRule);
       elementStyle._changed();
-      elementStyle.markOverriddenAll();
+      elementStyle.onRuleUpdated();
 
       // We install the new editor in place of the old -- you might
       // think we would replicate the list-modification logic above,
       // but that is complicated due to the way the UI installs
       // pseudo-element rules and the like.
       this.element.parentNode.replaceChild(editor.element, this.element);
 
       // Remove highlight for modified selector
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -177,16 +177,21 @@ TextPropertyEditor.prototype = {
     appendText(this.valueContainer, ";");
 
     this.warning = createChild(this.container, "div", {
       class: "ruleview-warning",
       hidden: "",
       title: l10n("rule.warning.title"),
     });
 
+    this.unusedState = createChild(this.container, "div", {
+      class: "ruleview-unused-warning",
+      hidden: "",
+    });
+
     // Filter button that filters for the current property name and is
     // displayed when the property is overridden by another rule.
     this.filterProperty = createChild(this.container, "div", {
       class: "ruleview-overridden-rule-filter",
       hidden: "",
       title: l10n("rule.filterProperty.title"),
     });
 
@@ -591,18 +596,18 @@ TextPropertyEditor.prototype = {
     // - all of the computed properties have defined values. In case the current property
     //   value contains CSS variables, then the computed properties will be missing and we
     //   want to avoid showing them.
     return this.prop.computed.some(c => c.name !== this.prop.name) &&
            !this.prop.computed.every(c => !c.value);
   },
 
   /**
-   * Update the visibility of the enable checkbox, the warning indicator and
-   * the filter property, as well as the overridden state of the property.
+   * Update the visibility of the enable checkbox, the warning indicator, the used
+   * indicator and the filter property, as well as the overridden state of the property.
    */
   updatePropertyState: function() {
     if (this.prop.enabled) {
       this.enable.style.removeProperty("visibility");
       this.enable.setAttribute("checked", "");
     } else {
       this.enable.style.visibility = "visible";
       this.enable.removeAttribute("checked");
@@ -623,16 +628,27 @@ TextPropertyEditor.prototype = {
 
     if (!this.editing &&
         (this.prop.overridden || !this.prop.enabled ||
          !this.prop.isKnownProperty)) {
       this.element.classList.add("ruleview-overridden");
     } else {
       this.element.classList.remove("ruleview-overridden");
     }
+
+    const { used, reasons } = this.prop.isUsed();
+
+    if (this.editing || this.prop.overridden || !this.prop.enabled || used) {
+      this.element.classList.remove("unused");
+      this.unusedState.hidden = true;
+    } else {
+      this.element.classList.add("unused");
+      this.unusedState.title = reasons.join("\n");
+      this.unusedState.hidden = false;
+    }
   },
 
   /**
    * Update the indicator for computed styles. The computed styles themselves
    * are populated on demand, when they become visible.
    */
   _updateComputed: function() {
     this.computed.innerHTML = "";
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -770,30 +770,30 @@ async function assertTooltipShownOnHover
  * @param {CssRuleView|ComputedView|...} view
  *        The instance of an inspector panel
  * @param {DOMElement} target
  *        The DOM Element on which a tooltip should appear
  *
  * @return a promise that resolves with the tooltip object
  */
 async function assertShowPreviewTooltip(view, target) {
+  const name = "previewTooltip";
+
+  // Get the tooltip. If it does not exist one will be created.
+  const tooltip = view.tooltips.getTooltip(name);
+  ok(tooltip, `Tooltip '${name}' has been instantiated`);
+
+  const shown = tooltip.once("shown");
   const mouseEvent = new target.ownerDocument.defaultView.MouseEvent("mousemove", {
     bubbles: true,
   });
   target.dispatchEvent(mouseEvent);
 
-  const name = "previewTooltip";
-  ok(view.tooltips._instances.has(name),
-    `Tooltip '${name}' has been instantiated`);
-  const tooltip = view.tooltips.getTooltip(name);
-
-  if (!tooltip.isVisible()) {
-    info("Waiting for tooltip to be shown");
-    await tooltip.once("shown");
-  }
+  info("Waiting for tooltip to be shown");
+  await shown;
 
   ok(tooltip.isVisible(), `The tooltip '${name}' is visible`);
 
   return tooltip;
 }
 
 /**
  * Given a `tooltip` instance, fake a mouse event on `target` DOM element
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -92,16 +92,17 @@ devtools.jar:
     skin/images/copy.svg (themes/images/copy.svg)
     skin/images/animation-fast-track.svg (themes/images/animation-fast-track.svg)
     skin/images/performance-details-waterfall.svg (themes/images/performance-details-waterfall.svg)
     skin/images/performance-details-call-tree.svg (themes/images/performance-details-call-tree.svg)
     skin/images/performance-details-flamegraph.svg (themes/images/performance-details-flamegraph.svg)
     skin/breadcrumbs.css (themes/breadcrumbs.css)
     skin/chart.css (themes/chart.css)
     skin/widgets.css (themes/widgets.css)
+    skin/images/alerticon-unused.svg (themes/images/alerticon-unused.svg)
     skin/rules.css (themes/rules.css)
     skin/images/command-paintflashing.svg (themes/images/command-paintflashing.svg)
     skin/images/command-screenshot.svg (themes/images/command-screenshot.svg)
     skin/images/command-responsivemode.svg (themes/images/command-responsivemode.svg)
     skin/images/command-replay.svg (themes/images/command-replay.svg)
     skin/images/command-pick.svg (themes/images/command-pick.svg)
     skin/images/command-pick-accessibility.svg (themes/images/command-pick-accessibility.svg)
     skin/images/command-frames.svg (themes/images/command-frames.svg)
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -483,8 +483,38 @@ markupView.scrollableBadge.label=scroll
 
 # LOCALIZATION NOTE (markupView.scrollableBadge.tooltip): This is the tooltip that is displayed
 # when hovering over badges next to scrollable elements in the inspector.
 markupView.scrollableBadge.tooltip=This element has scrollable overflow.
 
 # LOCALIZATION NOTE (rulePreviewTooltip.noAssociatedRule): This is the text displayed inside
 # the RulePreviewTooltip when a rule cannot be found for a CSS property declaration.
 rulePreviewTooltip.noAssociatedRule=No associated rule
+
+# LOCALIZATION NOTE (rule.inactive.css.not.flex.container): These properties
+# contain the text displayed inside the Rule View’s Inactive CSS Tooltip when a
+# property is not active. %S will be replaced with a property name.
+rule.inactive.css.not.flex.container=“%S” has no effect on this element since it’s not a flex container (try adding “display:flex” or “display:inline-flex”)
+
+# LOCALIZATION NOTE (rule.inactive.css.not.flex.item): These properties
+# contain the text displayed inside the Rule View’s Inactive CSS Tooltip when a
+# property is not active. %S will be replaced with a property name.
+rule.inactive.css.not.flex.item=“%S” has no effect on this element since it’s not a flex item (try adding “display:flex” or “display:inline-flex” to the item’s parent)
+
+# LOCALIZATION NOTE (rule.inactive.css.not.grid.container): These properties
+# contain the text displayed inside the Rule View’s Inactive CSS Tooltip when a
+# property is not active. %S will be replaced with a property name.
+rule.inactive.css.not.grid.container=“%S” has no effect on this element since it’s not a grid container (try adding “display:grid” or “display:inline-grid”)
+
+# LOCALIZATION NOTE (rule.inactive.css.not.grid.item): These properties
+# contain the text displayed inside the Rule View’s Inactive CSS Tooltip when a
+# property is not active. %S will be replaced with a property name.
+rule.inactive.css.not.grid.item=“%S” has no effect on this element since it’s not a grid item (try adding “display:grid” or “display:inline-grid” to the item’s parent)
+
+# LOCALIZATION NOTE (rule.inactive.css.not.grid.or.flex.item): These properties
+# contain the text displayed inside the Rule View’s Inactive CSS Tooltip when a
+# property is not active. %S will be replaced with a property name.
+rule.inactive.css.not.grid.or.flex.item=“%S” has no effect on this element since it’s not a grid or flex item (try adding “display:grid”, “display:flex”, “display:inline-grid” or “display:inline-flex” to the item’s parent)
+
+# LOCALIZATION NOTE (rule.inactive.css.not.grid.or.flex.container): These properties
+# contain the text displayed inside the Rule View’s Inactive CSS Tooltip when a
+# property is not active. %S will be replaced with a property name.
+rule.inactive.css.not.grid.or.flex.container=“%S” has no effect on this element since it’s neither a flex container nor a grid container (try adding “display:grid” or “display:flex”)
--- a/devtools/client/locales/en-US/toolbox.properties
+++ b/devtools/client/locales/en-US/toolbox.properties
@@ -225,16 +225,34 @@ toolbox.debugTargetInfo.runtimeLabel.thi
 # as a toolbox header in about:devtools-toolbox. about:devtools-toolbox is used for
 # instance when inspecting tabs in about:debugging.
 # e.g. Mozilla Fennec on Pixel 2 (65.0a1)
 # The name of runtime: %1$S
 # The version of runtime: %2$S
 # The device name of runtime: %3$S
 toolbox.debugTargetInfo.runtimeLabelWithDeviceName=%1$S on %3$S (%2$S)
 
+# LOCALIZATION NOTE (toolbox.debugTargetInfo.tabTitleRemote):
+# Used as the tab title for about:devtools-toolbox when connected to a remote target.
+# The connection type (see toolbox.debugTargetInfo.connection.*): %1$S
+# The target type (see toolbox.debugTargetInfo.targetType.*): %2$S
+# The target name (retrieved from DevTools, eg the extension's name): %3$S
+toolbox.debugTargetInfo.tabTitleRemote=Toolbox (%1$S) - %2$S / %3$S
+
+# LOCALIZATION NOTE (toolbox.debugTargetInfo.tabTitleLocal):
+# Used as the tab title for about:devtools-toolbox when connected to This Firefox.
+# The target type (see toolbox.debugTargetInfo.targetType.*): %1$S
+# The target name (retrieved from DevTools, eg the extension's name): %2$S
+toolbox.debugTargetInfo.tabTitleLocal=Toolbox - %1$S / %2$S
+
+# LOCALIZATION NOTE (toolbox.debugTargetInfo.tabTitleError):
+# Used as the tab title for about:devtools-toolbox when it failed to connect to the
+# target.
+toolbox.debugTargetInfo.tabTitleError=Toolbox - error occurred
+
 # LOCALIZATION NOTE (toolbox.debugTargetInfo.targetLabel): This is displayed as a toolbox
 # header in about:devtools-toolbox. about:devtools-toolbox is used for instance when
 # inspecting tabs in about:debugging.
 # e.g. Internet for people, not profit — Mozilla (tab)
 # The name of debug target: %1$S
 # The type of debug target: %2$S
 toolbox.debugTargetInfo.targetLabel=%1$S (%2$S)
 
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -45,16 +45,22 @@ pref("devtools.inspector.show_pseudo_ele
 // The default size for image preview tooltips in the rule-view/computed-view/markup-view
 pref("devtools.inspector.imagePreviewTooltipSize", 300);
 // Enable user agent style inspection in rule-view
 pref("devtools.inspector.showUserAgentStyles", false);
 // Show all native anonymous content
 pref("devtools.inspector.showAllAnonymousContent", false);
 // Show user agent shadow roots
 pref("devtools.inspector.showUserAgentShadowRoots", false);
+// Enable Inactive CSS detection
+#if defined(NIGHTLY_BUILD)
+pref("devtools.inspector.inactive.css.enabled", true);
+#else
+pref("devtools.inspector.inactive.css.enabled", false);
+#endif
 // Enable the new Rules View
 pref("devtools.inspector.new-rulesview.enabled", false);
 
 // Grid highlighter preferences
 pref("devtools.gridinspector.gridOutlineMaxColumns", 50);
 pref("devtools.gridinspector.gridOutlineMaxRows", 50);
 pref("devtools.gridinspector.showGridAreas", false);
 pref("devtools.gridinspector.showGridLineNumbers", false);
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/alerticon-unused.svg
@@ -0,0 +1,8 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 16 16">
+  <path stroke="context-stroke" fill="none" d="M15.5 8.5C15.5 12.36 12.36 15.5 8.5 15.5C4.63 15.5 1.5 12.36 1.5 8.5C1.5 4.64 4.63 1.5 8.5 1.5C12.36 1.5 15.5 4.64 15.5 8.5Z"/>
+  <path fill="context-fill" d="M8.98 7.47C9.52 7.47 9.96 7.91 9.96 8.45C9.96 9.42 9.96 11.33 9.96 12.29C9.96 12.83 9.52 13.27 8.98 13.27C8.59 13.27 8.4 13.27 8.01 13.27C7.47 13.27 7.03 12.83 7.03 12.29C7.03 11.33 7.03 9.42 7.03 8.45C7.03 7.91 7.47 7.47 8.01 7.47C8.4 7.47 8.59 7.47 8.98 7.47Z"/>
+  <path fill="context-fill" d="M9.96 5.36C9.96 6.16 9.3 6.81 8.49 6.81C7.69 6.81 7.03 6.16 7.03 5.36C7.03 4.57 7.69 3.92 8.49 3.92C9.3 3.92 9.96 4.57 9.96 5.36Z"/>
+</svg>
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -2,38 +2,41 @@
  * 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/. */
 
 /* CSS Variables specific to this panel that aren't defined by the themes */
 :root {
   --rule-highlight-background-color: var(--theme-highlight-yellow);
   --rule-header-background-color: var(--theme-toolbar-background);
   --rule-pseudo-class-text-color: var(--yellow-70) ;
+
   /* This should be --yellow-50 but since we need an opacity of 0.4, we hard-code the
   resulting color here for now. */
   --rule-property-highlight-background-color: #FFF697;
 }
 
 :root.theme-dark {
   --rule-highlight-background-color: #521C76;
   --rule-header-background-color: #222225;
   --rule-pseudo-class-text-color: var(--yellow-50);
+
   /* This should be --yellow-50 but since we need an opacity of 0.3, we hard-code the
   resulting color here for now. */
   --rule-property-highlight-background-color: #605913;
 }
 
 /* Rule View Tabpanel */
 
 #sidebar-panel-ruleview {
   margin: 0;
   display: flex;
   flex-direction: column;
   width: 100%;
   height: 100%;
+
   /* Override the min-width from .inspector-tabpanel, as the rule panel can support small
      widths */
   min-width: 100px;
 }
 
 /* Rule View Toolbar */
 
 #ruleview-toolbar-container {
@@ -42,17 +45,17 @@
   padding: 0;
 }
 
 #ruleview-toolbar {
   display: flex;
 }
 
 #ruleview-toolbar > .devtools-searchbox:first-child {
-  padding-inline-start: 0px;
+  padding-inline-start: 0;
 }
 
 #ruleview-command-toolbar {
   display: flex;
 }
 
 .ruleview-reveal-panel {
   background: var(--rule-header-background-color);
@@ -186,16 +189,18 @@
   cursor: pointer;
 }
 
 .ruleview-computedlist,
 .ruleview-expandable-container[hidden],
 .ruleview-overridden-items[hidden],
 .ruleview-overridden-rule-filter[hidden],
 .ruleview-warning[hidden],
+.ruleview-unused-warning[hidden],
+.ruleview-used[hidden],
 .ruleview-overridden .ruleview-grid {
   display: none;
 }
 
 .ruleview-computedlist[user-open],
 .ruleview-computedlist[filter-open],
 .ruleview-overridden-items {
   display: block;
@@ -254,17 +259,17 @@
   cursor: pointer;
 }
 
 .ruleview-expandable-header:hover {
   background-color: var(--theme-toolbar-background-hover);
 }
 
 .ruleview-rule-pseudo-element {
-  padding-left:20px;
+  padding-left: 20px;
   border-left: solid 10px;
 }
 
 .ruleview-rule {
   border-bottom: 1px solid var(--theme-splitter-color);
   padding: 2px 4px;
 }
 
@@ -312,16 +317,21 @@
 .ruleview-rule.uneditable .ruleview-propertyvaluecontainer >
 .ruleview-propertyvalue,
 .ruleview-rule[uneditable=true] .ruleview-namecontainer > .ruleview-propertyname,
 .ruleview-rule[uneditable=true] .ruleview-propertyvaluecontainer >
 .ruleview-propertyvalue {
   border-bottom-color: transparent;
 }
 
+.ruleview-property.unused .ruleview-namecontainer,
+.ruleview-property.unused .ruleview-propertyvaluecontainer {
+  opacity: 0.5;
+}
+
 .ruleview-overridden-rule-filter {
   display: inline-block;
   width: 14px;
   height: 14px;
   margin-inline-start: 3px;
   background-image: url(chrome://devtools/skin/images/filter-small.svg);
   background-position: center;
   background-repeat: no-repeat;
@@ -354,27 +364,41 @@
   position: relative;
   float: left;
   left: -38px;
   box-sizing: content-box;
   border-left: 10px solid transparent;
   background-clip: content-box;
 }
 
-.ruleview-warning {
+.ruleview-warning,
+.ruleview-unused-warning {
   display: inline-block;
   width: 12px;
   height: 12px;
   margin-inline-start: 5px;
   background-image: url(chrome://devtools/skin/images/alert.svg);
   background-size: cover;
   -moz-context-properties: fill;
   fill: var(--yellow-60);
 }
 
+.ruleview-unused-warning {
+  background-image: url(chrome://devtools/skin/images/alerticon-unused.svg);
+  background-color: var(--theme-sidebar-background);
+  -moz-context-properties: fill, stroke;
+  fill: var(--theme-icon-dimmed-color);
+  stroke: var(--theme-icon-dimmed-color);
+}
+
+.ruleview-unused-warning:hover {
+  fill: var(--theme-icon-color);
+  stroke: var(--theme-icon-color);
+}
+
 .ruleview-rule:not(:hover) .ruleview-enableproperty {
   visibility: hidden;
 }
 
 .ruleview-expander {
   vertical-align: middle;
 }
 
@@ -413,21 +437,21 @@
   position: relative;
 }
 
 .ruleview-overridden-item::before,
 .ruleview-overridden-item::after {
   content: "";
   position: absolute;
   display: block;
-  border: 0px solid var(--theme-text-color-alt);
+  border: 0 solid var(--theme-text-color-alt);
 }
 
 .ruleview-overridden-item::before {
-  top: 0px;
+  top: 0;
   left: -15px;
   height: 0.8em;
   width: 10px;
   border-left-width: 0.5px;
   border-bottom-width: 0.5px;
 }
 
 .ruleview-overridden-item::after {
@@ -450,16 +474,17 @@
 .ruleview-flex,
 .ruleview-grid,
 .ruleview-shapeswatch,
 .ruleview-swatch {
   cursor: pointer;
   width: 1em;
   height: 1em;
   vertical-align: middle;
+
   /* align the swatch with its value */
   margin-top: -1px;
   margin-inline-end: 5px;
   display: inline-block;
   position: relative;
 }
 
 /* Icon swatches not using the .ruleview-swatch class (flex, grid, shape) */
--- a/devtools/server/actors/array-buffer.js
+++ b/devtools/server/actors/array-buffer.js
@@ -1,86 +1,59 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
 
 "use strict";
 
+var protocol = require("devtools/shared/protocol");
+const {arrayBufferSpec} = require("devtools/shared/specs/array-buffer");
+
 /**
  * Creates an actor for the specified ArrayBuffer.
  *
+ * @param {DebuggerServerConnection} conn
+ *    The server connection.
  * @param buffer ArrayBuffer
  *        The buffer.
  */
-function ArrayBufferActor(buffer) {
-  this.buffer = buffer;
-  this.bufferLength = buffer.byteLength;
-}
-
-ArrayBufferActor.prototype = {
-  actorPrefix: "arrayBuffer",
+const ArrayBufferActor = protocol.ActorClassWithSpec(arrayBufferSpec, {
+  initialize: function(conn, buffer) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+    this.buffer = buffer;
+    this.bufferLength = buffer.byteLength;
+  },
 
   rawValue: function() {
     return this.buffer;
   },
 
-  destroy: function() {
-  },
-
-  grip() {
+  form: function() {
     return {
-      "type": "arrayBuffer",
-      "length": this.bufferLength,
-      "actor": this.actorID,
+      typeName: this.typeName,
+      length: this.bufferLength,
+      actor: this.actorID,
     };
   },
 
-  onSlice({start, count}) {
+  slice(start, count) {
     const slice = new Uint8Array(this.buffer, start, count);
     const parts = [];
     let offset = 0;
     const PortionSize = 0x6000; // keep it divisible by 3 for btoa() and join()
     while (offset + PortionSize < count) {
       parts.push(btoa(
         String.fromCharCode.apply(null, slice.subarray(offset, offset + PortionSize))));
       offset += PortionSize;
     }
     parts.push(btoa(String.fromCharCode.apply(null, slice.subarray(offset, count))));
     return {
       "from": this.actorID,
       "encoded": parts.join(""),
     };
   },
-};
-
-ArrayBufferActor.prototype.requestTypes = {
-  "slice": ArrayBufferActor.prototype.onSlice,
-};
-
-/**
- * Create a grip for the given ArrayBuffer.
- *
- * @param buffer ArrayBuffer
- *        The ArrayBuffer we are creating a grip for.
- * @param pool ActorPool
- *        The actor pool where the new actor will be added.
- */
-function arrayBufferGrip(buffer, pool) {
-  if (!pool.arrayBufferActors) {
-    pool.arrayBufferActors = new WeakMap();
-  }
-
-  if (pool.arrayBufferActors.has(buffer)) {
-    return pool.arrayBufferActors.get(buffer).grip();
-  }
-
-  const actor = new ArrayBufferActor(buffer);
-  pool.addActor(actor);
-  pool.arrayBufferActors.set(buffer, actor);
-  return actor.grip();
-}
+});
 
 module.exports = {
   ArrayBufferActor,
-  arrayBufferGrip,
 };
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -3,24 +3,24 @@
 /* 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/. */
 
 "use strict";
 
 const { Ci } = require("chrome");
 const { setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
-const { createValueGrip } = require("devtools/server/actors/object/utils");
 const { ActorClassWithSpec } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, fetch } = DevToolsUtils;
 const { joinURI } = require("devtools/shared/path");
 const { sourceSpec } = require("devtools/shared/specs/source");
 
-loader.lazyRequireGetter(this, "arrayBufferGrip", "devtools/server/actors/array-buffer", true);
+loader.lazyRequireGetter(this, "ArrayBufferActor", "devtools/server/actors/array-buffer", true);
+loader.lazyRequireGetter(this, "LongStringActor", "devtools/server/actors/string", true);
 
 function isEvalSource(source) {
   const introType = source.introductionType;
 
   // Script elements that are dynamically created are treated as eval sources.
   // We detect these by looking at whether there was another script on the stack
   // when the source was created.
   if (introType == "scriptElement" && source.introductionScript) {
@@ -322,34 +322,34 @@ const SourceActor = ActorClassWithSpec(s
     }
     return compressed;
   },
 
   /**
    * Handler for the "onSource" packet.
    * @return Object
    *         The return of this function contains a field `contentType`, and
-   *         a field `source`. `source` can either be an arrayBufferActor grip,
-   *         or a LongStringActor grip.
+   *         a field `source`. `source` can either be an ArrayBuffer or
+   *         a LongString.
    */
   onSource: function() {
     return Promise.resolve(this._init)
       .then(this._getSourceText)
       .then(({ content, contentType }) => {
         if (typeof content === "object" && content && content.constructor &&
             content.constructor.name === "ArrayBuffer") {
           return {
-            source: arrayBufferGrip(content, this.threadActor.threadLifetimePool),
+            source: new ArrayBufferActor(this.threadActor.conn, content),
             contentType,
           };
         }
+
         return {
-          source: createValueGrip(content, this.threadActor.threadLifetimePool,
-            this.threadActor.objectGrip),
-          contentType: contentType,
+          source: new LongStringActor(this.threadActor.conn, content),
+          contentType,
         };
       })
       .catch(error => {
         reportError(error, "Got an exception during SA_onSource: ");
         throw new Error("Could not load the source for " + this.url + ".\n" +
                         DevToolsUtils.safeErrorString(error));
       });
   },
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -16,16 +16,18 @@ const TrackChangeEmitter = require("devt
 const {pageStyleSpec, styleRuleSpec, ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 
 loader.lazyRequireGetter(this, "CssLogic", "devtools/server/actors/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "SharedCssLogic", "devtools/shared/inspector/css-logic");
 loader.lazyRequireGetter(this, "getDefinedGeometryProperties",
   "devtools/server/actors/highlighters/geometry-editor", true);
 loader.lazyRequireGetter(this, "isCssPropertyKnown",
   "devtools/server/actors/css-properties", true);
+loader.lazyRequireGetter(this, "inactivePropertyHelper",
+  "devtools/server/actors/utils/inactive-property-helper", true);
 loader.lazyRequireGetter(this, "parseNamedDeclarations",
   "devtools/shared/css/parsing-utils", true);
 loader.lazyRequireGetter(this, "prettifyCSS",
   "devtools/shared/inspector/css-logic", true);
 loader.lazyRequireGetter(this, "UPDATE_PRESERVING_RULES",
   "devtools/server/actors/stylesheets", true);
 loader.lazyRequireGetter(this, "UPDATE_GENERAL",
   "devtools/server/actors/stylesheets", true);
@@ -1278,24 +1280,28 @@ var StyleRuleActor = protocol.ActorClass
     // and so that we can safely determine if a declaration is valid rather than
     // have the client guess it.
     if (form.authoredText || form.cssText) {
       // authoredText may be an empty string when deleting all properties; it's ok to use.
       const cssText = (typeof form.authoredText === "string")
         ? form.authoredText
         : form.cssText;
       const declarations = parseNamedDeclarations(isCssPropertyKnown, cssText, true);
+      const el = this.pageStyle.cssLogic.viewedElement;
+      const style = this.pageStyle.cssLogic.computedStyle;
 
       // We need to grab CSS from the window, since calling supports() on the
       // one from the current global will fail due to not being an HTML global.
       const CSS = this.pageStyle.inspector.targetActor.window.CSS;
       form.declarations = declarations.map(decl => {
         // Use the 1-arg CSS.supports() call so that we also accept !important
         // in the value.
         decl.isValid = CSS.supports(`${decl.name}:${decl.value}`);
+        decl.isUsed = inactivePropertyHelper.isPropertyUsed(
+          el, style, this.rawRule, decl.name);
         // Check property name. All valid CSS properties support "initial" as a value.
         decl.isNameValid = CSS.supports(decl.name, "initial");
         return decl;
       });
       // Cache parsed declarations so we don't needlessly re-parse authoredText every time
       // we need need to check previous property names and values when tracking changes.
       this._declarations = declarations;
     }
--- a/devtools/server/actors/thread.js
+++ b/devtools/server/actors/thread.js
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
 const { Cr, Ci } = require("chrome");
 const { ActorPool } = require("devtools/server/actors/common");
 const { createValueGrip } = require("devtools/server/actors/object/utils");
-const { longStringGrip } = require("devtools/server/actors/object/long-string");
 const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, dumpn } = DevToolsUtils;
 const { threadSpec } = require("devtools/shared/specs/script");
 const {
   getAvailableEventBreakpoints,
 } = require("devtools/server/actors/utils/event-breakpoints");
 
@@ -1444,36 +1443,16 @@ const ThreadActor = ActorClassWithSpec(t
       const actor = this._pausePool.get(actorID);
       if (actor) {
         this.threadObjectGrip(actor);
       }
     }
     return {};
   },
 
-  /**
-   * Create a long string grip that is scoped to a pause.
-   *
-   * @param string String
-   *        The string we are creating a grip for.
-   */
-  pauseLongStringGrip: function(string) {
-    return longStringGrip(string, this._pausePool);
-  },
-
-  /**
-   * Create a long string grip that is scoped to a thread.
-   *
-   * @param string String
-   *        The string we are creating a grip for.
-   */
-  threadLongStringGrip: function(string) {
-    return longStringGrip(string, this._threadLifetimePool);
-  },
-
   // JS Debugger API hooks.
 
   /**
    * A function that the engine calls when a call to a debug event hook,
    * breakpoint handler, watchpoint handler, or similar function throws some
    * exception.
    *
    * @param exception exception
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/utils/inactive-property-helper.js
@@ -0,0 +1,366 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+const Services = require("Services");
+const { LocalizationHelper } = require("devtools/shared/l10n");
+
+const PREF_UNUSED_CSS_ENABLED = "devtools.inspector.inactive.css.enabled";
+const INSPECTOR_L10N =
+  new LocalizationHelper("devtools/client/locales/inspector.properties");
+
+class InactivePropertyHelper {
+  /**
+   * A list of rules for when CSS properties have no effect.
+   *
+   * In certain situations, CSS properties do not have any effect. A common
+   * example is trying to set a width on an inline element like a <span>.
+   *
+   * There are so many properties in CSS that it's difficult to remember which
+   * ones do and don't apply in certain situations. Some are straight-forward
+   * like `flex-wrap` only applying to an element that has `display:flex`.
+   * Others are less trivial like setting something other than a color on a
+   * `:visited` pseudo-class.
+   *
+   * This file contains "rules" in the form of objects with the following
+   * properties:
+   * {
+   *   invalidProperties (see note):
+   *     Array of CSS property names that are inactive if the rule matches.
+   *   validProperties (see note):
+   *     Array of CSS property names that are active if the rule matches.
+   *   when:
+   *     The rule itself, a JS function used to identify the conditions
+   *     indicating whether a property is valid or not.
+   *
+   *   error:
+   *     A JS function that returns a custom error message explaining why the
+   *     property is inactive in this situation. This function takes a single
+   *     argument: the property name.
+   * }
+   *
+   * NOTE: validProperties and invalidProperties are mutually exclusive.
+   *
+   * The main export is `isPropertyUsed()`, which can be used to check if a
+   * property is used or not, and why.
+   */
+  get VALIDATORS() {
+    return [
+      // Flex container property used on non-flex container.
+      {
+        invalidProperties: [
+          "flex-direction",
+          "flex-flow",
+          "flex-wrap",
+        ],
+        when: () => !this.flexContainer,
+        error: property => msg("rule.inactive.css.not.flex.container", property),
+      },
+      // Flex item property used on non-flex item.
+      {
+        invalidProperties: [
+          "flex",
+          "flex-basis",
+          "flex-grow",
+          "flex-shrink",
+          "order",
+        ],
+        when: () => !this.flexItem,
+        error: property => msg("rule.inactive.css.not.flex.item", property),
+      },
+      // Grid container property used on non-grid container.
+      {
+        invalidProperties: [
+          "grid-auto-columns",
+          "grid-auto-flow",
+          "grid-auto-rows",
+          "grid-template",
+          "grid-gap",
+          "row-gap",
+          "column-gap",
+          "justify-items",
+        ],
+        when: () => !this.gridContainer,
+        error: property => msg("rule.inactive.css.not.grid.container", property),
+      },
+      // Grid item property used on non-grid item.
+      {
+        invalidProperties: [
+          "grid-area",
+          "grid-column",
+          "grid-column-end",
+          "grid-column-start",
+          "grid-row",
+          "grid-row-end",
+          "grid-row-start",
+          "justify-self",
+        ],
+        when: () => !this.gridItem,
+        error: property => msg("rule.inactive.css.not.grid.item", property),
+      },
+      // Grid and flex item properties used on non-grid or non-flex item.
+      {
+        invalidProperties: [
+          "align-self",
+        ],
+        when: () => !this.gridItem && !this.flexItem,
+        error: property => msg("rule.inactive.css.not.grid.or.flex.item", property),
+      },
+      // Grid and flex container properties used on non-grid or non-flex container.
+      {
+        invalidProperties: [
+          "align-content",
+          "align-items",
+          "justify-content",
+        ],
+        when: () => !this.gridContainer && !this.flexContainer,
+        error: property => msg("rule.inactive.css.not.grid.or.flex.container", property),
+      },
+    ];
+  }
+
+  get unusedCssEnabled() {
+    if (!this._unusedCssEnabled) {
+      this._unusedCssEnabled = Services.prefs.getBoolPref(PREF_UNUSED_CSS_ENABLED);
+    }
+    return this._unusedCssEnabled;
+  }
+
+  /**
+   * Is this CSS property having any effect on this element?
+   *
+   * @param {DOMNode} el
+   *        The DOM element.
+   * @param {Style} elStyle
+   *        The computed style for this DOMNode.
+   * @param {DOMRule} cssRule
+   *        The CSS rule the property is defined in.
+   * @param {String} property
+   *        The CSS property name.
+   *
+   * @return {Object} object
+   * @return {Boolean} object.used
+   *         true if the property is used.
+   * @return {Array} object.reasons
+   *         A string array listing the reasons a property isn't used.
+   */
+  isPropertyUsed(el, elStyle, cssRule, property) {
+    if (!this.unusedCssEnabled) {
+      return {used: true};
+    }
+
+    const errors = [];
+
+    this.VALIDATORS.forEach(validator => {
+      // First check if this rule cares about this property.
+      let isRuleConcerned = false;
+
+      if (validator.invalidProperties) {
+        isRuleConcerned = validator.invalidProperties === "*" ||
+                          validator.invalidProperties.includes(property);
+      } else if (validator.validProperties) {
+        isRuleConcerned = !validator.validProperties.includes(property);
+      }
+
+      if (!isRuleConcerned) {
+        return;
+      }
+
+      this.select(el, elStyle, cssRule, property);
+
+      // And then run the validator, gathering the error message if the
+      // validator passes.
+      if (validator.when()) {
+        const error = validator.error(property);
+
+        if (typeof error === "string") {
+          errors.push(validator.error(property));
+        }
+      }
+    });
+
+    return {
+      used: !errors.length,
+      reasons: errors,
+    };
+  }
+
+  /**
+   * Focus on a node.
+   *
+   * @param {DOMNode} node
+   *        Node to focus on.
+   */
+  select(node, style, cssRule, property) {
+    this._node = node;
+    this._cssRule = cssRule;
+    this._property = property;
+    this._style = style;
+  }
+
+  /**
+   * Provide a public reference to node.
+   */
+  get node() {
+    return this._node;
+  }
+
+  /**
+   * Cache and provide node's computed style.
+   */
+  get style() {
+    return this._style;
+  }
+
+  /**
+   * Check if the current node's propName is set to one of the values passed in
+   * the values array.
+   *
+   * @param {String} propName
+   *        Property name to check.
+   * @param {Array} values
+   *        Values to compare against.
+   */
+  checkStyle(propName, values) {
+    return this.checkStyleForNode(this.node, propName, values);
+  }
+
+  /**
+   * Check if a node's propName is set to one of the values passed in the values
+   * array.
+   *
+   * @param {DOMNode} node
+   *        The node to check.
+   * @param {String} propName
+   *        Property name to check.
+   * @param {Array} values
+   *        Values to compare against.
+   */
+  checkStyleForNode(node, propName, values) {
+    return values.some(value => this.style[propName] === value);
+  }
+
+  /**
+   * Check if the current node is a flex container i.e. a node that has a style
+   * of `display:flex` or `display:inline-flex`.
+   */
+  get flexContainer() {
+    return this.checkStyle("display", ["flex", "inline-flex"]);
+  }
+
+  /**
+   * Check if the current node is a flex item.
+   */
+  get flexItem() {
+    return this.isFlexItem(this.node);
+  }
+
+  /**
+   * Check if the current node is a grid container i.e. a node that has a style
+   * of `display:grid` or `display:inline-grid`.
+   */
+  get gridContainer() {
+    return this.checkStyle("display", ["grid", "inline-grid"]);
+  }
+
+  /**
+   * Check if the current node is a grid item.
+   */
+  get gridItem() {
+    return this.isGridItem(this.node);
+  }
+
+  /**
+   * Check if a node is a flex item.
+   *
+   * @param {DOMNode} node
+   *        The node to check.
+   */
+  isFlexItem(node) {
+    return !!node.parentFlexElement;
+  }
+
+  /**
+   * Check if a node is a flex container.
+   *
+   * @param {DOMNode} node
+   *        The node to check.
+   */
+  isFlexContainer(node) {
+    return !!node.getAsFlexContainer();
+  }
+
+  /**
+   * Check if a node is a grid container.
+   *
+   * @param {DOMNode} node
+   *        The node to check.
+   */
+  isGridContainer(node) {
+    return !!node.getGridFragments().length > 0;
+  }
+
+  /**
+   * Check if a node is a grid item.
+   *
+   * @param {DOMNode} node
+   *        The node to check.
+   */
+  isGridItem(node) {
+    return !!this.getParentGridElement(this.node);
+  }
+
+  getParentGridElement(node) {
+    if (node.nodeType === node.ELEMENT_NODE) {
+      const display = this.style.display;
+
+      if (!display || display === "none" || display === "contents") {
+        // Doesn't generate a box, not a grid item.
+        return null;
+      }
+      const position = this.style.position;
+      if (position === "absolute" ||
+          position === "fixed" ||
+          this.style.cssFloat !== "none") {
+        // Out of flow, not a grid item.
+        return null;
+      }
+    } else if (node.nodeType !== node.TEXT_NODE) {
+      return null;
+    }
+
+    for (let p = node.flattenedTreeParentNode; p; p = p.flattenedTreeParentNode) {
+      const style = node.ownerGlobal.getComputedStyle(p);
+      const display = style.display;
+
+      if (display.includes("grid") && !!p.getGridFragments().length > 0) {
+        // It's a grid item!
+        return p;
+      }
+      if (display !== "contents") {
+        return null; // Not a grid item, for sure.
+      }
+      // display: contents, walk to the parent
+    }
+    return null;
+  }
+}
+
+/**
+ * Helper function that gets localized strings.
+ *
+ * @param  {String} propName
+ *         The property name to use. This property name must exist in the
+ *         `inspector.properties` file).
+ * @param  {*} values
+ *         Values to be used as replacement strings.
+ */
+function msg(...args) {
+  return INSPECTOR_L10N.getFormatStr(...args);
+}
+
+exports.inactivePropertyHelper = new InactivePropertyHelper();
--- a/devtools/server/actors/utils/moz.build
+++ b/devtools/server/actors/utils/moz.build
@@ -8,15 +8,16 @@ DevToolsModules(
     'accessibility.js',
     'actor-registry-utils.js',
     'actor-registry.js',
     'breakpoint-actor-map.js',
     'css-grid-utils.js',
     'dbg-source.js',
     'event-breakpoints.js',
     'event-loop.js',
+    'inactive-property-helper.js',
     'make-debugger.js',
     'shapes-utils.js',
     'stack.js',
     'TabSources.js',
     'track-change-emitter.js',
     'walker-search.js',
 )
--- a/devtools/server/tests/unit/head_dbg.js
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -31,16 +31,17 @@ Services.prefs.setBoolPref("devtools.deb
 Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
 
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { ActorRegistry } = require("devtools/server/actors/utils/actor-registry");
 const { DebuggerServer } = require("devtools/server/main");
 const { DebuggerServer: WorkerDebuggerServer } = worker.require("devtools/server/main");
 const { DebuggerClient } = require("devtools/shared/client/debugger-client");
 const ObjectClient = require("devtools/shared/client/object-client");
+const { LongStringFront } = require("devtools/shared/fronts/string");
 const {TargetFactory} = require("devtools/client/framework/target");
 
 const { addDebuggerToGlobal } = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm");
 
 const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
                         .createInstance(Ci.nsIPrincipal);
 
 var { loadSubScript, loadSubScriptWithOptions } = Services.scriptloader;
@@ -160,16 +161,27 @@ async function createMainProcessMemoryFr
     // destroyed when calling target.destroy.
     // Close the client to cleanup everything.
     await target.client.close();
   });
 
   return { client: target.client, memoryFront };
 }
 
+function createLongStringFront(conn, form) {
+  // CAUTION -- do not replicate in the codebase. Instead, use marshalling
+  // This code is simulating how the LongStringFront would be created by protocol.js
+  // We should not use it like this in the codebase, this is done only for testing
+  // purposes until we can return a proper LongStringFront from the server.
+  const front = new LongStringFront(conn, form);
+  front.actorID = form.actor;
+  front.manage(front);
+  return front;
+}
+
 function createTestGlobal(name) {
   const sandbox = Cu.Sandbox(Cc["@mozilla.org/systemprincipal;1"]
                            .createInstance(Ci.nsIPrincipal));
   sandbox.__name = name;
   return sandbox;
 }
 
 function connect(client) {
--- a/devtools/server/tests/unit/test_longstringgrips-01.js
+++ b/devtools/server/tests/unit/test_longstringgrips-01.js
@@ -51,20 +51,20 @@ function test_longstring_grip() {
     const grip = args[0];
 
     try {
       Assert.equal(grip.type, "longString");
       Assert.equal(grip.length, longString.length);
       Assert.equal(grip.initial,
                    longString.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH));
 
-      const longStringClient = gThreadClient.pauseLongString(grip);
-      longStringClient.substring(22, 28, function(response) {
+      const longStringFront = createLongStringFront(gClient, grip);
+      longStringFront.substring(22, 28).then(function(response) {
         try {
-          Assert.equal(response.substring, "monkey");
+          Assert.equal(response, "monkey");
         } finally {
           gThreadClient.resume(function() {
             finishClient(gClient);
           });
         }
       });
     } catch (error) {
       gThreadClient.resume(function() {
deleted file mode 100644
--- a/devtools/server/tests/unit/test_longstringgrips-02.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-var gDebuggee;
-var gClient;
-var gThreadClient;
-
-Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
-
-registerCleanupFunction(() => {
-  Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
-});
-
-function run_test() {
-  initTestDebuggerServer();
-  gDebuggee = addTestGlobal("test-grips");
-  gDebuggee.eval(function stopMe(arg1) {
-    debugger;
-  }.toString());
-
-  gClient = new DebuggerClient(DebuggerServer.connectPipe());
-  gClient.connect().then(function() {
-    attachTestTabAndResume(
-      gClient, "test-grips", function(response, targetFront, threadClient) {
-        gThreadClient = threadClient;
-        test_longstring_grip();
-      });
-  });
-  do_test_pending();
-}
-
-function test_longstring_grip() {
-  DebuggerServer.LONG_STRING_LENGTH = 200;
-
-  gThreadClient.addOneTimeListener("paused", function(event, packet) {
-    try {
-      const fakeLongStringGrip = {
-        type: "longString",
-        length: 1000000,
-        actor: "123fakeActor123",
-        initial: "",
-      };
-      const longStringClient = gThreadClient.pauseLongString(fakeLongStringGrip);
-      longStringClient.substring(22, 28, function(response) {
-        try {
-          Assert.ok(!!response.error,
-                    "We should not get a response, but an error.");
-        } finally {
-          gThreadClient.resume(function() {
-            finishClient(gClient);
-          });
-        }
-      });
-    } catch (error) {
-      gThreadClient.resume(function() {
-        finishClient(gClient);
-        do_throw(error);
-      });
-    }
-  });
-
-  gDebuggee.eval("stopMe()");
-}
-
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -193,17 +193,16 @@ skip-if = true # breakpoint sliding is n
 [test_framebindings-06.js]
 [test_framebindings-07.js]
 [test_pause_exceptions-01.js]
 [test_pause_exceptions-02.js]
 [test_pause_exceptions-03.js]
 [test_pause_exceptions-04.js]
 [test_longstringactor.js]
 [test_longstringgrips-01.js]
-[test_longstringgrips-02.js]
 [test_source-01.js]
 [test_source-02.js]
 [test_source-03.js]
 [test_source-04.js]
 [test_wasm_source-01.js]
 [test_breakpoint-actor-map.js]
 skip-if = true # tests for breakpoint actors are obsolete bug 1524374
 [test_unsafeDereference.js]
--- a/devtools/shared/client/environment-client.js
+++ b/devtools/shared/client/environment-client.js
@@ -1,16 +1,15 @@
 /* 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/. */
 
 "use strict";
 
 const {arg, DebuggerClient} = require("devtools/shared/client/debugger-client");
-const eventSource = require("devtools/shared/client/event-source");
 
 /**
  * Environment clients are used to manipulate the lexical environment actors.
  *
  * @param client DebuggerClient
  *        The debugger client parent.
  * @param form Object
  *        The form sent across the remote debugging protocol.
@@ -44,11 +43,9 @@ EnvironmentClient.prototype = {
    */
   assign: DebuggerClient.requester({
     type: "assign",
     name: arg(0),
     value: arg(1),
   }),
 };
 
-eventSource(EnvironmentClient.prototype);
-
 module.exports = EnvironmentClient;
--- a/devtools/shared/client/moz.build
+++ b/devtools/shared/client/moz.build
@@ -1,16 +1,15 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
-    'array-buffer-client.js',
     'connection-manager.js',
     'constants.js',
     'debugger-client.js',
     'environment-client.js',
     'event-source.js',
     'long-string-client.js',
     'object-client.js',
     'property-iterator-client.js',
--- a/devtools/shared/client/thread-client.js
+++ b/devtools/shared/client/thread-client.js
@@ -4,18 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {arg, DebuggerClient} = require("devtools/shared/client/debugger-client");
 const eventSource = require("devtools/shared/client/event-source");
 const {ThreadStateTypes} = require("devtools/shared/client/constants");
 
-loader.lazyRequireGetter(this, "ArrayBufferClient", "devtools/shared/client/array-buffer-client");
-loader.lazyRequireGetter(this, "LongStringClient", "devtools/shared/client/long-string-client");
 loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/object-client");
 loader.lazyRequireGetter(this, "SourceFront", "devtools/shared/fronts/source", true);
 
 /**
  * Creates a thread client for the remote debugging protocol server. This client
  * is a front to the thread actor created in the server side, hiding the
  * protocol details in a traditional JavaScript API.
  *
@@ -354,89 +352,16 @@ ThreadClient.prototype = {
     }
 
     const client = new ObjectClient(this.client, grip);
     this._pauseGrips[grip.actor] = client;
     return client;
   },
 
   /**
-   * Get or create a long string client, checking the grip client cache if it
-   * already exists.
-   *
-   * @param grip Object
-   *        The long string grip returned by the protocol.
-   * @param gripCacheName String
-   *        The property name of the grip client cache to check for existing
-   *        clients in.
-   */
-  _longString: function(grip, gripCacheName) {
-    if (grip.actor in this[gripCacheName]) {
-      return this[gripCacheName][grip.actor];
-    }
-
-    const client = new LongStringClient(this.client, grip);
-    this[gripCacheName][grip.actor] = client;
-    return client;
-  },
-
-  /**
-   * Return an instance of LongStringClient for the given long string grip that
-   * is scoped to the current pause.
-   *
-   * @param grip Object
-   *        The long string grip returned by the protocol.
-   */
-  pauseLongString: function(grip) {
-    return this._longString(grip, "_pauseGrips");
-  },
-
-  /**
-   * Return an instance of LongStringClient for the given long string grip that
-   * is scoped to the thread lifetime.
-   *
-   * @param grip Object
-   *        The long string grip returned by the protocol.
-   */
-  threadLongString: function(grip) {
-    return this._longString(grip, "_threadGrips");
-  },
-
-  /**
-   * Get or create an ArrayBuffer client, checking the grip client cache if it
-   * already exists.
-   *
-   * @param grip Object
-   *        The ArrayBuffer grip returned by the protocol.
-   * @param gripCacheName String
-   *        The property name of the grip client cache to check for existing
-   *        clients in.
-   */
-  _arrayBuffer: function(grip, gripCacheName) {
-    if (grip.actor in this[gripCacheName]) {
-      return this[gripCacheName][grip.actor];
-    }
-
-    const client = new ArrayBufferClient(this.client, grip);
-    this[gripCacheName][grip.actor] = client;
-    return client;
-  },
-
-  /**
-   * Return an instance of ArrayBufferClient for the given ArrayBuffer grip that
-   * is scoped to the thread lifetime.
-   *
-   * @param grip Object
-   *        The ArrayBuffer grip returned by the protocol.
-   */
-  threadArrayBuffer: function(grip) {
-    return this._arrayBuffer(grip, "_threadGrips");
-  },
-
-  /**
    * Clear and invalidate all the grip clients from the given cache.
    *
    * @param gripCacheName
    *        The property name of the grip cache we want to clear.
    */
   _clearObjectClients: function(gripCacheName) {
     for (const id in this[gripCacheName]) {
       this[gripCacheName][id].valid = false;
@@ -539,17 +464,17 @@ ThreadClient.prototype = {
   /**
    * Return an instance of SourceFront for the given source actor form.
    */
   source: function(form) {
     if (form.actor in this._threadGrips) {
       return this._threadGrips[form.actor];
     }
 
-    this._threadGrips[form.actor] = new SourceFront(this.client, form, this);
+    this._threadGrips[form.actor] = new SourceFront(this.client, form);
     return this._threadGrips[form.actor];
   },
 
   events: ["newSource", "progress"],
 };
 
 eventSource(ThreadClient.prototype);
 
rename from devtools/shared/client/array-buffer-client.js
rename to devtools/shared/fronts/array-buffer.js
--- a/devtools/shared/client/array-buffer-client.js
+++ b/devtools/shared/fronts/array-buffer.js
@@ -1,43 +1,21 @@
 /* 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/. */
 
 "use strict";
 
-const {arg, DebuggerClient} = require("devtools/shared/client/debugger-client");
+const { arrayBufferSpec } = require("devtools/shared/specs/array-buffer");
+const { FrontClassWithSpec, registerFront } = require("devtools/shared/protocol");
 
 /**
  * A ArrayBufferClient provides a way to access ArrayBuffer from the
  * debugger server.
- *
- * @param client DebuggerClient
- *        The debugger client parent.
- * @param grip Object
- *        A pause-lifetime ArrayBuffer grip returned by the protocol.
  */
-function ArrayBufferClient(client, grip) {
-  this._grip = grip;
-  this._client = client;
-  this.request = this._client.request;
+class ArrayBufferFront extends FrontClassWithSpec(arrayBufferSpec) {
+  form(json) {
+    this.length = json.length;
+  }
 }
-ArrayBufferClient.prototype = {
-  get actor() {
-    return this._grip.actor;
-  },
-  get length() {
-    return this._grip.length;
-  },
-  get _transport() {
-    return this._client._transport;
-  },
 
-  valid: true,
-
-  slice: DebuggerClient.requester({
-    type: "slice",
-    start: arg(0),
-    count: arg(1),
-  }),
-};
-
-module.exports = ArrayBufferClient;
+exports.ArrayBufferFront = ArrayBufferFront;
+registerFront(ArrayBufferFront);
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -10,16 +10,17 @@ DIRS += [
     'targets',
     'worker',
 ]
 
 DevToolsModules(
     'accessibility.js',
     'actor-registry.js',
     'animation.js',
+    'array-buffer.js',
     'changes.js',
     'css-properties.js',
     'device.js',
     'emulation.js',
     'framerate.js',
     'highlighters.js',
     'inspector.js',
     'layout.js',
--- a/devtools/shared/fronts/source.js
+++ b/devtools/shared/fronts/source.js
@@ -1,33 +1,30 @@
 /* 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/. */
 
 "use strict";
 
 const { sourceSpec } = require("devtools/shared/specs/source");
 const { FrontClassWithSpec, registerFront } = require("devtools/shared/protocol");
+const { ArrayBufferFront } = require("devtools/shared/fronts/array-buffer");
 
 /**
  * A SourceFront provides a way to access the source text of a script.
  *
  * @param client DebuggerClient
  *        The Debugger Client instance.
  * @param form Object
  *        The form sent across the remote debugging protocol.
- * @param activeThread ThreadClient
- *        The thread client parent. Used until the SourceFront marshalls LongStringFront
- *        and ArrayBuffer.
  */
 class SourceFront extends FrontClassWithSpec(sourceSpec) {
-  constructor(client, form, activeThread) {
+  constructor(client, form) {
     super(client);
     this._url = form.url;
-    this._activeThread = activeThread;
     // this is here for the time being, until the source front is managed
     // via protocol.js marshalling
     this.actorID = form.actor;
     this.manage(this);
   }
 
   get actor() {
     return this.actorID;
@@ -43,32 +40,28 @@ class SourceFront extends FrontClassWith
   }
 
   // Alias for source.unblackbox to avoid changing protocol.js packets
   unblackBox() {
     return this.unblackbox();
   }
 
   /**
-   * Get a long string grip for this SourceFront's source.
+   * Get a Front for either an ArrayBuffer or LongString
+   * for this SourceFront's source.
    */
   async source() {
     const response = await this.onSource();
     return this._onSourceResponse(response);
   }
 
   _onSourceResponse(response) {
-    if (typeof response.source === "string") {
-      return response;
-    }
-
     const { contentType, source } = response;
-    if (source.type === "arrayBuffer") {
-      const arrayBuffer = this._activeThread.threadArrayBuffer(source);
-      return arrayBuffer.slice(0, arrayBuffer.length).then(function(resp) {
+    if (source instanceof ArrayBufferFront) {
+      return source.slice(0, source.length).then(function(resp) {
         if (resp.error) {
           return resp;
         }
         // Keeping str as a string, ArrayBuffer/Uint8Array will not survive
         // setIn/mergeIn operations.
         const str = atob(resp.encoded);
         const newResponse = {
           source: {
@@ -76,24 +69,23 @@ class SourceFront extends FrontClassWith
             toString: () => "[wasm]",
           },
           contentType,
         };
         return newResponse;
       });
     }
 
-    const longString = this._activeThread.threadLongString(source);
-    return longString.substring(0, longString.length).then(function(resp) {
+    return source.substring(0, source.length).then(function(resp) {
       if (resp.error) {
         return resp;
       }
 
       const newResponse = {
-        source: resp.substring,
+        source: resp,
         contentType: contentType,
       };
       return newResponse;
     });
   }
 }
 
 exports.SourceFront = SourceFront;
new file mode 100644
--- /dev/null
+++ b/devtools/shared/specs/array-buffer.js
@@ -0,0 +1,24 @@
+/* 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/. */
+"use strict";
+
+const protocol = require("devtools/shared/protocol");
+const {Arg, RetVal, generateActorSpec} = protocol;
+
+const arrayBufferSpec = generateActorSpec({
+  typeName: "arraybuffer",
+
+  methods: {
+    slice: {
+      request: {
+        start: Arg(0),
+        count: Arg(1),
+      },
+      response: RetVal("json"),
+    },
+    release: { release: true },
+  },
+});
+
+exports.arrayBufferSpec = arrayBufferSpec;
--- a/devtools/shared/specs/index.js
+++ b/devtools/shared/specs/index.js
@@ -38,16 +38,21 @@ const Types = exports.__TypesForTests = 
     front: "devtools/shared/fronts/addon/webextension-inspected-window",
   },
   {
     types: ["animationplayer", "animations"],
     spec: "devtools/shared/specs/animation",
     front: "devtools/shared/fronts/animation",
   },
   {
+    types: ["arraybuffer"],
+    spec: "devtools/shared/specs/array-buffer",
+    front: "devtools/shared/fronts/array-buffer",
+  },
+  {
     types: ["changes"],
     spec: "devtools/shared/specs/changes",
     front: "devtools/shared/fronts/changes",
   },
   {
     types: ["cssProperties"],
     spec: "devtools/shared/specs/css-properties",
     front: "devtools/shared/fronts/css-properties",
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -9,16 +9,17 @@ DIRS += [
     'targets',
     'worker',
 ]
 
 DevToolsModules(
     'accessibility.js',
     'actor-registry.js',
     'animation.js',
+    'array-buffer.js',
     'changes.js',
     'css-properties.js',
     'device.js',
     'emulation.js',
     'environment.js',
     'frame.js',
     'framerate.js',
     'heap-snapshot-file.js',
--- a/devtools/shared/specs/source.js
+++ b/devtools/shared/specs/source.js
@@ -1,28 +1,55 @@
 /* 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/. */
 "use strict";
 
 const {Arg, RetVal, generateActorSpec, types} = require("devtools/shared/protocol");
 
+const longstringType = types.getType("longstring");
+const arraybufferType = types.getType("arraybuffer");
+// The sourcedata type needs some custom marshalling, because it is sometimes
+// returned as an arraybuffer and sometimes as a longstring.
+types.addType("sourcedata", {
+  write: (value, context, detail) => {
+    if (value.typeName === "arraybuffer") {
+      return arraybufferType.write(value, context, detail);
+    }
+    return longstringType.write(value, context, detail);
+  },
+  read: (value, context, detail) => {
+    // backward compatibility for FF67 or older: value might be an old style ArrayBuffer
+    // actor grip with type="arrayBuffer". The content should be the same so it can be
+    // translated to a regular ArrayBufferFront.
+    if (value.typeName === "arraybuffer" || value.type === "arrayBuffer") {
+      return arraybufferType.read(value, context, detail);
+    }
+    return longstringType.read(value, context, detail);
+  },
+});
+
 types.addDictType("sourceposition", {
   line: "number",
   column: "number",
 });
 types.addDictType("nullablesourceposition", {
   line: "nullable:number",
   column: "nullable:number",
 });
 types.addDictType("breakpointquery", {
   start: "nullable:nullablesourceposition",
   end: "nullable:nullablesourceposition",
 });
 
+types.addDictType("source.onsource", {
+  contentType: "nullable:string",
+  source: "nullable:sourcedata",
+});
+
 const sourceSpec = generateActorSpec({
   typeName: "source",
 
   methods: {
     getBreakpointPositions: {
       request: {
         query: Arg(0, "nullable:breakpointquery"),
       },
@@ -37,17 +64,17 @@ const sourceSpec = generateActorSpec({
       response: {
         positions: RetVal("json"),
       },
     },
     onSource: {
       // we are sending the type "source" to be compatible
       // with FF67 and older
       request: { type: "source" },
-      response: RetVal("json"),
+      response: RetVal("source.onsource"),
     },
     setPausePoints: {
       request: {
         pausePoints: Arg(0, "json"),
       },
     },
     blackbox: {
       request: { range: Arg(0, "nullable:json") },
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -259,23 +259,19 @@ void BrowsingContext::Attach(bool aFromI
 
   if (!aFromIPC) {
     // Send attach to our parent if we need to.
     if (XRE_IsContentProcess()) {
       ContentChild::GetSingleton()->SendAttachBrowsingContext(
           GetIPCInitializer());
     } else if (IsContent()) {
       MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
-      for (auto iter = Group()->ContentParentsIter(); !iter.Done();
-           iter.Next()) {
-        nsRefPtrHashKey<ContentParent>* entry = iter.Get();
-
-        Unused << entry->GetKey()->SendAttachBrowsingContext(
-            GetIPCInitializer());
-      }
+      Group()->EachParent([&](ContentParent* aParent) {
+        Unused << aParent->SendAttachBrowsingContext(GetIPCInitializer());
+      });
     }
   }
 }
 
 void BrowsingContext::Detach(bool aFromIPC) {
   MOZ_LOG(GetLog(), LogLevel::Debug,
           ("%s: Detaching 0x%08" PRIx64 " from 0x%08" PRIx64,
            XRE_IsParentProcess() ? "Parent" : "Child", Id(),
@@ -336,17 +332,17 @@ void BrowsingContext::CacheChildren(bool
 
 void BrowsingContext::RestoreChildren(Children&& aChildren, bool aFromIPC) {
   MOZ_LOG(GetLog(), LogLevel::Debug,
           ("%s: Restoring children of 0x%08" PRIx64 "",
            XRE_IsParentProcess() ? "Parent" : "Child", Id()));
 
   MOZ_DIAGNOSTIC_ASSERT(mChildren.IsEmpty());
 
-  for (BrowsingContext* child : mChildren) {
+  for (BrowsingContext* child : aChildren) {
     MOZ_DIAGNOSTIC_ASSERT(child->GetParent() == this);
     Unused << Group()->EvictCachedContext(child);
   }
 
   mChildren.SwapElements(aChildren);
 
   if (!aFromIPC && XRE_IsContentProcess()) {
     auto cc = ContentChild::GetSingleton();
@@ -770,24 +766,23 @@ void BrowsingContext::Transaction::Commi
 #define MOZ_BC_FIELD(...) /* nothing */
 #include "mozilla/dom/BrowsingContextFieldList.h"
 
     ContentChild* cc = ContentChild::GetSingleton();
     cc->SendCommitBrowsingContextTransaction(aBrowsingContext, *this,
                                              aBrowsingContext->mFieldEpochs);
   } else {
     MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
-    for (auto iter = aBrowsingContext->Group()->ContentParentsIter();
-         !iter.Done(); iter.Next()) {
-      RefPtr<ContentParent> child = iter.Get()->GetKey();
+
+    aBrowsingContext->Group()->EachParent([&](ContentParent* aParent) {
       const FieldEpochs& childEpochs =
-          aBrowsingContext->Canonical()->GetFieldEpochsForChild(child);
-      Unused << child->SendCommitBrowsingContextTransaction(aBrowsingContext,
-                                                            *this, childEpochs);
-    }
+          aBrowsingContext->Canonical()->GetFieldEpochsForChild(aParent);
+      Unused << aParent->SendCommitBrowsingContextTransaction(
+          aBrowsingContext, *this, childEpochs);
+    });
   }
 
   Apply(aBrowsingContext, nullptr);
 }
 
 void BrowsingContext::Transaction::Apply(BrowsingContext* aBrowsingContext,
                                          ContentParent* aSource,
                                          const FieldEpochs* aEpochs) {
--- a/docshell/base/BrowsingContextGroup.h
+++ b/docshell/base/BrowsingContextGroup.h
@@ -46,18 +46,16 @@ class BrowsingContextGroup final : publi
   void EnsureSubscribed(ContentParent* aProcess);
 
   // Methods interacting with cached contexts.
   bool IsContextCached(BrowsingContext* aContext) const;
   void CacheContext(BrowsingContext* aContext);
   void CacheContexts(const BrowsingContext::Children& aContexts);
   bool EvictCachedContext(BrowsingContext* aContext);
 
-  ContentParents::Iterator ContentParentsIter() { return mSubscribers.Iter(); }
-
   // Get a reference to the list of toplevel contexts in this
   // BrowsingContextGroup.
   BrowsingContext::Children& Toplevels() { return mToplevels; }
   void GetToplevels(BrowsingContext::Children& aToplevels) {
     aToplevels.AppendElements(mToplevels);
   }
 
   nsISupports* GetParentObject() const;
@@ -83,16 +81,38 @@ class BrowsingContextGroup final : publi
     MOZ_RELEASE_ASSERT(parent || aParentId == 0);
 
     RefPtr<BrowsingContext> opener = BrowsingContext::Get(aOpenerId);
     MOZ_RELEASE_ASSERT(opener || aOpenerId == 0);
 
     return Select(parent, opener);
   }
 
+  // For each 'ContentParent', except for 'aExcludedParent',
+  // associated with this group call 'aCallback'.
+  template <typename Func>
+  void EachOtherParent(ContentParent* aExcludedParent, Func&& aCallback) {
+    MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+    for (auto iter = mSubscribers.Iter(); !iter.Done(); iter.Next()) {
+      if (iter.Get()->GetKey() != aExcludedParent) {
+        aCallback(iter.Get()->GetKey());
+      }
+    }
+  }
+
+  // For each 'ContentParent' associated with
+  // this group call 'aCallback'.
+  template <typename Func>
+  void EachParent(Func&& aCallback) {
+    MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+    for (auto iter = mSubscribers.Iter(); !iter.Done(); iter.Next()) {
+      aCallback(iter.Get()->GetKey());
+    }
+  }
+
  private:
   friend class CanonicalBrowsingContext;
 
   ~BrowsingContextGroup();
 
   // A BrowsingContextGroup contains a series of BrowsingContext objects. They
   // are addressed using a hashtable to avoid linear lookup when adding or
   // removing elements from the set.
--- a/docshell/base/CanonicalBrowsingContext.cpp
+++ b/docshell/base/CanonicalBrowsingContext.cpp
@@ -167,20 +167,20 @@ void CanonicalBrowsingContext::NotifySta
   // tab bar. That's clear user intent to play, so gesture activate the browsing
   // context so that the block-autoplay logic allows the media to autoplay.
   NotifyUserGestureActivation();
   AUTOPLAY_LOG("NotifyStartDelayedAutoplayMedia for chrome bc 0x%08" PRIx64,
                Id());
   StartDelayedAutoplayMediaComponents();
   // Notfiy all content browsing contexts which are related with the canonical
   // browsing content tree to start delayed autoplay media.
-  for (auto iter = Group()->ContentParentsIter(); !iter.Done(); iter.Next()) {
-    auto entry = iter.Get();
-    Unused << entry->GetKey()->SendStartDelayedAutoplayMediaComponents(this);
-  }
+
+  Group()->EachParent([&](ContentParent* aParent) {
+    Unused << aParent->SendStartDelayedAutoplayMediaComponents(this);
+  });
 }
 
 void CanonicalBrowsingContext::SetFieldEpochsForChild(
     ContentParent* aChild, const BrowsingContext::FieldEpochs& aEpochs) {
   mChildFieldEpochs.Put(aChild->ChildID(), aEpochs);
 }
 
 const BrowsingContext::FieldEpochs&
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7863,17 +7863,19 @@ nsresult nsDocShell::RestoreFromHistory(
     childShell->SetAllowContentRetargetingOnChildren(
         allowContentRetargetingOnChildren);
     childShell->SetDefaultLoadFlags(defaultLoadFlags);
 
     rv = childShell->BeginRestore(nullptr, false);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  GetBrowsingContext()->RestoreChildren(std::move(contexts));
+  if (!contexts.IsEmpty()) {
+    GetBrowsingContext()->RestoreChildren(std::move(contexts));
+  }
 
   // Make sure to restore the window state after adding the child shells back
   // to the tree.  This is necessary for Thaw() and Resume() to propagate
   // properly.
   rv = privWin->RestoreWindowState(windowState);
   NS_ENSURE_SUCCESS(rv, rv);
 
   RefPtr<PresShell> presShell = GetPresShell();
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -2849,19 +2849,16 @@ nsresult Document::InitCSP(nsIChannel* a
     return NS_OK;
   }
 
   MOZ_LOG(gCspPRLog, LogLevel::Debug,
           ("Document is an add-on or CSP header specified %p", this));
 
   // ----- if the doc is an addon, apply its CSP.
   if (addonPolicy) {
-    nsCOMPtr<nsIAddonPolicyService> aps =
-        do_GetService("@mozilla.org/addons/policy-service;1");
-
     nsAutoString addonCSP;
     Unused << ExtensionPolicyService::GetSingleton().GetBaseCSP(addonCSP);
     csp->AppendPolicy(addonCSP, false, false);
 
     csp->AppendPolicy(addonPolicy->ContentSecurityPolicy(), false, false);
   }
 
   // ----- if there's a full-strength CSP header, apply it.
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1506,21 +1506,23 @@ void Navigator::OnNavigation() {
 }
 
 JSObject* Navigator::WrapObject(JSContext* cx,
                                 JS::Handle<JSObject*> aGivenProto) {
   return Navigator_Binding::Wrap(cx, this, aGivenProto);
 }
 
 /* static */
-bool Navigator::HasUserMediaSupport(JSContext* /* unused */,
-                                    JSObject* /* unused */) {
-  // Make enabling peerconnection enable getUserMedia() as well
-  return Preferences::GetBool("media.navigator.enabled", false) ||
-         Preferences::GetBool("media.peerconnection.enabled", false);
+bool Navigator::HasUserMediaSupport(JSContext* cx, JSObject* obj) {
+  // Make enabling peerconnection enable getUserMedia() as well.
+  // Emulate [SecureContext] unless media.devices.insecure.enabled=true
+  return (StaticPrefs::media_navigator_enabled() ||
+          StaticPrefs::media_peerconnection_enabled()) &&
+         (IsSecureContextOrObjectIsFromSecureContext(cx, obj) ||
+          StaticPrefs::media_devices_insecure_enabled());
 }
 
 /* static */
 already_AddRefed<nsPIDOMWindowInner> Navigator::GetWindowFromGlobal(
     JSObject* aGlobal) {
   nsCOMPtr<nsPIDOMWindowInner> win = xpc::WindowOrNull(aGlobal);
   return win.forget();
 }
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -75,16 +75,17 @@
 #include "mozilla/dom/BrowserBridgeChild.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/BrowserParent.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/XULCommandEvent.h"
 #include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/net/CookieSettings.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/Likely.h"
@@ -8209,16 +8210,17 @@ nsContentUtils::StorageAccess nsContentU
 
   if (Document* document = aWindow->GetExtantDoc()) {
     nsCOMPtr<nsIPrincipal> principal = document->NodePrincipal();
     // Note that GetChannel() below may return null, but that's OK, since the
     // callee is able to deal with a null channel argument, and if passed null,
     // will only fail to notify the UI in case storage gets blocked.
     nsIChannel* channel = document->GetChannel();
     return InternalStorageAllowedCheck(principal, aWindow, nullptr, channel,
+                                       document->CookieSettings(),
                                        *aRejectedReason);
   }
 
   // No document? Let's return a generic rejected reason.
   return StorageAccess::eDeny;
 }
 
 // static, public
@@ -8229,59 +8231,71 @@ nsContentUtils::StorageAccess nsContentU
   if (nsPIDOMWindowInner* inner = aDoc->GetInnerWindow()) {
     nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
     // Note that GetChannel() below may return null, but that's OK, since the
     // callee is able to deal with a null channel argument, and if passed null,
     // will only fail to notify the UI in case storage gets blocked.
     nsIChannel* channel = aDoc->GetChannel();
 
     uint32_t rejectedReason = 0;
-    return InternalStorageAllowedCheck(principal, inner, nullptr, channel,
-                                       rejectedReason);
+    return InternalStorageAllowedCheck(
+        principal, inner, nullptr, channel,
+        const_cast<Document*>(aDoc)->CookieSettings(), rejectedReason);
   }
 
   return StorageAccess::eDeny;
 }
 
 // static, public
 nsContentUtils::StorageAccess nsContentUtils::StorageAllowedForNewWindow(
     nsIPrincipal* aPrincipal, nsIURI* aURI, nsPIDOMWindowInner* aParent) {
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(aURI);
   // parent may be nullptr
 
   uint32_t rejectedReason = 0;
-  return InternalStorageAllowedCheck(aPrincipal, aParent, aURI, nullptr,
+  nsCOMPtr<nsICookieSettings> cs;
+  if (aParent && aParent->GetExtantDoc()) {
+    cs = aParent->GetExtantDoc()->CookieSettings();
+  } else {
+    cs = net::CookieSettings::Create();
+  }
+  return InternalStorageAllowedCheck(aPrincipal, aParent, aURI, nullptr, cs,
                                      rejectedReason);
 }
 
 // static, public
 nsContentUtils::StorageAccess nsContentUtils::StorageAllowedForChannel(
     nsIChannel* aChannel) {
   MOZ_DIAGNOSTIC_ASSERT(sSecurityManager);
   MOZ_DIAGNOSTIC_ASSERT(aChannel);
 
   nsCOMPtr<nsIPrincipal> principal;
   Unused << sSecurityManager->GetChannelResultPrincipal(
       aChannel, getter_AddRefs(principal));
   NS_ENSURE_TRUE(principal, nsContentUtils::StorageAccess::eDeny);
 
+  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+  nsCOMPtr<nsICookieSettings> cookieSettings;
+  nsresult rv = loadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
+  NS_ENSURE_SUCCESS(rv, nsContentUtils::StorageAccess::eDeny);
+
   uint32_t rejectedReason = 0;
   nsContentUtils::StorageAccess result = InternalStorageAllowedCheck(
-      principal, nullptr, nullptr, aChannel, rejectedReason);
+      principal, nullptr, nullptr, aChannel, cookieSettings, rejectedReason);
 
   return result;
 }
 
 // static, public
 nsContentUtils::StorageAccess nsContentUtils::StorageAllowedForServiceWorker(
-    nsIPrincipal* aPrincipal) {
+    nsIPrincipal* aPrincipal, nsICookieSettings* aCookieSettings) {
   uint32_t rejectedReason = 0;
   return InternalStorageAllowedCheck(aPrincipal, nullptr, nullptr, nullptr,
-                                     rejectedReason);
+                                     aCookieSettings, rejectedReason);
 }
 
 // static, private
 void nsContentUtils::GetCookieLifetimePolicyFromCookieSettings(
     nsICookieSettings* aCookieSettings, nsIPrincipal* aPrincipal,
     uint32_t* aLifetimePolicy) {
   *aLifetimePolicy = StaticPrefs::network_cookie_lifetimePolicy();
 
@@ -8400,21 +8414,20 @@ bool nsContentUtils::IsThirdPartyTrackin
       do_QueryInterface(document->GetChannel());
   if (!httpChannel) {
     return false;
   }
 
   return httpChannel->IsThirdPartyTrackingResource();
 }
 
-static bool StorageDisabledByAntiTrackingInternal(nsPIDOMWindowInner* aWindow,
-                                                  nsIChannel* aChannel,
-                                                  nsIPrincipal* aPrincipal,
-                                                  nsIURI* aURI,
-                                                  uint32_t& aRejectedReason) {
+static bool StorageDisabledByAntiTrackingInternal(
+    nsPIDOMWindowInner* aWindow, nsIChannel* aChannel, nsIPrincipal* aPrincipal,
+    nsIURI* aURI, nsICookieSettings* aCookieSettings,
+    uint32_t& aRejectedReason) {
   MOZ_ASSERT(aWindow || aChannel || aPrincipal);
 
   if (aWindow) {
     nsIURI* documentURI = aURI ? aURI : aWindow->GetDocumentURI();
     return !documentURI ||
            !AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
                aWindow, documentURI, &aRejectedReason);
   }
@@ -8431,27 +8444,40 @@ static bool StorageDisabledByAntiTrackin
       return false;
     }
 
     return !AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
         httpChannel, uri, &aRejectedReason);
   }
 
   MOZ_ASSERT(aPrincipal);
-  return !AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(aPrincipal);
+  return !AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
+      aPrincipal, aCookieSettings);
 }
 
 // static public
 bool nsContentUtils::StorageDisabledByAntiTracking(nsPIDOMWindowInner* aWindow,
                                                    nsIChannel* aChannel,
                                                    nsIPrincipal* aPrincipal,
                                                    nsIURI* aURI,
                                                    uint32_t& aRejectedReason) {
+  nsCOMPtr<nsICookieSettings> cookieSettings;
+  if (aWindow) {
+    if (aWindow->GetExtantDoc()) {
+      cookieSettings = aWindow->GetExtantDoc()->CookieSettings();
+    }
+  } else if (aChannel) {
+    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+    Unused << loadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
+  }
+  if (!cookieSettings) {
+    cookieSettings = net::CookieSettings::Create();
+  }
   bool disabled = StorageDisabledByAntiTrackingInternal(
-      aWindow, aChannel, aPrincipal, aURI, aRejectedReason);
+      aWindow, aChannel, aPrincipal, aURI, cookieSettings, aRejectedReason);
   if (sAntiTrackingControlCenterUIEnabled) {
     if (aWindow) {
       AntiTrackingCommon::NotifyBlockingDecision(
           aWindow,
           disabled ? AntiTrackingCommon::BlockingDecision::eBlock
                    : AntiTrackingCommon::BlockingDecision::eAllow,
           aRejectedReason);
     } else if (aChannel) {
@@ -8463,23 +8489,23 @@ bool nsContentUtils::StorageDisabledByAn
     }
   }
   return disabled;
 }
 
 // static, private
 nsContentUtils::StorageAccess nsContentUtils::InternalStorageAllowedCheck(
     nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aWindow, nsIURI* aURI,
-    nsIChannel* aChannel, uint32_t& aRejectedReason) {
+    nsIChannel* aChannel, nsICookieSettings* aCookieSettings,
+    uint32_t& aRejectedReason) {
   MOZ_ASSERT(aPrincipal);
 
   aRejectedReason = 0;
 
   StorageAccess access = StorageAccess::eAllow;
-  nsCOMPtr<nsICookieSettings> cookieSettings;
 
   // We don't allow storage on the null principal, in general. Even if the
   // calling context is chrome.
   if (aPrincipal->GetIsNullPrincipal()) {
     return StorageAccess::eDeny;
   }
 
   if (aWindow) {
@@ -8488,37 +8514,28 @@ nsContentUtils::StorageAccess nsContentU
     if (document && document->GetSandboxFlags() & SANDBOXED_ORIGIN) {
       return StorageAccess::eDeny;
     }
 
     // Check if we are in private browsing, and record that fact
     if (IsInPrivateBrowsing(document)) {
       access = StorageAccess::ePrivateBrowsing;
     }
-
-    if (document) {
-      cookieSettings = document->CookieSettings();
-    }
-  }
-
-  if (aChannel) {
-    nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
-    loadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
   }
 
   uint32_t lifetimePolicy;
 
   // WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior
   // and ACCEPT_NORMALLY as lifetimePolicy (See Bug 1406675 for rationale).
   auto policy = BasePrincipal::Cast(aPrincipal)->AddonPolicy();
 
   if (policy) {
     lifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
   } else {
-    GetCookieLifetimePolicyFromCookieSettings(cookieSettings, aPrincipal,
+    GetCookieLifetimePolicyFromCookieSettings(aCookieSettings, aPrincipal,
                                               &lifetimePolicy);
   }
 
   // Check if we should only allow storage for the session, and record that fact
   if (lifetimePolicy == nsICookieService::ACCEPT_SESSION) {
     // Storage could be StorageAccess::ePrivateBrowsing or StorageAccess::eAllow
     // so perform a std::min comparison to make sure we preserve
     // ePrivateBrowsing if it has been set.
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -3012,17 +3012,18 @@ class nsContentUtils {
    * permissions, mozIThirdPartyUtil.isThirdPartyChannel().
    */
   static StorageAccess StorageAllowedForChannel(nsIChannel* aChannel);
 
   /*
    * Checks if storage for the given principal is permitted by the user's
    * preferences. This method should be used only by ServiceWorker loading.
    */
-  static StorageAccess StorageAllowedForServiceWorker(nsIPrincipal* aPrincipal);
+  static StorageAccess StorageAllowedForServiceWorker(
+      nsIPrincipal* aPrincipal, nsICookieSettings* aCookieSettings);
 
   /*
    * Returns true if this document should disable storages because of the
    * anti-tracking feature.
    */
   static bool StorageDisabledByAntiTracking(Document* aDocument, nsIURI* aURI) {
     uint32_t rejectedReason = 0;
     // Note that GetChannel() below may return null, but that's OK, since the
@@ -3444,21 +3445,20 @@ class nsContentUtils {
    * are also checked.  If aURI is non-null, then it is used as the comparison
    * against aWindow to determine if this is a third-party load.  We also
    * allow a channel instead of the window reference when determining 3rd party
    * status.
    *
    * Used in the implementation of StorageAllowedForWindow,
    * StorageAllowedForChannel and StorageAllowedForServiceWorker.
    */
-  static StorageAccess InternalStorageAllowedCheck(nsIPrincipal* aPrincipal,
-                                                   nsPIDOMWindowInner* aWindow,
-                                                   nsIURI* aURI,
-                                                   nsIChannel* aChannel,
-                                                   uint32_t& aRejectedReason);
+  static StorageAccess InternalStorageAllowedCheck(
+      nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aWindow, nsIURI* aURI,
+      nsIChannel* aChannel, nsICookieSettings* aCookieSettings,
+      uint32_t& aRejectedReason);
 
   static nsINode* GetCommonAncestorHelper(nsINode* aNode1, nsINode* aNode2);
   static nsIContent* GetCommonFlattenedTreeAncestorHelper(
       nsIContent* aContent1, nsIContent* aContent2);
 
   static nsIXPConnect* sXPConnect;
 
   static nsIScriptSecurityManager* sSecurityManager;
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -188,17 +188,16 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 /******************************************************************/
 /* mozilla::EventStateManager                                     */
 /******************************************************************/
 
 static uint32_t sESMInstanceCount = 0;
 
-uint64_t EventStateManager::sUserInputCounter = 0;
 int32_t EventStateManager::sUserInputEventDepth = 0;
 int32_t EventStateManager::sUserKeyboardEventDepth = 0;
 bool EventStateManager::sNormalLMouseEventInProcess = false;
 EventStateManager* EventStateManager::sActiveESM = nullptr;
 Document* EventStateManager::sMouseOverDocument = nullptr;
 AutoWeakFrame EventStateManager::sLastDragOverFrame = nullptr;
 LayoutDeviceIntPoint EventStateManager::sPreLockPoint =
     LayoutDeviceIntPoint(0, 0);
@@ -4050,17 +4049,16 @@ bool EventStateManager::IsHandlingUserIn
 /*static*/
 bool EventStateManager::IsHandlingKeyboardInput() {
   return sUserKeyboardEventDepth > 0;
 }
 
 /*static*/
 void EventStateManager::StartHandlingUserInput(EventMessage aMessage) {
   ++sUserInputEventDepth;
-  ++sUserInputCounter;
   if (sUserInputEventDepth == 1) {
     sLatestUserInputStart = sHandlingInputStart = TimeStamp::Now();
   }
   if (WidgetEvent::IsKeyEventMessage(aMessage)) {
     ++sUserKeyboardEventDepth;
   }
 }
 
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -264,23 +264,16 @@ class EventStateManager : public nsSuppo
    * over events.  And the latter returns true when one of the user inputs
    * is an input from keyboard.  If these methods are called from asynchronously
    * executed code, such as during layout reflows, it will return false.
    */
   static bool IsHandlingUserInput();
   static bool IsHandlingKeyboardInput();
 
   /**
-   * Get the number of user inputs handled since process start. This
-   * includes anything that is initiated by user, with the exception
-   * of page load events or mouse over events.
-   */
-  static uint64_t UserInputCount() { return sUserInputCounter; }
-
-  /**
    * Get the timestamp at which the latest user input was handled.
    *
    * Guaranteed to be monotonic. Until the first user input, return
    * the epoch.
    */
   static TimeStamp LatestUserInputStart() { return sLatestUserInputStart; }
 
   nsPresContext* GetPresContext() { return mPresContext; }
@@ -1251,21 +1244,16 @@ class EventStateManager : public nsSuppo
   nsRefPtrHashtable<nsUint32HashKey, OverOutElementsWrapper>
       mPointersEnterLeaveHelper;
 
  public:
   static nsresult UpdateUserActivityTimer(void);
   // Array for accesskey support
   nsCOMArray<nsIContent> mAccessKeys;
 
-  // The number of user inputs handled since process start. This
-  // includes anything that is initiated by user, with the exception
-  // of page load events or mouse over events.
-  static uint64_t sUserInputCounter;
-
   // The current depth of user and keyboard inputs. sUserInputEventDepth
   // is the number of any user input events, page load events and mouse over
   // events.  sUserKeyboardEventDepth is the number of keyboard input events.
   // Incremented whenever we start handling a user input, decremented when we
   // have finished handling a user input. This depth is *not* reset in case
   // of nested event loops.
   static int32_t sUserInputEventDepth;
   static int32_t sUserKeyboardEventDepth;
--- a/dom/html/HTMLIFrameElement.cpp
+++ b/dom/html/HTMLIFrameElement.cpp
@@ -50,16 +50,20 @@ const DOMTokenListSupportedToken HTMLIFr
         nullptr};
 
 HTMLIFrameElement::HTMLIFrameElement(
     already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
     FromParser aFromParser)
     : nsGenericHTMLFrameElement(std::move(aNodeInfo), aFromParser) {
   // We always need a featurePolicy, even if not exposed.
   mFeaturePolicy = new FeaturePolicy(this);
+
+  nsCOMPtr<nsIPrincipal> origin = GetFeaturePolicyDefaultOrigin();
+  MOZ_ASSERT(origin);
+  mFeaturePolicy->SetDefaultOrigin(origin);
 }
 
 HTMLIFrameElement::~HTMLIFrameElement() {}
 
 NS_IMPL_ELEMENT_CLONE(HTMLIFrameElement)
 
 nsresult HTMLIFrameElement::BindToTree(Document* aDocument, nsIContent* aParent,
                                        nsIContent* aBindingParent) {
new file mode 100644
--- /dev/null
+++ b/dom/html/crashtests/1547057.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+    <script>
+      function start() {
+        const iframe = document.createElement('iframe')
+        iframe.policy.allowedFeatures()
+      }
+      window.addEventListener('load', start)
+    </script>
+</head>
+</html>
--- a/dom/html/crashtests/crashtests.list
+++ b/dom/html/crashtests/crashtests.list
@@ -83,8 +83,9 @@ load 1290904.html
 load 1343886-1.html
 load 1343886-2.xml
 load 1343886-3.xml
 load 1350972.html
 load 1386905.html
 asserts(0-4) load 1401726.html
 load 1412173.html
 load 1440523.html
+load 1547057.html
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2385,22 +2385,18 @@ void ContentParent::InitInternal(Process
   // This is only implemented (returns a non-empty list) by MacOSX and Linux
   // at present.
   nsTArray<SystemFontListEntry> fontList;
   gfxPlatform::GetPlatform()->ReadSystemFontList(&fontList);
   nsTArray<LookAndFeelInt> lnfCache = LookAndFeel::GetIntCache();
 
   // Content processes have no permission to access profile directory, so we
   // send the file URL instead.
-  StyleSheet* ucs = nsLayoutStylesheetCache::Singleton()->GetUserContentSheet();
-  if (ucs) {
-    SerializeURI(ucs->GetSheetURI(), xpcomInit.userContentSheetURL());
-  } else {
-    SerializeURI(nullptr, xpcomInit.userContentSheetURL());
-  }
+  nsIURI* ucsURI = nsLayoutStylesheetCache::Singleton()->GetUserContentCSSURL();
+  SerializeURI(ucsURI, xpcomInit.userContentSheetURL());
 
   // 1. Build ContentDeviceData first, as it may affect some gfxVars.
   gfxPlatform::GetPlatform()->BuildContentDeviceData(
       &xpcomInit.contentDeviceData());
   // 2. Gather non-default gfxVars.
   xpcomInit.gfxNonDefaultVarUpdates() = gfxVars::FetchNonDefaultVars();
   // 3. Start listening for gfxVars updates, to notify content process later on.
   gfxVars::AddReceiver(this);
@@ -5024,18 +5020,17 @@ mozilla::ipc::IPCResult ContentParent::R
     const uint32_t& aGeneration, const bool& aDefer, bool* aLoaded) {
   gfxPlatformFontList::PlatformFontList()->InitOtherFamilyNames(aGeneration,
                                                                 aDefer);
   *aLoaded = true;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvSetupFamilyCharMap(
-    const uint32_t& aGeneration,
-    const mozilla::fontlist::Pointer& aFamilyPtr) {
+    const uint32_t& aGeneration, const mozilla::fontlist::Pointer& aFamilyPtr) {
   gfxPlatformFontList::PlatformFontList()->SetupFamilyCharMap(aGeneration,
                                                               aFamilyPtr);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvGraphicsError(
     const nsCString& aError) {
   gfx::LogForwarder* lf = gfx::Factory::GetLogForwarder();
@@ -5706,26 +5701,19 @@ mozilla::ipc::IPCResult ContentParent::R
   if (!child) {
     RefPtr<BrowsingContextGroup> group =
         BrowsingContextGroup::Select(aInit.mParentId, aInit.mOpenerId);
     child = BrowsingContext::CreateFromIPC(std::move(aInit), group, this);
   }
 
   child->Attach(/* aFromIPC */ true);
 
-  for (auto iter = child->Group()->ContentParentsIter(); !iter.Done();
-       iter.Next()) {
-    nsRefPtrHashKey<ContentParent>* entry = iter.Get();
-    if (entry->GetKey() == this) {
-      continue;
-    }
-
-    Unused << entry->GetKey()->SendAttachBrowsingContext(
-        child->GetIPCInitializer());
-  }
+  child->Group()->EachOtherParent(this, [&](ContentParent* aParent) {
+    Unused << aParent->SendAttachBrowsingContext(child->GetIPCInitializer());
+  });
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvDetachBrowsingContext(
     BrowsingContext* aContext) {
   if (!aContext) {
     MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
@@ -5743,25 +5731,19 @@ mozilla::ipc::IPCResult ContentParent::R
     MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Warning,
             ("ParentIPC: Trying to detach out of process context 0x%08" PRIx64,
              aContext->Id()));
     return IPC_OK();
   }
 
   aContext->Detach(/* aFromIPC */ true);
 
-  for (auto iter = aContext->Group()->ContentParentsIter(); !iter.Done();
-       iter.Next()) {
-    nsRefPtrHashKey<ContentParent>* entry = iter.Get();
-    if (entry->GetKey() == this) {
-      continue;
-    }
-
-    Unused << entry->GetKey()->SendDetachBrowsingContext(aContext);
-  }
+  aContext->Group()->EachOtherParent(this, [&](ContentParent* aParent) {
+    Unused << aParent->SendDetachBrowsingContext(aContext);
+  });
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvCacheBrowsingContextChildren(
     BrowsingContext* aContext) {
   if (!aContext) {
     MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
@@ -5779,25 +5761,19 @@ mozilla::ipc::IPCResult ContentParent::R
     MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Warning,
             ("ParentIPC: Trying to cache out of process context 0x%08" PRIx64,
              aContext->Id()));
     return IPC_OK();
   }
 
   aContext->CacheChildren(/* aFromIPC */ true);
 
-  for (auto iter = aContext->Group()->ContentParentsIter(); !iter.Done();
-       iter.Next()) {
-    nsRefPtrHashKey<ContentParent>* entry = iter.Get();
-    if (entry->GetKey() == this) {
-      continue;
-    }
-
-    Unused << entry->GetKey()->SendCacheBrowsingContextChildren(aContext);
-  }
+  aContext->Group()->EachOtherParent(this, [&](ContentParent* aParent) {
+    Unused << aParent->SendCacheBrowsingContextChildren(aContext);
+  });
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult ContentParent::RecvRestoreBrowsingContextChildren(
     BrowsingContext* aContext, nsTArray<BrowsingContextId>&& aChildren) {
   if (!aContext) {
     MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Debug,
@@ -5822,26 +5798,19 @@ mozilla::ipc::IPCResult ContentParent::R
 
   for (auto id : aChildren) {
     RefPtr<BrowsingContext> child = BrowsingContext::Get(id);
     children.AppendElement(child);
   }
 
   aContext->RestoreChildren(std::move(children), /* aFromIPC */ true);
 
-  for (auto iter = aContext->Group()->ContentParentsIter(); !iter.Done();
-       iter.Next()) {
-    nsRefPtrHashKey<ContentParent>* entry = iter.Get();
-    if (entry->GetKey() == this) {
-      continue;
-    }
-
-    Unused << entry->GetKey()->SendRestoreBrowsingContextChildren(aContext,
-                                                                  aChildren);
-  }
+  aContext->Group()->EachOtherParent(this, [&](ContentParent* aParent) {
+    Unused << aParent->SendRestoreBrowsingContextChildren(aContext, aChildren);
+  });
 
   return IPC_OK();
 }
 
 void ContentParent::RegisterRemoteWorkerActor() { ++mRemoteWorkerActors; }
 
 void ContentParent::UnregisterRemoveWorkerActor() {
   MOZ_ASSERT(NS_IsMainThread());
@@ -5960,26 +5929,21 @@ mozilla::ipc::IPCResult ContentParent::R
 
   // Check if the transaction is valid.
   if (!aContext->Canonical()->ValidateTransaction(aTransaction, this)) {
     MOZ_LOG(BrowsingContext::GetLog(), LogLevel::Error,
             ("ParentIPC: Trying to run invalid transaction."));
     return IPC_FAIL_NO_REASON(this);
   }
 
-  for (auto iter = aContext->Group()->ContentParentsIter(); !iter.Done();
-       iter.Next()) {
-    auto* entry = iter.Get();
-    ContentParent* parent = entry->GetKey();
-    if (parent != this) {
-      Unused << parent->SendCommitBrowsingContextTransaction(
-          aContext, aTransaction,
-          aContext->Canonical()->GetFieldEpochsForChild(parent));
-    }
-  }
+  aContext->Group()->EachOtherParent(this, [&](ContentParent* aParent) {
+    Unused << aParent->SendCommitBrowsingContextTransaction(
+        aContext, aTransaction,
+        aContext->Canonical()->GetFieldEpochsForChild(aParent));
+  });
 
   aTransaction.Apply(aContext, this);
   aContext->Canonical()->SetFieldEpochsForChild(this, aEpochs);
 
   return IPC_OK();
 }
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -156,17 +156,17 @@ LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/bindings',
     '/dom/events',
     '/dom/filesystem',
     '/dom/geolocation',
     '/dom/media/webspeech/synth/ipc',
     '/dom/security',
     '/dom/storage',
-    '/extensions/cookie',
+    '/extensions/permissions',
     '/extensions/spellcheck/src',
     '/gfx/2d',
     '/hal/sandbox',
     '/js/xpconnect/loader',
     '/js/xpconnect/src',
     '/layout/base',
     '/media/webrtc',
     '/netwerk/base',
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -2351,16 +2351,19 @@ RefPtr<MediaManager::StreamPromise> Medi
   if (!docURI) {
     return StreamPromise::CreateAndReject(
         MakeRefPtr<MediaMgrError>(MediaMgrError::Name::AbortError), __func__);
   }
   bool isChrome = (aCallerType == dom::CallerType::System);
   bool privileged =
       isChrome ||
       Preferences::GetBool("media.navigator.permission.disabled", false);
+  bool isSecure = aWindow->IsSecureContext();
+  // Note: isHTTPS is for legacy telemetry only! Use isSecure for security, as
+  // it handles things like https iframes in http pages correctly.
   bool isHTTPS = false;
   bool isHandlingUserInput = EventStateManager::IsHandlingUserInput();
   docURI->SchemeIs("https", &isHTTPS);
   nsCString host;
   nsresult rv = docURI->GetHost(host);
   // Test for some other schemes that ServiceWorker recognizes
   bool isFile;
   docURI->SchemeIs("file", &isFile);
@@ -2405,18 +2408,19 @@ RefPtr<MediaManager::StreamPromise> Medi
 
   dom::Document* doc = aWindow->GetExtantDoc();
   if (NS_WARN_IF(!doc)) {
     return StreamPromise::CreateAndReject(
         MakeRefPtr<MediaMgrError>(MediaMgrError::Name::SecurityError),
         __func__);
   }
 
-  // Disallow access to null principal pages.
-  if (principal->GetIsNullPrincipal()) {
+  // Disallow access to null principal pages and http pages (unless pref)
+  if (principal->GetIsNullPrincipal() ||
+      !(isSecure || StaticPrefs::media_getusermedia_insecure_enabled())) {
     return StreamPromise::CreateAndReject(
         MakeRefPtr<MediaMgrError>(MediaMgrError::Name::NotAllowedError),
         __func__);
   }
 
   // This principal needs to be sent to different threads and so via IPC.
   // For this reason it's better to convert it to PrincipalInfo right now.
   ipc::PrincipalInfo principalInfo;
@@ -2765,18 +2769,19 @@ RefPtr<MediaManager::StreamPromise> Medi
             LOG("GetUserMedia: post enumeration EnumerateDevicesImpl "
                 "failure callback called!");
             return BadConstraintsPromise::CreateAndReject(std::move(aError),
                                                           __func__);
           })
       ->Then(
           GetCurrentThreadSerialEventTarget(), __func__,
           [self, windowID, c, windowListener, sourceListener, askPermission,
-           prefs, isHTTPS, isHandlingUserInput, callID, principalInfo, isChrome,
-           devices, resistFingerprinting](const char* badConstraint) mutable {
+           prefs, isSecure, isHandlingUserInput, callID, principalInfo,
+           isChrome, devices,
+           resistFingerprinting](const char* badConstraint) mutable {
             LOG("GetUserMedia: starting post enumeration promise2 success "
                 "callback!");
 
             // Ensure that the window is still good.
             RefPtr<nsPIDOMWindowInner> window =
                 nsGlobalWindowInner::GetInnerWindowWithId(windowID);
             if (!window || !self->IsWindowListenerStillActive(windowListener)) {
               LOG("GetUserMedia: bad window (%" PRIu64
@@ -2854,17 +2859,17 @@ RefPtr<MediaManager::StreamPromise> Medi
             array->AppendElement(callID);
 
             nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
             if (!askPermission) {
               obs->NotifyObservers(devicesCopy, "getUserMedia:privileged:allow",
                                    callID.BeginReading());
             } else {
               auto req = MakeRefPtr<GetUserMediaRequest>(
-                  window, callID, c, isHTTPS, isHandlingUserInput);
+                  window, callID, c, isSecure, isHandlingUserInput);
               if (!Preferences::GetBool("media.navigator.permission.force") &&
                   array->Length() > 1) {
                 // there is at least 1 pending gUM request
                 // For the scarySources test case, always send the
                 // request
                 self->mPendingGUMRequest.AppendElement(req.forget());
               } else {
                 obs->NotifyObservers(req, "getUserMedia:request", nullptr);
--- a/dom/media/PeerConnection.jsm
+++ b/dom/media/PeerConnection.jsm
@@ -1332,38 +1332,61 @@ class RTCPeerConnection {
     this._queueTaskWithClosedCheck(() => {
       if (this._negotiationNeeded) {
         this.dispatchEvent(new this._win.Event("negotiationneeded"));
       }
     });
   }
 
   _processTrackAdditionsAndRemovals() {
-    let postProcessing = {
-      updateStreamFunctions: [],
-      muteTracks: [],
-      trackEvents: [],
-    };
+    const removeList = [];
+    const addList = [];
+    const muteTracks = [];
+    const trackEventInits = [];
 
-    for (let transceiver of this._transceivers) {
+    for (const transceiver of this._transceivers) {
       transceiver.receiver.processTrackAdditionsAndRemovals(transceiver,
-                                                            postProcessing);
+        {removeList, addList, muteTracks, trackEventInits});
     }
 
-    for (let f of postProcessing.updateStreamFunctions) {
-      f();
+    muteTracks.forEach(track => {
+      // Check this as late as possible, in case JS has messed with this state.
+      if (!track.muted) {
+        track.mutedChanged(true);
+      }
+    });
+
+    for (const {stream, track} of removeList) {
+      // Check this as late as possible, in case JS messes with the track lists.
+      if (stream.getTracks().includes(track)) {
+        stream.removeTrack(track);
+        // Removing tracks from JS does not result in the stream getting a
+        // removetrack event, so we need to do that here.
+        stream.dispatchEvent(
+            new this._win.MediaStreamTrackEvent("removetrack", { track }));
+      }
     }
 
-    for (let t of postProcessing.muteTracks) {
-      t.mutedChanged(true);
+    for (const {stream, track} of addList) {
+      // Check this as late as possible, in case JS messes with the track lists.
+      if (!stream.getTracks().includes(track)) {
+        stream.addTrack(track);
+        // Adding tracks from JS does not result in the stream getting an
+        // addtrack event, so we need to do that here.
+        stream.dispatchEvent(
+            new this._win.MediaStreamTrackEvent("addtrack", { track }));
+      }
     }
 
-    for (let ev of postProcessing.trackEvents) {
-      this.dispatchEvent(ev);
-    }
+    trackEventInits.forEach(init => {
+      this.dispatchEvent(new this._win.RTCTrackEvent("track", init));
+      // Fire legacy event as well for a little bit.
+      this.dispatchEvent(new this._win.MediaStreamTrackEvent("addtrack",
+          { track: init.track }));
+    });
   }
 
   // TODO(Bug 1241291): Legacy event, remove eventually
   _fireLegacyAddStreamEvents() {
     for (let stream of this._newStreams) {
       let ev = new this._win.MediaStreamEvent("addstream", { stream });
       this.dispatchEvent(ev);
     }
@@ -1869,21 +1892,16 @@ class PeerConnectionObserver {
     chromeobj.makeStatsPublic();
     pc._onGetStatsSuccess(webidlobj);
   }
 
   onGetStatsError(message) {
     this._dompc._onGetStatsFailure(this.newError({name: "OperationError", message}));
   }
 
-  _getTransceiverWithRecvTrack(webrtcTrackId) {
-    return this._dompc.getTransceivers().find(
-        transceiver => transceiver.remoteTrackIdIs(webrtcTrackId));
-  }
-
   onTransceiverNeeded(kind, transceiverImpl) {
     this._dompc._onTransceiverNeeded(kind, transceiverImpl);
   }
 
   notifyDataChannel(channel) {
     this.dispatchEvent(new this._dompc._win.RTCDataChannelEvent("datachannel",
                                                                 { channel }));
   }
@@ -2090,25 +2108,25 @@ class RTCRtpSender {
 setupPrototype(RTCRtpSender, {
   classID: PC_SENDER_CID,
   contractID: PC_SENDER_CONTRACT,
   QueryInterface: ChromeUtils.generateQI([]),
 });
 
 class RTCRtpReceiver {
   constructor(pc, transceiverImpl) {
-    // We do not set the track here; that is done when _transceiverImpl is set
     Object.assign(this,
         {
           _pc: pc,
           _transceiverImpl: transceiverImpl,
           track: transceiverImpl.getReceiveTrack(),
-          _remoteSetSendBit: false,
-          _ontrackFired: false,
-          streamIds: [],
+          _recvBit: false,
+          _oldRecvBit: false,
+          _streams: [],
+          _oldstreams: [],
           // Sync and contributing sources must be kept cached so that timestamps
           // remain stable, as the timestamp offset can vary
           // note key = entry.source + entry.sourceType
           _rtpSources: new Map(),
           _rtpSourcesJsTimestamp: null,
         });
   }
 
@@ -2191,102 +2209,76 @@ class RTCRtpReceiver {
     return this._getRtpSourcesByType("contributing");
   }
 
   getSynchronizationSources() {
     return this._getRtpSourcesByType("synchronization");
   }
 
   setStreamIds(streamIds) {
-    this.streamIds = streamIds;
+    this._streams = streamIds.map(id => this._pc._getOrCreateStream(id));
   }
 
-  setRemoteSendBit(sendBit) {
-    this._remoteSetSendBit = sendBit;
+  setRecvBit(recvBit) {
+    this._recvBit = recvBit;
   }
 
   processTrackAdditionsAndRemovals(transceiver,
-                                   {updateStreamFunctions, muteTracks, trackEvents}) {
-    let streamsWithTrack = this.streamIds
-      .map(id => this._pc._getOrCreateStream(id));
-
-    let streamsWithoutTrack = this._pc.getRemoteStreams()
-      .filter(s => !this.streamIds.includes(s.id));
+      {removeList, addList, muteTracks, trackEventInits}) {
+    const receiver = this.__DOM_IMPL__;
+    const track = this.track;
+    const streams = this._streams;
+    const streamsAdded = streams.filter(s => !this._oldstreams.includes(s));
+    const streamsRemoved = this._oldstreams.filter(s => !streams.includes(s));
 
-    updateStreamFunctions.push(...streamsWithTrack.map(stream => () => {
-      if (!stream.getTracks().includes(this.track)) {
-        stream.addTrack(this.track);
-        // Adding tracks from JS does not result in the stream getting
-        // onaddtrack, so we need to do that here.
-        stream.dispatchEvent(
-            new this._pc._win.MediaStreamTrackEvent(
-              "addtrack", { track: this.track }));
-      }
-    }));
+    addList.push(...streamsAdded.map(stream => ({stream, track})));
+    removeList.push(...streamsRemoved.map(stream => ({stream, track})));
+    this._oldstreams = this._streams;
+
+    let needsTrackEvent = (streamsAdded.length != 0);
 
-    updateStreamFunctions.push(...streamsWithoutTrack.map(stream => () => {
-      // Content JS might remove this track from the stream before this function fires (ugh)
-      if (stream.getTracks().includes(this.track)) {
-        stream.removeTrack(this.track);
-        // Removing tracks from JS does not result in the stream getting
-        // onremovetrack, so we need to do that here.
-        stream.dispatchEvent(
-            new this._pc._win.MediaStreamTrackEvent(
-              "removetrack", { track: this.track }));
+    if (this._recvBit != this._oldRecvBit) {
+      this._oldRecvBit = this._recvBit;
+      if (this._recvBit) {
+        // New track, set in case streamsAdded is empty
+        needsTrackEvent = true;
+      } else {
+        muteTracks.push(track);
       }
-    }));
+    }
 
-    if (!this._remoteSetSendBit) {
-      // remote used "recvonly" or "inactive"
-      this._ontrackFired = false;
-      if (!this.track.muted) {
-        muteTracks.push(this.track);
-      }
-    } else if (!this._ontrackFired) {
-      // remote used "sendrecv" or "sendonly", and we haven't fired ontrack
-      let ev = new this._pc._win.RTCTrackEvent("track", {
-        receiver: this.__DOM_IMPL__,
-        track: this.track,
-        streams: streamsWithTrack,
-        transceiver });
-      trackEvents.push(ev);
-      this._ontrackFired = true;
-
-      // Fire legacy event as well for a little bit.
-      ev = new this._pc._win.MediaStreamTrackEvent("addtrack",
-          { track: this.track });
-      trackEvents.push(ev);
+    if (needsTrackEvent) {
+      trackEventInits.push({track, streams, receiver, transceiver});
     }
   }
 }
 setupPrototype(RTCRtpReceiver, {
   classID: PC_RECEIVER_CID,
   contractID: PC_RECEIVER_CONTRACT,
   QueryInterface: ChromeUtils.generateQI([]),
 });
 
 class RTCRtpTransceiver {
   constructor(pc, transceiverImpl, init, kind, sendTrack) {
     let receiver = pc._win.RTCRtpReceiver._create(
-        pc._win, new RTCRtpReceiver(pc, transceiverImpl, kind));
+        pc._win, new RTCRtpReceiver(pc, transceiverImpl));
     let streams = (init && init.streams) || [];
     let sender = pc._win.RTCRtpSender._create(
         pc._win, new RTCRtpSender(pc, transceiverImpl, this, sendTrack, kind, streams));
 
     let direction = (init && init.direction) || "sendrecv";
     Object.assign(this,
         {
           _pc: pc,
           mid: null,
           sender,
           receiver,
           stopped: false,
           _direction: direction,
           currentDirection: null,
-          _remoteTrackId: null,
           addTrackMagic: false,
           shouldRemove: false,
           _hasBeenUsedToSend: false,
           // the receiver starts out without a track, so record this here
           _kind: kind,
           _transceiverImpl: transceiverImpl,
         });
   }
@@ -2336,28 +2328,16 @@ class RTCRtpTransceiver {
   getKind() {
     return this._kind;
   }
 
   hasBeenUsedToSend() {
     return this._hasBeenUsedToSend;
   }
 
-  setRemoteTrackId(webrtcTrackId) {
-    this._remoteTrackId = webrtcTrackId;
-  }
-
-  remoteTrackIdIs(webrtcTrackId) {
-    return this._remoteTrackId == webrtcTrackId;
-  }
-
-  getRemoteTrackId() {
-    return this._remoteTrackId;
-  }
-
   setAddTrackMagic() {
     this.addTrackMagic = true;
   }
 
   sync() {
     if (this._syncing) {
       throw new DOMException("Reentrant sync! This is a bug!", "InternalError");
     }
--- a/dom/media/test/crashtests/crashtests.list
+++ b/dom/media/test/crashtests/crashtests.list
@@ -109,14 +109,15 @@ load convolver-memory-report-1.html
 skip-if(verify&&isDebugBuild&&gtkWidget) HTTP load media-element-source-seek-1.html
 skip-if(verify&&isDebugBuild&&gtkWidget) load offline-buffer-source-ended-1.html
 load oscillator-ended-1.html
 load oscillator-ended-2.html
 skip-if(Android&&AndroidVersion=='22') load video-replay-after-audio-end.html # bug 1315125, bug 1358876
 # This needs to run at the end to avoid leaking busted state into other tests.
 skip-if(Android) load 691096-1.html # Bug 1365451
 load 1236639.html
-test-pref(media.navigator.permission.disabled,true) load 1388372.html
+test-pref(media.navigator.permission.disabled,true) test-pref(media.getusermedia.insecure.enabled,true) test-pref(media.getusermedia.insecure.enabled,true) load 1388372.html
 load 1494073.html
 skip-if(Android) load 1526044.html # Bug 1528391
 load 1533909.html
+load empty-samples.webm # Bug 1540580
 test-pref(media.autoplay.block-webaudio,false) load 1545133.html
 load track-with-zero-dimensions.mp4
new file mode 100644
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -875,16 +875,17 @@ scheme=https
 [test_empty_resource.html]
 [test_error_in_video_document.html]
 [test_error_on_404.html]
 [test_fastSeek.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_fastSeek-forwards.html]
 skip-if = toolkit == 'android' # bug 1337590, android(bug 1232305)
 [test_imagecapture.html]
+scheme=https
 [test_info_leak.html]
 [test_invalid_reject.html]
 skip-if = android_version == '17' # android(bug 1232305)
 [test_invalid_reject_play.html]
 skip-if = android_version <= '17' # android(bug 1232305)
 [test_invalid_seek.html]
 [test_load.html]
 skip-if = android_version == '17' # android(bug 1232305)
@@ -898,16 +899,17 @@ skip-if = toolkit == 'android' # android
 [test_loop.html]
 skip-if = toolkit == 'android' # bug 1242112, android(bug 1232305)
 [test_looping_eventsOrder.html]
 [test_media_selection.html]
 [test_media_sniffer.html]
 skip-if = android_version == '17' # android(bug 1232305)
 [test_mediarecorder_avoid_recursion.html]
 skip-if = (os == 'win' && !debug) || (android_version == '17') # bug 1228605, android(bug 1232305)
+scheme=https
 tags=msg
 [test_mediarecorder_bitrate.html]
 skip-if = toolkit == 'android' # bug 1297432, android(bug 1232305)
 tags=msg
 [test_mediarecorder_creation.html]
 skip-if = android_version == '17' # android(bug 1232305)
 tags=msg capturestream
 [test_mediarecorder_creation_fail.html]
@@ -951,19 +953,21 @@ tags=msg
 [test_mediarecorder_record_upsize_resolution.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_downsize_resolution.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_gum_video_timeslice.html]
 skip-if = android_version == '17' # bug 1297298, android(bug 1232305)
+scheme=https
 tags=msg
 [test_mediarecorder_record_gum_video_timeslice_mixed.html]
 skip-if = android_version == '17' # bug 1297298, android(bug 1232305)
+scheme=https
 tags=msg
 [test_mediarecorder_record_immediate_stop.html]
 skip-if = android_version == '17' # android(bug 1232305)
 tags=msg capturestream
 [test_mediarecorder_record_no_timeslice.html]
 skip-if = android_version == '17' # bug 1306513, android(bug 1232305)
 tags=msg capturestream
 [test_mediarecorder_record_session.html]
@@ -979,42 +983,46 @@ skip-if = android_version == '17' # andr
 tags=msg capturestream
 [test_mediarecorder_state_transition.html]
 skip-if = android_version == '17' # android(bug 1232305)
 tags=msg capturestream
 [test_mediarecorder_state_event_order.html]
 skip-if = android_version == '17' # android(bug 1232305)
 tags=msg capturestream
 [test_mediarecorder_unsupported_src.html]
+scheme=https
 tags=msg
 [test_mediarecorder_webm_support.html]
 skip-if = android_version == '17' # android(bug 1232305)
 tags=msg
 [test_mediarecorder_record_getdata_afterstart.html]
 skip-if = android_version == '17' # bug 1240299, android(bug 1232305)
 tags=msg capturestream
 [test_mediatrack_consuming_mediaresource.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_mediatrack_consuming_mediastream.html]
 skip-if = android_version == '17' # android(bug 1232305)
+scheme=https
 tags=msg
 [test_mediatrack_events.html]
 skip-if = android_version == '17' # android(bug 1232305)
+scheme=https
 [test_mediatrack_parsing_ogg.html]
 skip-if = android_version == '17' # android(bug 1232305)
 [test_mediatrack_replay_from_end.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_metadata.html]
 [test_midflight_redirect_blocked.html]
 [test_mixed_principals.html]
 skip-if = toolkit == 'android' # bug 1309814, android(bug 1232305)
 [test_mozHasAudio.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 [test_multiple_mediastreamtracks.html]
 skip-if = android_version == '17' # android(bug 1232305)
+scheme=https
 [test_networkState.html]
 skip-if = android_version == '17' # android(bug 1232305)
 [test_new_audio.html]
 skip-if = toolkit == 'android' # bug 1372457
 [test_no_load_event.html]
 skip-if = android_version == '17' # android(bug 1232305)
 [test_paused.html]
 skip-if = android_version == '17' # android(bug 1232305)
@@ -1186,16 +1194,17 @@ tags=msg capturestream
 [test_streams_element_capture_twice.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg capturestream
 [test_streams_gc.html]
 skip-if = android_version == '17' || (android_version == '19' && debug) # android(bug 1232305)
 tags=msg capturestream
 [test_streams_individual_pause.html]
 skip-if = android_version == '17' || android_version == '19' # android(bug 1232305)
+scheme=https
 tags=msg
 [test_streams_srcObject.html]
 skip-if = toolkit == 'android' # bug 1300443, android(bug 1232305)
 tags=msg capturestream
 [test_streams_tracks.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=msg capturestream
 [test_texttrack.html]
--- a/dom/media/tests/crashtests/crashtests.list
+++ b/dom/media/tests/crashtests/crashtests.list
@@ -1,9 +1,9 @@
-default-preferences  pref(media.peerconnection.enabled,true) pref(media.navigator.permission.disabled,true) pref(dom.disable_open_during_load,false)
+default-preferences  pref(media.peerconnection.enabled,true) pref(media.navigator.permission.disabled,true) pref(dom.disable_open_during_load,false) pref(media.getusermedia.insecure.enabled,true) pref(media.getusermedia.insecure.enabled,true)
 
 load 780790.html
 load 791270.html
 load 791278.html
 load 791330.html
 load 799419.html
 load 802982.html
 load 812785.html
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -410,17 +410,16 @@ function setupEnvironment() {
       // We can't use the Fake H.264 GMP encoder with a real decoder until
       // bug 1509012 is done. So force using the Fake H.264 GMP decoder for now.
       ['media.navigator.mediadatadecoder_h264_enabled', false],
       ['media.peerconnection.enabled', true],
       ['media.peerconnection.identity.enabled', true],
       ['media.peerconnection.identity.timeout', 120000],
       ['media.peerconnection.ice.stun_client_maximum_transmits', 14],
       ['media.peerconnection.ice.trickle_grace_period', 30000],
-      ['media.peerconnection.remoteTrackId.enabled', true],
       ['media.peerconnection.rtpsourcesapi.enabled', true],
       ['media.navigator.permission.disabled', true],
       // If either fake audio or video is desired we enable fake streams.
       // If loopback devices are set they will be chosen instead of fakes in gecko.
       ['media.navigator.streams.fake', WANT_FAKE_AUDIO || WANT_FAKE_VIDEO],
       ['media.getusermedia.audiocapture.enabled', true],
       ['media.getusermedia.screensharing.enabled', true],
       ['media.getusermedia.window.focus_source.enabled', false],
--- a/dom/media/tests/mochitest/identity/mochitest.ini
+++ b/dom/media/tests/mochitest/identity/mochitest.ini
@@ -28,23 +28,28 @@ support-files =
   /.well-known/idp-proxy/idp-redirect-https-odd-path.js
   /.well-known/idp-proxy/idp-redirect-https-odd-path.js^headers^
   /.well-known/idp-min.js
   /.well-known/idp-proxy/idp-bad.js
 skip-if = android_version == '22' # bug 1358876, bug 1361325
 
 [test_fingerprints.html]
 skip-if = android_version == '22' # bug 1329257, bug 1358876, bug 1361325
+scheme=https
 [test_getIdentityAssertion.html]
 skip-if = android_version == '22' # bug 1358876, bug 1361325
 [test_setIdentityProvider.html]
 skip-if = android_version == '22' # bug 1358876, bug 1361325
+scheme=https
 [test_setIdentityProviderWithErrors.html]
 skip-if = android_version == '22' # bug 1358876, bug 1361325
+scheme=https
 [test_peerConnection_peerIdentity.html]
 skip-if = android_version == '22' # bug 1358876, bug 1361325
+scheme=https
 [test_peerConnection_asymmetricIsolation.html]
 skip-if = android_version == '22' # bug 1358876, bug 1361325
+scheme=https
 [test_loginNeeded.html]
 support-files =
   /.well-known/idp-proxy/login.html
   /.well-known/idp-proxy/idp.sjs
 skip-if = android_version == '22' # bug 1358876, bug 1361325
--- a/dom/media/tests/mochitest/mediaStreamPlayback.js
+++ b/dom/media/tests/mochitest/mediaStreamPlayback.js
@@ -162,17 +162,18 @@ function createHTML(options) {
 //
 // It relies on the fact that, by spec, device labels from enumerateDevices are
 // only visible during active gum calls. They're also visible when persistent
 // permissions are granted, so turn off media.navigator.permission.disabled
 // (which is normally on otherwise in our tests). Lastly, we must turn on
 // media.navigator.permission.fake otherwise fake devices don't count as active.
 
 var noGum = () => pushPrefs(["media.navigator.permission.disabled", false],
-                            ["media.navigator.permission.fake", true])
+                            ["media.navigator.permission.fake", true],
+                            ["media.devices.insecure.enabled", true])
   .then(() => navigator.mediaDevices.enumerateDevices())
   .then(([device]) => device &&
       is(device.label, "", "Test must leave no active gUM streams behind."));
 
 var runTest = testFunction => scriptsReady
   .then(() => runTestWhenReady(testFunction))
   .then(() => noGum())
   .then(() => finish());
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -1,11 +1,12 @@
 [DEFAULT]
 tags = msg webrtc
 subsuite = media
+scheme = https
 support-files =
   head.js
   dataChannel.js
   mediaStreamPlayback.js
   network.js
   nonTrickleIce.js
   pc.js
   templates.js
@@ -24,32 +25,36 @@ support-files =
   !/dom/media/test/320x240.ogv
   !/dom/media/test/r11025_s16_c1.wav
   !/dom/media/test/bug461281.ogg
   !/dom/media/test/seek.webm
   !/dom/media/test/gizmo.mp4
 
 [test_1488832.html]
 [test_a_noOp.html]
+scheme=http
 [test_dataChannel_basicAudio.html]
 skip-if = (android_version == '18') # Bug 962984 for debug, bug 963244 for opt
 [test_dataChannel_basicAudioVideo.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_basicAudioVideoNoBundle.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_basicAudioVideoCombined.html]
 skip-if = toolkit == 'android'  # Bug 1189784
 [test_dataChannel_basicDataOnly.html]
+scheme=http
 [test_dataChannel_basicVideo.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_bug1013809.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_dataChannel_dataOnlyBufferedAmountLow.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
+scheme=http
 [test_dataChannel_noOffer.html]
+scheme=http
 [test_enumerateDevices.html]
 [test_enumerateDevices_navigation.html]
 [test_groupId.html]
 [test_ondevicechange.html]
 skip-if = os == 'android' || verify
 [test_getUserMedia_active_autoplay.html]
 [test_getUserMedia_audioCapture.html]
 skip-if = toolkit == 'android' || (os == "win" && processor == "aarch64") # android(Bug 1189784, timeouts on 4.3 emulator), android(Bug 1264333), aarch64 due to 1538359
@@ -123,22 +128,26 @@ skip-if = (android_version == '18') # an
 [test_peerConnection_audioSynchronizationSourcesUnidirectional.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_audioContributingSources.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_checkPacketDumpHook.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioNATSrflx.html]
 skip-if = toolkit == 'android' || (verify && (os == 'linux')) || socketprocess_e10s # websockets don't work on android (bug 1266217, IPV6 is busted in try which causes timeouts in socket process case (bug 1521117))
+scheme=http
 [test_peerConnection_basicAudioNATRelay.html]
 skip-if = toolkit == 'android' || (verify && debug && (os == 'linux')) || socketprocess_e10s # websockets don't work on android (bug 1266217, IPV6 is busted in try which causes timeouts in socket process case (bug 1521117))
+scheme=http
 [test_peerConnection_basicAudioNATRelayTCP.html]
 skip-if = toolkit == 'android' || socketprocess_e10s # websockets don't work on android (bug 1266217, IPV6 is busted in try which causes timeouts in socket process case (bug 1521117))
+scheme=http
 [test_peerConnection_basicAudioNATRelayTLS.html]
 skip-if = true # need pyopenssl on builders, see bug 1323439 # toolkit == 'android' || socketprocess_e10s # websockets don't work on android (bug 1266217), IPV6 is busted in try which causes timeouts in socket process case (bug 1521117)
+scheme=http
 [test_peerConnection_basicAudioRequireEOC.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioPcmaPcmuOnly.html]
 skip-if = android_version == '18'
 [test_peerConnection_basicAudioDynamicPtMissingRtpmap.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
@@ -168,45 +177,56 @@ skip-if = (android_version == '18') # an
 # frequent timeouts/crashes on e10s (bug 1048455)
 skip-if = toolkit == 'android' # no screenshare on android
 [test_peerConnection_basicWindowshare.html]
 # frequent timeouts/crashes on e10s (bug 1048455)
 skip-if = toolkit == 'android' # no screenshare on android
 [test_peerConnection_basicH264Video.html]
 skip-if = toolkit == 'android' # Bug 1043403, Bug 1355786, Bug 1149374
 [test_peerConnection_bug822674.html]
+scheme=http
 [test_peerConnection_bug825703.html]
+scheme=http
 [test_peerConnection_bug827843.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_bug834153.html]
+scheme=http
 [test_peerConnection_bug1013809.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_bug1042791.html]
 skip-if = toolkit == 'android' # Bug 1043403, Bug 1355786, Bug 1149374
 [test_peerConnection_capturedVideo.html]
 tags=capturestream
 skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator), Bug 1264340
 [test_peerConnection_captureStream_canvas_2d.html]
 skip-if = android_version == '18' # android(Bug 1319019, timeouts on 4.3 emulator)
+scheme=http
 [test_peerConnection_captureStream_canvas_2d_noSSRC.html]
 skip-if = android_version == '18' # android(Bug 1319019, timeouts on 4.3 emulator)
+scheme=http
 [test_peerConnection_multiple_captureStream_canvas_2d.html]
 skip-if = (android_version == '18' && debug) # android(Bug 1189784, timeouts on 4.3 emulator)
+scheme=http
 [test_peerConnection_captureStream_canvas_webgl.html]
 # Cross process WebGL doesn't seem to work in the emulator
 skip-if = (toolkit == 'android' && e10s && is_emulator) || (android_version <= '17' || android_version == '18') # (<17: bug 1346630, 18: bug 1385950)
+scheme=http
 [test_peerConnection_certificates.html]
 disabled=bug 1180968
+scheme=http
 [test_peerConnection_close.html]
+scheme=http
 [test_peerConnection_closeDuringIce.html]
 [test_peerConnection_constructedStream.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_errorCallbacks.html]
+scheme=http
 [test_peerConnection_iceFailure.html]
 skip-if = os == 'linux' || os == 'mac' || os == 'win' || toolkit == 'android' # (Bug 1180388 for win, mac and linux), android(Bug 1189784, timeouts on 4.3 emulator), Bug 1180388
+scheme=http
 [test_peerConnection_insertDTMF.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_forwarding_basicAudioVideoCombined.html]
 skip-if = toolkit == 'android'  # Bug 1189784
 [test_peerConnection_maxFsConstraint.html]
 [test_peerConnection_noTrickleAnswer.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_noTrickleOffer.html]
@@ -217,16 +237,17 @@ skip-if = (android_version == '18') # an
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_offerRequiresReceiveVideo.html]
 [test_peerConnection_offerRequiresReceiveVideoAudio.html]
 skip-if = toolkit == 'android' # android(Bug 1189784)
 [test_peerConnection_promiseSendOnly.html]
 skip-if = (android_version == '18') # android(Bug 1189784, 1318809 timeouts on 4.3 emulator)
 [test_peerConnection_renderAfterRenegotiation.html]
 skip-if = (android_version == '18') # android(Bug 1189784, 1326005 timeouts on 4.3 emulator)
+scheme=http
 [test_peerConnection_restartIce.html]
 skip-if = toolkit == 'android'
 [test_peerConnection_restartIceNoBundle.html]
 skip-if = toolkit == 'android' || (os == "win" && processor == "aarch64") # aarch64 due to 1538440
 [test_peerConnection_restartIceNoBundleNoRtcpMux.html]
 skip-if = toolkit == 'android'
 [test_peerConnection_restartIceNoRtcpMux.html]
 skip-if = toolkit == 'android'
@@ -263,16 +284,17 @@ skip-if = android_version == '18' # andr
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_setParameters_scaleResolutionDownBy.html]
 skip-if = (android_version == '18') || (os == 'win' && processor == 'aarch64') # android(Bug 1189784, timeouts on 4.3 emulator), aarch64 due to bug 1537567
 [test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html]
 [test_peerConnection_setRemoteAnswerInStable.html]
 [test_peerConnection_setRemoteOfferInHaveLocalOffer.html]
 [test_peerConnection_throwInCallbacks.html]
 [test_peerConnection_toJSON.html]
+scheme=http
 [test_peerConnection_trackDisabling_clones.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_trackDisabling.html]
 skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator), Bug 1265878
 [test_peerConnection_twoAudioStreams.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_twoAudioTracksInOneStream.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
@@ -326,42 +348,46 @@ skip-if = android_version == '18' # andr
 skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator), android(Bug 1409203, failures on Autophone)
 [test_peerConnection_audioRenegotiationInactiveAnswer.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_videoRenegotiationInactiveAnswer.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_webAudio.html]
 tags = webaudio webrtc
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
+scheme=http
 [test_peerConnection_localRollback.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_localReofferRollback.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_remoteRollback.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_remoteReofferRollback.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_threeUnbundledConnections.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_selftest.html]
 # Bug 1227781: Crash with bogus TURN server.
+scheme=http
 [test_peerConnection_bug1227781.html]
+scheme=http
 [test_peerConnection_stats.html]
 skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator, Bug 1373858)
 [test_peerConnection_stats_relayProtocol.html]
 skip-if = toolkit == 'android' || socketprocess_e10s # android(Bug 1189784, timeouts on 4.3 emulator, Bug 1373858, Bug 1521117)
+scheme=http
 [test_peerConnection_sender_and_receiver_stats.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_trackless_sender_stats.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_verifyDescriptions.html]
 skip-if = (android_version == '18')
 [test_fingerprinting_resistance.html]
 [test_getUserMedia_nonDefaultRate.html]
 [test_peerConnection_nonDefaultRate.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_forceSampleRate.html]
+scheme=http
 [test_peerConnection_bug1512281.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 fail-if = 1
-
 [test_setSinkId.html]
 skip-if = os != 'linux' # the only platform with real devices
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -784,19 +784,18 @@ function PeerConnectionWrapper(label, co
   this.localRequiresTrickleIce = false;
   this.remoteRequiresTrickleIce = false;
   this.localMediaElements = [];
   this.remoteMediaElements = [];
   this.audioElementsOnly = false;
 
   this._sendStreams = [];
 
-  this.expectedLocalTrackInfoById = {};
-  this.expectedSignalledTrackInfoById = {};
-  this.observedRemoteTrackInfoById = {};
+  this.expectedLocalTrackInfo = [];
+  this.remoteStreamsByTrackId = new Map();
 
   this.disableRtpCountChecking = false;
 
   this.iceConnectedResolve;
   this.iceConnectedReject;
   this.iceConnected = new Promise((resolve, reject) => {
     this.iceConnectedResolve = resolve;
     this.iceConnectedReject = reject;
@@ -970,27 +969,22 @@ PeerConnectionWrapper.prototype = {
    *        MediaStream to use as container for `track` on remote side
    */
   attachLocalTrack : function(track, stream) {
     info("Got a local " + track.kind + " track");
 
     this.expectNegotiationNeeded();
     var sender = this._pc.addTrack(track, stream);
     is(sender.track, track, "addTrack returns sender");
+    is(this._pc.getSenders().pop(), sender, "Sender should be the last element in getSenders()");
 
     ok(track.id, "track has id");
     ok(track.kind, "track has kind");
     ok(stream.id, "stream has id");
-    this.expectedLocalTrackInfoById[track.id] = {
-      type: track.kind,
-      streamId: stream.id,
-    };
-    this.expectedSignalledTrackInfoById[track.id] =
-      this.expectedLocalTrackInfoById[track.id];
-
+    this.expectedLocalTrackInfo.push({track, sender, streamId: stream.id});
     this.addSendStream(stream);
 
     // This will create one media element per track, which might not be how
     // we set up things with the RTCPeerConnection. It's the only way
     // we can ensure all sent tracks are flowing however.
     this.ensureMediaElement(track, "local");
 
     return this.observedNegotiationNeeded;
@@ -1029,42 +1023,40 @@ PeerConnectionWrapper.prototype = {
       });
     }
 
     this.addSendStream(stream);
 
     stream.getTracks().forEach(track => {
       ok(track.id, "track has id");
       ok(track.kind, "track has kind");
-      this.expectedLocalTrackInfoById[track.id] = {
-          type: track.kind,
-          streamId: stream.id
-        };
-      this.expectedSignalledTrackInfoById[track.id] =
-        this.expectedLocalTrackInfoById[track.id];
+      const sender = this._pc.getSenders().find(s => s.track == track);
+      ok(sender, "track has a sender");
+      this.expectedLocalTrackInfo.push({track, sender, streamId: stream.id});
       this.ensureMediaElement(track, "local");
     });
 
     return this.observedNegotiationNeeded;
   },
 
   removeSender : function(index) {
     var sender = this._pc.getSenders()[index];
-    delete this.expectedLocalTrackInfoById[sender.track.id];
+    this.expectedLocalTrackInfo =
+      this.expectedLocalTrackInfo.filter(i => i.sender != sender);
     this.expectNegotiationNeeded();
     this._pc.removeTrack(sender);
     return this.observedNegotiationNeeded;
   },
 
   senderReplaceTrack : function(sender, withTrack, stream) {
-    delete this.expectedLocalTrackInfoById[sender.track.id];
-    this.expectedLocalTrackInfoById[withTrack.id] = {
-        type: withTrack.kind,
-        streamId: stream.id
-      };
+    const info = this.expectedLocalTrackInfo.find(i => i.sender == sender);
+    if (!info) {
+      return; // replaceTrack on a null track, probably
+    }
+    info.track = withTrack;
     this.addSendStream(stream);
     this.ensureMediaElement(withTrack, 'local');
     return sender.replaceTrack(withTrack);
   },
 
   getUserMedia : async function(constraints) {
     var stream = await getUserMedia(constraints);
     if (constraints.audio) {
@@ -1260,73 +1252,90 @@ PeerConnectionWrapper.prototype = {
         ok(signalingStateTransitions[oldstate].includes(newstate), this + ": legal signaling state transition from " + oldstate + " to " + newstate);
       } else {
         ok(false, this + ": old signaling state " + oldstate + " missing in signaling transition array");
       }
       this.signalingStateLog.push(newstate);
     });
   },
 
-  /**
-   * Checks whether a given track is expected, has not been observed yet, and
-   * is of the correct type. Then, moves the track from
-   * |expectedTrackInfoById| to |observedTrackInfoById|.
-   */
-  checkTrackIsExpected : function(trackId,
-                                  kind,
-                                  expectedTrackInfoById,
-                                  observedTrackInfoById) {
-    ok(expectedTrackInfoById[trackId], "track id " + trackId + " was expected");
-    ok(!observedTrackInfoById[trackId], "track id " + trackId + " was not yet observed");
-    var observedKind = kind;
-    var expectedKind = expectedTrackInfoById[trackId].type;
-    is(observedKind, expectedKind,
-        "track id " + trackId + " was of kind " +
-        observedKind + ", which matches " + expectedKind);
-    observedTrackInfoById[trackId] = expectedTrackInfoById[trackId];
-  },
-
   isTrackOnPC: function(track) {
     return !!this.getStreamForRecvTrack(track);
   },
 
   allExpectedTracksAreObserved: function(expected, observed) {
     return Object.keys(expected).every(trackId => observed[trackId]);
   },
 
-  getWebrtcTrackId: function(receiveTrack) {
-    let matchingTransceiver = this._pc.getTransceivers().find(
-        transceiver => transceiver.receiver.track == receiveTrack);
-    if (!matchingTransceiver) {
-      return null;
-    }
+  setupStreamEventHandlers: function(stream) {
+    const myTrackIds = new Set(stream.getTracks().map(t => t.id));
 
-    return matchingTransceiver.getRemoteTrackId();
+    stream.addEventListener('addtrack', ({track}) => {
+      ok(!myTrackIds.has(track.id), "Duplicate addtrack callback: "
+        + `stream id=${stream.id} track id=${track.id}`);
+      myTrackIds.add(track.id);
+      // addtrack events happen before track events, so the track callback hasn't
+      // heard about this yet.
+      let streams = this.remoteStreamsByTrackId.get(track.id);
+      ok(!streams || !streams.has(stream.id),
+        `In addtrack for stream id=${stream.id}` +
+        `there should not have been a track event for track id=${track.id} ` +
+        " containing this stream yet.");
+      ok(stream.getTracks().includes(track), "In addtrack, stream id=" +
+        `${stream.id} should already contain track id=${track.id}`);
+    });
+
+    stream.addEventListener('removetrack', ({track}) => {
+      ok(myTrackIds.has(track.id), "Duplicate removetrack callback: "
+        + `stream id=${stream.id} track id=${track.id}`);
+      myTrackIds.delete(track.id);
+      // Also remove the association from remoteStreamsByTrackId
+      const streams = this.remoteStreamsByTrackId.get(track.id);
+      ok(streams, `In removetrack for stream id=${stream.id}, track id=` +
+        `${track.id} should have had a track callback for the stream.`);
+      streams.delete(stream.id);
+      ok(!stream.getTracks().includes(track), "In removetrack, stream id=" +
+        `${stream.id} should not contain track id=${track.id}`);
+    });
   },
 
   setupTrackEventHandler: function() {
-    this._pc.addEventListener('track', event => {
-      info(this + ": 'ontrack' event fired for " + event.track.id +
-                  "(SDP msid is " + this.getWebrtcTrackId(event.track) +
-                  ")");
+    this._pc.addEventListener('track', ({track, streams}) => {
+      info(`${this}: 'ontrack' event fired for ${track.id}`);
+      ok(this.isTrackOnPC(track), `Found track ${track.id}`);
+
+      let gratuitousEvent = true;
+      let streamsContainingTrack = this.remoteStreamsByTrackId.get(track.id);
+      if (!streamsContainingTrack) {
+        gratuitousEvent = false; // Told us about a new track
+        this.remoteStreamsByTrackId.set(track.id, new Set());
+        streamsContainingTrack = this.remoteStreamsByTrackId.get(track.id);
+      }
+
+      for (const stream of streams) {
+        ok(stream.getTracks().includes(track),
+          `In track event, track id=${track.id}` +
+          ` should already be in stream id=${stream.id}`);
 
-      // TODO(bug 1403238): Checking for remote tracks needs to be completely
-      // reworked, because with the latest spec the identifiers aren't the same
-      // as they are on the other end. Ultimately, what we need to check is
-      // whether the _transceivers_ are in line with what is expected, and
-      // whether the callbacks are consistent with the transceivers.
-      let trackId = this.getWebrtcTrackId(event.track);
-      ok(!this.observedRemoteTrackInfoById[trackId],
-         "track id " + trackId + " was not yet observed");
-      this.observedRemoteTrackInfoById[trackId] = {
-        type: event.track.kind
-      };
-      ok(this.isTrackOnPC(event.track), "Found track " + event.track.id);
+        if (!streamsContainingTrack.has(stream.id)) {
+          gratuitousEvent = false; // Told us about a new stream
+          streamsContainingTrack.add(stream.id);
+          this.setupStreamEventHandlers(stream);
+        }
+      }
 
-      this.ensureMediaElement(event.track, 'remote');
+      ok(!gratuitousEvent, "track event told us something new")
+
+      // So far, we've verified consistency between the current state of the
+      // streams, addtrack/removetrack events on the streams, and track events
+      // on the peerconnection. We have also verified that we have not gotten
+      // any gratuitous events. We have not done anything to verify that the
+      // current state of affairs matches what we were expecting it to.
+
+      this.ensureMediaElement(track, 'remote');
     });
   },
 
   /**
    * Either adds a given ICE candidate right away or stores it to be added
    * later, depending on the state of the PeerConnection.
    *
    * @param {object} candidate
@@ -1443,64 +1452,74 @@ PeerConnectionWrapper.prototype = {
 
       ok(typeof anEvent.candidate.sdpMLineIndex === 'number', "SDP MLine Index needs to exist");
       this._local_ice_candidates.push(anEvent.candidate);
       candidateHandler(this.label, anEvent.candidate);
     };
   },
 
   checkLocalMediaTracks : function() {
-    var observed = {};
-    info(this + " Checking local tracks " + JSON.stringify(this.expectedLocalTrackInfoById));
-    this._pc.getSenders().forEach(sender => {
-      if (sender.track) {
-        this.checkTrackIsExpected(sender.track.id,
-                                  sender.track.kind,
-                                  this.expectedLocalTrackInfoById,
-                                  observed);
-      }
+    info(`${this}: Checking local tracks ${JSON.stringify(this.expectedLocalTrackInfo)}`);
+    const sendersWithTrack = this._pc.getSenders().filter(({track}) => track);
+    is(sendersWithTrack.length, this.expectedLocalTrackInfo.length,
+      "The number of senders with a track should be equal to the number of " +
+      "expected local tracks.");
+
+    // expectedLocalTrackInfo is in the same order that the tracks were added, and
+    // so should the output of getSenders.
+    this.expectedLocalTrackInfo.forEach((info, i) => {
+      const sender = sendersWithTrack[i];
+      is(sender, info.sender, `Sender ${i} should match`);
+      is(sender.track, info.track, `Track ${i} should match`);
     });
-
-    Object.keys(this.expectedLocalTrackInfoById).forEach(
-        id => ok(observed[id], this + " local id " + id + " was observed"));
   },
 
   /**
    * Checks that we are getting the media tracks we expect.
    */
   checkMediaTracks : function() {
     this.checkLocalMediaTracks();
   },
 
-  checkMsids: function() {
-    var checkSdpForMsids = (desc, expectedTrackInfo, side) => {
-      Object.keys(expectedTrackInfo).forEach(trackId => {
-        var streamId = expectedTrackInfo[trackId].streamId;
-        ok(desc.sdp.match(new RegExp("a=msid:" + streamId + " " + trackId)),
-           this + ": " + side + " SDP contains stream " + streamId +
-           " and track " + trackId );
-      });
+  checkLocalMsids: function() {
+    const sdp = this.localDescription.sdp;
+    const msections = sdputils.getMSections(sdp);
+    const expectedStreamIdCounts = new Map();
+    for (const {track, sender, streamId} of this.expectedLocalTrackInfo) {
+      const transceiver = this._pc.getTransceivers().find(t => t.sender == sender);
+      ok(transceiver, "There should be a transceiver for each sender");
+      if (transceiver.mid) {
+        const midFinder = new RegExp(`^a=mid:${transceiver.mid}$`, "m");
+        const msection = msections.find(m => m.match(midFinder));
+        ok(msection, `There should be a media section for mid = ${transceiver.mid}`);
+        ok(msection.startsWith(`m=${track.kind}`),
+          `Media section should be of type ${track.kind}`);
+        const msidFinder = new RegExp(`^a=msid:${streamId} \\S+$`, "m");
+        ok(msection.match(msidFinder),
+          `Should find a=msid:${streamId} in media section`
+          + " (with any track id for now)");
+        const count = expectedStreamIdCounts.get(streamId) || 0;
+        expectedStreamIdCounts.set(streamId, count + 1);
+      }
     };
 
-    checkSdpForMsids(this.localDescription, this.expectedSignalledTrackInfoById,
-                     "local");
-  },
-
-  markRemoteTracksAsNegotiated: function() {
-    Object.values(this.observedRemoteTrackInfoById).forEach(
-        trackInfo => trackInfo.negotiated = true);
-  },
-
-  rollbackRemoteTracksIfNotNegotiated: function() {
-    Object.keys(this.observedRemoteTrackInfoById).forEach(
-        id => {
-          if (!this.observedRemoteTrackInfoById[id].negotiated) {
-            delete this.observedRemoteTrackInfoById[id];
-          }
-        });
+    // Check for any unexpected msids.
+    const allMsids = sdp.match(new RegExp("^a=msid:\\S+", "mg"));
+    if (!allMsids) {
+      return;
+    }
+    const allStreamIds =
+      allMsids.map(msidAttr => msidAttr.replace('a=msid:', ''));
+    allStreamIds.forEach(id => {
+      const count = expectedStreamIdCounts.get(id);
+      ok(count, `Unexpected stream id ${id} found in local description.`);
+      if (count) {
+        expectedStreamIdCounts.set(id, count - 1);
+      }
+    });
   },
 
   /**
    * Check that media flow is present for the given media element by checking
    * that it reaches ready state HAVE_ENOUGH_DATA and progresses time further
    * than the start of the check.
    *
    * This ensures, that the stream being played is producing
@@ -1591,34 +1610,22 @@ PeerConnectionWrapper.prototype = {
                (t.currentDirection != "inactive") &&
                (t.currentDirection != "sendonly");
       })
       .map(t => {
         info("Found transceiver that should be receiving RTP: mid=" + t.mid +
              " currentDirection=" + t.currentDirection + " kind=" +
              t.receiver.track.kind + " track-id=" + t.receiver.track.id);
         return t.receiver.track;
-      });
+      })
+      .filter(t => t);
   },
 
   getExpectedSendTracks : function() {
-    return Object.keys(this.expectedLocalTrackInfoById)
-              .map(id => this.findSendTrackByWebrtcId(id));
-  },
-
-  findReceiveTrackByWebrtcId : function(webrtcId) {
-    return this._pc.getReceivers().map(receiver => receiver.track)
-              .find(track => this.getWebrtcTrackId(track) == webrtcId);
-  },
-
-  // Send tracks use the same identifiers that go in the signaling
-  findSendTrackByWebrtcId : function(webrtcId) {
-    return this._pc.getSenders().map(sender => sender.track)
-              .filter(track => track) // strip out null
-              .find(track => track.id == webrtcId);
+    return this._pc.getSenders().map(s => s.track).filter(t => t);
   },
 
   /**
    * Wait for presence of video flow on all media elements and rtp flow on
    * all sending and receiving track involved in this test.
    *
    * @returns {Promise}
    *        A promise that resolves when media flows for all elements and tracks
@@ -1868,17 +1875,17 @@ PeerConnectionWrapper.prototype = {
     }
 
     var nin = this._pc.getTransceivers()
       .filter(t => {
         return !t.stopped &&
                (t.currentDirection != "inactive") &&
                (t.currentDirection != "sendonly");
       }).length;
-    var nout = Object.keys(this.expectedLocalTrackInfoById).length;
+    const nout = Object.keys(this.expectedLocalTrackInfo).length;
     var ndata = this.dataChannels.length;
 
     // TODO(Bug 957145): Restore stronger inbound-rtp test once Bug 948249 is fixed
     //is((counters["inbound-rtp"] || 0), nin, "Have " + nin + " inbound-rtp stat(s)");
     ok((counters["inbound-rtp"] || 0) >= nin, "Have at least " + nin + " inbound-rtp stat(s) *");
 
     is(counters["outbound-rtp"] || 0, nout, "Have " + nout + " outbound-rtp stat(s)");
 
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -192,30 +192,16 @@ var commandsPeerConnectionOfferAnswer = 
   function PC_LOCAL_SETUP_ICE_HANDLER(test) {
     test.pcLocal.setupIceCandidateHandler(test);
   },
 
   function PC_REMOTE_SETUP_ICE_HANDLER(test) {
     test.pcRemote.setupIceCandidateHandler(test);
   },
 
-  function PC_LOCAL_STEEPLECHASE_SIGNAL_EXPECTED_LOCAL_TRACKS(test) {
-    if (test.testOptions.steeplechase) {
-      send_message({"type": "local_expected_tracks",
-                    "expected_tracks": test.pcLocal.expectedLocalTrackInfoById});
-    }
-  },
-
-  function PC_REMOTE_STEEPLECHASE_SIGNAL_EXPECTED_LOCAL_TRACKS(test) {
-    if (test.testOptions.steeplechase) {
-      send_message({"type": "remote_expected_tracks",
-                    "expected_tracks": test.pcRemote.expectedLocalTrackInfoById});
-    }
-  },
-
   function PC_LOCAL_CREATE_OFFER(test) {
     return test.createOffer(test.pcLocal).then(offer => {
       is(test.pcLocal.signalingState, STABLE,
          "Local create offer does not change signaling state");
     });
   },
 
   function PC_LOCAL_STEEPLECHASE_SIGNAL_OFFER(test) {
@@ -296,18 +282,17 @@ var commandsPeerConnectionOfferAnswer = 
       });
   },
 
   function PC_REMOTE_SET_LOCAL_DESCRIPTION(test) {
     return test.setLocalDescription(test.pcRemote, test.originalAnswer, STABLE)
       .then(() => {
         is(test.pcRemote.signalingState, STABLE,
            "signalingState after remote setLocalDescription is 'stable'");
-      })
-      .then(() => test.pcRemote.markRemoteTracksAsNegotiated());
+      });
   },
 
   function PC_LOCAL_GET_ANSWER(test) {
     if (!test.testOptions.steeplechase) {
       test._remote_answer = test.originalAnswer;
       test._answer_constraints = test.pcRemote.constraints;
       return Promise.resolve();
     }
@@ -319,18 +304,17 @@ var commandsPeerConnectionOfferAnswer = 
     });
   },
 
   function PC_LOCAL_SET_REMOTE_DESCRIPTION(test) {
     return test.setRemoteDescription(test.pcLocal, test._remote_answer, STABLE)
       .then(() => {
         is(test.pcLocal.signalingState, STABLE,
            "signalingState after local setRemoteDescription is 'stable'");
-      })
-      .then(() => test.pcLocal.markRemoteTracksAsNegotiated());
+      });
   },
 
   function PC_REMOTE_SANE_LOCAL_SDP(test) {
     test.pcRemote.localRequiresTrickleIce =
       sdputils.verifySdp(test._remote_answer, "answer",
                          test._offer_constraints, test._offer_options,
                          test.testOptions);
   },
@@ -410,20 +394,20 @@ var commandsPeerConnectionOfferAnswer = 
 
   function PC_REMOTE_CHECK_ICE_CONNECTIONS(test) {
     return test.pcRemote.getStats().then(stats => {
       test.pcRemote.checkStatsIceConnections(stats, test.testOptions);
     });
   },
 
   function PC_LOCAL_CHECK_MSID(test) {
-    return test.pcLocal.checkMsids();
+    return test.pcLocal.checkLocalMsids();
   },
   function PC_REMOTE_CHECK_MSID(test) {
-    return test.pcRemote.checkMsids();
+    return test.pcRemote.checkLocalMsids();
   },
 
   function PC_LOCAL_CHECK_TRACK_STATS(test) {
     return checkAllTrackStats(test.pcLocal);
   },
   function PC_REMOTE_CHECK_TRACK_STATS(test) {
     return checkAllTrackStats(test.pcRemote);
   },
--- a/dom/media/tests/mochitest/test_enumerateDevices.html
+++ b/dom/media/tests/mochitest/test_enumerateDevices.html
@@ -77,17 +77,17 @@ runTest(async () => {
                      () => gUM({ video: { deviceId: { exact: unknownId } } }));
   await mustFailWith("unknown exact deviceId on audio",
                      "OverconstrainedError", "deviceId",
                      () => gUM({ audio: { deviceId: { exact: unknownId } } }));
 
   // Check that deviceIds are stable for same origin and differ across origins.
 
   const path = "/tests/dom/media/tests/mochitest/test_enumerateDevices_iframe.html";
-  const origins = ["http://mochi.test:8888", "http://test1.mochi.test:8888"];
+  const origins = ["https://example.com", "https://test1.example.com"];
   info(window.location);
 
   let haveDevicesMap = new Promise(resolve => {
     let map = new Map();
     window.addEventListener("message", ({origin, data}) => {
       ok(origins.includes(origin), "Got message from expected origin");
       map.set(origin, JSON.parse(data));
       if (map.size < origins.length) return;
--- a/dom/media/tests/mochitest/test_enumerateDevices_iframe.html
+++ b/dom/media/tests/mochitest/test_enumerateDevices_iframe.html
@@ -9,15 +9,15 @@
 
 var pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
 var gUM = c => navigator.mediaDevices.getUserMedia(c);
 
 (async () => {
   await pushPrefs(["media.navigator.streams.fake", true]);
 
   let devices = await navigator.mediaDevices.enumerateDevices();
-  parent.postMessage(JSON.stringify(devices), "http://mochi.test:8888");
+  parent.postMessage(JSON.stringify(devices), "https://example.com:443");
 
 })().catch(e => setTimeout(() => { throw e; }));
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_getUserMedia_permission.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_permission.html
@@ -36,31 +36,31 @@ runTest(async () => {
       let message;
       let stream;
       try {
         stream = await gUM({ video: true });
         message = 'success';
       } catch(e) {
         message = e.name;
       }
-      parent.postMessage(message, 'http://mochi.test:8888');
+      parent.postMessage(message, 'https://example.com:443');
 
       if (message == "success") {
         stream.getTracks().forEach(track => track.stop());
       }
     } catch (e) {
       setTimeout(() => { throw e; });
     }
   }
 
   const source = `<html\><script\>(${sourceFn.toString()})()</script\></html\>`;
 
   // Test gUM in sandboxed vs. regular iframe.
 
-  for (const origin of ["http://mochi.test:8888", "http://test1.mochi.test:8888"]) {
+  for (const origin of ["https://example.com", "https://test1.example.com"]) {
     const src = origin + path;
     is(await iframeGum({ src, sandbox: "allow-scripts" }),
        "NotAllowedError", "gUM fails in sandboxed iframe " + origin);
     is(await iframeGum({ src, sandbox: "allow-scripts allow-same-origin " + origin }),
        "success", "gUM works in regular iframe");
   }
 
   // Test gUM in sandboxed vs regular srcdoc iframe
--- a/dom/media/tests/mochitest/test_getUserMedia_permission_iframe.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_permission_iframe.html
@@ -13,17 +13,17 @@ const gUM = c => navigator.mediaDevices.
   let message;
   let stream;
   try {
     stream = await gUM({ video: true });
     message = "success";
   } catch(e) {
     message = e.name;
   }
-  parent.postMessage(message, "http://mochi.test:8888");
+  parent.postMessage(message, "https://example.com:443");
 
   if (message == "success") {
     stream.getTracks().forEach(track => track.stop());
   }
 })().catch(e => setTimeout(() => { throw e; }));
 </script>
 </pre>
 </body>
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelay.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelay.html
@@ -7,38 +7,35 @@
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1231975",
     title: "Basic audio-only peer connection with port dependent NAT"
   });
 
-  var test;
-  runNetworkTest(options => {
-    SpecialPowers.pushPrefEnv(
-      {
-        'set': [
-          ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
-          ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT']
-        ]
-      }, function (options) {
-        options = options || {};
-        options.expectedLocalCandidateType = "srflx";
-        options.expectedRemoteCandidateType = "relay";
-        // If both have TURN, it is a toss-up which one will end up using a
-        // relay.
-        options.turn_disabled_local = true;
-        test = new PeerConnectionTest(options);
-        // Make sure we don't end up choosing the wrong thing due to delays in
-        // trickle. Once we are willing to accept trickle after ICE success, we
-        // can maybe wait a bit to allow things to stabilize.
-        // TODO(bug 1238249)
-        makeOffererNonTrickle(test.chain);
-        makeAnswererNonTrickle(test.chain);
-        test.setMediaConstraints([{audio: true}], [{audio: true}]);
-        test.run();
-      })
+  // This test uses the NAT simulator, which doesn't work in https, so we turn
+  // on getUserMedia in http.
+  runNetworkTest(async (options = {}) => {
+    await pushPrefs(
+        ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
+        ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
+        ['media.devices.insecure.enabled', true],
+        ['media.getusermedia.insecure.enabled', true]);
+    options.expectedLocalCandidateType = "srflx";
+    options.expectedRemoteCandidateType = "relay";
+    // If both have TURN, it is a toss-up which one will end up using a
+    // relay.
+    options.turn_disabled_local = true;
+    const test = new PeerConnectionTest(options);
+    // Make sure we don't end up choosing the wrong thing due to delays in
+    // trickle. Once we are willing to accept trickle after ICE success, we
+    // can maybe wait a bit to allow things to stabilize.
+    // TODO(bug 1238249)
+    makeOffererNonTrickle(test.chain);
+    makeAnswererNonTrickle(test.chain);
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.run();
   }, { useIceServer: true });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelayTCP.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelayTCP.html
@@ -1,37 +1,34 @@
-<!DOCTYPE HTML>
+<!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1231975",
     title: "Basic audio-only peer connection with port dependent NAT that blocks UDP"
   });
 
-  var test;
-  runNetworkTest(options => {
-    SpecialPowers.pushPrefEnv(
-      {
-        'set': [
-          ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
-          ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
-          ['media.peerconnection.nat_simulator.block_udp', true]
-        ]
-      }, function (options) {
-        options = options || {};
-        options.expectedLocalCandidateType = "relay-tcp";
-        options.expectedRemoteCandidateType = "relay-tcp";
-        // No reason to wait for gathering to complete like the other NAT tests,
-        // since relayed-tcp is the only thing that can work.
-        test = new PeerConnectionTest(options);
-        test.setMediaConstraints([{audio: true}], [{audio: true}]);
-        test.run();
-      })
+  // This test uses the NAT simulator, which doesn't work in https, so we turn
+  // on getUserMedia in http.
+  runNetworkTest(async (options = {}) => {
+    await pushPrefs(
+        ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
+        ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
+        ['media.peerconnection.nat_simulator.block_udp', true],
+        ['media.devices.insecure.enabled', true],
+        ['media.getusermedia.insecure.enabled', true]);
+    options.expectedLocalCandidateType = "relay-tcp";
+    options.expectedRemoteCandidateType = "relay-tcp";
+    // No reason to wait for gathering to complete like the other NAT tests,
+    // since relayed-tcp is the only thing that can work.
+    const test = new PeerConnectionTest(options);
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.run();
   }, { useIceServer: true });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelayTLS.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioNATRelayTLS.html
@@ -1,38 +1,35 @@
-<!DOCTYPE HTML>
+<!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1231975",
     title: "Basic audio-only peer connection with port dependent NAT that blocks STUN"
   });
 
-  var test;
-  runNetworkTest(options => {
-    SpecialPowers.pushPrefEnv(
-      {
-        'set': [
-          ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
-          ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
-          ['media.peerconnection.nat_simulator.block_udp', true],
-          ['media.peerconnection.nat_simulator.block_tcp', true]
-        ]
-      }, function (options) {
-        options = options || {};
-        options.expectedLocalCandidateType = "relay-tcp";
-        options.expectedRemoteCandidateType = "relay-tcp";
-        // No reason to wait for gathering to complete like the other NAT tests,
-        // since relayed-tcp is the only thing that can work.
-        test = new PeerConnectionTest(options);
-        test.setMediaConstraints([{audio: true}], [{audio: true}]);
-        test.run();
-      })
+  // This test uses the NAT simulator, which doesn't work in https, so we turn
+  // on getUserMedia in http.
+  runNetworkTest(async (options = {}) => {
+    await pushPrefs(
+        ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
+        ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
+        ['media.peerconnection.nat_simulator.block_udp', true],
+        ['media.peerconnection.nat_simulator.block_tcp', true],
+        ['media.devices.insecure.enabled', true],
+        ['media.getusermedia.insecure.enabled', true]);
+    options.expectedLocalCandidateType = "relay-tcp";
+    options.expectedRemoteCandidateType = "relay-tcp";
+    // No reason to wait for gathering to complete like the other NAT tests,
+    // since relayed-tcp is the only thing that can work.
+    const test = new PeerConnectionTest(options);
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.run();
   }, { useIceServer: true });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_basicAudioNATSrflx.html
+++ b/dom/media/tests/mochitest/test_peerConnection_basicAudioNATSrflx.html
@@ -1,41 +1,38 @@
-<!DOCTYPE HTML>
+<!DOCTYPE HTML>
 <html>
 <head>
   <script type="application/javascript" src="nonTrickleIce.js"></script>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
     bug: "1231975",
     title: "Basic audio-only peer connection with endpoint independent NAT"
   });
 
-  var test;
-  runNetworkTest(options => {
-    SpecialPowers.pushPrefEnv(
-      {
-        'set': [
-          ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
-          ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT']
-        ]
-      }, function (options) {
-        options = options || {};
-        options.expectedLocalCandidateType = "srflx";
-        options.expectedRemoteCandidateType = "srflx";
-        test = new PeerConnectionTest(options);
-        // Make sure we don't end up choosing the wrong thing due to delays in
-        // trickle. Once we are willing to accept trickle after ICE success, we
-        // can maybe wait a bit to allow things to stabilize.
-        // TODO(bug 1238249)
-        makeOffererNonTrickle(test.chain);
-        makeAnswererNonTrickle(test.chain);
-        test.setMediaConstraints([{audio: true}], [{audio: true}]);
-        test.run();
-      })
+  // This test uses the NAT simulator in order to get srflx candidates.
+  // It doesn't work in https, so we turn on getUserMedia in http.
+  runNetworkTest(async (options = {}) => {
+    await pushPrefs(
+        ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
+        ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'],
+        ['media.devices.insecure.enabled', true],
+        ['media.getusermedia.insecure.enabled', true]);
+    options.expectedLocalCandidateType = "srflx";
+    options.expectedRemoteCandidateType = "srflx";
+    const test = new PeerConnectionTest(options);
+    // Make sure we don't end up choosing the wrong thing due to delays in
+    // trickle. Once we are willing to accept trickle after ICE success, we
+    // can maybe wait a bit to allow things to stabilize.
+    // TODO(bug 1238249)
+    makeOffererNonTrickle(test.chain);
+    makeAnswererNonTrickle(test.chain);
+    test.setMediaConstraints([{audio: true}], [{audio: true}]);
+    test.run();
   }, { useIceServer: true });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/tests/mochitest/test_peerConnection_remoteReofferRollback.html
+++ b/dom/media/tests/mochitest/test_peerConnection_remoteReofferRollback.html
@@ -31,18 +31,17 @@
             test.pcLocal.endOfTrickleIce.then(() => {
               send_message({"type": "end_of_trickle_ice"});
             });
           }
         },
 
         function PC_REMOTE_ROLLBACK(test) {
           return test.setRemoteDescription(test.pcRemote, { type: "rollback" },
-                                           STABLE)
-            .then(() => test.pcRemote.rollbackRemoteTracksIfNotNegotiated());
+                                           STABLE);
         },
 
         function PC_LOCAL_ROLLBACK(test) {
           // We haven't negotiated the new stream yet.
           test.pcLocal.expectNegotiationNeeded();
           return test.setLocalDescription(
               test.pcLocal,
               new RTCSessionDescription({ type: "rollback", sdp: ""}),
--- a/dom/media/tests/mochitest/test_peerConnection_remoteRollback.html
+++ b/dom/media/tests/mochitest/test_peerConnection_remoteRollback.html
@@ -16,18 +16,17 @@
     test = new PeerConnectionTest(options);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     test.chain.removeAfter('PC_REMOTE_CHECK_CAN_TRICKLE_SYNC');
     test.chain.append([
       function PC_REMOTE_ROLLBACK(test) {
         // We still haven't negotiated the tracks
         test.pcRemote.expectNegotiationNeeded();
         return test.setRemoteDescription(test.pcRemote, { type: "rollback" },
-                                         STABLE)
-          .then(() => test.pcRemote.rollbackRemoteTracksIfNotNegotiated());
+                                         STABLE);
       },
 
       function PC_REMOTE_CHECK_CAN_TRICKLE_REVERT_SYNC(test) {
         is(test.pcRemote._pc.canTrickleIceCandidates, null,
            "Remote canTrickleIceCandidates is reverted to null");
       },
 
       function PC_LOCAL_ROLLBACK(test) {
--- a/dom/media/tests/mochitest/test_peerConnection_stats_relayProtocol.html
+++ b/dom/media/tests/mochitest/test_peerConnection_stats_relayProtocol.html
@@ -25,32 +25,30 @@ var PC_LOCAL_TEST_LOCAL_STATS_RELAYCANDI
     ok(haveRelayProtocol["relay-tcp"], "Has TCP relay candidate");
     // TURN/TLS does not work, see https://bugzilla.mozilla.org/show_bug.cgi?id=1323439
     // With TURN/TLS working, we should have exactly five entries in haveRelayProtocol.
     todo(haveRelayProtocol["relay-tls"], "Has TLS relay candidate. See https://bugzilla.mozilla.org/show_bug.cgi?id=1323439");
     is(Object.keys(haveRelayProtocol).length, 4, "All candidate types are accounted for");
   });
 }
 
-runNetworkTest(options => {
-  // uses NAT simulator in order to get srflx candidates.
-  SpecialPowers.pushPrefEnv(
-    {
-      'set': [
-        ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
-        ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT']
-      ]
-    }, function (options) {
-      const test = new PeerConnectionTest(options);
-      makeOffererNonTrickle(test.chain);
-      makeAnswererNonTrickle(test.chain);
+// This test uses the NAT simulator in order to get srflx candidates.
+// It doesn't work in https, so we turn on getUserMedia in http.
+runNetworkTest(async (options = {}) => {
+  await pushPrefs(
+      ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
+      ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'],
+      ['media.devices.insecure.enabled', true],
+      ['media.getusermedia.insecure.enabled', true]);
+  const test = new PeerConnectionTest(options);
+  makeOffererNonTrickle(test.chain);
+  makeAnswererNonTrickle(test.chain);
 
-      test.chain.removeAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW");
-      test.chain.append([PC_LOCAL_TEST_LOCAL_STATS_RELAYCANDIDATE]);
+  test.chain.removeAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW");
+  test.chain.append([PC_LOCAL_TEST_LOCAL_STATS_RELAYCANDIDATE]);
 
-      test.setMediaConstraints([{ audio: true }], [{ audio: true }]);
-      test.run();
-    })
+  test.setMediaConstraints([{ audio: true }], [{ audio: true }]);
+  test.run();
 }, { useIceServer: true });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -188,16 +188,17 @@ skip-if = toolkit == 'android' # bug 133
 [test_mediaElementAudioSourceNodeCrossOrigin.html]
 tags=capturestream
 skip-if = toolkit == 'android' # bug 1145816
 [test_mediaStreamAudioDestinationNode.html]
 [test_mediaStreamAudioSourceNode.html]
 [test_mediaStreamAudioSourceNodeCrossOrigin.html]
 tags=capturestream
 [test_mediaStreamAudioSourceNodeNoGC.html]
+scheme=https
 [test_mediaStreamAudioSourceNodePassThrough.html]
 [test_mediaStreamAudioSourceNodeResampling.html]
 tags=capturestream
 [test_mediaStreamTrackAudioSourceNode.html]
 skip-if = !debug #bug 1543496
 [test_mediaStreamTrackAudioSourceNodeVideo.html]
 [test_mediaStreamTrackAudioSourceNodeCrossOrigin.html]
 [test_mixingRules.html]
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -1088,16 +1088,22 @@ RefPtr<WebMTrackDemuxer::SamplesPromise>
   nsresult rv = NS_ERROR_DOM_MEDIA_END_OF_STREAM;
 
   while (aNumSamples) {
     RefPtr<MediaRawData> sample;
     rv = NextSample(sample);
     if (NS_FAILED(rv)) {
       break;
     }
+    // Ignore empty samples.
+    if (sample->Size() == 0) {
+      WEBM_DEBUG(
+          "0 sized sample encountered while getting samples, skipping it");
+      continue;
+    }
     if (mNeedKeyframe && !sample->mKeyframe) {
       continue;
     }
     mNeedKeyframe = false;
     samples->mSamples.AppendElement(sample);
     aNumSamples--;
   }
 
--- a/dom/permission/moz.build
+++ b/dom/permission/moz.build
@@ -15,16 +15,16 @@ EXPORTS.mozilla.dom += [
 UNIFIED_SOURCES += [
     'PermissionObserver.cpp',
     'Permissions.cpp',
     'PermissionStatus.cpp',
     'PermissionUtils.cpp',
 ]
 
 LOCAL_INCLUDES += [
-    '/extensions/cookie',
+    '/extensions/permissions',
 ]
 
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 
 FINAL_LIBRARY = 'xul'
 
 include('/ipc/chromium/chromium-config.mozbuild')
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -1725,22 +1725,22 @@ nsresult ServiceWorkerPrivate::SpawnWork
     return NS_ERROR_FAILURE;
   }
   info.mLoadingPrincipal = info.mPrincipal;
 
   // StoragePrincipal for ServiceWorkers is equal to mPrincipal because, at the
   // moment, ServiceWorkers are not exposed in partitioned contexts.
   info.mStoragePrincipal = info.mPrincipal;
 
-  info.mStorageAccess =
-      nsContentUtils::StorageAllowedForServiceWorker(info.mPrincipal);
-
   info.mCookieSettings = mozilla::net::CookieSettings::Create();
   MOZ_ASSERT(info.mCookieSettings);
 
+  info.mStorageAccess = nsContentUtils::StorageAllowedForServiceWorker(
+      info.mPrincipal, info.mCookieSettings);
+
   info.mOriginAttributes = mInfo->GetOriginAttributes();
 
   // Verify that we don't have any CSP on pristine principal.
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   nsCOMPtr<nsIContentSecurityPolicy> csp;
   Unused << info.mPrincipal->GetCsp(getter_AddRefs(csp));
   MOZ_DIAGNOSTIC_ASSERT(!csp);
 #endif
--- a/dom/webidl/RTCRtpReceiver.webidl
+++ b/dom/webidl/RTCRtpReceiver.webidl
@@ -15,14 +15,14 @@ interface RTCRtpReceiver {
   [Pref="media.peerconnection.rtpsourcesapi.enabled"]
   sequence<RTCRtpContributingSource>    getContributingSources();
   [Pref="media.peerconnection.rtpsourcesapi.enabled"]
   sequence<RTCRtpSynchronizationSource> getSynchronizationSources();
 
   [ChromeOnly]
   void setStreamIds(sequence<DOMString> streamIds);
   [ChromeOnly]
-  void setRemoteSendBit(boolean sendBit);
+  void setRecvBit(boolean recvBit);
   [ChromeOnly]
   void processTrackAdditionsAndRemovals(
       RTCRtpTransceiver transceiver,
       object postProcessing);
 };
--- a/dom/webidl/RTCRtpTransceiver.webidl
+++ b/dom/webidl/RTCRtpTransceiver.webidl
@@ -33,25 +33,16 @@ interface RTCRtpTransceiver {
              attribute RTCRtpTransceiverDirection  direction;
     readonly attribute RTCRtpTransceiverDirection? currentDirection;
 
     void stop();
     // TODO: bug 1396922
     // void setCodecPreferences(sequence<RTCRtpCodecCapability> codecs);
 
     [ChromeOnly]
-    void setRemoteTrackId(DOMString trackId);
-    [ChromeOnly]
-    boolean remoteTrackIdIs(DOMString trackId);
-
-    // Mostly for testing
-    [Pref="media.peerconnection.remoteTrackId.enabled"]
-    DOMString getRemoteTrackId();
-
-    [ChromeOnly]
     void setAddTrackMagic();
     [ChromeOnly]
     readonly attribute boolean addTrackMagic;
     [ChromeOnly]
     attribute boolean shouldRemove;
     [ChromeOnly]
     void setCurrentDirection(RTCRtpTransceiverDirection direction);
     [ChromeOnly]
deleted file mode 100644
--- a/extensions/cookie/components.conf
+++ /dev/null
@@ -1,16 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-Classes = [
-    {
-        'cid': '{4f6b5e00-0c36-11d5-a535-0010a401eb10}',
-        'contract_ids': ['@mozilla.org/permissionmanager;1'],
-        'singleton': True,
-        'type': 'nsIPermissionManager',
-        'constructor': 'nsPermissionManager::GetXPCOMSingleton',
-        'headers': ['/extensions/cookie/nsPermissionManager.h'],
-    },
-]
deleted file mode 100644
--- a/extensions/cookie/moz.build
+++ /dev/null
@@ -1,30 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-TEST_DIRS += ['test']
-
-UNIFIED_SOURCES += [
-    'nsPermission.cpp',
-    'nsPermissionManager.cpp',
-]
-
-XPCOM_MANIFESTS += [
-    'components.conf',
-]
-
-LOCAL_INCLUDES += [
-    '/caps',
-]
-
-include('/ipc/chromium/chromium-config.mozbuild')
-
-FINAL_LIBRARY = 'xul'
-
-if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
-    CXXFLAGS += ['-Wno-error=shadow']
-
-with Files('**'):
-    BUG_COMPONENT = ('Core', 'Networking: Cookies')
deleted file mode 100644
--- a/extensions/cookie/test/file_favicon.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!DOCTYPE html>
-
-<html>
-  <head>
-    <link rel="shortcut icon" href="http://example.org/browser/extensions/cookie/test/damonbowling.jpg">
-  </head>
-</html>
deleted file mode 100644
index 272d67c0ce680c4793b7290fdcbf8fb0f15419fa..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/extensions/cookie/test/mochitest.ini
+++ /dev/null
@@ -1,39 +0,0 @@
-[DEFAULT]
-support-files =
-  beltzner.jpg
-  beltzner.jpg^headers^
-  file_chromecommon.js
-  file_domain_hierarchy_inner.html
-  file_domain_hierarchy_inner_inner.html
-  file_domain_hierarchy_inner_inner_inner.html
-  file_domain_inner.html
-  file_domain_inner_inner.html
-  file_image_inner.html
-  file_image_inner_inner.html
-  file_loadflags_inner.html
-  file_localhost_inner.html
-  file_loopback_inner.html
-  file_subdomain_inner.html
-  file_testcommon.js
-  file_testloadflags.js
-  file_testloadflags_chromescript.js
-  image1.png
-  image1.png^headers^
-  image2.png
-  image2.png^headers^
-  test1.css
-  test1.css^headers^
-  test2.css
-  test2.css^headers^
-
-[test_different_domain_in_hierarchy.html]
-[test_differentdomain.html]
-[test_image.html]
-[test_loadflags.html]
-[test_same_base_domain.html]
-[test_same_base_domain_2.html]
-[test_same_base_domain_3.html]
-[test_same_base_domain_4.html]
-[test_same_base_domain_5.html]
-[test_same_base_domain_6.html]
-[test_samedomain.html]
deleted file mode 100644
--- a/extensions/cookie/test/test2.css
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
--- a/extensions/permissions/components.conf
+++ b/extensions/permissions/components.conf
@@ -8,9 +8,17 @@ Classes = [
     {
         'cid': '{4ca6b67b-5cc7-4e71-a98a-97af1c134862}',
         'contract_ids': ['@mozilla.org/permissions/contentblocker;1'],
         'type': 'nsContentBlocker',
         'headers': ['/extensions/permissions/nsContentBlocker.h'],
         'init_method': 'Init',
         'categories': {'content-policy': '@mozilla.org/permissions/contentblocker;1'},
     },
+    {
+        'cid': '{4f6b5e00-0c36-11d5-a535-0010a401eb10}',
+        'contract_ids': ['@mozilla.org/permissionmanager;1'],
+        'singleton': True,
+        'type': 'nsIPermissionManager',
+        'constructor': 'nsPermissionManager::GetXPCOMSingleton',
+        'headers': ['/extensions/permissions/nsPermissionManager.h'],
+    },
 ]
--- a/extensions/permissions/moz.build
+++ b/extensions/permissions/moz.build
@@ -1,18 +1,31 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+TEST_DIRS += ['test']
+
 UNIFIED_SOURCES += [
     'nsContentBlocker.cpp',
+    'nsPermission.cpp',
+    'nsPermissionManager.cpp',
 ]
 
 XPCOM_MANIFESTS += [
     'components.conf',
 ]
 
+LOCAL_INCLUDES += [
+    '/caps',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
 FINAL_LIBRARY = 'xul'
 
+if CONFIG['CC_TYPE'] in ('clang', 'gcc'):
+    CXXFLAGS += ['-Wno-error=shadow']
+
 with Files('**'):
-    BUG_COMPONENT = ('Core', 'DOM: Core & HTML')
+    BUG_COMPONENT = ('Core', 'Permission Manager')
rename from extensions/cookie/nsPermission.cpp
rename to extensions/permissions/nsPermission.cpp
rename from extensions/cookie/nsPermission.h
rename to extensions/permissions/nsPermission.h
rename from extensions/cookie/nsPermissionManager.cpp
rename to extensions/permissions/nsPermissionManager.cpp
rename from extensions/cookie/nsPermissionManager.h
rename to extensions/permissions/nsPermissionManager.h
rename from extensions/cookie/test/.eslintrc.js
rename to extensions/permissions/test/.eslintrc.js
rename from extensions/cookie/test/browser.ini
rename to extensions/permissions/test/browser.ini
--- a/extensions/cookie/test/browser.ini
+++ b/extensions/permissions/test/browser.ini
@@ -1,15 +1,9 @@
 [DEFAULT]
 
-[browser_test_favicon.js]
-skip-if = (verify && (os == 'linux' || os == 'mac'))
-support-files =
-  damonbowling.jpg
-  damonbowling.jpg^headers^
-  file_favicon.html
 [browser_permmgr_sync.js]
 # The browser_permmgr_sync test tests e10s specific behavior, and runs code
 # paths which would hit the debug only assertion in
 # nsPermissionManager::PermissionKey::CreateFromPrincipal. Because of this, it
 # is only run in e10s opt builds.
 skip-if = debug || !e10s
 [browser_permmgr_viewsrc.js]
rename from extensions/cookie/test/browser_permmgr_sync.js
rename to extensions/permissions/test/browser_permmgr_sync.js
rename from extensions/cookie/test/browser_permmgr_viewsrc.js
rename to extensions/permissions/test/browser_permmgr_viewsrc.js
rename from extensions/cookie/test/gtest/PermissionManagerTest.cpp
rename to extensions/permissions/test/gtest/PermissionManagerTest.cpp
rename from extensions/cookie/test/gtest/moz.build
rename to extensions/permissions/test/gtest/moz.build
--- a/extensions/cookie/test/gtest/moz.build
+++ b/extensions/permissions/test/gtest/moz.build
@@ -4,13 +4,13 @@
 # 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/.
 
 UNIFIED_SOURCES += [
     'PermissionManagerTest.cpp',
 ]
 
 LOCAL_INCLUDES += [
-    '/extensions/cookie',
+    '/extensions/permissions',
 ]
 
 FINAL_LIBRARY = 'xul-gtest'
 
rename from extensions/cookie/test/moz.build
rename to extensions/permissions/test/moz.build
--- a/extensions/cookie/test/moz.build
+++ b/extensions/permissions/test/moz.build
@@ -7,12 +7,10 @@
 TEST_DIRS += [
     'gtest',
 ]
 
 XPCSHELL_TESTS_MANIFESTS += [
     'unit/xpcshell.ini',
 ]
 
-MOCHITEST_MANIFESTS += ['mochitest.ini']
-
 BROWSER_CHROME_MANIFESTS += ['browser.ini']
 
new file mode 100644
--- /dev/null
+++ b/extensions/permissions/test/unit/head.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+
+// Helper to step a generator function and catch a StopIteration exception.
+function do_run_generator(generator) {
+  try {
+    generator.next();
+  } catch (e) {
+    do_throw("caught exception " + e, Components.stack.caller);
+  }
+}
+
+// Helper to finish a generator function test.
+function do_finish_generator_test(generator) {
+  executeSoon(function() {
+    generator.return();
+    do_test_finished();
+  });
+}
+
+function do_count_enumerator(enumerator) {
+  let i = 0;
+  for (let item of enumerator) {
+    void item;
+    ++i;
+  }
+  return i;
+}
+
rename from extensions/cookie/test/unit/test_permmanager_cleardata.js
rename to extensions/permissions/test/unit/test_permmanager_cleardata.js
rename from extensions/cookie/test/unit/test_permmanager_default_pref.js
rename to extensions/permissions/test/unit/test_permmanager_default_pref.js
rename from extensions/cookie/test/unit/test_permmanager_defaults.js
rename to extensions/permissions/test/unit/test_permmanager_defaults.js
rename from extensions/cookie/test/unit/test_permmanager_expiration.js
rename to extensions/permissions/test/unit/test_permmanager_expiration.js
rename from extensions/cookie/test/unit/test_permmanager_getAllForURI.js
rename to extensions/permissions/test/unit/test_permmanager_getAllForURI.js
rename from extensions/cookie/test/unit/test_permmanager_getAllWithTypePrefix.js
rename to extensions/permissions/test/unit/test_permmanager_getAllWithTypePrefix.js
rename from extensions/cookie/test/unit/test_permmanager_getPermissionObject.js
rename to extensions/permissions/test/unit/test_permmanager_getPermissionObject.js
rename from extensions/cookie/test/unit/test_permmanager_idn.js
rename to extensions/permissions/test/unit/test_permmanager_idn.js
rename from extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js
rename to extensions/permissions/test/unit/test_permmanager_load_invalid_entries.js
rename from extensions/cookie/test/unit/test_permmanager_local_files.js
rename to extensions/permissions/test/unit/test_permmanager_local_files.js
rename from extensions/cookie/test/unit/test_permmanager_matches.js
rename to extensions/permissions/test/unit/test_permmanager_matches.js
rename from extensions/cookie/test/unit/test_permmanager_matchesuri.js
rename to extensions/permissions/test/unit/test_permmanager_matchesuri.js
rename from extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
rename to extensions/permissions/test/unit/test_permmanager_migrate_4-7.js
rename from extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
rename to extensions/permissions/test/unit/test_permmanager_migrate_4-7_no_history.js
rename from extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
rename to extensions/permissions/test/unit/test_permmanager_migrate_5-7a.js
rename from extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
rename to extensions/permissions/test/unit/test_permmanager_migrate_5-7b.js
rename from extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
rename to extensions/permissions/test/unit/test_permmanager_migrate_6-7a.js
rename from extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js
rename to extensions/permissions/test/unit/test_permmanager_migrate_6-7b.js
rename from extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
rename to extensions/permissions/test/unit/test_permmanager_migrate_7-8.js
rename from extensions/cookie/test/unit/test_permmanager_notifications.js
rename to extensions/permissions/test/unit/test_permmanager_notifications.js
rename from extensions/cookie/test/unit/test_permmanager_removeall.js
rename to extensions/permissions/test/unit/test_permmanager_removeall.js
rename from extensions/cookie/test/unit/test_permmanager_removebytype.js
rename to extensions/permissions/test/unit/test_permmanager_removebytype.js
rename from extensions/cookie/test/unit/test_permmanager_removebytypesince.js
rename to extensions/permissions/test/unit/test_permmanager_removebytypesince.js
rename from extensions/cookie/test/unit/test_permmanager_removeforapp.js
rename to extensions/permissions/test/unit/test_permmanager_removeforapp.js
rename from extensions/cookie/test/unit/test_permmanager_removepermission.js
rename to extensions/permissions/test/unit/test_permmanager_removepermission.js
rename from extensions/cookie/test/unit/test_permmanager_removesince.js
rename to extensions/permissions/test/unit/test_permmanager_removesince.js
rename from extensions/cookie/test/unit/test_permmanager_subdomains.js
rename to extensions/permissions/test/unit/test_permmanager_subdomains.js
rename from extensions/cookie/test/unit/xpcshell.ini
rename to extensions/permissions/test/unit/xpcshell.ini
--- a/extensions/cookie/test/unit/xpcshell.ini
+++ b/extensions/permissions/test/unit/xpcshell.ini
@@ -1,27 +1,12 @@
 [DEFAULT]
-head = head_cookies.js
+head = head.js
 skip-if = toolkit == 'android'
 
-[test_bug526789.js]
-[test_bug650522.js]
-[test_bug667087.js]
-[test_cookies_async_failure.js]
-[test_cookies_persistence.js]
-skip-if = true # Bug 863738
-[test_cookies_privatebrowsing.js]
-[test_cookies_profile_close.js]
-[test_cookies_read.js]
-[test_cookies_sync_failure.js]
-[test_cookies_thirdparty.js]
-[test_cookies_thirdparty_nonsecure_session.js]
-[test_cookies_thirdparty_session.js]
-[test_domain_eviction.js]
-[test_eviction.js]
 [test_permmanager_default_pref.js]
 [test_permmanager_defaults.js]
 [test_permmanager_expiration.js]
 [test_permmanager_getAllForURI.js]
 [test_permmanager_getAllWithTypePrefix.js]
 [test_permmanager_getPermissionObject.js]
 [test_permmanager_notifications.js]
 [test_permmanager_removeall.js]
@@ -30,18 +15,16 @@ skip-if = true # Bug 863738
 [test_permmanager_removesince.js]
 [test_permmanager_removeforapp.js]
 [test_permmanager_load_invalid_entries.js]
 skip-if = debug == true
 [test_permmanager_idn.js]
 [test_permmanager_subdomains.js]
 [test_permmanager_local_files.js]
 [test_permmanager_cleardata.js]
-[test_schema_2_migration.js]
-[test_schema_3_migration.js]
 [test_permmanager_removepermission.js]
 [test_permmanager_matchesuri.js]
 [test_permmanager_matches.js]
 [test_permmanager_migrate_4-7.js]
 [test_permmanager_migrate_5-7a.js]
 [test_permmanager_migrate_5-7b.js]
 [test_permmanager_migrate_6-7a.js]
 [test_permmanager_migrate_6-7b.js]
--- a/gfx/thebes/gfxDWriteFontList.cpp
+++ b/gfx/thebes/gfxDWriteFontList.cpp
@@ -929,17 +929,21 @@ gfxFontEntry* gfxDWriteFontList::MakePla
   }
 
   return entry;
 }
 
 gfxFontEntry* gfxDWriteFontList::CreateFontEntry(
     fontlist::Face* aFace, const fontlist::Family* aFamily) {
   IDWriteFontCollection* collection =
+#ifdef MOZ_BUNDLED_FONTS
       aFamily->IsBundled() ? mBundledFonts : mSystemFonts;
+#else
+      mSystemFonts;
+#endif
   RefPtr<IDWriteFontFamily> family;
   collection->GetFontFamily(aFamily->Index(), getter_AddRefs(family));
   RefPtr<IDWriteFont> font;
   family->GetFont(aFace->mIndex, getter_AddRefs(font));
   nsAutoCString faceName;
   HRESULT hr = GetDirectWriteFontName(font, faceName);
   if (FAILED(hr)) {
     return nullptr;
@@ -979,17 +983,21 @@ void gfxDWriteFontList::AppendFamiliesFr
         key, name, i, false, aCollection != mSystemFonts, bad, classic));
   }
 }
 
 void gfxDWriteFontList::GetFacesInitDataForFamily(
     const fontlist::Family* aFamily,
     nsTArray<fontlist::Face::InitData>& aFaces) const {
   IDWriteFontCollection* collection =
+#ifdef MOZ_BUNDLED_FONTS
       aFamily->IsBundled() ? mBundledFonts : mSystemFonts;
+#else
+      mSystemFonts;
+#endif
   if (!collection) {
     return;
   }
   RefPtr<IDWriteFontFamily> family;
   collection->GetFontFamily(aFamily->Index(), getter_AddRefs(family));
   for (unsigned i = 0; i < family->GetFontCount(); ++i) {
     RefPtr<IDWriteFont> dwFont;
     family->GetFont(i, getter_AddRefs(dwFont));
@@ -1036,17 +1044,21 @@ void gfxDWriteFontList::GetFacesInitData
                                                   weight, stretch, slant});
   }
 }
 
 bool gfxDWriteFontList::ReadFaceNames(fontlist::Family* aFamily,
                                       fontlist::Face* aFace, nsCString& aPSName,
                                       nsCString& aFullName) {
   IDWriteFontCollection* collection =
+#ifdef MOZ_BUNDLED_FONTS
       aFamily->IsBundled() ? mBundledFonts : mSystemFonts;
+#else
+      mSystemFonts;
+#endif
   RefPtr<IDWriteFontFamily> family;
   if (FAILED(collection->GetFontFamily(aFamily->Index(),
                                        getter_AddRefs(family)))) {
     MOZ_ASSERT_UNREACHABLE("failed to get font-family");
     return false;
   }
   RefPtr<IDWriteFont> dwFont;
   if (FAILED(family->GetFont(aFace->mIndex, getter_AddRefs(dwFont)))) {
@@ -1100,17 +1112,21 @@ bool gfxDWriteFontList::ReadFaceNames(fo
 void gfxDWriteFontList::ReadFaceNamesForFamily(
     fontlist::Family* aFamily, bool aNeedFullnamePostscriptNames) {
   if (!aFamily->IsInitialized()) {
     if (!InitializeFamily(aFamily)) {
       return;
     }
   }
   IDWriteFontCollection* collection =
+#ifdef MOZ_BUNDLED_FONTS
       aFamily->IsBundled() ? mBundledFonts : mSystemFonts;
+#else
+      mSystemFonts;
+#endif
   RefPtr<IDWriteFontFamily> family;
   if (FAILED(collection->GetFontFamily(aFamily->Index(),
                                        getter_AddRefs(family)))) {
     return;
   }
   fontlist::FontList* list = SharedFontList();
   const fontlist::Pointer* facePtrs = aFamily->Faces(list);
   nsAutoCString familyName(aFamily->DisplayName().AsString(list));
--- a/gfx/wr/appveyor.yml
+++ b/gfx/wr/appveyor.yml
@@ -3,18 +3,18 @@ before_test:
   - ps: Set-ScreenResolution 1920 1080
 
 environment:
   PATH: 'C:\msys64\mingw64\bin;C:\msys64\usr\bin;%PATH%;C:\Rust\bin'
   RUST_BACKTRACE: 1
   TARGET: x86_64-pc-windows-msvc
 
 install:
-  - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.30.0-${env:TARGET}.msi"
-  - msiexec /passive /i "rust-1.30.0-%TARGET%.msi" ADDLOCAL=Rustc,Cargo,Std INSTALLDIR=C:\Rust
+  - ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-1.34.0-${env:TARGET}.msi"
+  - msiexec /passive /i "rust-1.34.0-%TARGET%.msi" ADDLOCAL=Rustc,Cargo,Std INSTALLDIR=C:\Rust
   - rustc -V
   - cargo -V
 
 build: false
 
 test_script:
   - cmd.exe /c ci-scripts\windows-tests.cmd
   - cmd.exe /c ci-scripts\windows-pathfinder.cmd
--- a/js/src/builtin/Promise.cpp
+++ b/js/src/builtin/Promise.cpp
@@ -92,20 +92,22 @@ enum RejectFunctionSlots {
   RejectFunctionSlot_ResolveFunction,
 };
 
 enum PromiseAllResolveElementFunctionSlots {
   PromiseAllResolveElementFunctionSlot_Data = 0,
   PromiseAllResolveElementFunctionSlot_ElementIndex,
 };
 
+#ifdef NIGHTLY_BUILD
 enum PromiseAllSettledElementFunctionSlots {
   PromiseAllSettledElementFunctionSlot_Data = 0,
   PromiseAllSettledElementFunctionSlot_ElementIndex,
 };
+#endif
 
 enum ReactionJobSlots {
   ReactionJobSlot_ReactionRecord = 0,
 };
 
 enum ThenableJobSlots {
   // The handler to use as the Promise reaction. It is a callable object
   // that's guaranteed to be from the same compartment as the
@@ -2272,25 +2274,33 @@ class MOZ_STACK_CLASS PromiseForOfIterat
     return index != NOT_ARRAY && IsPackedArray(iterator);
   }
 };
 
 static MOZ_MUST_USE bool PerformPromiseAll(
     JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
     Handle<PromiseCapability> resultCapability, bool* done);
 
+#ifdef NIGHTLY_BUILD
 static MOZ_MUST_USE bool PerformPromiseAllSettled(
     JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
     Handle<PromiseCapability> resultCapability, bool* done);
+#endif
 
 static MOZ_MUST_USE bool PerformPromiseRace(
     JSContext* cx, PromiseForOfIterator& iterator, HandleObject C,
     Handle<PromiseCapability> resultCapability, bool* done);
 
-enum class IterationMode { All, AllSettled, Race };
+enum class IterationMode {
+  All,
+#ifdef NIGHTLY_BUILD
+  AllSettled,
+#endif
+  Race
+};
 
 // ES2020 draft rev a09fc232c137800dbf51b6204f37fdede4ba1646
 //
 // Unified implementation of
 // 25.6.4.1 Promise.all ( iterable )
 // 25.6.4.3 Promise.race ( iterable )
 //
 // Promise.allSettled (Stage 3 proposal)
@@ -2304,19 +2314,21 @@ static MOZ_MUST_USE bool CommonStaticAll
   // Step 2 (reordered).
   HandleValue CVal = args.thisv();
   if (!CVal.isObject()) {
     const char* message;
     switch (mode) {
       case IterationMode::All:
         message = "Receiver of Promise.all call";
         break;
+#ifdef NIGHTLY_BUILD
       case IterationMode::AllSettled:
         message = "Receiver of Promise.allSettled call";
         break;
+#endif
       case IterationMode::Race:
         message = "Receiver of Promise.race call";
         break;
     }
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                               JSMSG_NOT_NONNULL_OBJECT, message);
     return false;
   }
@@ -2337,19 +2349,21 @@ static MOZ_MUST_USE bool CommonStaticAll
   }
 
   if (!iter.valueIsIterable()) {
     const char* message;
     switch (mode) {
       case IterationMode::All:
         message = "Argument of Promise.all";
         break;
+#ifdef NIGHTLY_BUILD
       case IterationMode::AllSettled:
         message = "Argument of Promise.allSettled";
         break;
+#endif
       case IterationMode::Race:
         message = "Argument of Promise.race";
         break;
     }
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_ITERABLE,
                               message);
     return AbruptRejectPromise(cx, args, promiseCapability);
   }
@@ -2357,19 +2371,21 @@ static MOZ_MUST_USE bool CommonStaticAll
   // Step 6 (implicit).
 
   // Step 7.
   bool done, result;
   switch (mode) {
     case IterationMode::All:
       result = PerformPromiseAll(cx, iter, C, promiseCapability, &done);
       break;
+#ifdef NIGHTLY_BUILD
     case IterationMode::AllSettled:
       result = PerformPromiseAllSettled(cx, iter, C, promiseCapability, &done);
       break;
+#endif
     case IterationMode::Race:
       result = PerformPromiseRace(cx, iter, C, promiseCapability, &done);
       break;
   }
 
   // Step 8.
   if (!result) {
     // Step 8.a.
@@ -3139,16 +3155,17 @@ static MOZ_MUST_USE bool PerformPromiseR
   };
 
   // Step 3.
   return CommonPerformPromiseAllRace(cx, iterator, C,
                                      resultCapability.promise(), done,
                                      isDefaultResolveFn, getResolveAndReject);
 }
 
+#ifdef NIGHTLY_BUILD
 enum class PromiseAllSettledElementFunctionKind { Resolve, Reject };
 
 // Promise.allSettled (Stage 3 proposal)
 // https://tc39.github.io/proposal-promise-allSettled/
 //
 // Promise.allSettled Resolve Element Functions
 // Promise.allSettled Reject Element Functions
 template <PromiseAllSettledElementFunctionKind Kind>
@@ -3417,16 +3434,17 @@ static bool PromiseAllSettledElementFunc
       return false;
     }
   }
 
   // Step 15.
   args.rval().setUndefined();
   return true;
 }
+#endif  // NIGHTLY_BUILD
 
 // https://tc39.github.io/ecma262/#sec-promise.reject
 //
 // Unified implementation of
 // 25.6.4.4 Promise.reject ( r )
 // 25.6.4.5 Promise.resolve ( x )
 // 25.6.4.5.1 PromiseResolve ( C, x )
 static MOZ_MUST_USE JSObject* CommonStaticResolveRejectImpl(
--- a/layout/base/tests/bug1496118-ref.html
+++ b/layout/base/tests/bug1496118-ref.html
@@ -3,22 +3,23 @@
 <head>
   <script src="/tests/SimpleTest/EventUtils.js"></script>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <style>
     input, input:active { border: none; }
   </style>
 </head>
 <body>
-<input id="input1">
+<input id="input1" value="aaaaaaaaaaaaaaaaaaaa">
 <div id=div1 style="height: 100px;">
   <textarea id="textarea1" style="display: none;"></textarea>
 </div>
 <script>
 SimpleTest.waitForFocus(() => {
   let input1 = document.getElementById('input1');
   input1.focus();
+  input1.selectionStart = input1.selectionEnd = 0;
   synthesizeKey("A");
   document.documentElement.removeAttribute("class");
 });
 </script>
 </body>
 </html>
--- a/layout/build/moz.build
+++ b/layout/build/moz.build
@@ -44,17 +44,17 @@ LOCAL_INCLUDES += [
     '/dom/svg',
     '/dom/xbl',
     '/dom/xslt/base',
     '/dom/xslt/xml',
     '/dom/xslt/xpath',
     '/dom/xslt/xslt',
     '/dom/xul',
     '/editor/composer',
-    '/extensions/cookie',
+    '/extensions/permissions',
     '/js/xpconnect/loader',
     '/js/xpconnect/src',
     '/netwerk/base',
     '/netwerk/cookie',
     '/view',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -385,18 +385,18 @@ include ../../toolkit/content/tests/reft
 skip-if(winWidget&&!browserIsRemote) include transform/reftest.list
 
 # 3d transforms
 include transform-3d/reftest.list
 
 # unicode/ (verify that we don't do expend effort doing unicode-aware case checks)
 include unicode/reftest.list
 
-# usercss
-include usercss/reftest.list
+# usercss - only supported in content processes.
+skip-if(!browserIsRemote) include usercss/reftest.list
 
 include view-source/reftest.list
 
 # vr
 include ../../dom/vr/test/reftest/reftest.list
 
 # web-animations
 include web-animations/reftest.list
--- a/layout/style/nsLayoutStylesheetCache.cpp
+++ b/layout/style/nsLayoutStylesheetCache.cpp
@@ -164,21 +164,28 @@ void nsLayoutStylesheetCache::Shutdown()
     r = nullptr;
   }
   // Some content processes don't get around to consuming the shared memory
   // buffer we store in sSharedMemory (e.g. a preloaded content process that
   // doesn't get a document loaded in it), so clear it out here to avoid leaks.
   sSharedMemory = nullptr;
 }
 
+/* static */
 void nsLayoutStylesheetCache::SetUserContentCSSURL(nsIURI* aURI) {
   MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
   gUserContentSheetURL = aURI;
 }
 
+/* static */
+nsIURI* nsLayoutStylesheetCache::GetUserContentCSSURL() {
+  MOZ_ASSERT(XRE_IsParentProcess(), "Only used in parent processes.");
+  return gUserContentSheetURL;
+}
+
 MOZ_DEFINE_MALLOC_SIZE_OF(LayoutStylesheetCacheMallocSizeOf)
 
 NS_IMETHODIMP
 nsLayoutStylesheetCache::CollectReports(nsIHandleReportCallback* aHandleReport,
                                         nsISupports* aData, bool aAnonymize) {
   MOZ_COLLECT_REPORT("explicit/layout/style-sheet-cache/unshared", KIND_HEAP,
                      UNITS_BYTES,
                      SizeOfIncludingThis(LayoutStylesheetCacheMallocSizeOf),
@@ -230,18 +237,17 @@ nsLayoutStylesheetCache::nsLayoutStylesh
   // Load user style sheets.
   InitFromProfile();
 
   if (XRE_IsParentProcess()) {
     // We know we need xul.css for the UI, so load that now too:
     XULSheet();
   }
 
-  if (gUserContentSheetURL) {
-    MOZ_ASSERT(XRE_IsContentProcess(), "Only used in content processes.");
+  if (gUserContentSheetURL && XRE_IsContentProcess()) {
     LoadSheet(gUserContentSheetURL, &mUserContentSheet, eUserSheetFeatures,
               eLogToConsole);
     gUserContentSheetURL = nullptr;
   }
 
   // If we are the in the parent process, then we load all of the UA sheets that
   // are shareable and store them into shared memory.  In both the parent and
   // the content process, we load these sheets out of shared memory.
@@ -418,34 +424,42 @@ void nsLayoutStylesheetCache::InitFromPr
   if (appInfo) {
     bool inSafeMode = false;
     appInfo->GetInSafeMode(&inSafeMode);
     if (inSafeMode) return;
   }
   nsCOMPtr<nsIFile> contentFile;
   nsCOMPtr<nsIFile> chromeFile;
 
-  NS_GetSpecialDirectory(NS_APP_USER_CHROME_DIR, getter_AddRefs(contentFile));
-  if (!contentFile) {
+  NS_GetSpecialDirectory(NS_APP_USER_CHROME_DIR, getter_AddRefs(chromeFile));
+  if (!chromeFile) {
     // if we don't have a profile yet, that's OK!
     return;
   }
 
-  contentFile->Clone(getter_AddRefs(chromeFile));
-  if (!chromeFile) return;
+  chromeFile->Clone(getter_AddRefs(contentFile));
+  if (!contentFile) return;
 
   contentFile->Append(NS_LITERAL_STRING("userContent.css"));
   chromeFile->Append(NS_LITERAL_STRING("userChrome.css"));
 
-  LoadSheetFile(contentFile, &mUserContentSheet, eUserSheetFeatures,
-                eLogToConsole);
   LoadSheetFile(chromeFile, &mUserChromeSheet, eUserSheetFeatures,
                 eLogToConsole);
 
   if (XRE_IsParentProcess()) {
+    bool exists = false;
+    contentFile->Exists(&exists);
+    if (exists) {
+      // We don't need to load this sheet in the parent process, but we do need
+      // the file URI so that we can send it down to the content processes.
+      nsCOMPtr<nsIURI> uri;
+      NS_NewFileURI(getter_AddRefs(uri), contentFile);
+      gUserContentSheetURL = uri;
+    }
+
     // We're interested specifically in potential chrome customizations,
     // so we only need data points from the parent process
     Telemetry::Accumulate(Telemetry::USER_CHROME_CSS_LOADED,
                           mUserChromeSheet != nullptr);
   }
 }
 
 void nsLayoutStylesheetCache::LoadSheetURL(const char* aURL,
--- a/layout/style/nsLayoutStylesheetCache.h
+++ b/layout/style/nsLayoutStylesheetCache.h
@@ -64,16 +64,17 @@ class nsLayoutStylesheetCache final : pu
   mozilla::StyleSheet* ChromePreferenceSheet();
   mozilla::StyleSheet* ContentPreferenceSheet();
 
   static void InvalidatePreferenceSheets();
 
   static void Shutdown();
 
   static void SetUserContentCSSURL(nsIURI* aURI);
+  static nsIURI* GetUserContentCSSURL();
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
   // Set the shared memory segment to load the shared UA sheets from.
   // Called early on in a content process' life from
   // ContentChild::InitSharedUASheets, before the nsLayoutStylesheetCache
   // singleton has been created.
   static void SetSharedMemory(const base::SharedMemoryHandle& aHandle,
--- a/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_session_unittest.cpp
@@ -211,20 +211,20 @@ class JsepSessionTest : public JsepSessi
   JsepTrack RemoveTrack(JsepSession& side, size_t index) {
     if (side.GetTransceivers().size() <= index) {
       EXPECT_TRUE(false) << "Index " << index << " out of bounds!";
       return JsepTrack(SdpMediaSection::kAudio, sdp::kSend);
     }
 
     RefPtr<JsepTransceiver>& transceiver(side.GetTransceivers()[index]);
     JsepTrack& track = transceiver->mSendTrack;
-    EXPECT_FALSE(track.GetTrackId().empty()) << "No track at index " << index;
+    EXPECT_FALSE(track.GetStreamIds().empty()) << "No track at index " << index;
 
     JsepTrack original(track);
-    track.ClearTrackIds();
+    track.ClearStreamIds();
     transceiver->mJsDirection &= SdpDirectionAttribute::Direction::kRecvonly;
     return original;
   }
 
   void SetDirection(JsepSession& side, size_t index,
                     SdpDirectionAttribute::Direction direction) {
     ASSERT_LT(index, side.GetTransceivers().size())
         << "Index " << index << " out of bounds!";
@@ -319,18 +319,18 @@ class JsepSessionTest : public JsepSessi
       }
 
       std::cerr << "Updating send track for transceiver " << i << std::endl;
       if (magic == ADDTRACK_MAGIC) {
         transceivers[i]->SetAddTrackMagic();
       }
       transceivers[i]->mJsDirection |=
           SdpDirectionAttribute::Direction::kSendonly;
-      transceivers[i]->mSendTrack.UpdateTrackIds(
-          std::vector<std::string>(1, stream_id), track_id);
+      transceivers[i]->mSendTrack.UpdateStreamIds(
+          std::vector<std::string>(1, stream_id));
     }
   }
 
   bool HasMediaStream(const std::vector<JsepTrack>& tracks) const {
     for (const auto& track : tracks) {
       if (track.GetMediaType() != SdpMediaSection::kApplication) {
         return true;
       }
@@ -571,20 +571,16 @@ class JsepSessionTest : public JsepSessi
     if (t1.GetDirection() != t2.GetDirection()) {
       return false;
     }
 
     if (t1.GetStreamIds() != t2.GetStreamIds()) {
       return false;
     }
 
-    if (t1.GetTrackId() != t2.GetTrackId()) {
-      return false;
-    }
-
     if (t1.GetActive() != t2.GetActive()) {
       return false;
     }
 
     if (t1.GetCNAME() != t2.GetCNAME()) {
       return false;
     }
 
@@ -788,18 +784,16 @@ class JsepSessionTest : public JsepSessi
         }
         const auto& track(transceiver->mSendTrack);
         size_t level = transceiver->GetLevel();
         ASSERT_FALSE(IsNull(track));
         ASSERT_EQ(types[level], track.GetMediaType());
         if (track.GetMediaType() != SdpMediaSection::kApplication) {
           std::string msidAttr("a=msid:");
           msidAttr += track.GetStreamIds()[0];
-          msidAttr += " ";
-          msidAttr += track.GetTrackId();
           ASSERT_NE(std::string::npos, offer.find(msidAttr))
               << "Did not find " << msidAttr << " in offer";
         }
       }
       if (types.size() == 1 && types[0] == SdpMediaSection::kApplication) {
         ASSERT_EQ(std::string::npos, offer.find("a=ssrc"))
             << "Data channel should not contain SSRC";
       }
@@ -872,18 +866,16 @@ class JsepSessionTest : public JsepSessi
         // These might have been in the SDP, or might have been randomly
         // chosen by JsepSessionImpl
         ASSERT_FALSE(IsNull(recvTrack));
         ASSERT_EQ(types[level], recvTrack.GetMediaType());
 
         if (recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
           std::string msidAttr("a=msid:");
           msidAttr += sendTrack.GetStreamIds()[0];
-          msidAttr += " ";
-          msidAttr += sendTrack.GetTrackId();
           ASSERT_NE(std::string::npos, answer.find(msidAttr))
               << "Did not find " << msidAttr << " in answer";
         }
       }
       if (types.size() == 1 && types[0] == SdpMediaSection::kApplication) {
         ASSERT_EQ(std::string::npos, answer.find("a=ssrc"))
             << "Data channel should not contain SSRC";
       }
@@ -1297,18 +1289,17 @@ class JsepSessionTest : public JsepSessi
         auto role = sdp->GetMediaSection(i).GetAttributeList().GetSetup().mRole;
         ASSERT_EQ(expectedRole, role);
       }
     }
   }
 
   void DumpTrack(const JsepTrack& track) {
     const JsepTrackNegotiatedDetails* details = track.GetNegotiatedDetails();
-    std::cerr << "  type=" << track.GetMediaType()
-              << " track-id=" << track.GetTrackId() << std::endl;
+    std::cerr << "  type=" << track.GetMediaType() << std::endl;
     if (!details) {
       std::cerr << "  not negotiated" << std::endl;
       return;
     }
     std::cerr << "  encodings=" << std::endl;
     for (size_t i = 0; i < details->GetEncodingCount(); ++i) {
       const JsepTrackEncoding& encoding = details->GetEncoding(i);
       std::cerr << "    id=" << encoding.mRid << std::endl;
@@ -1802,55 +1793,50 @@ TEST_P(JsepSessionTest, RenegotiationBot
     auto oHasStream = HasMediaStream(GetLocalTracks(*mSessionOff));
     auto aHasStream = HasMediaStream(GetLocalTracks(*mSessionAns));
     ASSERT_EQ(oHasStream, !GetLocalUniqueStreamIds(*mSessionOff).empty());
     ASSERT_EQ(aHasStream, !GetLocalUniqueStreamIds(*mSessionAns).empty());
   }
 }
 
 // The JSEP draft explicitly forbids changing the msid on an m-section, but
-// that is a new restriction that older versions of Firefox do not follow.
-// JS will not see the msid change, since that is filtered out (except for
-// RTCRtpTransceiver.remoteTrackId)
+// that is a bug.
 TEST_P(JsepSessionTest, RenegotiationOffererChangesMsid) {
   AddTracks(*mSessionOff);
   AddTracks(*mSessionAns);
 
   OfferAnswer();
 
   std::string offer = CreateOffer();
   SetLocalOffer(offer);
 
   RefPtr<JsepTransceiver> transceiver =
       GetNegotiatedTransceiver(*mSessionOff, 0);
   ASSERT_TRUE(transceiver);
   if (transceiver->GetMediaType() == SdpMediaSection::kApplication) {
     return;
   }
   std::string streamId = transceiver->mSendTrack.GetStreamIds()[0];
-  std::string trackId = transceiver->mSendTrack.GetTrackId();
   std::string msidToReplace("a=msid:");
   msidToReplace += streamId;
-  msidToReplace += " ";
-  msidToReplace += trackId;
   size_t msidOffset = offer.find(msidToReplace);
   ASSERT_NE(std::string::npos, msidOffset);
-  offer.replace(msidOffset, msidToReplace.size(), "a=msid:foo bar");
+  offer.replace(msidOffset, msidToReplace.size(), "a=msid:foo");
 
   SetRemoteOffer(offer);
   transceiver = GetNegotiatedTransceiver(*mSessionAns, 0);
   ASSERT_EQ("foo", transceiver->mRecvTrack.GetStreamIds()[0]);
 
   std::string answer = CreateAnswer();
   SetLocalAnswer(answer);
   SetRemoteAnswer(answer);
 }
 
 // The JSEP draft explicitly forbids changing the msid on an m-section, but
-// that is a new restriction that older versions of Firefox do not follow.
+// that is a bug.
 TEST_P(JsepSessionTest, RenegotiationAnswererChangesMsid) {
   AddTracks(*mSessionOff);
   AddTracks(*mSessionAns);
 
   OfferAnswer();
 
   RefPtr<JsepTransceiver> transceiver =
       GetNegotiatedTransceiver(*mSessionOff, 0);
@@ -1866,24 +1852,21 @@ TEST_P(JsepSessionTest, RenegotiationAns
   SetLocalAnswer(answer);
 
   transceiver = GetNegotiatedTransceiver(*mSessionAns, 0);
   ASSERT_TRUE(transceiver);
   if (transceiver->GetMediaType() == SdpMediaSection::kApplication) {
     return;
   }
   std::string streamId = transceiver->mSendTrack.GetStreamIds()[0];
-  std::string trackId = transceiver->mSendTrack.GetTrackId();
   std::string msidToReplace("a=msid:");
   msidToReplace += streamId;
-  msidToReplace += " ";
-  msidToReplace += trackId;
   size_t msidOffset = answer.find(msidToReplace);
   ASSERT_NE(std::string::npos, msidOffset);
-  answer.replace(msidOffset, msidToReplace.size(), "a=msid:foo bar");
+  answer.replace(msidOffset, msidToReplace.size(), "a=msid:foo");
 
   SetRemoteAnswer(answer);
 
   transceiver = GetNegotiatedTransceiver(*mSessionOff, 0);
   ASSERT_EQ("foo", transceiver->mRecvTrack.GetStreamIds()[0]);
 }
 
 TEST_P(JsepSessionTest, RenegotiationOffererStopsTransceiver) {
@@ -2116,61 +2099,53 @@ TEST_P(JsepSessionTest, RenegotiationBot
   mSessionOff->GetTransceivers()[0]->Stop();
   mSessionOff->GetTransceivers()[1]->Stop();
 
   OfferAnswer(CHECK_SUCCESS);
   ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsStopped());
   ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsStopped());
 }
 
-TEST_P(JsepSessionTest, RenegotiationOffererReplacesTrack) {
+TEST_P(JsepSessionTest, RenegotiationOffererChangesStreamId) {
   AddTracks(*mSessionOff);
   AddTracks(*mSessionAns);
 
   if (mSessionOff->GetTransceivers()[0]->GetMediaType() ==
       SdpMediaSection::kApplication) {
     return;
   }
 
   OfferAnswer();
 
-  mSessionOff->GetTransceivers()[0]->mSendTrack.UpdateTrackIds(
-      std::vector<std::string>(1, "newstream"), "newtrack");
+  mSessionOff->GetTransceivers()[0]->mSendTrack.UpdateStreamIds(
+      std::vector<std::string>(1, "newstream"));
 
   OfferAnswer(CHECK_SUCCESS);
 
-  // Latest JSEP spec says the msid never changes, so the other side will not
-  // notice track replacement.
-  ASSERT_NE("newtrack",
-            mSessionAns->GetTransceivers()[0]->mRecvTrack.GetTrackId());
-  ASSERT_NE("newstream",
+  ASSERT_EQ("newstream",
             mSessionAns->GetTransceivers()[0]->mRecvTrack.GetStreamIds()[0]);
 }
 
-TEST_P(JsepSessionTest, RenegotiationAnswererReplacesTrack) {
+TEST_P(JsepSessionTest, RenegotiationAnswererChangesStreamId) {
   AddTracks(*mSessionOff);
   AddTracks(*mSessionAns);
 
   if (mSessionOff->GetTransceivers()[0]->GetMediaType() ==
       SdpMediaSection::kApplication) {
     return;
   }
 
   OfferAnswer();
 
-  mSessionAns->GetTransceivers()[0]->mSendTrack.UpdateTrackIds(
-      std::vector<std::string>(1, "newstream"), "newtrack");
+  mSessionAns->GetTransceivers()[0]->mSendTrack.UpdateStreamIds(
+      std::vector<std::string>(1, "newstream"));
 
   OfferAnswer(CHECK_SUCCESS);
 
-  // Latest JSEP spec says the msid never changes, so the other side will not
-  // notice track replacement.
-  ASSERT_NE("newtrack",
-            mSessionOff->GetTransceivers()[0]->mRecvTrack.GetTrackId());
-  ASSERT_NE("newstream",
+  ASSERT_EQ("newstream",
             mSessionOff->GetTransceivers()[0]->mRecvTrack.GetStreamIds()[0]);
 }
 
 // Tests whether auto-assigned remote msids (ie; what happens when the other
 // side doesn't use msid attributes) are stable across renegotiation.
 TEST_P(JsepSessionTest, RenegotiationAutoAssignedMsidIsStable) {
   AddTracks(*mSessionOff);
   std::string offer = CreateOffer();
@@ -2191,18 +2166,16 @@ TEST_P(JsepSessionTest, RenegotiationAut
 
   ASSERT_EQ(origOffererTransceivers.size(), origAnswererTransceivers.size());
   for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
     ASSERT_FALSE(IsNull(origOffererTransceivers[i]->mRecvTrack));
     ASSERT_FALSE(IsNull(origAnswererTransceivers[i]->mSendTrack));
     // These should not match since we've monkeyed with the msid
     ASSERT_NE(origOffererTransceivers[i]->mRecvTrack.GetStreamIds(),
               origAnswererTransceivers[i]->mSendTrack.GetStreamIds());
-    ASSERT_NE(origOffererTransceivers[i]->mRecvTrack.GetTrackId(),
-              origAnswererTransceivers[i]->mSendTrack.GetTrackId());
   }
 
   offer = CreateOffer();
   SetLocalOffer(offer);
   SetRemoteOffer(offer);
   answer = CreateAnswer();
   SetLocalAnswer(answer);
 
@@ -2358,17 +2331,36 @@ TEST_P(JsepSessionTest, RenegotiationAns
   SetLocalAnswer(answer);
 
   DisableMsid(&answer);
 
   SetRemoteAnswer(answer, CHECK_SUCCESS);
 
   auto newOffererTransceivers = mSessionOff->GetTransceivers();
 
-  ASSERT_TRUE(Equals(origOffererTransceivers, newOffererTransceivers));
+  ASSERT_EQ(origOffererTransceivers.size(), newOffererTransceivers.size());
+  for (size_t i = 0; i < origOffererTransceivers.size(); ++i) {
+    ASSERT_EQ(origOffererTransceivers[i]->mRecvTrack.GetMediaType(),
+              newOffererTransceivers[i]->mRecvTrack.GetMediaType());
+
+    ASSERT_TRUE(Equals(origOffererTransceivers[i]->mSendTrack,
+                       newOffererTransceivers[i]->mSendTrack));
+    ASSERT_TRUE(Equals(origOffererTransceivers[i]->mTransport,
+                       newOffererTransceivers[i]->mTransport));
+
+    if (origOffererTransceivers[i]->mRecvTrack.GetMediaType() ==
+        SdpMediaSection::kApplication) {
+      ASSERT_TRUE(Equals(origOffererTransceivers[i]->mRecvTrack,
+                         newOffererTransceivers[i]->mRecvTrack));
+    } else {
+      // This should be the only difference
+      ASSERT_FALSE(Equals(origOffererTransceivers[i]->mRecvTrack,
+                          newOffererTransceivers[i]->mRecvTrack));
+    }
+  }
 }
 
 // Tests behavior when offerer does not use bundle on the initial offer/answer,
 // but does on renegotiation.
 TEST_P(JsepSessionTest, RenegotiationOffererEnablesBundle) {
   AddTracks(*mSessionOff);
   AddTracks(*mSessionAns);
 
@@ -3147,23 +3139,23 @@ TEST_F(JsepSessionTest, OfferToReceiveVi
   ASSERT_EQ(1U, answer->GetMediaSectionCount());
   ASSERT_EQ(SdpMediaSection::kVideo, answer->GetMediaSection(0).GetMediaType());
   ASSERT_EQ(SdpDirectionAttribute::kInactive,
             answer->GetMediaSection(0).GetAttributeList().GetDirection());
 }
 
 TEST_F(JsepSessionTest, CreateOfferNoDatachannelDefault) {
   RefPtr<JsepTransceiver> audio(new JsepTransceiver(SdpMediaSection::kAudio));
-  audio->mSendTrack.UpdateTrackIds(
-      std::vector<std::string>(1, "offerer_stream"), "a1");
+  audio->mSendTrack.UpdateStreamIds(
+      std::vector<std::string>(1, "offerer_stream"));
   mSessionOff->AddTransceiver(audio);
 
   RefPtr<JsepTransceiver> video(new JsepTransceiver(SdpMediaSection::kVideo));
-  video->mSendTrack.UpdateTrackIds(
-      std::vector<std::string>(1, "offerer_stream"), "v1");
+  video->mSendTrack.UpdateStreamIds(
+      std::vector<std::string>(1, "offerer_stream"));
   mSessionOff->AddTransceiver(video);
 
   std::string offer = CreateOffer();
 
   UniquePtr<Sdp> outputSdp(Parse(offer));
   ASSERT_TRUE(!!outputSdp);
 
   ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
@@ -3173,23 +3165,23 @@ TEST_F(JsepSessionTest, CreateOfferNoDat
             outputSdp->GetMediaSection(1).GetMediaType());
 }
 
 TEST_F(JsepSessionTest, ValidateOfferedVideoCodecParams) {
   types.push_back(SdpMediaSection::kAudio);
   types.push_back(SdpMediaSection::kVideo);
 
   RefPtr<JsepTransceiver> audio(new JsepTransceiver(SdpMediaSection::kAudio));
-  audio->mSendTrack.UpdateTrackIds(
-      std::vector<std::string>(1, "offerer_stream"), "a1");
+  audio->mSendTrack.UpdateStreamIds(
+      std::vector<std::string>(1, "offerer_stream"));
   mSessionOff->AddTransceiver(audio);
 
   RefPtr<JsepTransceiver> video(new JsepTransceiver(SdpMediaSection::kVideo));
-  video->mSendTrack.UpdateTrackIds(
-      std::vector<std::string>(1, "offerer_stream"), "v1");
+  video->mSendTrack.UpdateStreamIds(
+      std::vector<std::string>(1, "offerer_stream"));
   mSessionOff->AddTransceiver(video);
 
   std::string offer = CreateOffer();
 
   UniquePtr<Sdp> outputSdp(Parse(offer));
   ASSERT_TRUE(!!outputSdp);
 
   ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
@@ -3302,23 +3294,23 @@ TEST_F(JsepSessionTest, ValidateOfferedV
   ASSERT_EQ(123, parsed_red_params.encodings[4]);
 }
 
 TEST_F(JsepSessionTest, ValidateOfferedAudioCodecParams) {
   types.push_back(SdpMediaSection::kAudio);
   types.push_back(SdpMediaSection::kVideo);
 
   RefPtr<JsepTransceiver> audio(new JsepTransceiver(SdpMediaSection::kAudio));
-  audio->mSendTrack.UpdateTrackIds(
-      std::vector<std::string>(1, "offerer_stream"), "a1");
+  audio->mSendTrack.UpdateStreamIds(
+      std::vector<std::string>(1, "offerer_stream"));
   mSessionOff->AddTransceiver(audio);
 
   RefPtr<JsepTransceiver> video(new JsepTransceiver(SdpMediaSection::kVideo));
-  video->mSendTrack.UpdateTrackIds(
-      std::vector<std::string>(1, "offerer_stream"), "v1");
+  video->mSendTrack.UpdateStreamIds(
+      std::vector<std::string>(1, "offerer_stream"));
   mSessionOff->AddTransceiver(video);
 
   std::string offer = CreateOffer();
 
   UniquePtr<Sdp> outputSdp(Parse(offer));
   ASSERT_TRUE(!!outputSdp);
 
   ASSERT_EQ(2U, outputSdp->GetMediaSectionCount());
@@ -6112,17 +6104,17 @@ TEST_F(JsepSessionTest, AddTrackMagicWit
   ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsAssociated());
   ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->HasLevel());
   ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsStopped());
   ASSERT_FALSE(mSessionAns->GetTransceivers()[2]->IsAssociated());
   ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
 
   // Ok, transceiver 2 is "magical". Ensure it still has this "magical"
   // auto-matching property even if we null it out with replaceTrack.
-  mSessionAns->GetTransceivers()[2]->mSendTrack.ClearTrackIds();
+  mSessionAns->GetTransceivers()[2]->mSendTrack.ClearStreamIds();
   mSessionAns->GetTransceivers()[2]->mJsDirection =
       SdpDirectionAttribute::Direction::kRecvonly;
 
   OfferAnswer(CHECK_SUCCESS);
 
   ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
   ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
   ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
@@ -6159,18 +6151,17 @@ TEST_F(JsepSessionTest, NoAddTrackMagicR
   OfferAnswer();
 
   ASSERT_EQ(2U, mSessionOff->GetTransceivers().size());
   ASSERT_EQ(2U, mSessionAns->GetTransceivers().size());
   AddTracks(*mSessionOff, "audio");
   mSessionAns->AddTransceiver(
       new JsepTransceiver(SdpMediaSection::MediaType::kAudio));
 
-  mSessionAns->GetTransceivers()[2]->mSendTrack.UpdateTrackIds({"newstream"},
-                                                               "newtrack");
+  mSessionAns->GetTransceivers()[2]->mSendTrack.UpdateStreamIds({"newstream"});
 
   ASSERT_EQ(3U, mSessionAns->GetTransceivers().size());
   ASSERT_EQ(0U, mSessionAns->GetTransceivers()[0]->GetLevel());
   ASSERT_FALSE(mSessionAns->GetTransceivers()[0]->IsStopped());
   ASSERT_TRUE(mSessionAns->GetTransceivers()[0]->IsAssociated());
   ASSERT_EQ(1U, mSessionAns->GetTransceivers()[1]->GetLevel());
   ASSERT_FALSE(mSessionAns->GetTransceivers()[1]->IsStopped());
   ASSERT_TRUE(mSessionAns->GetTransceivers()[1]->IsAssociated());
@@ -6300,27 +6291,26 @@ TEST_F(JsepSessionTest, ComplicatedRemot
   ASSERT_FALSE(mSessionAns->GetTransceivers()[4]->HasAddTrackMagic());
   ASSERT_TRUE(mSessionAns->GetTransceivers()[4]->WasCreatedBySetRemote());
 
   // This will cause the first audio transceiver to become "magical", and
   // thereby it will stick around after rollback, even though we clear it out
   // with replaceTrack.
   AddTracks(*mSessionAns, "audio");
   ASSERT_TRUE(mSessionAns->GetTransceivers()[2]->HasAddTrackMagic());
-  mSessionAns->GetTransceivers()[2]->mSendTrack.ClearTrackIds();
+  mSessionAns->GetTransceivers()[2]->mSendTrack.ClearStreamIds();
   mSessionAns->GetTransceivers()[2]->mJsDirection =
       SdpDirectionAttribute::Direction::kRecvonly;
 
   // We do nothing with the second audio transceiver; when we rollback, it will
   // disappear entirely.
 
   // This will not cause the third audio transceiver to stick around; having a
   // track is _not_ enough to preserve it. It must have addTrack "magic"!
-  mSessionAns->GetTransceivers()[4]->mSendTrack.UpdateTrackIds({"newstream"},
-                                                               "newtrack");
+  mSessionAns->GetTransceivers()[4]->mSendTrack.UpdateStreamIds({"newstream"});
 
   // Create a fourth audio transceiver. Rollback will leave it alone, since we
   // created it.
   mSessionAns->AddTransceiver(
       new JsepTransceiver(SdpMediaSection::MediaType::kAudio,
                           SdpDirectionAttribute::Direction::kRecvonly));
 
   ASSERT_FALSE(
--- a/media/webrtc/signaling/gtest/jsep_track_unittest.cpp
+++ b/media/webrtc/signaling/gtest/jsep_track_unittest.cpp
@@ -82,27 +82,25 @@ class JsepTrackTest : public ::testing::
   void InitCodecs() {
     mOffCodecs = MakeCodecs();
     mAnsCodecs = MakeCodecs();
   }
 
   void InitTracks(SdpMediaSection::MediaType type) {
     mSendOff = JsepTrack(type, sdp::kSend);
     if (type != SdpMediaSection::MediaType::kApplication) {
-      mSendOff.UpdateTrackIds(std::vector<std::string>(1, "stream_id"),
-                              "track_id");
+      mSendOff.UpdateStreamIds(std::vector<std::string>(1, "stream_id"));
     }
     mRecvOff = JsepTrack(type, sdp::kRecv);
     mSendOff.PopulateCodecs(mOffCodecs);
     mRecvOff.PopulateCodecs(mOffCodecs);
 
     mSendAns = JsepTrack(type, sdp::kSend);
     if (type != SdpMediaSection::MediaType::kApplication) {
-      mSendAns.UpdateTrackIds(std::vector<std::string>(1, "stream_id"),
-                              "track_id");
+      mSendAns.UpdateStreamIds(std::vector<std::string>(1, "stream_id"));
     }
     mRecvAns = JsepTrack(type, sdp::kRecv);
     mSendAns.PopulateCodecs(mAnsCodecs);
     mRecvAns.PopulateCodecs(mAnsCodecs);
   }
 
   void InitSdp(SdpMediaSection::MediaType type) {
     std::vector<std::string> msids(1, "*");
@@ -124,27 +122,27 @@ class JsepTrackTest : public ::testing::
     helper.SetupMsidSemantic(msids, mAnswer.get());
   }
 
   SdpMediaSection& GetOffer() { return mOffer->GetMediaSection(0); }
 
   SdpMediaSection& GetAnswer() { return mAnswer->GetMediaSection(0); }
 
   void CreateOffer() {
-    mSendOff.AddToOffer(mSsrcGenerator, true, &GetOffer());
-    mRecvOff.AddToOffer(mSsrcGenerator, true, &GetOffer());
+    mSendOff.AddToOffer(mSsrcGenerator, &GetOffer());
+    mRecvOff.AddToOffer(mSsrcGenerator, &GetOffer());
   }
 
   void CreateAnswer() {
     if (mRecvAns.GetMediaType() != SdpMediaSection::MediaType::kApplication) {
       mRecvAns.UpdateRecvTrack(*mOffer, GetOffer());
     }
 
-    mSendAns.AddToAnswer(GetOffer(), mSsrcGenerator, true, &GetAnswer());
-    mRecvAns.AddToAnswer(GetOffer(), mSsrcGenerator, true, &GetAnswer());
+    mSendAns.AddToAnswer(GetOffer(), mSsrcGenerator, &GetAnswer());
+    mRecvAns.AddToAnswer(GetOffer(), mSsrcGenerator, &GetAnswer());
   }
 
   void Negotiate() {
     std::cerr << "Offer SDP: " << std::endl;
     mOffer->Serialize(std::cerr);
 
     std::cerr << "Answer SDP: " << std::endl;
     mAnswer->Serialize(std::cerr);
@@ -177,21 +175,21 @@ class JsepTrackTest : public ::testing::
   // invokes the ASSERT_)
   static void CheckEncodingCount(size_t expected, const JsepTrack& send,
                                  const JsepTrack& recv) {
     if (expected) {
       ASSERT_TRUE(send.GetNegotiatedDetails());
       ASSERT_TRUE(recv.GetNegotiatedDetails());
     }
 
-    if (!send.GetTrackId().empty() && send.GetNegotiatedDetails()) {
+    if (!send.GetStreamIds().empty() && send.GetNegotiatedDetails()) {
       ASSERT_EQ(expected, send.GetNegotiatedDetails()->GetEncodingCount());
     }
 
-    if (!recv.GetTrackId().empty() && recv.GetNegotiatedDetails()) {
+    if (!recv.GetStreamIds().empty() && recv.GetNegotiatedDetails()) {
       ASSERT_EQ(expected, recv.GetNegotiatedDetails()->GetEncodingCount());
     }
   }
 
   void CheckOffEncodingCount(size_t expected) const {
     CheckEncodingCount(expected, mSendOff, mRecvAns);
   }
 
@@ -380,18 +378,17 @@ class CheckForCodecType {
 };
 
 TEST_F(JsepTrackTest, CheckForMismatchedAudioCodecAndVideoTrack) {
   std::vector<UniquePtr<JsepCodecDescription>> offerCodecs;
 
   // make codecs including telephone-event (an audio codec)
   offerCodecs = MakeCodecs(false, false, true);
   JsepTrack videoTrack(SdpMediaSection::kVideo, sdp::kSend);
-  videoTrack.UpdateTrackIds(std::vector<std::string>(1, "stream_id"),
-                            "track_id");
+  videoTrack.UpdateStreamIds(std::vector<std::string>(1, "stream_id"));
   // populate codecs and then make sure we don't have any audio codecs
   // in the video track
   videoTrack.PopulateCodecs(offerCodecs);
 
   bool found = false;
   videoTrack.ForEachCodec(CheckForCodecType(SdpMediaSection::kAudio, &found));
   ASSERT_FALSE(found);
 
--- a/media/webrtc/signaling/src/jsep/JsepSession.h
+++ b/media/webrtc/signaling/src/jsep/JsepSession.h
@@ -184,22 +184,22 @@ class JsepSession {
   virtual bool CheckNegotiationNeeded() const = 0;
 
   void CountTracks(uint16_t (&receiving)[SdpMediaSection::kMediaTypes],
                    uint16_t (&sending)[SdpMediaSection::kMediaTypes]) const {
     memset(receiving, 0, sizeof(receiving));
     memset(sending, 0, sizeof(sending));
 
     for (const auto& transceiver : GetTransceivers()) {
-      if (!transceiver->mRecvTrack.GetTrackId().empty() ||
+      if (!transceiver->mRecvTrack.GetActive() ||
           transceiver->GetMediaType() == SdpMediaSection::kApplication) {
         receiving[transceiver->mRecvTrack.GetMediaType()]++;
       }
 
-      if (!transceiver->mSendTrack.GetTrackId().empty() ||
+      if (!transceiver->mSendTrack.GetActive() ||
           transceiver->GetMediaType() == SdpMediaSection::kApplication) {
         sending[transceiver->mSendTrack.GetMediaType()]++;
       }
     }
   }
 
  protected:
   const std::string mName;
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.cpp
@@ -102,25 +102,24 @@ nsresult JsepSessionImpl::AddTransceiver
 
   if (transceiver->GetMediaType() != SdpMediaSection::kApplication) {
     // Make sure we have an ssrc. Might already be set.
     transceiver->mSendTrack.EnsureSsrcs(mSsrcGenerator);
     transceiver->mSendTrack.SetCNAME(mCNAME);
 
     // Make sure we have identifiers for send track, just in case.
     // (man I hate this)
-    if (transceiver->mSendTrack.GetTrackId().empty()) {
+    if (mEncodeTrackId) {
       std::string trackId;
       if (!mUuidGen->Generate(&trackId)) {
         JSEP_SET_ERROR("Failed to generate UUID for JsepTrack");
         return NS_ERROR_FAILURE;
       }
 
-      transceiver->mSendTrack.UpdateTrackIds(std::vector<std::string>(),
-                                             trackId);
+      transceiver->mSendTrack.SetTrackId(trackId);
     }
   } else {
     // Datachannel transceivers should always be sendrecv. Just set it instead
     // of asserting.
     transceiver->mJsDirection = SdpDirectionAttribute::kSendrecv;
 #ifdef DEBUG
     for (auto& transceiver : mTransceivers) {
       MOZ_ASSERT(transceiver->GetMediaType() != SdpMediaSection::kApplication);
@@ -253,18 +252,18 @@ nsresult JsepSessionImpl::CreateOfferMse
     // Set RTCP-MUX.
     msection->GetAttributeList().SetAttribute(
         new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
   }
 
   nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  transceiver.mSendTrack.AddToOffer(mSsrcGenerator, mEncodeTrackId, msection);
-  transceiver.mRecvTrack.AddToOffer(mSsrcGenerator, mEncodeTrackId, msection);
+  transceiver.mSendTrack.AddToOffer(mSsrcGenerator, msection);
+  transceiver.mRecvTrack.AddToOffer(mSsrcGenerator, msection);
 
   AddExtmap(msection);
 
   std::string mid;
   // We do not set the mid on the transceiver, that happens when a description
   // is set.
   if (transceiver.IsAssociated()) {
     mid = transceiver.GetMid();
@@ -322,35 +321,16 @@ void JsepSessionImpl::SetupBundle(Sdp* s
 
   if (!mids.empty()) {
     UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
     groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids);
     sdp->GetAttributeList().SetAttribute(groupAttr.release());
   }
 }
 
-nsresult JsepSessionImpl::GetRemoteIds(const Sdp& sdp,
-                                       const SdpMediaSection& msection,
-                                       std::vector<std::string>* streamIds,
-                                       std::string* trackId) {
-  // Generate random track ids.
-  if (!mUuidGen->Generate(trackId)) {
-    JSEP_SET_ERROR("Failed to generate UUID for JsepTrack");
-    return NS_ERROR_FAILURE;
-  }
-
-  nsresult rv = mSdpHelper.GetIdsFromMsid(sdp, msection, streamIds);
-  if (rv == NS_ERROR_NOT_AVAILABLE) {
-    streamIds->push_back(mDefaultRemoteStreamId);
-    return NS_OK;
-  }
-
-  return rv;
-}
-
 JsepSession::Result JsepSessionImpl::CreateOffer(
     const JsepOfferOptions& options, std::string* offer) {
   mLastError.clear();
 
   if (mState != kJsepStateStable && mState != kJsepStateHaveLocalOffer) {
     JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
     // Spec doesn't seem to say this is an error. It probably should.
     return dom::PCError::InvalidStateError;
@@ -372,22 +352,22 @@ JsepSession::Result JsepSessionImpl::Cre
   }
 
   SetupBundle(sdp.get());
 
   if (mCurrentLocalDescription) {
     rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentLocalDescription,
                                      *sdp, sdp.get());
     NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
-    CopyPreviousMsid(*mCurrentLocalDescription, sdp.get());
   }
 
   *offer = sdp->ToString();
   mGeneratedOffer = std::move(sdp);
   ++mSessionVersion;
+  MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: CreateOffer \nSDP=\n" << *offer);
 
   return Result();
 }
 
 std::string JsepSessionImpl::GetLocalDescription(
     JsepDescriptionPendingOrCurrent type) const {
   std::ostringstream os;
   mozilla::Sdp* sdp = GetParsedLocalDescription(type);
@@ -503,22 +483,22 @@ JsepSession::Result JsepSessionImpl::Cre
     NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
   }
 
   if (mCurrentLocalDescription) {
     // per discussion with bwc, 3rd parm here should be offer, not *sdp. (mjf)
     rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentRemoteDescription,
                                      offer, sdp.get());
     NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
-    CopyPreviousMsid(*mCurrentLocalDescription, sdp.get());
   }
 
   *answer = sdp->ToString();
   mGeneratedAnswer = std::move(sdp);
   ++mSessionVersion;
+  MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: CreateAnswer \nSDP=\n" << *answer);
 
   return Result();
 }
 
 nsresult JsepSessionImpl::CreateAnswerMsection(
     const JsepAnswerOptions& options, JsepTransceiver& transceiver,
     const SdpMediaSection& remoteMsection, Sdp* sdp) {
   MOZ_ASSERT(transceiver.GetMediaType() == remoteMsection.GetMediaType());
@@ -555,20 +535,18 @@ nsresult JsepSessionImpl::CreateAnswerMs
   } else {
     rv = DetermineAnswererSetupRole(remoteMsection, &role);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   rv = AddTransportAttributes(&msection, role);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  transceiver.mSendTrack.AddToAnswer(remoteMsection, mSsrcGenerator,
-                                     mEncodeTrackId, &msection);
-  transceiver.mRecvTrack.AddToAnswer(remoteMsection, mSsrcGenerator,
-                                     mEncodeTrackId, &msection);
+  transceiver.mSendTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
+  transceiver.mRecvTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
 
   // Add extmap attributes. This logic will probably be moved to the track,
   // since it can be specified on a per-sender basis in JS.
   // We will need some validation to ensure that the ids are identical for
   // RTP streams that are bundled together, though (bug 1406529).
   AddCommonExtmaps(remoteMsection, &msection);
 
   if (msection.GetFormats().empty()) {
@@ -1214,32 +1192,16 @@ nsresult JsepSessionImpl::CopyPreviousTr
           &newLocal->GetMediaSection(i));
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   return NS_OK;
 }
 
-void JsepSessionImpl::CopyPreviousMsid(const Sdp& oldLocal, Sdp* newLocal) {
-  for (size_t i = 0; i < oldLocal.GetMediaSectionCount(); ++i) {
-    const SdpMediaSection& oldMsection(oldLocal.GetMediaSection(i));
-    SdpMediaSection& newMsection(newLocal->GetMediaSection(i));
-    if (oldMsection.GetAttributeList().HasAttribute(
-            SdpAttribute::kMsidAttribute) &&
-        !mSdpHelper.MsectionIsDisabled(newMsection)) {
-      // JSEP says this cannot change, no matter what is happening in JS land.
-      // It can only be updated if there is an intermediate SDP that clears the
-      // msid.
-      newMsection.GetAttributeList().SetAttribute(
-          new SdpMsidAttributeList(oldMsection.GetAttributeList().GetMsid()));
-    }
-  }
-}
-
 nsresult JsepSessionImpl::ParseSdp(const std::string& sdp,
                                    UniquePtr<Sdp>* parsedp) {
   UniquePtr<Sdp> parsed = mSipccParser.Parse(sdp);
   if (!parsed) {
     std::string error = "Failed to parse SDP: ";
     mSdpHelper.appendSdpParseErrors(mSipccParser.GetParseErrors(), &error);
     JSEP_SET_ERROR(error);
     return NS_ERROR_INVALID_ARG;
@@ -1498,27 +1460,24 @@ nsresult JsepSessionImpl::UpdateTranscei
       continue;
     }
 
     if (msection.GetMediaType() == SdpMediaSection::MediaType::kApplication) {
       continue;
     }
 
     // Interop workaround for endpoints that don't support msid.
-    // Ensures that there is a default track id set.
-    // TODO(bug 1426005): Remove this
-    if (msection.IsSending() && transceiver->mRecvTrack.GetTrackId().empty()) {
-      std::vector<std::string> streamIds;
-      std::string trackId;
+    // Ensures that there is a default stream id set, provided the remote is
+    // sending.
+    // TODO(bug 1426005): Remove this, or at least move it to JsepTrack.
+    transceiver->mRecvTrack.UpdateStreamIds({mDefaultRemoteStreamId});
 
-      nsresult rv = GetRemoteIds(remote, msection, &streamIds, &trackId);
-      NS_ENSURE_SUCCESS(rv, rv);
-      transceiver->mRecvTrack.UpdateTrackIds(streamIds, trackId);
-    }
-
+    // This will process a=msid if present, or clear the stream ids if the
+    // msection is not sending. If the msection is sending, and there are no
+    // a=msid, the previously set default will stay.
     transceiver->mRecvTrack.UpdateRecvTrack(remote, msection);
   }
 
   return NS_OK;
 }
 
 JsepTransceiver* JsepSessionImpl::FindUnassociatedTransceiver(
     SdpMediaSection::MediaType type, bool magic) {
--- a/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
+++ b/media/webrtc/signaling/src/jsep/JsepSessionImpl.h
@@ -187,22 +187,18 @@ class JsepSessionImpl : public JsepSessi
   void RollbackRemoteOffer();
   nsresult HandleNegotiatedSession(const UniquePtr<Sdp>& local,
                                    const UniquePtr<Sdp>& remote);
   nsresult AddTransportAttributes(SdpMediaSection* msection,
                                   SdpSetupAttribute::Role dtlsRole);
   nsresult CopyPreviousTransportParams(const Sdp& oldAnswer,
                                        const Sdp& offerersPreviousSdp,
                                        const Sdp& newOffer, Sdp* newLocal);
-  void CopyPreviousMsid(const Sdp& oldLocal, Sdp* newLocal);
   void EnsureMsid(Sdp* remote);
   void SetupBundle(Sdp* sdp) const;
-  nsresult GetRemoteIds(const Sdp& sdp, const SdpMediaSection& msection,
-                        std::vector<std::string>* streamIds,
-                        std::string* trackId);
   nsresult CreateOfferMsection(const JsepOfferOptions& options,
                                JsepTransceiver& transceiver, Sdp* local);
   nsresult CreateAnswerMsection(const JsepAnswerOptions& options,
                                 JsepTransceiver& transceiver,
                                 const SdpMediaSection& remoteMsection,
                                 Sdp* sdp);
   nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection,
                                       SdpSetupAttribute::Role* rolep);
--- a/media/webrtc/signaling/src/jsep/JsepTrack.cpp
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.cpp
@@ -101,41 +101,41 @@ void JsepTrack::PopulateCodecs(
       mPrototypeCodecs.emplace_back(prototypeCodec->Clone());
       mPrototypeCodecs.back()->mDirection = mDirection;
     }
   }
 
   EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs);
 }
 
-void JsepTrack::AddToOffer(SsrcGenerator& ssrcGenerator, bool encodeTrackId,
+void JsepTrack::AddToOffer(SsrcGenerator& ssrcGenerator,
                            SdpMediaSection* offer) {
-  AddToMsection(mPrototypeCodecs, encodeTrackId, offer);
+  AddToMsection(mPrototypeCodecs, offer);
 
   if (mDirection == sdp::kSend) {
     std::vector<JsConstraints> constraints;
     if (offer->IsSending()) {
       constraints = mJsEncodeConstraints;
     }
     AddToMsection(constraints, sdp::kSend, ssrcGenerator, offer);
   }
 }
 
 void JsepTrack::AddToAnswer(const SdpMediaSection& offer,
-                            SsrcGenerator& ssrcGenerator, bool encodeTrackId,
+                            SsrcGenerator& ssrcGenerator,
                             SdpMediaSection* answer) {
   // We do not modify mPrototypeCodecs here, since we're only creating an
   // answer. Once offer/answer concludes, we will update mPrototypeCodecs.
   std::vector<UniquePtr<JsepCodecDescription>> codecs =
       NegotiateCodecs(offer, true);
   if (codecs.empty()) {
     return;
   }
 
-  AddToMsection(codecs, encodeTrackId, answer);
+  AddToMsection(codecs, answer);
 
   if (mDirection == sdp::kSend) {
     std::vector<JsConstraints> constraints;
     if (answer->IsSending()) {
       constraints = mJsEncodeConstraints;
       std::vector<SdpRidAttributeList::Rid> rids;
       GetRids(offer, sdp::kRecv, &rids);
       NegotiateRids(rids, &constraints);
@@ -164,31 +164,31 @@ bool JsepTrack::SetJsConstraints(
     }
   }
 
   return constraintsChanged;
 }
 
 void JsepTrack::AddToMsection(
     const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
-    bool encodeTrackId, SdpMediaSection* msection) {
+    SdpMediaSection* msection) {
   MOZ_ASSERT(msection->GetMediaType() == mType);
   MOZ_ASSERT(!codecs.empty());
 
   for (const auto& codec : codecs) {
     codec->AddToMediaSection(*msection);
   }
 
   if ((mDirection == sdp::kSend) && (mType != SdpMediaSection::kApplication) &&
       msection->IsSending()) {
     if (mStreamIds.empty()) {
-      msection->AddMsid("-", encodeTrackId ? mTrackId : "");
+      msection->AddMsid("-", mTrackId);
     } else {
       for (const std::string& streamId : mStreamIds) {
-        msection->AddMsid(streamId, encodeTrackId ? mTrackId : "");
+        msection->AddMsid(streamId, mTrackId);
       }
     }
   }
 }
 
 // Updates the |id| values in |constraintsList| with the rid values in |rids|,
 // where necessary.
 void JsepTrack::NegotiateRids(
--- a/media/webrtc/signaling/src/jsep/JsepTrack.h
+++ b/media/webrtc/signaling/src/jsep/JsepTrack.h
@@ -80,26 +80,23 @@ class JsepTrack {
   JsepTrack(mozilla::SdpMediaSection::MediaType type, sdp::Direction direction)
       : mType(type),
         mDirection(direction),
         mActive(false),
         mRemoteSetSendBit(false) {}
 
   virtual ~JsepTrack() {}
 
-  void UpdateTrackIds(const std::vector<std::string>& streamIds,
-                      const std::string& trackId) {
+  void UpdateStreamIds(const std::vector<std::string>& streamIds) {
     mStreamIds = streamIds;
-    mTrackId = trackId;
   }
 
-  void ClearTrackIds() {
-    mStreamIds.clear();
-    mTrackId.clear();
-  }
+  void SetTrackId(const std::string& aTrackId) { mTrackId = aTrackId; }
+
+  void ClearStreamIds() { mStreamIds.clear(); }
 
   void UpdateRecvTrack(const Sdp& sdp, const SdpMediaSection& msection) {
     MOZ_ASSERT(mDirection == sdp::kRecv);
     MOZ_ASSERT(msection.GetMediaType() !=
                SdpMediaSection::MediaType::kApplication);
     std::string error;
     SdpHelper helper(&error);
 
@@ -154,18 +151,16 @@ class JsepTrack {
   virtual mozilla::SdpMediaSection::MediaType GetMediaType() const {
     return mType;
   }
 
   virtual const std::vector<std::string>& GetStreamIds() const {
     return mStreamIds;
   }
 
-  virtual const std::string& GetTrackId() const { return mTrackId; }
-
   virtual const std::string& GetCNAME() const { return mCNAME; }
 
   virtual void SetCNAME(const std::string& cname) { mCNAME = cname; }
 
   virtual sdp::Direction GetDirection() const { return mDirection; }
 
   virtual const std::vector<uint32_t>& GetSsrcs() const { return mSsrcs; }
 
@@ -186,20 +181,19 @@ class JsepTrack {
   }
 
   template <class BinaryPredicate>
   void SortCodecs(BinaryPredicate sorter) {
     std::stable_sort(mPrototypeCodecs.begin(), mPrototypeCodecs.end(), sorter);
   }
 
   // These two are non-const because this is where ssrcs are chosen.
-  virtual void AddToOffer(SsrcGenerator& ssrcGenerator, bool encodeTrackId,
-                          SdpMediaSection* offer);
+  virtual void AddToOffer(SsrcGenerator& ssrcGenerator, SdpMediaSection* offer);
   virtual void AddToAnswer(const SdpMediaSection& offer,
-                           SsrcGenerator& ssrcGenerator, bool encodeTrackId,
+                           SsrcGenerator& ssrcGenerator,
                            SdpMediaSection* answer);
 
   virtual void Negotiate(const SdpMediaSection& answer,
                          const SdpMediaSection& remote);
   static void SetUniquePayloadTypes(std::vector<JsepTrack*>& tracks);
   virtual void GetNegotiatedPayloadTypes(
       std::vector<uint16_t>* payloadTypes) const;
 
@@ -243,17 +237,17 @@ class JsepTrack {
  private:
   std::vector<UniquePtr<JsepCodecDescription>> GetCodecClones() const;
   static void EnsureNoDuplicatePayloadTypes(
       std::vector<UniquePtr<JsepCodecDescription>>* codecs);
   static void GetPayloadTypes(
       const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
       std::vector<uint16_t>* pts);
   void AddToMsection(const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
-                     bool encodeTrackId, SdpMediaSection* msection);
+                     SdpMediaSection* msection);
   void GetRids(const SdpMediaSection& msection, sdp::Direction direction,
                std::vector<SdpRidAttributeList::Rid>* rids) const;
   void CreateEncodings(
       const SdpMediaSection& remote,
       const std::vector<UniquePtr<JsepCodecDescription>>& negotiatedCodecs,
       JsepTrackNegotiatedDetails* details);
 
   virtual std::vector<UniquePtr<JsepCodecDescription>> NegotiateCodecs(
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1417,18 +1417,18 @@ PeerConnectionImpl::SetRemoteDescription
       // Audio or video transceiver, need to tell JS about it.
       RefPtr<TransceiverImpl> transceiverImpl =
           CreateTransceiverImpl(jsepTransceiver, nullptr, jrv);
       if (jrv.Failed()) {
         return NS_ERROR_FAILURE;
       }
 
       const JsepTrack& receiving(jsepTransceiver->mRecvTrack);
-      CSFLogInfo(LOGTAG, "%s: pc = %s, asking JS to create transceiver for %s",
-                 __FUNCTION__, mHandle.c_str(), receiving.GetTrackId().c_str());
+      CSFLogInfo(LOGTAG, "%s: pc = %s, asking JS to create transceiver",
+                 __FUNCTION__, mHandle.c_str());
       switch (receiving.GetMediaType()) {
         case SdpMediaSection::MediaType::kAudio:
           mPCObserver->OnTransceiverNeeded(NS_ConvertASCIItoUTF16("audio"),
                                            *transceiverImpl, jrv);
           break;
         case SdpMediaSection::MediaType::kVideo:
           mPCObserver->OnTransceiverNeeded(NS_ConvertASCIItoUTF16("video"),
                                            *transceiverImpl, jrv);
@@ -2931,16 +2931,20 @@ void PeerConnectionImpl::startCallTelem(
 
   // Increment session call counter
   // If we want to track Loop calls independently here, we need two histograms.
   Telemetry::Accumulate(Telemetry::WEBRTC_CALL_COUNT_2, 1);
 }
 
 nsresult PeerConnectionImpl::DTMFState::Notify(nsITimer* timer) {
   MOZ_ASSERT(NS_IsMainThread());
+  if (!mTransceiver->IsSending()) {
+    mSendTimer->Cancel();
+    return NS_OK;
+  }
 
   nsString eventTone;
   if (!mTones.IsEmpty()) {
     uint16_t toneChar = mTones.CharAt(0);
     int tone = GetDTMFToneCode(toneChar);
 
     eventTone.Assign(toneChar);
 
--- a/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/TransceiverImpl.cpp
@@ -383,46 +383,32 @@ void TransceiverImpl::SyncWithJS(dom::RT
   }
 
   // Update send track ids in JSEP
   RefPtr<dom::RTCRtpSender> sender = aJsTransceiver.GetSender(aRv);
   if (aRv.Failed()) {
     return;
   }
 
-  RefPtr<dom::MediaStreamTrack> sendTrack = sender->GetTrack(aRv);
-  if (aRv.Failed()) {
-    return;
-  }
-
-  std::string trackId = mJsepTransceiver->mSendTrack.GetTrackId();
-
-  if (sendTrack) {
-    nsString wideTrackId;
-    sendTrack->GetId(wideTrackId);
-    trackId = NS_ConvertUTF16toUTF8(wideTrackId).get();
-    MOZ_ASSERT(!trackId.empty());
-  }
-
   nsTArray<RefPtr<DOMMediaStream>> streams;
   sender->GetStreams(streams, aRv);
   if (aRv.Failed()) {
     return;
   }
 
   std::vector<std::string> streamIds;
   for (const auto& stream : streams) {
     nsString wideStreamId;
     stream->GetId(wideStreamId);
     std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get();
     MOZ_ASSERT(!streamId.empty());
     streamIds.push_back(streamId);
   }
 
-  mJsepTransceiver->mSendTrack.UpdateTrackIds(streamIds, trackId);
+  mJsepTransceiver->mSendTrack.UpdateStreamIds(streamIds);
 
   // Update RTCRtpParameters
   // TODO: Both ways for things like ssrc, codecs, header extensions, etc
 
   dom::RTCRtpParameters parameters;
   sender->GetParameters(parameters, aRv);
 
   if (aRv.Failed()) {
@@ -450,32 +436,16 @@ void TransceiverImpl::SyncWithJS(dom::RT
   if (mJsepTransceiver->mSendTrack.SetJsConstraints(constraints)) {
     if (mTransmitPipeline->Transmitting()) {
       WebrtcGmpPCHandleSetter setter(mPCHandle);
       DebugOnly<nsresult> rv = UpdateConduit();
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
   }
 
-  // Update webrtc track id in JS; the ids in SDP are not surfaced to content,
-  // because they don't follow the rules that track/stream ids must. Our JS
-  // code must be able to map the SDP ids to the actual tracks/streams, and
-  // this is how the mapping for track ids is updated.
-  nsString webrtcTrackId =
-      NS_ConvertUTF8toUTF16(mJsepTransceiver->mRecvTrack.GetTrackId().c_str());
-  MOZ_MTLOG(ML_DEBUG, mPCHandle
-                          << "[" << mMid << "]: " << __FUNCTION__
-                          << " Setting webrtc track id: "
-                          << mJsepTransceiver->mRecvTrack.GetTrackId().c_str());
-  aJsTransceiver.SetRemoteTrackId(webrtcTrackId, aRv);
-
-  if (aRv.Failed()) {
-    return;
-  }
-
   // mid from JSEP
   if (mJsepTransceiver->IsAssociated()) {
     aJsTransceiver.SetMid(
         NS_ConvertUTF8toUTF16(mJsepTransceiver->GetMid().c_str()), aRv);
   } else {
     aJsTransceiver.UnsetMid(aRv);
   }
 
@@ -519,18 +489,17 @@ void TransceiverImpl::SyncWithJS(dom::RT
   for (const auto& id : mJsepTransceiver->mRecvTrack.GetStreamIds()) {
     receiveStreamIds.AppendElement(NS_ConvertUTF8toUTF16(id.c_str()), fallible);
   }
   receiver->SetStreamIds(receiveStreamIds, aRv);
   if (aRv.Failed()) {
     return;
   }
 
-  receiver->SetRemoteSendBit(mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit(),
-                             aRv);
+  receiver->SetRecvBit(mJsepTransceiver->mRecvTrack.GetRemoteSetSendBit(), aRv);
   if (aRv.Failed()) {
     return;
   }
 
   // AddTrack magic from JS
   if (aJsTransceiver.GetAddTrackMagic(aRv)) {
     mJsepTransceiver->SetAddTrackMagic();
   }
--- a/media/webrtc/signaling/src/peerconnection/TransceiverImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/TransceiverImpl.h
@@ -108,16 +108,21 @@ class TransceiverImpl : public nsISuppor
   }
 
   void AddRIDExtension(unsigned short aExtensionId);
 
   void AddRIDFilter(const nsAString& aRid);
 
   bool IsVideo() const;
 
+  bool IsSending() const {
+    return !mJsepTransceiver->IsStopped() &&
+           mJsepTransceiver->mSendTrack.GetActive();
+  }
+
   void GetRtpSources(const int64_t aTimeNow,
                      nsTArray<dom::RTCRtpSourceEntry>& outSources) const;
 
   // test-only: insert fake CSRCs and audio levels for testing
   void InsertAudioLevelForContributingSource(uint32_t aSource,
                                              int64_t aTimestamp, bool aHasLevel,
                                              uint8_t aLevel);
 
--- a/media/webrtc/signaling/src/sdp/SdpHelper.cpp
+++ b/media/webrtc/signaling/src/sdp/SdpHelper.cpp
@@ -393,17 +393,19 @@ nsresult SdpHelper::GetIdsFromMsid(const
 
   if (allMsids.empty()) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   streamIds->clear();
   for (const auto& msid : allMsids) {
     // "-" means no stream, see draft-ietf-mmusic-msid
-    if (msid.identifier != "-") {
+    // Remove duplicates, but leave order the same
+    if (msid.identifier != "-" &&
+        !std::count(streamIds->begin(), streamIds->end(), msid.identifier)) {
       streamIds->push_back(msid.identifier);
     }
   }
 
   return NS_OK;
 }
 
 nsresult SdpHelper::GetMsids(const SdpMediaSection& msection,
copy from mobile/android/config/mozconfigs/android-aarch64/nightly
copy to mobile/android/config/mozconfigs/android-aarch64/beta
--- a/mobile/android/config/mozconfigs/android-aarch64/nightly
+++ b/mobile/android/config/mozconfigs/android-aarch64/beta
@@ -1,15 +1,15 @@
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 # Android
 ac_add_options --with-android-min-sdk=21
 ac_add_options --target=aarch64-linux-android
 
-ac_add_options --with-branding=mobile/android/branding/nightly
+ac_add_options --with-branding=mobile/android/branding/beta
 
 export AR="$topsrcdir/clang/bin/llvm-ar"
 export NM="$topsrcdir/clang/bin/llvm-nm"
 export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
 
 export MOZ_LTO=1
 
 export MOZILLA_OFFICIAL=1
copy from mobile/android/config/mozconfigs/android-aarch64/debug
copy to mobile/android/config/mozconfigs/android-aarch64/debug-beta
--- a/mobile/android/config/mozconfigs/android-aarch64/debug
+++ b/mobile/android/config/mozconfigs/android-aarch64/debug-beta
@@ -6,11 +6,11 @@ ac_add_options --enable-debug
 # Android
 ac_add_options --with-android-min-sdk=21
 ac_add_options --target=aarch64-linux-android
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET
 
-ac_add_options --with-branding=mobile/android/branding/nightly
+ac_add_options --with-branding=mobile/android/branding/beta
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
copy from mobile/android/config/mozconfigs/android-api-16/nightly
copy to mobile/android/config/mozconfigs/android-api-16/beta
--- a/mobile/android/config/mozconfigs/android-api-16/nightly
+++ b/mobile/android/config/mozconfigs/android-api-16/beta
@@ -4,17 +4,17 @@
 # Warning: Before increasing the with-android-min-sdk value, please note several places in and out
 # of tree have to be changed. Otherwise, places like Treeherder or archive.mozilla.org will
 # advertise a bad API level. This may confuse people. As an example, please look at bug 1384482.
 # If you think you can't handle the whole set of changes, please reach out to the Release
 # Engineering team.
 ac_add_options --with-android-min-sdk=16
 ac_add_options --target=arm-linux-androideabi
 
-ac_add_options --with-branding=mobile/android/branding/nightly
+ac_add_options --with-branding=mobile/android/branding/beta
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_MMA=1
 export MOZ_ANDROID_POCKET=1
 
 export AR="$topsrcdir/clang/bin/llvm-ar"
 export NM="$topsrcdir/clang/bin/llvm-nm"
copy from mobile/android/config/mozconfigs/android-api-16/debug
copy to mobile/android/config/mozconfigs/android-api-16/debug-beta
--- a/mobile/android/config/mozconfigs/android-api-16/debug
+++ b/mobile/android/config/mozconfigs/android-api-16/debug-beta
@@ -10,11 +10,11 @@ ac_add_options --enable-debug
 # If you think you can't handle the whole set of changes, please reach out to the Release
 # Engineering team.
 ac_add_options --with-android-min-sdk=16
 ac_add_options --target=arm-linux-androideabi
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 
-ac_add_options --with-branding=mobile/android/branding/nightly
+ac_add_options --with-branding=mobile/android/branding/beta
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
copy from mobile/android/config/mozconfigs/android-x86/nightly
copy to mobile/android/config/mozconfigs/android-x86/beta
--- a/mobile/android/config/mozconfigs/android-x86/nightly
+++ b/mobile/android/config/mozconfigs/android-x86/beta
@@ -3,17 +3,17 @@
 # Warning: Before increasing the with-android-min-sdk value, please note several places in and out
 # of tree have to be changed. Otherwise, places like Treeherder or archive.mozilla.org will
 # advertise a bad API level. This may confuse people. As an example, please look at bug 1384482.
 # If you think you can't handle the whole set of changes, please reach out to the Release
 # Engineering team.
 ac_add_options --target=i686-linux-android
 ac_add_options --with-android-min-sdk=16
 
-ac_add_options --with-branding=mobile/android/branding/nightly
+ac_add_options --with-branding=mobile/android/branding/beta
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET=1
 
 export AR="$topsrcdir/clang/bin/llvm-ar"
 export NM="$topsrcdir/clang/bin/llvm-nm"
 export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
copy from mobile/android/config/mozconfigs/android-x86/debug
copy to mobile/android/config/mozconfigs/android-x86/debug-beta
--- a/mobile/android/config/mozconfigs/android-x86/debug
+++ b/mobile/android/config/mozconfigs/android-x86/debug-beta
@@ -11,11 +11,11 @@ ac_add_options --enable-debug
 # Engineering team.
 ac_add_options --target=i686-linux-android
 ac_add_options --with-android-min-sdk=16
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET=1
 
-ac_add_options --with-branding=mobile/android/branding/nightly
+ac_add_options --with-branding=mobile/android/branding/beta
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
copy from mobile/android/config/mozconfigs/android-x86_64/nightly
copy to mobile/android/config/mozconfigs/android-x86_64/beta
--- a/mobile/android/config/mozconfigs/android-x86_64/nightly
+++ b/mobile/android/config/mozconfigs/android-x86_64/beta
@@ -1,15 +1,15 @@
 . "$topsrcdir/mobile/android/config/mozconfigs/common"
 
 # Android
 ac_add_options --with-android-min-sdk=21
 ac_add_options --target=x86_64-linux-android
 
-ac_add_options --with-branding=mobile/android/branding/nightly
+ac_add_options --with-branding=mobile/android/branding/beta
 
 export AR="$topsrcdir/clang/bin/llvm-ar"
 export NM="$topsrcdir/clang/bin/llvm-nm"
 export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
 
 export MOZ_LTO=1
 
 export MOZILLA_OFFICIAL=1
copy from mobile/android/config/mozconfigs/android-x86_64/debug
copy to mobile/android/config/mozconfigs/android-x86_64/debug-beta
--- a/mobile/android/config/mozconfigs/android-x86_64/debug
+++ b/mobile/android/config/mozconfigs/android-x86_64/debug-beta
@@ -6,11 +6,11 @@ ac_add_options --enable-debug
 # Android
 ac_add_options --with-android-min-sdk=21
 ac_add_options --target=x86_64-linux-android
 
 export MOZILLA_OFFICIAL=1
 export MOZ_TELEMETRY_REPORTING=1
 export MOZ_ANDROID_POCKET
 
-ac_add_options --with-branding=mobile/android/branding/nightly
+ac_add_options --with-branding=mobile/android/branding/beta
 
 . "$topsrcdir/mobile/android/config/mozconfigs/common.override"
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PanZoomController.java
@@ -1,20 +1,25 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.geckoview;
 
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
+import android.app.UiModeManager;
+import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.support.annotation.NonNull;
 import android.support.annotation.UiThread;
 import android.support.annotation.IntDef;
 import android.util.Log;
 import android.util.Pair;
 import android.view.MotionEvent;
@@ -26,16 +31,18 @@ import java.lang.annotation.RetentionPol
 import java.util.ArrayList;
 
 @UiThread
 public class PanZoomController {
     private static final String LOGTAG = "GeckoNPZC";
     private static final int EVENT_SOURCE_SCROLL = 0;
     private static final int EVENT_SOURCE_MOTION = 1;
     private static final int EVENT_SOURCE_MOUSE = 2;
+    private static final String PREF_MOUSE_AS_TOUCH = "ui.android.mouse_as_touch";
+    private static boolean sTreatMouseAsTouch = true;
 
     private final GeckoSession mSession;
     private final Rect mTempRect = new Rect();
     private boolean mAttached;
     private float mPointerScrollFactor = 64.0f;
     private long mLastDownTime;
 
     @Retention(RetentionPolicy.SOURCE)
@@ -223,16 +230,40 @@ public class PanZoomController {
 
         return mNative.handleMouseEvent(event.getActionMasked(), event.getEventTime(),
                                         event.getMetaState(), x, y, event.getButtonState());
     }
 
     protected PanZoomController(final GeckoSession session) {
         mSession = session;
         enableEventQueue();
+        initMouseAsTouch();
+    }
+
+    private static void initMouseAsTouch() {
+        PrefsHelper.PrefHandler prefHandler = new PrefsHelper.PrefHandlerBase() {
+            @Override
+            public void prefValue(final String pref, final int value) {
+                if (!PREF_MOUSE_AS_TOUCH.equals(pref)) {
+                    return;
+                }
+                if (value == 0) {
+                    sTreatMouseAsTouch = false;
+                } else if (value == 1) {
+                    sTreatMouseAsTouch = true;
+                } else if (value == 2) {
+                    Context c = GeckoAppShell.getApplicationContext();
+                    UiModeManager m = (UiModeManager)c.getSystemService(Context.UI_MODE_SERVICE);
+                    // on TV devices, treat mouse as touch. everywhere else, don't
+                    sTreatMouseAsTouch = (m.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION);
+                }
+            }
+        };
+        PrefsHelper.addObserver(new String[] { PREF_MOUSE_AS_TOUCH }, prefHandler);
+        PrefsHelper.getPref(PREF_MOUSE_AS_TOUCH, prefHandler);
     }
 
     /**
      * Set the current scroll factor. The scroll factor is the maximum scroll amount that
      * one scroll event may generate, in device pixels.
      *
      * @param factor Scroll factor.
      */
@@ -256,16 +287,20 @@ public class PanZoomController {
      * "touch" rather than as "mouse". Pointer coordinates should be relative to the
      * display surface.
      *
      * @param event MotionEvent to process.
      * @return True if the event was handled.
      */
     public boolean onTouchEvent(final @NonNull MotionEvent event) {
         ThreadUtils.assertOnUiThread();
+
+        if (!sTreatMouseAsTouch && event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+            return handleMouseEvent(event);
+        }
         return handleMotionEvent(event);
     }
 
     /**
      * Process a touch event through the pan-zoom controller. Treat any mouse events as
      * "mouse" rather than as "touch". Pointer coordinates should be relative to the
      * display surface.
      *
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -1480,17 +1480,57 @@ VARCACHE_PREF(
 VARCACHE_PREF(
   "media.android-media-codec.preferred",
    MediaAndroidMediaCodecPreferred,
   RelaxedAtomicBool, true
 )
 
 #endif // ANDROID
 
-// WebRTC
+//---------------------------------------------------------------------------
+// MediaCapture prefs
+//---------------------------------------------------------------------------
+
+// Enables navigator.mediaDevices and getUserMedia() support. See also
+// media.peerconnection.enabled
+VARCACHE_PREF(
+              "media.navigator.enabled",
+              media_navigator_enabled,
+              bool, true
+              )
+
+// This pref turns off [SecureContext] on the navigator.mediaDevices object, for
+// more compatible legacy behavior.
+VARCACHE_PREF(
+              "media.devices.insecure.enabled",
+              media_devices_insecure_enabled,
+              bool, true
+              )
+
+// If the above pref is also enabled, this pref enabled getUserMedia() support
+// in http, bypassing the instant NotAllowedError you get otherwise.
+VARCACHE_PREF(
+              "media.getusermedia.insecure.enabled",
+              media_getusermedia_insecure_enabled,
+              bool, false
+              )
+
+//---------------------------------------------------------------------------
+// WebRTC prefs
+//---------------------------------------------------------------------------
+
+// Enables RTCPeerConnection support. Note that, when true, this pref enables
+// navigator.mediaDevices and getUserMedia() support as well.
+// See also media.navigator.enabled
+VARCACHE_PREF(
+              "media.peerconnection.enabled",
+              media_peerconnection_enabled,
+              bool, true
+              )
+
 #ifdef MOZ_WEBRTC
 #ifdef ANDROID
 
 VARCACHE_PREF(
   "media.navigator.hardware.vp8_encode.acceleration_remote_enabled",
    MediaNavigatorHardwareVp8encodeAccelerationRemoteEnabled,
   bool, true
 )
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -279,16 +279,18 @@ pref("dom.script_loader.binast_encoding.
 pref("dom.window.event.enabled", true);
 
 // Fastback caching - if this pref is negative, then we calculate the number
 // of content viewers to cache based on the amount of available memory.
 pref("browser.sessionhistory.max_tot