Merge mozilla-central to mozilla-inbound. CLOSED TREE
authorCsoregi Natalia <ncsoregi@mozilla.com>
Wed, 10 Apr 2019 16:22:06 +0300
changeset 468825 06ea3fe18fda8ef6f63cd1ee4b05d53bde35a179
parent 468824 0d99df46e4c92268e27439dde4b15902e8a061e6 (current diff)
parent 468719 a1eb490ba4480b756fcc4f92f8dbc753c66d7900 (diff)
child 468826 c6317f06ed07283b251c9c7329cee8a235c52c42
push id35851
push userdvarga@mozilla.com
push dateWed, 10 Apr 2019 21:56:12 +0000
treeherdermozilla-central@30ca3c3abfe6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
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 mozilla-inbound. CLOSED TREE
devtools/shared/test-helpers/moz.build
netwerk/test/unit/test_crossOriginOpenerPolicy.js
netwerk/test/unit_ipc/test_crossOriginOpenerPolicy_wrap.js
--- a/Makefile.in
+++ b/Makefile.in
@@ -19,16 +19,23 @@ ifndef TEST_MOZBUILD
 ifdef MOZ_BUILD_APP
 include $(wildcard $(topsrcdir)/$(MOZ_BUILD_APP)/build.mk)
 endif
 endif
 
 include $(topsrcdir)/config/config.mk
 
 GARBAGE_DIRS += _javagen _profile staticlib
+# To share compilation of dependencies, Rust libraries all set their
+# CARGO_TARGET_DIR as a subdirectory of topobjdir.  Normally, we would add
+# RUST*TARGET to GARBAGE_DIRS for those directories building Rust libraries.
+# But the directories building Rust libraries don't actually have
+# subdirectories to remove.  So we add to GARBAGE_DIRS once here, globally,
+# for it to have the desired effect.
+GARBAGE_DIRS += $(RUST_TARGET)
 DIST_GARBAGE = config.cache config.log config.status* config-defs.h \
    config/autoconf.mk \
    mozilla-config.h \
    netwerk/necko-config.h xpcom/xpcom-config.h xpcom/xpcom-private.h \
    .mozconfig.mk
 
 ifndef MOZ_PROFILE_USE
 # Automation builds should always have a new buildid, but for the sake of not
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1049,17 +1049,17 @@ pref("security.sandbox.rdd.win32k-disabl
 pref("security.sandbox.gmp.win32k-disable", false);
 #endif
 
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
 // Start the Mac sandbox early during child process startup instead
 // of when messaged by the parent after the message loop is running.
 pref("security.sandbox.content.mac.earlyinit", true);
 // Remove this pref once RDD early init is stable on Release.
-pref("security.sandbox.rdd.mac.earlyinit", false);
+pref("security.sandbox.rdd.mac.earlyinit", true);
 
 // This pref is discussed in bug 1083344, the naming is inspired from its
 // Windows counterpart, but on Mac it's an integer which means:
 // 0 -> "no sandbox" (nightly only)
 // 1 -> "preliminary content sandboxing enabled: write access to
 //       home directory is prevented"
 // 2 -> "preliminary content sandboxing enabled with profile protection:
 //       write access to home directory is prevented, read and write access
@@ -1474,25 +1474,18 @@ pref("media.gmp-widevinecdm.visible", tr
 pref("media.gmp-widevinecdm.enabled", true);
 #endif
 
 pref("media.gmp-gmpopenh264.visible", true);
 pref("media.gmp-gmpopenh264.enabled", true);
 
 // Switch block autoplay logic to v2, and enable UI.
 pref("media.autoplay.enabled.user-gestures-needed", true);
-
-#ifdef NIGHTLY_BUILD
 // Set Firefox to block autoplay, asking for permission by default.
 pref("media.autoplay.default", 1); // 0=Allowed, 1=Blocked
-#else
-// Set Firefox to block autoplay, asking for permission by default.
-pref("media.autoplay.default", 0); // 0=Allowed, 1=Blocked
-#endif
-
 
 #ifdef NIGHTLY_BUILD
 // Block WebAudio from playing automatically.
 pref("media.autoplay.block-webaudio", true);
 #else
 pref("media.autoplay.block-webaudio", false);
 #endif
 
--- a/browser/components/enterprisepolicies/Policies.jsm
+++ b/browser/components/enterprisepolicies/Policies.jsm
@@ -675,16 +675,22 @@ var Policies = {
 
   "NetworkPrediction": {
     onBeforeAddons(manager, param) {
       setAndLockPref("network.dns.disablePrefetch", !param);
       setAndLockPref("network.dns.disablePrefetchFromHTTPS", !param);
     },
   },
 
+  "NewTabPage": {
+    onBeforeAddons(manager, param) {
+      setAndLockPref("browser.newtabpage.enabled", param);
+    },
+  },
+
   "NoDefaultBookmarks": {
     onProfileAfterChange(manager, param) {
       if (param) {
         manager.disallowFeature("defaultBookmarks");
       }
     },
   },
 
@@ -768,17 +774,21 @@ var Policies = {
       } else {
         ProxyPolicies.configureProxySettings(param, setDefaultPref);
       }
     },
   },
 
   "RequestedLocales": {
     onBeforeAddons(manager, param) {
-      Services.locale.requestedLocales = param;
+      if (Array.isArray(param)) {
+        Services.locale.requestedLocales = param;
+      } else {
+        Services.locale.requestedLocales = param.split(",");
+      }
     },
   },
 
   "SanitizeOnShutdown": {
     onBeforeUIStartup(manager, param) {
       setAndLockPref("privacy.sanitize.sanitizeOnShutdown", param);
       if (param) {
         setAndLockPref("privacy.clearOnShutdown.cache", true);
--- a/browser/components/enterprisepolicies/schemas/policies-schema.json
+++ b/browser/components/enterprisepolicies/schemas/policies-schema.json
@@ -388,16 +388,20 @@
         }
       }
     },
 
     "NetworkPrediction": {
       "type": "boolean"
     },
 
+    "NewTabPage": {
+      "type": "boolean"
+    },
+
     "NoDefaultBookmarks": {
       "type": "boolean"
     },
 
     "OfferToSaveLogins": {
       "type": "boolean"
     },
 
@@ -617,17 +621,17 @@
 
         "AutoLogin": {
           "type": "boolean"
         }
       }
     },
 
     "RequestedLocales": {
-      "type": "array",
+      "type": ["string", "array"],
       "items": {
         "type": "string"
       }
     },
 
     "SanitizeOnShutdown": {
       "type": "boolean"
     },
--- a/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_pref_policies.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policies_simple_pref_policies.js
@@ -263,16 +263,26 @@ const POLICIES_TESTS = [
     policies: {
       "DisableFirefoxStudies": true,
     },
     lockedPrefs: {
       "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.addons": false,
       "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features": false,
     },
   },
+
+  // POLICY: NewTabPage
+  {
+    policies: {
+      "NewTabPage": false,
+    },
+    lockedPrefs: {
+      "browser.newtabpage.enabled": false,
+    },
+  },
 ];
 
 add_task(async function test_policy_remember_passwords() {
   for (let test of POLICIES_TESTS) {
     await setupPolicyEngineWithJson({
       "policies": test.policies,
     });
 
--- a/browser/components/enterprisepolicies/tests/browser/browser_policy_locale.js
+++ b/browser/components/enterprisepolicies/tests/browser/browser_policy_locale.js
@@ -1,35 +1,47 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const REQ_LOC_CHANGE_EVENT = "intl:requested-locales-changed";
 
-function promiseLocaleChanged() {
+function promiseLocaleChanged(requestedLocale) {
   return new Promise(resolve => {
     let localeObserver = {
       observe(aSubject, aTopic, aData) {
         switch (aTopic) {
           case REQ_LOC_CHANGE_EVENT:
             let reqLocs = Services.locale.requestedLocales;
-            is(reqLocs[0], "de");
+            is(reqLocs[0], requestedLocale);
             Services.obs.removeObserver(localeObserver, REQ_LOC_CHANGE_EVENT);
             resolve();
         }
       },
     };
     Services.obs.addObserver(localeObserver, REQ_LOC_CHANGE_EVENT);
   });
 }
 
-add_task(async function test_requested_locale() {
+add_task(async function test_requested_locale_array() {
   let originalLocales = Services.locale.requestedLocales;
-  let localePromise = promiseLocaleChanged();
+  let localePromise = promiseLocaleChanged("de");
   await setupPolicyEngineWithJson({
     "policies": {
       "RequestedLocales": ["de"],
     },
   });
   await localePromise;
   Services.locale.requestedLocales = originalLocales;
 });
+
+add_task(async function test_requested_locale_string() {
+  let originalLocales = Services.locale.requestedLocales;
+  let localePromise = promiseLocaleChanged("fr");
+  await setupPolicyEngineWithJson({
+    "policies": {
+      "RequestedLocales": "fr",
+    },
+  });
+  await localePromise;
+  Services.locale.requestedLocales = originalLocales;
+});
--- a/browser/components/preferences/in-content/tests/browser_applications_selection.js
+++ b/browser/components/preferences/in-content/tests/browser_applications_selection.js
@@ -190,21 +190,27 @@ add_task(async function checkDropdownBeh
 });
 
 add_task(async function sortingCheck() {
   let win = gBrowser.selectedBrowser.contentWindow;
   const handlerView = win.document.getElementById("handlersView");
   const typeColumn = win.document.getElementById("typeColumn");
   Assert.ok(typeColumn, "typeColumn is present in handlersView.");
 
+  let expectedNumberOfItems = handlerView.querySelectorAll("richlistitem").length;
+
   // Test default sorting
   assertSortByType("ascending");
 
   const oldDir = typeColumn.getAttribute("sortDirection");
 
+  // click on an item and sort again:
+  let itemToUse = handlerView.querySelector("richlistitem[type=mailto]");
+  itemToUse.scrollIntoView({block: "center"});
+  itemToUse.closest("richlistbox").selectItem(itemToUse);
 
   // Test sorting on the type column
   typeColumn.click();
   assertSortByType("descending");
   Assert.notEqual(oldDir,
                typeColumn.getAttribute("sortDirection"),
                "Sort direction should change");
 
@@ -225,20 +231,20 @@ add_task(async function sortingCheck() {
   actionColumn.click();
   assertSortByAction("descending");
 
   // Restore the default sort order
   typeColumn.click();
   assertSortByType("ascending");
 
   function assertSortByAction(order) {
-  Assert.equal(actionColumn.getAttribute("sortDirection"),
-               order,
-               `Sort direction should be ${order}`);
+    Assert.equal(actionColumn.getAttribute("sortDirection"), order,
+                 `Sort direction should be ${order}`);
     let siteItems = handlerView.getElementsByTagName("richlistitem");
+    Assert.equal(siteItems.length, expectedNumberOfItems, "Number of items should not change.");
     for (let i = 0; i < siteItems.length - 1; ++i) {
       let aType = siteItems[i].getAttribute("actionDescription").toLowerCase();
       let bType = siteItems[i + 1].getAttribute("actionDescription").toLowerCase();
       let result = 0;
       if (aType > bType) {
         result = 1;
       } else if (bType > aType) {
         result = -1;
@@ -247,20 +253,21 @@ add_task(async function sortingCheck() {
         Assert.lessOrEqual(result, 0, "Should sort applications in the ascending order by action");
       } else {
         Assert.greaterOrEqual(result, 0, "Should sort applications in the descending order by action");
       }
     }
   }
 
   function assertSortByType(order) {
-  Assert.equal(typeColumn.getAttribute("sortDirection"),
-               order,
-               `Sort direction should be ${order}`);
+    Assert.equal(typeColumn.getAttribute("sortDirection"), order,
+                 `Sort direction should be ${order}`);
+
     let siteItems = handlerView.getElementsByTagName("richlistitem");
+    Assert.equal(siteItems.length, expectedNumberOfItems, "Number of items should not change.");
     for (let i = 0; i < siteItems.length - 1; ++i) {
       let aType = siteItems[i].getAttribute("typeDescription").toLowerCase();
       let bType = siteItems[i + 1].getAttribute("typeDescription").toLowerCase();
       let result = 0;
       if (aType > bType) {
         result = 1;
       } else if (bType > aType) {
         result = -1;
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -184,17 +184,19 @@
 @RESPATH@/components/servicesComponents.manifest
 @RESPATH@/components/servicesSettings.manifest
 @RESPATH@/components/cryptoComponents.manifest
 
 @RESPATH@/components/Push.manifest
 
 ; CDP remote agent
 #ifdef ENABLE_REMOTE_AGENT
-@RESPATH@/components/RemoteAgent.js
+@RESPATH@/chrome/remote@JAREXT@
+@RESPATH@/chrome/remote.manifest
+@RESPATH@/components/command-line-handler.js
 @RESPATH@/components/RemoteAgent.manifest
 @RESPATH@/defaults/pref/remote.js
 #endif
 
 ; Marionette remote control protocol
 #ifdef ENABLE_MARIONETTE
 @RESPATH@/chrome/marionette@JAREXT@
 @RESPATH@/chrome/marionette.manifest
--- a/browser/locales/en-US/browser/policies/policies-descriptions.ftl
+++ b/browser/locales/en-US/browser/policies/policies-descriptions.ftl
@@ -94,16 +94,18 @@ policy-HardwareAcceleration = If false, 
 
 # “lock” means that the user won’t be able to change this setting
 policy-Homepage = Set and optionally lock the homepage.
 
 policy-InstallAddonsPermission = Allow certain websites to install add-ons.
 
 policy-NetworkPrediction = Enable or disable network prediction (DNS prefetching).
 
+policy-NewTabPage = Enable or disable the New Tab page.
+
 policy-NoDefaultBookmarks = Disable creation of the default bookmarks bundled with { -brand-short-name }, and the Smart Bookmarks (Most Visited, Recent Tags). Note: this policy is only effective if used before the first run of the profile.
 
 policy-OfferToSaveLogins = Enforce the setting to allow { -brand-short-name } to offer to remember saved logins and passwords. Both true and false values are accepted.
 
 policy-OverrideFirstRunPage = Override the first run page. Set this policy to blank if you want to disable the first run page.
 
 policy-OverridePostUpdatePage = Override the post-update “What’s New” page. Set this policy to blank if you want to disable the post-update page.
 
--- a/build.gradle
+++ b/build.gradle
@@ -76,17 +76,17 @@ buildscript {
     ext.jacoco_version = '0.8.1'
 
     if (gradle.mozconfig.substs.MOZ_ANDROID_GOOGLE_PLAY_SERVICES) {
         ext.google_play_services_version = '15.0.1'
         ext.google_play_services_cast_version = '16.0.0'
     }
 
     dependencies {
-        classpath 'org.mozilla.apilint:apilint:0.1.8'
+        classpath 'org.mozilla.apilint:apilint:0.1.9'
         classpath 'com.android.tools.build:gradle:3.1.4'
         classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.2'
         classpath 'org.apache.commons:commons-exec:1.3'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
     }
 }
 
 // A stream that processes bytes line by line, prepending a tag before sending
--- a/build/autoconf/toolchain.m4
+++ b/build/autoconf/toolchain.m4
@@ -7,16 +7,39 @@ dnl or AC_HEADER_STDC, meaning they are 
 dnl them explicitly.
 dnl However, theses checks are not necessary and python configure sets
 dnl the corresponding variables already, so just skip those tests
 dnl entirely.
 define([AC_PROG_CPP],[])
 define([AC_PROG_CXXCPP],[])
 define([AC_HEADER_STDC], [])
 
+dnl AC_LANG_* set ac_link to the C/C++ compiler, which works fine with
+dnl gcc and clang, but not great with clang-cl, where the build system
+dnl currently expects to run the linker independently. So LDFLAGS are not
+dnl really adapted to be used with clang-cl, which then proceeds to
+dnl execute link.exe rather than lld-link.exe.
+dnl So when the compiler is clang-cl, we modify ac_link to use a separate
+dnl linker call.
+define([_MOZ_AC_LANG_C], defn([AC_LANG_C]))
+define([AC_LANG_C],
+[_MOZ_AC_LANG_C
+if test "$CC_TYPE" = "clang-cl"; then
+  ac_link="$ac_compile"' && ${LINKER} -OUT:conftest${ac_exeext} $LDFLAGS conftest.obj $LIBS 1>&AC_FD_CC'
+fi
+])
+
+define([_MOZ_AC_LANG_CPLUSPLUS], defn([AC_LANG_CPLUSPLUS]))
+define([AC_LANG_CPLUSPLUS],
+[_MOZ_AC_LANG_CPLUSPLUS
+if test "$CC_TYPE" = "clang-cl"; then
+  ac_link="$ac_compile"' && ${LINKER} -OUT:conftest${ac_exeext} $LDFLAGS conftest.obj $LIBS 1>&AC_FD_CC'
+fi
+])
+
 AC_DEFUN([MOZ_TOOL_VARIABLES],
 [
 GNU_CC=
 GNU_CXX=
 if test "$CC_TYPE" = "gcc"; then
     GNU_CC=1
     GNU_CXX=1
 fi
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -124,45 +124,64 @@ with only_when(target_is_osx):
     add_old_configure_assignment('MACOSX_DEPLOYMENT_TARGET', macos_target)
 
     # MacOS SDK
     # =========
 
     option('--with-macos-sdk', env='MACOS_SDK_DIR', nargs=1,
            help='Location of platform SDK to use')
 
-    @depends_if('--with-macos-sdk')
+    @depends('--with-macos-sdk')
     @imports(_from='os.path', _import='isdir')
-    @imports(_from='plistlib', _import='readPlist')
+    @imports(_from='biplist', _import='readPlist')
+    @imports('subprocess')
+    @imports('which')
     def macos_sdk(value):
         sdk_min_version = Version('10.11')
         sdk_max_version = Version('10.13')
 
-        if not isdir(value[0]):
+        sdk_path = None
+        if value:
+            sdk_path = value[0]
+        else:
+            try:
+                xcrun = which.which('xcrun')
+                args = [xcrun, '--sdk', 'macosx', '--show-sdk-path']
+                sdk_path = subprocess.check_output(args).strip()
+            except which.WhichError:
+                # On a Linux cross-compile, we don't have xcrun, so just leave
+                # the SDK unset if --with-macos-sdk isn't specified.
+                pass
+
+        if not sdk_path:
+            return
+
+        if not isdir(sdk_path):
             die('SDK not found in %s. When using --with-macos-sdk, you must specify a '
                 'valid SDK. SDKs are installed when the optional cross-development '
                 'tools are selected during the Xcode/Developer Tools installation.'
-                % value[0])
-        obj = readPlist(os.path.join(value[0], 'SDKSettings.plist'))
+                % sdk_path)
+        obj = readPlist(os.path.join(sdk_path, 'SDKSettings.plist'))
         if not obj:
-            die('Error parsing SDKSettings.plist in the SDK directory: %s' % value[0])
+            die('Error parsing SDKSettings.plist in the SDK directory: %s' % sdk_path)
         if 'Version' not in obj:
-            die('Error finding Version information in SDKSettings.plist from the SDK: %s' % value[0])
+            die('Error finding Version information in SDKSettings.plist from the '
+                'SDK: %s' % sdk_path)
         version = Version(obj['Version'])
         if version < sdk_min_version:
             die('SDK version "%s" is too old. Please upgrade to at least %s. '
                 'You may need to point to it using --with-macos-sdk=<path> in your '
                 'mozconfig. Various SDK versions are available from '
                 'https://github.com/phracker/MacOSX-SDKs' % (version, sdk_min_version))
         if version > sdk_max_version:
             die('SDK version "%s" is unsupported. Please downgrade to version '
                 '%s. You may need to point to it using --with-macos-sdk=<path> in '
                 'your mozconfig. Various SDK versions are available from '
                 'https://github.com/phracker/MacOSX-SDKs' % (version, sdk_max_version))
-        return value[0]
+        return sdk_path
 
     set_config('MACOS_SDK_DIR', macos_sdk)
 
     with only_when(cross_compiling):
         option('--with-macos-private-frameworks',
                env="MACOS_PRIVATE_FRAMEWORKS_DIR", nargs=1,
                help='Location of private frameworks to use')
 
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -3,16 +3,17 @@ mozilla.pth:python/mozboot
 mozilla.pth:python/mozbuild
 mozilla.pth:python/mozlint
 mozilla.pth:python/mozrelease
 mozilla.pth:python/mozterm
 mozilla.pth:python/mozversioncontrol
 mozilla.pth:python/l10n
 mozilla.pth:third_party/python/atomicwrites
 mozilla.pth:third_party/python/attrs/src
+mozilla.pth:third_party/python/biplist
 mozilla.pth:third_party/python/blessings
 mozilla.pth:third_party/python/Click
 mozilla.pth:third_party/python/compare-locales
 mozilla.pth:third_party/python/configobj
 mozilla.pth:third_party/python/cram
 mozilla.pth:third_party/python/dlmanager
 mozilla.pth:third_party/python/enum34
 mozilla.pth:third_party/python/fluent
--- a/config/makefiles/rust.mk
+++ b/config/makefiles/rust.mk
@@ -285,29 +285,35 @@ force-cargo-host-library-build:
 force-cargo-host-library-check:
 	$(call CARGO_CHECK) --lib $(cargo_host_flag) $(host_rust_features_flag)
 else
 force-cargo-host-library-check:
 	@true
 endif # HOST_RUST_LIBRARY_FILE
 
 ifdef RUST_PROGRAMS
+
+GARBAGE_DIRS += $(RUST_TARGET)
+
 force-cargo-program-build:
 	$(REPORT_BUILD)
 	$(call CARGO_BUILD) $(addprefix --bin ,$(RUST_CARGO_PROGRAMS)) $(cargo_target_flag)
 
 $(RUST_PROGRAMS): force-cargo-program-build
 
 force-cargo-program-check:
 	$(call CARGO_CHECK) $(addprefix --bin ,$(RUST_CARGO_PROGRAMS)) $(cargo_target_flag)
 else
 force-cargo-program-check:
 	@true
 endif # RUST_PROGRAMS
 ifdef HOST_RUST_PROGRAMS
+
+GARBAGE_DIRS += $(RUST_HOST_TARGET)
+
 force-cargo-host-program-build:
 	$(REPORT_BUILD)
 	$(call CARGO_BUILD) $(addprefix --bin ,$(HOST_RUST_CARGO_PROGRAMS)) $(cargo_host_flag)
 
 $(HOST_RUST_PROGRAMS): force-cargo-host-program-build
 
 force-cargo-host-program-check:
 	$(REPORT_BUILD)
--- a/devtools/client/debugger/src/actions/breakpoints/index.js
+++ b/devtools/client/debugger/src/actions/breakpoints/index.js
@@ -239,33 +239,41 @@ export function toggleBreakpointAtLine(c
         sourceId: selectedSource.id,
         sourceUrl: selectedSource.url,
         line: line
       })
     );
   };
 }
 
-export function addBreakpointAtLine(cx: Context, line: number) {
+export function addBreakpointAtLine(
+  cx: Context,
+  line: number,
+  shouldLog: ?boolean = false
+) {
   return ({ dispatch, getState, client, sourceMaps }: ThunkArgs) => {
     const state = getState();
     const source = getSelectedSource(state);
 
     if (!source || isEmptyLineInSource(state, line, source.id)) {
       return;
     }
+    const breakpointLocation = {
+      sourceId: source.id,
+      sourceUrl: source.url,
+      column: undefined,
+      line
+    };
 
-    return dispatch(
-      addBreakpoint(cx, {
-        sourceId: source.id,
-        sourceUrl: source.url,
-        column: undefined,
-        line
-      })
-    );
+    const options = {};
+    if (shouldLog) {
+      options.logValue = "displayName";
+    }
+
+    return dispatch(addBreakpoint(cx, breakpointLocation, options));
   };
 }
 
 export function removeBreakpointsAtLine(
   cx: Context,
   sourceId: string,
   line: number
 ) {
--- a/devtools/client/debugger/src/components/Editor/ConditionalPanel.css
+++ b/devtools/client/debugger/src/components/Editor/ConditionalPanel.css
@@ -10,19 +10,25 @@
   align-items: center;
   background: var(--theme-toolbar-background);
   border-top: 1px solid var(--theme-splitter-color);
   border-bottom: 1px solid var(--theme-splitter-color);
 }
 
 .conditional-breakpoint-panel .prompt {
   font-size: 1.8em;
-  color: var(--theme-conditional-breakpoint-color);
+  color: var(--theme-graphs-orange);
   padding-left: 3px;
   padding-right: 3px;
   padding-bottom: 3px;
   text-align: right;
   width: 30px;
+  align-self: baseline;
+  margin-top: 3px;
+}
+
+.conditional-breakpoint-panel.log-point .prompt {
+  color: var(--purple-60);
 }
 
 .conditional-breakpoint-panel .CodeMirror {
   margin: 6px 10px;
 }
--- a/devtools/client/debugger/src/components/Editor/ConditionalPanel.js
+++ b/devtools/client/debugger/src/components/Editor/ConditionalPanel.js
@@ -15,55 +15,66 @@ import {
   getBreakpointForLocation,
   getConditionalPanelLocation,
   getLogPointStatus,
   getContext
 } from "../../selectors";
 
 import type { SourceLocation, Context } from "../../types";
 
+function addNewLine(doc: Object) {
+  const cursor = doc.getCursor();
+  const pos = { line: cursor.line, ch: cursor.ch };
+  doc.replaceRange("\n", pos);
+}
+
 type Props = {
   cx: Context,
   breakpoint: ?Object,
   setBreakpointOptions: typeof actions.setBreakpointOptions,
   location: SourceLocation,
   log: boolean,
   editor: Object,
   openConditionalPanel: typeof actions.openConditionalPanel,
   closeConditionalPanel: typeof actions.closeConditionalPanel
 };
 
 export class ConditionalPanel extends PureComponent<Props> {
   cbPanel: null | Object;
-  input: ?HTMLInputElement;
+  input: ?HTMLTextAreaElement;
+  codeMirror: ?Object;
   panelNode: ?HTMLDivElement;
   scrollParent: ?HTMLElement;
 
   constructor() {
     super();
     this.cbPanel = null;
   }
 
   keepFocusOnInput() {
     if (this.input) {
       this.input.focus();
     }
   }
 
   saveAndClose = () => {
     if (this.input) {
-      this.setBreakpoint(this.input.value);
+      this.setBreakpoint(this.input.value.trim());
     }
 
     this.props.closeConditionalPanel();
   };
 
-  onKey = (e: SyntheticKeyboardEvent<HTMLInputElement>) => {
+  onKey = (e: SyntheticKeyboardEvent<HTMLTextAreaElement>) => {
     if (e.key === "Enter") {
-      this.saveAndClose();
+      if (this.codeMirror && e.altKey) {
+        addNewLine(this.codeMirror.doc);
+      } else {
+        this.saveAndClose();
+      }
     } else if (e.key === "Escape") {
       this.props.closeConditionalPanel();
     }
   };
 
   setBreakpoint(value: string) {
     const { cx, location, log, breakpoint } = this.props;
     const options = breakpoint ? breakpoint.options : {};
@@ -141,55 +152,65 @@ export class ConditionalPanel extends Pu
 
       if (this.scrollParent) {
         this.scrollParent.addEventListener("scroll", this.repositionOnScroll);
         this.repositionOnScroll();
       }
     }
   }
 
+  createEditor = (input: ?HTMLTextAreaElement) => {
+    const { log, editor } = this.props;
+
+    const codeMirror = editor.CodeMirror.fromTextArea(input, {
+      mode: "javascript",
+      theme: "mozilla",
+      placeholder: L10N.getStr(
+        log
+          ? "editor.conditionalPanel.logPoint.placeholder2"
+          : "editor.conditionalPanel.placeholder2"
+      )
+    });
+    const codeMirrorWrapper = codeMirror.getWrapperElement();
+
+    codeMirrorWrapper.addEventListener("keydown", e => {
+      codeMirror.save();
+      this.onKey(e);
+    });
+
+    this.input = input;
+    this.codeMirror = codeMirror;
+    codeMirror.focus();
+    codeMirror.setCursor(codeMirror.lineCount(), 0);
+  };
+
+  getDefaultValue() {
+    const { breakpoint, log } = this.props;
+    const options = (breakpoint && breakpoint.options) || {};
+    return log ? options.logValue : options.condition;
+  }
+
   renderConditionalPanel(props: Props) {
-    const { breakpoint, log, editor } = props;
-    const options = (breakpoint && breakpoint.options) || {};
-    const condition = log ? options.logValue : options.condition;
+    const { log } = props;
+    const defaultValue = this.getDefaultValue();
 
     const panel = document.createElement("div");
     ReactDOM.render(
       <div
         className={classNames("conditional-breakpoint-panel", {
           "log-point": log
         })}
         onClick={() => this.keepFocusOnInput()}
-        onBlur={this.props.closeConditionalPanel}
+        // onBlur={this.props.closeConditionalPanel}
         ref={node => (this.panelNode = node)}
       >
         <div className="prompt">»</div>
-        <input
-          defaultValue={condition}
-          ref={input => {
-            const codeMirror = editor.CodeMirror.fromTextArea(input, {
-              mode: "javascript",
-              theme: "mozilla",
-              placeholder: L10N.getStr(
-                log
-                  ? "editor.conditionalPanel.logPoint.placeholder"
-                  : "editor.conditionalPanel.placeholder"
-              )
-            });
-            const codeMirrorWrapper = codeMirror.getWrapperElement();
-
-            codeMirrorWrapper.addEventListener("keydown", e => {
-              codeMirror.save();
-              this.onKey(e);
-            });
-
-            this.input = input;
-            codeMirror.focus();
-            codeMirror.setCursor(codeMirror.lineCount(), 0);
-          }}
+        <textarea
+          defaultValue={defaultValue}
+          ref={input => this.createEditor(input)}
         />
       </div>,
       panel
     );
     return panel;
   }
 
   render() {
--- a/devtools/client/debugger/src/components/Editor/Editor.css
+++ b/devtools/client/debugger/src/components/Editor/Editor.css
@@ -25,24 +25,16 @@
 }
 
 .CodeMirror.cm-s-mozilla,
 .CodeMirror-scroll,
 .CodeMirror-sizer {
   overflow-anchor: none;
 }
 
-.theme-dark {
-  --theme-conditional-breakpoint-color: #9fa4a9;
-}
-
-.theme-light {
-  --theme-conditional-breakpoint-color: var(--theme-body-color);
-}
-
 /**
  * There's a known codemirror flex issue with chrome that this addresses.
  * BUG https://github.com/firefox-devtools/debugger/issues/63
  */
 .editor-wrapper {
   position: absolute;
   width: calc(100% - 1px);
   top: var(--editor-header-height);
--- a/devtools/client/debugger/src/components/Editor/index.js
+++ b/devtools/client/debugger/src/components/Editor/index.js
@@ -413,17 +413,17 @@ class Editor extends PureComponent<Props
     if (typeof sourceLine !== "number") {
       return;
     }
 
     if (ev.metaKey) {
       return continueToHere(cx, sourceLine);
     }
 
-    return addBreakpointAtLine(cx, sourceLine);
+    return addBreakpointAtLine(cx, sourceLine, ev.altKey);
   };
 
   onGutterContextMenu = (event: MouseEvent) => {
     return this.openMenu(event);
   };
 
   onClick(e: MouseEvent) {
     const { cx, selectedSource, jumpToMappedLocation } = this.props;
--- a/devtools/client/debugger/test/mochitest/.eslintrc
+++ b/devtools/client/debugger/test/mochitest/.eslintrc
@@ -30,34 +30,39 @@
     "todo_is": false,
     "todo_isnot": false,
     "waitForClipboard": false,
     "waitForExplicitFinish": false,
     "waitForFocus": false,
     "selectSource": false,
     "prettyPrint": false,
     "closeTab": false,
+    "pushPref": false,
 
     // Globals introduced in debugger-specific head.js
     "getCM": false,
+    "getDebuggerSplitConsole": false,
+    "hasConsoleMessage": false,
+    "findConsoleMessage": false,
     "promise": false,
     "BrowserToolboxProcess": false,
     "OS": false,
     "selectors": false,
     "waitForNextDispatch": false,
     "waitForDispatch": false,
     "waitForThreadEvents": false,
     "waitForState": false,
     "waitForElement": false,
     "waitForElementWithSelector": false,
     "waitForPaused": false,
     "waitForSources": false,
     "waitForSource": false,
     "waitForLoadedSource": false,
     "waitForSelectedSource": false,
+    "waitForBreakpoint": false,
     "waitForBreakpointCount": false,
     "isPaused": false,
     "assertSourceCount": false,
     "assertEditorBreakpoint": false,
     "assertBreakpointSnippet": false,
     "assertEmptyLines": false,
     "assertPausedLocation": false,
     "assertHighlightLocation": false,
@@ -80,16 +85,17 @@
     "removeBreakpoint": false,
     "addBreakpoint": false,
     "toggleCallStack": false,
     "toggleScopes": false,
     "isVisibleWithin": false,
     "clickElement": false,
     "clickElementWithSelector": false,
     "clickDOMElement": false,
+    "altClickElement": false,
     "rightClickElement": false,
     "clickGutter": false,
     "selectMenuItem": false,
     "selectContextMenuItem": false,
     "togglePauseOnExceptions": false,
     "type": false,
     "pressKey": false,
     "synthesizeContextMenuEvent": false,
--- a/devtools/client/debugger/test/mochitest/browser_dbg-breakpoints-cond.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-breakpoints-cond.js
@@ -31,20 +31,16 @@ function assertEditorBreakpoint(
   );
 }
 
 function waitForElementFocus(dbg, el) {
   const doc = dbg.win.document;
   return waitFor(() => doc.activeElement == el && doc.hasFocus());
 }
 
-function waitForBreakpoint(dbg, url, line) {
-  return waitForState(dbg, () => findBreakpoint(dbg, url, line));
-}
-
 function waitForBreakpointWithCondition(dbg, url, line, cond) {
   return waitForState(dbg, () => {
     const bp = findBreakpoint(dbg, url, line);
     return (
       bp && bp.options.condition && (!cond || bp.options.condition == cond)
     );
   });
 }
@@ -58,50 +54,43 @@ function waitForBreakpointWithLog(dbg, u
 
 function waitForBreakpointWithoutCondition(dbg, url, line) {
   return waitForState(dbg, () => {
     const bp = findBreakpoint(dbg, url, line);
     return bp && !bp.options.condition;
   });
 }
 
-async function assertConditionalBreakpointIsFocused(dbg) {
-  const input = findElement(dbg, "conditionalPanelInput");
-  await waitForElementFocus(dbg, input);
-}
-
 async function setConditionalBreakpoint(dbg, index, condition) {
   const {
     addConditionalBreakpoint,
     editConditionalBreakpoint
   } = selectors.gutterContextMenu;
   // Make this work with either add or edit menu items
   const selector = `${addConditionalBreakpoint},${editConditionalBreakpoint}`;
 
   rightClickElement(dbg, "gutter", index);
   selectContextMenuItem(dbg, selector);
   await waitForElement(dbg, "conditionalPanelInput");
-  await assertConditionalBreakpointIsFocused(dbg);
 
   // Position cursor reliably at the end of the text.
   pressKey(dbg, "End");
   type(dbg, condition);
   pressKey(dbg, "Enter");
 }
 
 async function setLogPoint(dbg, index, value) {
   const { addLogPoint, editLogPoint } = selectors.gutterContextMenu;
 
   // Make this work with either add or edit menu items
   const selector = `${addLogPoint},${editLogPoint}`;
 
   rightClickElement(dbg, "gutter", index);
   selectContextMenuItem(dbg, selector);
   await waitForElement(dbg, "conditionalPanelInput");
-  await assertConditionalBreakpointIsFocused(dbg);
 
   // Position cursor reliably at the end of the text.
   pressKey(dbg, "End");
   type(dbg, value);
   pressKey(dbg, "Enter");
 }
 
 add_task(async function() {
@@ -153,9 +142,13 @@ add_task(async function() {
 
   info('Add "log point"');
   await setLogPoint(dbg, 5, "44");
   await waitForBreakpointWithLog(dbg, "simple2", 5);
   await assertEditorBreakpoint(dbg, 5, { hasLog: true });
 
   bp = findBreakpoint(dbg, "simple2", 5);
   is(bp.options.logValue, "44", "breakpoint condition removed");
+
+  await altClickElement(dbg, "gutter", 6);
+  bp = await waitForBreakpoint(dbg, "simple2", 6);
+  is(bp.options.logValue, "displayName", "logPoint has default value");
 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg-log-points.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-log-points.js
@@ -5,26 +5,32 @@
 /*
  * Tests that log points are correctly logged to the console
  */
 
 add_task(async function() {
   Services.prefs.setBoolPref("devtools.toolbox.splitconsoleEnabled", true);
   const dbg = await initDebugger("doc-script-switching.html", "switching-01");
 
-  const source = findSource(dbg, "switching-01")
+  const source = findSource(dbg, "switching-01");
   await selectSource(dbg, "switching-01");
 
   await getDebuggerSplitConsole(dbg);
 
+  await altClickElement(dbg, "gutter", 7);
+  await waitForBreakpoint(dbg, "switching-01", 7);
+
   await dbg.actions.addBreakpoint(
     getContext(dbg),
-    { line: 5, sourceId: source.id },
+    { line: 8, sourceId: source.id },
     { logValue: "'a', 'b', 'c'" }
   );
+
   invokeInTab("firstCall");
   await waitForPaused(dbg);
 
   await hasConsoleMessage(dbg, "a b c");
+  await hasConsoleMessage(dbg, "firstCall");
+
   const { link, value } = await findConsoleMessage(dbg, "a b c");
-  is(link, "script-switching-01.js:5:2", "logs should have the relevant link");
+  is(link, "script-switching-01.js:8:2", "logs should have the relevant link");
   is(value, "a b c", "logs should have multiple values");
 });
--- a/devtools/client/debugger/test/mochitest/examples/script-switching-01.js
+++ b/devtools/client/debugger/test/mochitest/examples/script-switching-01.js
@@ -1,6 +1,9 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* 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/>. */
 
+function a() {}
 function firstCall() {
+  a();
   secondCall();
 }
--- a/devtools/client/debugger/test/mochitest/helpers.js
+++ b/devtools/client/debugger/test/mochitest/helpers.js
@@ -204,24 +204,21 @@ async function waitForElement(dbg, name,
   return findElement(dbg, name, ...args);
 }
 
 async function waitForElementWithSelector(dbg, selector) {
   await waitUntil(() => findElementWithSelector(dbg, selector));
   return findElementWithSelector(dbg, selector);
 }
 
-function waitForSelectedLocation(dbg, line ) {
-  return waitForState(
-    dbg,
-    state => {
-      const location = dbg.selectors.getSelectedLocation(state)
-      return location && location.line == line
-    }
-  );
+function waitForSelectedLocation(dbg, line) {
+  return waitForState(dbg, state => {
+    const location = dbg.selectors.getSelectedLocation(state);
+    return location && location.line == line;
+  });
 }
 
 function waitForSelectedSource(dbg, url) {
   const {
     getSelectedSource,
     hasSymbols,
     hasBreakpointPositions
   } = dbg.selectors;
@@ -460,16 +457,20 @@ async function waitForLoadedScopes(dbg) 
 
 function waitForBreakpointCount(dbg, count) {
   return waitForState(
     dbg,
     state => dbg.selectors.getBreakpointCount(state) == count
   );
 }
 
+function waitForBreakpoint(dbg, url, line) {
+  return waitForState(dbg, () => findBreakpoint(dbg, url, line));
+}
+
 /**
  * Waits for the debugger to be fully paused.
  *
  * @memberof mochitest/waits
  * @param {Object} dbg
  * @static
  */
 async function waitForPaused(dbg, url) {
@@ -791,17 +792,21 @@ function getFirstBreakpointColumn(dbg, {
  * @param {Number} col
  * @return {Promise}
  * @static
  */
 async function addBreakpoint(dbg, source, line, column, options) {
   source = findSource(dbg, source);
   const sourceId = source.id;
   const bpCount = dbg.selectors.getBreakpointCount(dbg.getState());
-  await dbg.actions.addBreakpoint(getContext(dbg), { sourceId, line, column }, options);
+  await dbg.actions.addBreakpoint(
+    getContext(dbg),
+    { sourceId, line, column },
+    options
+  );
   is(
     dbg.selectors.getBreakpointCount(dbg.getState()),
     bpCount + 1,
     "a new breakpoint was created"
   );
 }
 
 function disableBreakpoint(dbg, source, line, column) {
@@ -810,18 +815,22 @@ function disableBreakpoint(dbg, source, 
   const location = { sourceId: source.id, sourceUrl: source.url, line, column };
   const bp = dbg.selectors.getBreakpointForLocation(dbg.getState(), location);
   return dbg.actions.disableBreakpoint(getContext(dbg), bp);
 }
 
 function setBreakpointOptions(dbg, source, line, column, options) {
   source = findSource(dbg, source);
   const sourceId = source.id;
-  column = column || getFirstBreakpointColumn(dbg, {line, sourceId});
-  return dbg.actions.setBreakpointOptions(getContext(dbg), { sourceId, line, column }, options);
+  column = column || getFirstBreakpointColumn(dbg, { line, sourceId });
+  return dbg.actions.setBreakpointOptions(
+    getContext(dbg),
+    { sourceId, line, column },
+    options
+  );
 }
 
 function findBreakpoint(dbg, url, line) {
   const {
     selectors: { getBreakpoint, getBreakpointsList },
     getState
   } = dbg;
   const source = findSource(dbg, url);
@@ -1314,16 +1323,24 @@ function dblClickElement(dbg, elementNam
 
   return EventUtils.synthesizeMouseAtCenter(
     findElementWithSelector(dbg, selector),
     { clickCount: 2 },
     dbg.win
   );
 }
 
+function altClickElement(dbg, elementName, ...args) {
+  const selector = getSelector(elementName, ...args);
+  const el = findElementWithSelector(dbg, selector);
+  el.scrollIntoView();
+
+  return EventUtils.synthesizeMouseAtCenter(el, { altKey: true }, dbg.win);
+}
+
 function rightClickElement(dbg, elementName, ...args) {
   const selector = getSelector(elementName, ...args);
   const doc = dbg.win.document;
 
   return EventUtils.synthesizeMouseAtCenter(
     doc.querySelector(selector),
     { type: "contextmenu" },
     dbg.win
--- a/devtools/client/framework/menu.js
+++ b/devtools/client/framework/menu.js
@@ -93,16 +93,17 @@ Menu.prototype.popup = function(screenX,
   let popup = popupset.querySelector("menupopup[menu-api=\"true\"]");
   if (popup) {
     popup.hidePopup();
   }
 
   popup = doc.createXULElement("menupopup");
   popup.setAttribute("menu-api", "true");
   popup.setAttribute("consumeoutsideclicks", "false");
+  popup.setAttribute("incontentshell", "false");
 
   if (this.id) {
     popup.id = this.id;
   }
   this._createMenuItems(popup);
 
   // Remove the menu from the DOM once it's hidden.
   popup.addEventListener("popuphidden", (e) => {
@@ -126,16 +127,18 @@ Menu.prototype._createMenuItems = functi
   const doc = parent.ownerDocument;
   this.menuitems.forEach(item => {
     if (!item.visible) {
       return;
     }
 
     if (item.submenu) {
       const menupopup = doc.createXULElement("menupopup");
+      menupopup.setAttribute("incontentshell", "false");
+
       item.submenu._createMenuItems(menupopup);
 
       const menu = doc.createXULElement("menu");
       menu.appendChild(menupopup);
       applyItemAttributesToNode(item, menu);
       parent.appendChild(menu);
     } else if (item.type === "separator") {
       const menusep = doc.createXULElement("menuseparator");
--- a/devtools/client/framework/moz.build
+++ b/devtools/client/framework/moz.build
@@ -1,15 +1,16 @@
 # -*- 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/.
 
 BROWSER_CHROME_MANIFESTS += [
+    'test/allocations/browser_allocations_target.ini',
     'test/browser.ini',
     'test/metrics/browser_metrics_debugger.ini',
     'test/metrics/browser_metrics_inspector.ini',
     'test/metrics/browser_metrics_netmonitor.ini',
     'test/metrics/browser_metrics_webconsole.ini',
 ]
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_target.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  !/devtools/shared/test-helpers/allocation-tracker.js
+
+# Each metrics tests is loaded in a separate .ini file. This way the test is executed
+# individually, without any other test being executed before or after.
+[browser_allocations_target.js]
+skip-if = os != 'linux' || debug || asan || pgo # Results should be platform agnostic - only run on linux64-opt
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/allocations/browser_allocations_target.js
@@ -0,0 +1,106 @@
+const TEST_URL = "data:text/html;charset=UTF-8,<div>Target allocations test</div>";
+
+// Load the tracker very first in order to ensure tracking all objects created by DevTools.
+// Loader.jsm shouldn't be loaded, not any other DevTools module until an explicit user action.
+let tracker;
+{
+  const { DevToolsLoader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+  const loader = new DevToolsLoader();
+  loader.invisibleToDebugger = true;
+  const { allocationTracker } = loader.require("chrome://mochitests/content/browser/devtools/shared/test-helpers/allocation-tracker");
+  tracker = allocationTracker({ watchDevToolsGlobals: true });
+}
+
+// So that PERFHERDER data can be extracted from the logs.
+SimpleTest.requestCompleteLog();
+
+const {require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+
+const {TargetFactory} = require("devtools/client/framework/target");
+
+async function doGC() {
+  // In order to get stable results, we really have to do 3 GC attempts
+  // *and* do wait for 1s between each GC.
+  const numCycles = 3;
+  for (let i = 0; i < numCycles; i++) {
+    Cu.forceGC();
+    Cu.forceCC();
+    await new Promise(resolve => Cu.schedulePreciseShrinkingGC(resolve));
+
+    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+    await new Promise(resolve => setTimeout(resolve, 1000));
+  }
+}
+
+async function addTab(url) {
+  const tab = BrowserTestUtils.addTab(gBrowser, url);
+  gBrowser.selectedTab = tab;
+  await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  return tab;
+}
+
+async function testScript(tab) {
+  const target = await TargetFactory.forTab(tab);
+  await target.attach();
+
+  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+  await new Promise(resolve => setTimeout(resolve, 1000));
+
+  await target.destroy();
+}
+
+add_task(async function() {
+  const tab = await addTab(TEST_URL);
+
+  // Run the test scenario first before recording in order to load all the
+  // modules. Otherwise they get reported as "still allocated" objects,
+  // whereas we do expect them to be kept in memory as they are loaded via
+  // the main DevTools loader, which keeps the module loaded until the
+  // shutdown of Firefox
+  await testScript(tab);
+
+  // Do a first pass of GC, to ensure all to-be-freed objects from the first run
+  // are really freed.
+  await doGC();
+
+  // Then, record what was already allocated, which should not be declared
+  // as potential leaks. For ex, there is all the modules already loaded
+  // in the main DevTools loader.
+  const totalBefore = tracker.stillAllocatedObjects();
+
+  // Now, run the test script. This time, we record this run.
+  await testScript(tab);
+
+  // After that, re-do some GCs in order to free all what is to-be-freed.
+  await doGC();
+
+  // Ensure that Memory API didn't ran out of buffers
+  ok(!tracker.overflowed, "Allocation were all recorded");
+
+  // And finally, retrieve the number of objects that are still allocated.
+  const totalAfter = tracker.stillAllocatedObjects();
+
+  gBrowser.removeTab(tab);
+
+  const PERFHERDER_DATA = {
+    framework: {
+      name: "devtools",
+    },
+    suites: [{
+      name: "total-after-gc",
+      value: totalAfter - totalBefore,
+      subtests: [
+        {
+          name: "before",
+          value: totalBefore,
+        },
+        {
+          name: "after",
+          value: totalAfter - totalBefore,
+        },
+      ],
+    }],
+  };
+  info("PERFHERDER_DATA: " + JSON.stringify(PERFHERDER_DATA));
+  ok(true, "Test succeeded");
+});
--- a/devtools/client/locales/en-US/debugger.properties
+++ b/devtools/client/locales/en-US/debugger.properties
@@ -550,23 +550,23 @@ editor.addLogPoint.accesskey=l
 editor.editLogPoint=Edit log
 editor.editLogPoint.accesskey=E
 
 # LOCALIZATION NOTE (editor.removeLogPoint): Context menu item for removing
 # a log point on a line.
 editor.removeLogPoint.label=Remove log
 editor.removeLogPoint.accesskey=V
 
-# LOCALIZATION NOTE (editor.conditionalPanel.placeholder): Placeholder text for
+# LOCALIZATION NOTE (editor.conditionalPanel.placeholder2): Placeholder text for
 # input element inside ConditionalPanel component
-editor.conditionalPanel.placeholder=This breakpoint will pause when the expression is true
+editor.conditionalPanel.placeholder2=Breakpoint condition, e.g. items.length > 0
 
-# LOCALIZATION NOTE (editor.conditionalPanel.logPoint.placeholder): Placeholder text for
+# LOCALIZATION NOTE (editor.conditionalPanel.logPoint.placeholder2): Placeholder text for
 # input element inside ConditionalPanel component when a log point is set
-editor.conditionalPanel.logPoint.placeholder=This breakpoint will log the result of the expression
+editor.conditionalPanel.logPoint.placeholder2=Log message, e.g. displayName
 
 # LOCALIZATION NOTE (editor.conditionalPanel.close): Tooltip text for
 # close button inside ConditionalPanel component
 editor.conditionalPanel.close=Cancel edit breakpoint and close
 
 # LOCALIZATION NOTE (editor.jumpToMappedLocation1): Context menu item
 # for navigating to a source mapped location
 editor.jumpToMappedLocation1=Jump to %S location
--- a/devtools/client/responsive.html/components/DeviceModal.js
+++ b/devtools/client/responsive.html/components/DeviceModal.js
@@ -50,16 +50,17 @@ class DeviceModal extends PureComponent 
     this.validateAddDeviceFormNameField = this.validateAddDeviceFormNameField.bind(this);
   }
 
   componentDidMount() {
     window.addEventListener("keydown", this.onKeyDown, true);
   }
 
   componentWillUnmount() {
+    this.onDeviceModalSubmit();
     window.removeEventListener("keydown", this.onKeyDown, true);
   }
 
   onAddCustomDevice(device) {
     this.props.onAddCustomDevice(device);
     // Default custom devices to enabled
     this.setState({
       [device.name]: true,
@@ -245,23 +246,16 @@ class DeviceModal extends PureComponent 
             this.renderAddDeviceForm(devices, deviceAdderViewportTemplate)
           ),
           dom.div({
             className: `device-modal-content
               ${this.state.isDeviceFormShown ? " form-shown" : ""}`,
           },
           this.renderDevices()
           ),
-          dom.button(
-            {
-              id: "device-submit-button",
-              onClick: this.onDeviceModalSubmit,
-            },
-            getStr("responsive.done")
-          )
         ),
         dom.div(
           {
             className: "modal-overlay",
             onClick: () => onUpdateDeviceModal(false),
           }
         )
       )
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -2,27 +2,23 @@
  * React component group on how to best handle CSS. */
 
 /**
  * CSS Variables specific to the responsive design mode
  */
 
 :root {
   --rdm-box-shadow: 0 4px 4px 0 rgba(155, 155, 155, 0.26);
-  --submit-button-active-background-color: rgba(0,0,0,0.12);
-  --submit-button-active-color: var(--theme-body-color);
   --viewport-active-color: #3b3b3b;
   --input-invalid-border-color: var(--red-60);
   --custom-device-button-hover: var(--grey-30);
 }
 
 :root.theme-dark {
   --rdm-box-shadow: 0 4px 4px 0 rgba(105, 105, 105, 0.26);
-  --submit-button-active-background-color: var(--theme-toolbar-hover-active);
-  --submit-button-active-color: var(--theme-selection-color);
   --viewport-active-color: #fcfcfc;
   --input-invalid-border-color: var(--red-50);
   --custom-device-button-hover: var(--grey-10-a20)
 }
 
 * {
   box-sizing: border-box;
 }
@@ -394,17 +390,17 @@ body,
     opacity: 0;
     transform: translateY(5px);
     visibility: hidden;
   }
 }
 
 .device-modal {
   display: grid;
-  grid-template-rows: minmax(80px, auto) auto 20px;
+  grid-template-rows: minmax(80px, auto) auto;
   background-color: var(--theme-toolbar-background);
   border: 1px solid var(--theme-splitter-color);
   border-radius: 2px;
   box-shadow: var(--rdm-box-shadow);
   position: absolute;
   margin: auto;
   top: 0;
   bottom: 0;
@@ -608,38 +604,16 @@ body,
 .device-name {
   flex: 1;
 }
 
 .device-remove-button:empty::before {
   background-image: url("chrome://devtools/skin/images/close.svg");
 }
 
-#device-submit-button {
-  background-color: var(--theme-tab-toolbar-background);
-  border-width: 1px 0 0 0;
-  border-top-width: 1px;
-  border-top-style: solid;
-  border-top-color: var(--theme-splitter-color);
-  color: var(--theme-body-color);
-  width: 100%;
-  height: 20px;
-  position: absolute;
-  bottom: 0;
-}
-
-#device-submit-button:hover {
-  background-color: var(--theme-toolbar-hover);
-}
-
-#device-submit-button:hover:active {
-  background-color: var(--submit-button-active-background-color);
-  color: var(--submit-button-active-color);
-}
-
 /**
  * Device Form
  */
 
 #device-form {
   display: grid;
   width: 100%;
   background-color: var(--theme-toolbar-background);
--- a/devtools/client/responsive.html/test/browser/browser_device_custom.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_custom.js
@@ -50,20 +50,19 @@ addRDMTask(TEST_URL, async function({ ui
 
   info("Fill out device adder form and save");
   await addDeviceInModal(ui, device);
 
   info("Verify device defaults to enabled in modal");
   const deviceCb = [...document.querySelectorAll(".device-input-checkbox")].find(cb => {
     return cb.value == device.name;
   });
-  const submitButton = document.getElementById("device-submit-button");
   ok(deviceCb, "Custom device checkbox added to modal");
   ok(deviceCb.checked, "Custom device enabled");
-  submitButton.click();
+  document.getElementById("device-close-button").click();
 
   info("Look for custom device in device selector");
   const deviceSelector = document.getElementById("device-selector");
   await testMenuItems(toolWindow, deviceSelector, items => {
     const menuItem = items.find(item => item.getAttribute("label") === device.name);
     ok(menuItem, "Custom device menu item added to device selector");
   });
 });
@@ -85,24 +84,26 @@ addRDMTask(TEST_URL, async function({ ui
   const adderShow = document.getElementById("device-add-button");
   adderShow.click();
   testDeviceAdder(ui, Object.assign({}, device, {
     name: "Test Device (Custom)",
   }));
 
   info("Remove previously added custom device");
   const deviceRemoveButton = document.querySelector(".device-remove-button");
-  const submitButton = document.getElementById("device-submit-button");
   const removed = Promise.all([
     waitUntilState(store, state => state.devices.custom.length == 0),
     once(ui, "device-association-removed"),
   ]);
   deviceRemoveButton.click();
   await removed;
-  submitButton.click();
+
+  info("Close the form before submitting.");
+  document.getElementById("device-form-save").click();
+  document.getElementById("device-close-button").click();
 
   info("Ensure custom device was removed from device selector");
   await waitUntilState(store, state => state.viewports[0].device == "");
   const deviceSelectorTitle = document.querySelector("#device-selector .title");
   is(deviceSelectorTitle.textContent, "Responsive", "Device selector reset to no device");
 
   info("Look for custom device in device selector");
   const deviceSelector = document.getElementById("device-selector");
@@ -131,20 +132,19 @@ addRDMTask(TEST_URL, async function({ ui
 
   info("Fill out device adder form by setting details to unicode device and save");
   await addDeviceInModal(ui, unicodeDevice);
 
   info("Verify unicode device defaults to enabled in modal");
   const deviceCb = [...document.querySelectorAll(".device-input-checkbox")].find(cb => {
     return cb.value == unicodeDevice.name;
   });
-  const submitButton = document.getElementById("device-submit-button");
   ok(deviceCb, "Custom unicode device checkbox added to modal");
   ok(deviceCb.checked, "Custom unicode device enabled");
-  submitButton.click();
+  document.getElementById("device-close-button").click();
 
   info("Look for custom unicode device in device selector");
   const deviceSelector = document.getElementById("device-selector");
   await testMenuItems(toolWindow, deviceSelector, items => {
     const menuItem = items.find(i => i.getAttribute("label") === unicodeDevice.name);
     ok(menuItem, "Custom unicode device option added to device selector");
   });
 });
--- a/devtools/client/responsive.html/test/browser/browser_device_custom_remove.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_custom_remove.js
@@ -48,24 +48,23 @@ addRDMTask(TEST_URL, async function({ ui
   info("Reveal device adder form");
   adderShow = document.querySelector("#device-add-button");
   adderShow.click();
 
   info("Add test device 2");
   await addDeviceInModal(ui, device2);
 
   info("Verify all custom devices default to enabled in modal");
-  const submitButton = document.getElementById("device-submit-button");
   const deviceCbs =
     [...document.querySelectorAll(".device-type-custom .device-input-checkbox")];
   is(deviceCbs.length, 2, "Both devices have a checkbox in modal");
   for (const cb of deviceCbs) {
     ok(cb.checked, "Custom device enabled");
   }
-  submitButton.click();
+  document.getElementById("device-close-button").click();
 
   info("Look for device 1 and 2 in device selector");
 
   await testMenuItems(toolWindow, deviceSelector, menuItems => {
     const deviceItem1 = menuItems.find(i => i.getAttribute("label") === device1.name);
     const deviceItem2 = menuItems.find(i => i.getAttribute("label") === device2.name);
     ok(deviceItem1, "Test device 1 menu item added to device selector");
     ok(deviceItem2, "Test device 2 menu item added to device selector");
@@ -74,17 +73,17 @@ addRDMTask(TEST_URL, async function({ ui
   await openDeviceModal(ui);
 
   info("Remove device 2");
   const deviceRemoveButtons = [...document.querySelectorAll(".device-remove-button")];
   is(deviceRemoveButtons.length, 2, "Both devices have a remove button in modal");
   const removed = waitUntilState(store, state => state.devices.custom.length == 1);
   deviceRemoveButtons[1].click();
   await removed;
-  submitButton.click();
+  document.getElementById("device-close-button").click();
 
   info("Ensure device 2 is no longer in device selector");
   await testMenuItems(toolWindow, deviceSelector, menuItems => {
     const deviceItem1 = menuItems.find(i => i.getAttribute("label") === device1.name);
     const deviceItem2 = menuItems.find(i => i.getAttribute("label") === device2.name);
     ok(deviceItem1, "Test device 1 menu item exists");
     ok(!deviceItem2, "Test device 2 menu item removed");
   });
--- a/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_modal_exit.js
@@ -26,15 +26,15 @@ addRDMTask(TEST_URL, async function({ ui
   uncheckedCb.click();
   document.getElementById("device-close-button").click();
 
   ok(!store.getState().devices.isModalOpen, "The device modal is closed on exit.");
 
   info("Check that the device list remains unchanged after exitting.");
   const preferredDevicesAfter = _loadPreferredDevices();
 
-  is(preferredDevicesBefore.added.size, preferredDevicesAfter.added.size,
+  is(preferredDevicesAfter.added.size - preferredDevicesBefore.added.size, 1,
     "Got expected number of added devices.");
   is(preferredDevicesBefore.removed.size, preferredDevicesAfter.removed.size,
     "Got expected number of removed devices.");
   ok(!preferredDevicesAfter.removed.has(value),
     value + " was not added to removed device list.");
 });
--- a/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_modal_submit.js
@@ -53,17 +53,17 @@ addRDMTask(TEST_URL, async function({ ui
   }
 
   // Tests where the user adds a non-featured device
   info("Check the first unchecked device and submit new device list.");
   const uncheckedCb = [...document.querySelectorAll(".device-input-checkbox")]
     .filter(cb => !cb.checked)[0];
   const value = uncheckedCb.value;
   uncheckedCb.click();
-  document.getElementById("device-submit-button").click();
+  document.getElementById("device-close-button").click();
 
   ok(!store.getState().devices.isModalOpen, "The device modal is closed on submit.");
 
   info("Checking that the new device is added to the user preference list.");
   let preferredDevices = _loadPreferredDevices();
   ok(preferredDevices.added.has(value), value + " in user added list.");
 
   info("Checking new device is added to the device selector.");
@@ -83,17 +83,17 @@ addRDMTask(TEST_URL, async function({ ui
     value + " is checked in the device modal.");
 
   // Tests where the user removes a featured device
   info("Uncheck the first checked device different than the previous one");
   const checkedCb = [...document.querySelectorAll(".device-input-checkbox")]
     .filter(cb => cb.checked && cb.value != value)[0];
   const checkedVal = checkedCb.value;
   checkedCb.click();
-  document.getElementById("device-submit-button").click();
+  document.getElementById("device-close-button").click();
 
   info("Checking that the device is removed from the user preference list.");
   preferredDevices = _loadPreferredDevices();
   ok(preferredDevices.removed.has(checkedVal), checkedVal + " in removed list");
 
   info("Checking that the device is not in the device selector.");
   await testMenuItems(toolWindow, deviceSelector, menuItems => {
     is(menuItems.length - 2, featuredCount,
--- a/devtools/client/scratchpad/index.xul
+++ b/devtools/client/scratchpad/index.xul
@@ -133,17 +133,17 @@
        command="key_gotoLine"
        modifiers="accel"/>
 
 </keyset>
 
 <toolbar type="menubar" id="sp-menu-toolbar">
 <menubar>
   <menu id="sp-file-menu" label="&fileMenu.label;" accesskey="&fileMenu.accesskey;">
-    <menupopup id="sp-menu-filepopup">
+    <menupopup id="sp-menu-filepopup" incontentshell="false">
       <menuitem id="sp-menu-newscratchpad"
                 label="&newWindowCmd.label;"
                 accesskey="&newWindowCmd.accesskey;"
                 key="sp-key-window"
                 command="sp-cmd-newWindow"/>
       <menuseparator/>
 
       <menuitem id="sp-menu-open"
@@ -178,17 +178,17 @@
                 key="sp-key-close"
                 accesskey="&closeCmd.accesskey;"
                 command="sp-cmd-close"/>
     </menupopup>
   </menu>
 
   <menu id="sp-edit-menu" label="&editMenu.label;"
         accesskey="&editMenu.accesskey;">
-    <menupopup id="sp-menu_editpopup">
+    <menupopup id="sp-menu_editpopup" incontentshell="false">
       <menuitem id="menu_undo" label="&undoCmd.label;"
                 key="key_undo" accesskey="&undoCmd.accesskey;"
                 command="cmd_undo"/>
       <menuitem id="menu_redo" label="&redoCmd.label;"
                 key="key_redo" accesskey="&redoCmd.accesskey;"
                 command="cmd_redo"/>
       <menuseparator/>
       <menuitem id="menu_cut" label="&cutCmd.label;"
@@ -216,17 +216,17 @@
           label="&gotoLineCmd.label;"
           accesskey="&gotoLineCmd.accesskey;"
           key="key_gotoLine"
           command="cmd_gotoLine"/>
     </menupopup>
   </menu>
 
   <menu id="sp-view-menu" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;">
-    <menupopup id="sp-menu-viewpopup">
+    <menupopup id="sp-menu-viewpopup" incontentshell="false">
       <menuitem id="sp-menu-line-numbers"
                 label="&lineNumbers.label;"
                 accesskey="&lineNumbers.accesskey;"
                 type="checkbox"
                 command="sp-cmd-line-numbers"/>
       <menuitem id="sp-menu-word-wrap"
                 label="&wordWrap.label;"
                 accesskey="&wordWrap.accesskey;"
@@ -253,17 +253,17 @@
                 key="sp-menu-normal-font"
                 accesskey="&normalSize.accesskey;"
                 command="sp-cmd-normal-font"/>
     </menupopup>
   </menu>
 
   <menu id="sp-execute-menu" label="&executeMenu.label;"
         accesskey="&executeMenu.accesskey;">
-    <menupopup id="sp-menu_executepopup">
+    <menupopup id="sp-menu_executepopup" incontentshell="false">
       <menuitem id="sp-text-run"
                 label="&run.label;"
                 accesskey="&run.accesskey;"
                 key="sp-key-run"
                 command="sp-cmd-run"/>
       <menuitem id="sp-text-inspect"
                 label="&inspect.label;"
                 accesskey="&inspect.accesskey;"
@@ -287,17 +287,17 @@
                 command="sp-cmd-evalFunction"/>
     </menupopup>
   </menu>
 
   <menu id="sp-environment-menu"
         label="&environmentMenu.label;"
         accesskey="&environmentMenu.accesskey;"
         hidden="true">
-    <menupopup id="sp-menu-environment">
+    <menupopup id="sp-menu-environment" incontentshell="false">
       <menuitem id="sp-menu-content"
                 label="&contentContext.label;"
                 accesskey="&contentContext.accesskey;"
                 command="sp-cmd-contentContext"
                 checked="true"
                 type="radio"/>
       <menuitem id="sp-menu-browser"
                 command="sp-cmd-browserContext"
@@ -306,17 +306,17 @@
                 type="radio"/>
     </menupopup>
   </menu>
 
   <menu id="sp-help-menu"
         label="&helpMenu.label;"
         accesskey="&helpMenu.accesskey;"
         accesskeywindows="&helpMenuWin.accesskey;">
-    <menupopup id="sp-menu-help">
+    <menupopup id="sp-menu-help" incontentshell="false">
       <menuitem id="sp-menu-documentation"
                 label="&documentationLink.label;"
                 accesskey="&documentationLink.accesskey;"
                 command="sp-cmd-documentationLink"
                 key="key_openHelp"/>
     </menupopup>
   </menu>
 </menubar>
@@ -348,17 +348,17 @@
   <toolbarbutton id="sp-toolbar-display"
                  class="devtools-toolbarbutton"
                  label="&display.label;"
                  command="sp-cmd-display"/>
 </toolbar>
 
 
 <popupset id="scratchpad-popups">
-  <menupopup id="scratchpad-text-popup">
+  <menupopup id="scratchpad-text-popup" incontentshell="false">
     <menuitem id="cMenu_cut" label="&cutCmd.label;"
               accesskey="&cutCmd.accesskey;" command="cmd_cut"/>
     <menuitem id="cMenu_copy" label="&copyCmd.label;"
               accesskey="&copyCmd.accesskey;" command="cmd_copy"/>
     <menuitem id="cMenu_paste" label="&pasteCmd.label;"
               accesskey="&pasteCmd.accesskey;" command="cmd_paste"/>
     <menuitem id="cMenu_delete" label="&deleteCmd.label;"
               accesskey="&deleteCmd.accesskey;" command="cmd_delete"/>
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -20,17 +20,17 @@ if (DEBUG_ALLOCATIONS) {
   // Use a custom loader with `invisibleToDebugger` flag for the allocation tracker
   // as it instantiates custom Debugger API instances and has to be running in a distinct
   // compartments from DevTools and system scopes (JSMs, XPCOM,...)
   const { DevToolsLoader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
   const loader = new DevToolsLoader();
   loader.invisibleToDebugger = true;
 
   const { allocationTracker } = loader.require("devtools/shared/test-helpers/allocation-tracker");
-  const tracker = allocationTracker();
+  const tracker = allocationTracker({ watchAllGlobals: true });
   registerCleanupFunction(() => {
     if (DEBUG_ALLOCATIONS == "normal") {
       tracker.logCount();
     } else if (DEBUG_ALLOCATIONS == "verbose") {
       tracker.logAllocationSites();
     }
     tracker.stop();
   });
--- a/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
+++ b/devtools/client/shared/widgets/tooltip/HTMLTooltip.js
@@ -915,16 +915,17 @@ HTMLTooltip.prototype = {
 
   _createXulPanelWrapper: function() {
     const panel = this.doc.createXULElement("panel");
 
     // XUL panel is only a way to display DOM elements outside of the document viewport,
     // so disable all features that impact the behavior.
     panel.setAttribute("animate", false);
     panel.setAttribute("consumeoutsideclicks", false);
+    panel.setAttribute("incontentshell", false);
     panel.setAttribute("noautofocus", true);
     panel.setAttribute("ignorekeys", true);
     panel.setAttribute("tooltip", "aHTMLTooltip");
 
     // Use type="arrow" to prevent side effects (see Bug 1285206)
     panel.setAttribute("type", "arrow");
 
     panel.setAttribute("level", "top");
--- a/devtools/client/styleeditor/index.xul
+++ b/devtools/client/styleeditor/index.xul
@@ -34,16 +34,17 @@
 
       ['cmd_undo', 'cmd_redo', 'cmd_cut', 'cmd_paste',
        'cmd_delete', 'cmd_find', 'cmd_findAgain'].forEach(goUpdateCommand);
     }
   </script>
 
   <popupset id="style-editor-popups">
     <menupopup id="sourceEditorContextMenu"
+               incontentshell="false"
                onpopupshowing="goUpdateSourceEditorMenuItems()">
       <menuitem id="cMenu_undo" label="&undoCmd.label;"
                 accesskey="&undoCmd.accesskey;" command="cmd_undo"/>
       <menuseparator/>
       <menuitem id="cMenu_cut" label="&cutCmd.label;"
                 accesskey="&cutCmd.accesskey;" command="cmd_cut"/>
       <menuitem id="cMenu_copy" label="&copyCmd.label;"
                 accesskey="&copyCmd.accesskey;" command="cmd_copy"/>
@@ -61,17 +62,17 @@
                 accesskey="&findAgainCmd.accesskey;" command="cmd_findAgain"/>
       <menuseparator/>
       <menuitem id="se-menu-gotoLine"
           label="&gotoLineCmd.label;"
           accesskey="&gotoLineCmd.accesskey;"
           key="key_gotoLine"
           command="cmd_gotoLine"/>
     </menupopup>
-    <menupopup id="sidebar-context">
+    <menupopup id="sidebar-context" incontentshell="false">
       <menuitem id="context-openlinknewtab"
         label="&openLinkNewTab.label;"/>
       <menuitem id="context-copyurl"
         label="&copyUrl.label;"/>
     </menupopup>
   </popupset>
 
   <commandset id="sourceEditorCommands">
--- a/devtools/client/webconsole/reducers/messages.js
+++ b/devtools/client/webconsole/reducers/messages.js
@@ -954,159 +954,171 @@ function passCssFilters(message, filters
 /**
  * Returns true if the message shouldn't be hidden because of search filter state.
  *
  * @param {Object} message - The message to check the filter against.
  * @param {FilterState} filters - redux "filters" state.
  * @returns {Boolean}
  */
 function passSearchFilters(message, filters) {
-  const text = (filters.text || "").trim();
+  const text = (filters.text || "").trim().toLocaleLowerCase();
+  let regex;
+  if (text.startsWith("/") && text.endsWith("/") && text.length > 2) {
+    try {
+      regex = new RegExp(text.slice(1, -1), "im");
+    } catch (e) {
+
+    }
+  }
 
   // If there is no search, the message passes the filter.
   if (!text) {
     return true;
   }
 
   return (
     // Look for a match in parameters.
-    isTextInParameters(text, message.parameters)
+    isTextInParameters(text, regex, message.parameters)
     // Look for a match in location.
-    || isTextInFrame(text, message.frame)
+    || isTextInFrame(text, regex, message.frame)
     // Look for a match in net events.
-    || isTextInNetEvent(text, message.request)
+    || isTextInNetEvent(text, regex, message.request)
     // Look for a match in stack-trace.
-    || isTextInStackTrace(text, message.stacktrace)
+    || isTextInStackTrace(text, regex, message.stacktrace)
     // Look for a match in messageText.
-    || isTextInMessageText(text, message.messageText)
+    || isTextInMessageText(text, regex, message.messageText)
     // Look for a match in notes.
-    || isTextInNotes(text, message.notes)
+    || isTextInNotes(text, regex, message.notes)
     // Look for a match in prefix.
-    || isTextInPrefix(text, message.prefix)
+    || isTextInPrefix(text, regex, message.prefix)
   );
 }
 
 /**
 * Returns true if given text is included in provided stack frame.
 */
-function isTextInFrame(text, frame) {
+function isTextInFrame(text, regex, frame) {
   if (!frame) {
     return false;
   }
 
   const {
     functionName,
     line,
     column,
     source,
   } = frame;
   const { short } = getSourceNames(source);
   const unicodeShort = getUnicodeUrlPath(short);
 
-  const includes =
-    `${functionName ? functionName + " " : ""}${unicodeShort}:${line}:${column}`
-    .toLocaleLowerCase()
-    .includes(text.toLocaleLowerCase());
-  return includes;
+  const str =
+    `${functionName ? functionName + " " : ""}${unicodeShort}:${line}:${column}`;
+  return regex ? regex.test(str) : str.toLocaleLowerCase().includes(text);
 }
 
 /**
 * Returns true if given text is included in provided parameters.
 */
-function isTextInParameters(text, parameters) {
+function isTextInParameters(text, regex, parameters) {
   if (!parameters) {
     return false;
   }
 
-  text = text.toLocaleLowerCase();
-  return getAllProps(parameters).some(prop =>
-    (prop + "").toLocaleLowerCase().includes(text)
-  );
+  return getAllProps(parameters).some(prop => {
+    const str = (prop + "");
+    return regex ? regex.test(str) : str.toLocaleLowerCase().includes(text);
+  });
 }
 
 /**
 * Returns true if given text is included in provided net event grip.
 */
-function isTextInNetEvent(text, request) {
+function isTextInNetEvent(text, regex, request) {
   if (!request) {
     return false;
   }
 
-  text = text.toLocaleLowerCase();
-
-  const method = request.method.toLocaleLowerCase();
-  const url = request.url.toLocaleLowerCase();
-  return method.includes(text) || url.includes(text);
+  const method = request.method;
+  const url = request.url;
+  return regex ? regex.test(method) || regex.test(url) :
+    method.toLocaleLowerCase().includes(text) || url.toLocaleLowerCase().includes(text);
 }
 
 /**
 * Returns true if given text is included in provided stack trace.
 */
-function isTextInStackTrace(text, stacktrace) {
+function isTextInStackTrace(text, regex, stacktrace) {
   if (!Array.isArray(stacktrace)) {
     return false;
   }
 
   // isTextInFrame expect the properties of the frame object to be in the same
   // order they are rendered in the Frame component.
-  return stacktrace.some(frame => isTextInFrame(text, {
+  return stacktrace.some(frame => isTextInFrame(text, regex, {
     functionName: frame.functionName || l10n.getStr("stacktrace.anonymousFunction"),
     source: frame.filename,
     lineNumber: frame.lineNumber,
     columnNumber: frame.columnNumber,
   }));
 }
 
 /**
 * Returns true if given text is included in `messageText` field.
 */
-function isTextInMessageText(text, messageText) {
+function isTextInMessageText(text, regex, messageText) {
   if (!messageText) {
     return false;
   }
 
   if (typeof messageText === "string") {
-    return messageText.toLocaleLowerCase().includes(text.toLocaleLowerCase());
+    return regex ? regex.test(messageText) :
+      messageText.toLocaleLowerCase().includes(text);
   }
 
   if (messageText.type === "longString") {
-    return messageText.initial.toLocaleLowerCase().includes(text.toLocaleLowerCase());
+    return regex ? regex.test(messageText.initial) :
+      messageText.initial.toLocaleLowerCase().includes(text);
   }
 
   return true;
 }
 
 /**
 * Returns true if given text is included in notes.
 */
-function isTextInNotes(text, notes) {
+function isTextInNotes(text, regex, notes) {
   if (!Array.isArray(notes)) {
     return false;
   }
 
   return notes.some(note =>
     // Look for a match in location.
-    isTextInFrame(text, note.frame) ||
+    isTextInFrame(text, regex, note.frame) ||
     // Look for a match in messageBody.
     (
       note.messageBody &&
-      note.messageBody.toLocaleLowerCase().includes(text.toLocaleLowerCase())
+      (
+        regex ? regex.test(note.messageBody) :
+          note.messageBody.toLocaleLowerCase().includes(text)
+      )
     )
   );
 }
 
 /**
 * Returns true if given text is included in prefix.
 */
-function isTextInPrefix(text, prefix) {
+function isTextInPrefix(text, regex, prefix) {
   if (!prefix) {
     return false;
   }
 
-  return `${prefix}: `.toLocaleLowerCase().includes(text.toLocaleLowerCase());
+  const str = `${prefix}: `;
+
+  return regex ? regex.test(str) : str.toLocaleLowerCase().includes(text);
 }
 
 /**
  * Get a flat array of all the grips and their properties.
  *
  * @param {Array} Grips
  * @return {Array} Flat array of the grips and their properties.
  */
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -27,16 +27,17 @@ support-files =
   test-cspro.html^headers^
   test-iframe-child.html
   test-iframe-parent.html
   test-certificate-messages.html
   test-click-function-to-source.html
   test-click-function-to-source.js
   test-closure-optimized-out.html
   test-console-filters.html
+  test-console-filter-by-regex-input.html
   test-console-group.html
   test-console-iframes.html
   test-console-table.html
   test-console-workers.html
   test-console.html
   test-cu-reporterror.js
   test-data.json
   test-data.json^headers^
@@ -292,16 +293,17 @@ subsuite = clipboard
 [browser_webconsole_eval_in_debugger_stackframe.js]
 [browser_webconsole_eval_in_debugger_stackframe2.js]
 [browser_webconsole_eval_sources.js]
 [browser_webconsole_execution_scope.js]
 [browser_webconsole_external_script_errors.js]
 [browser_webconsole_file_uri.js]
 skip-if = true #	Bug 1404382
 [browser_webconsole_filter_by_input.js]
+[browser_webconsole_filter_by_regex_input.js]
 [browser_webconsole_filter_scroll.js]
 [browser_webconsole_filters.js]
 [browser_webconsole_filters_persist.js]
 [browser_webconsole_highlighter_console_helper.js]
 [browser_webconsole_hpkp_invalid-headers.js]
 [browser_webconsole_hsts_invalid-headers.js]
 [browser_webconsole_iframe_wrong_hud.js]
 [browser_webconsole_ineffective_iframe_sandbox_warning.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_filter_by_regex_input.js
@@ -0,0 +1,84 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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";
+
+const MESSAGES = [
+  "123-456-7890",
+  "foo@bar.com",
+  "http://abc.com/q?fizz=buzz&alpha=beta/",
+  "https://xyz.com/?path=/world",
+  "foooobaaaar",
+  "123 working",
+];
+
+const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
+                  "test/mochitest/test-console-filter-by-regex-input.html";
+
+add_task(async function() {
+  const hud = await openNewTabAndConsole(TEST_URI);
+  const { outputNode } = hud.ui;
+
+  await waitFor(() => findMessage(hud, MESSAGES[5]), null, 200);
+
+  let filteredNodes;
+
+  info("Filter out messages that begin with numbers");
+  await setFilterInput(hud, "/^[0-9]/", MESSAGES[5]);
+  filteredNodes = outputNode.querySelectorAll(".message");
+  checkFilteredMessages(filteredNodes, [
+    MESSAGES[0],
+    MESSAGES[5],
+  ], 2);
+
+  info("Filter out messages that are phone numbers");
+  await setFilterInput(hud, "/\\d{3}\\-\\d{3}\\-\\d{4}/", MESSAGES[0]);
+  filteredNodes = outputNode.querySelectorAll(".message");
+  checkFilteredMessages(filteredNodes, [MESSAGES[0]], 1);
+
+  info("Filter out messages that are an email address");
+  await setFilterInput(hud, "/^\\w+@[a-zA-Z]+\\.[a-zA-Z]{2,3}$/", MESSAGES[1]);
+  filteredNodes = outputNode.querySelectorAll(".message");
+  checkFilteredMessages(filteredNodes, [MESSAGES[1]], 1);
+
+  info("Filter out messages that contain query strings");
+  await setFilterInput(hud, "/\\?([^=&]+=[^=&]+&?)*\\//", MESSAGES[2]);
+  filteredNodes = outputNode.querySelectorAll(".message");
+  checkFilteredMessages(filteredNodes, [MESSAGES[2]], 1);
+
+  // If regex is invalid, do a normal text search instead
+  info("Filter messages using normal text search if regex is invalid");
+  await setFilterInput(hud, "/?path=/", MESSAGES[3]);
+  filteredNodes = outputNode.querySelectorAll(".message");
+  checkFilteredMessages(filteredNodes, [MESSAGES[3]], 1);
+
+  info("Filter out messages not ending with numbers");
+  await setFilterInput(hud, "/[^0-9]$/", MESSAGES[5]);
+  filteredNodes = outputNode.querySelectorAll(".message");
+  checkFilteredMessages(filteredNodes, [
+    MESSAGES[1],
+    MESSAGES[2],
+    MESSAGES[3],
+    MESSAGES[4],
+    MESSAGES[5],
+  ], 5);
+});
+
+async function setFilterInput(hud, value, lastMessage) {
+  hud.ui.filterBox.focus();
+  hud.ui.filterBox.select();
+  EventUtils.sendString(value);
+  await waitFor(() => findMessage(hud, lastMessage), null, 200);
+}
+
+function checkFilteredMessages(filteredNodes, expectedMessages, expectedCount) {
+  is(filteredNodes.length, expectedCount,
+    `${expectedCount} messages should be displayed`);
+
+  filteredNodes.forEach((node, id) => {
+    const messageBody = node.querySelector(".message-body").textContent;
+    ok(messageBody, expectedMessages[id]);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/test/mochitest/test-console-filter-by-regex-input.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Web Console test regex input.</title>
+  </head>
+  <body>
+    <p>Web Console test for filtering messages by regex input.</p>
+    <script>
+      "use strict";
+
+      console.log("123-456-7890");
+      console.log("foo@bar.com");
+      console.log("http://abc.com/q?fizz=buzz&alpha=beta/");
+      console.log("https://xyz.com/?path=/world");
+      console.log("foooobaaaar");
+      console.log("123 working");
+    </script>
+  </body>
+</html>
--- a/devtools/server/actors/breakpoint.js
+++ b/devtools/server/actors/breakpoint.js
@@ -3,16 +3,18 @@
 /* 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/. */
 
 /* global assert */
 
 "use strict";
 
+const { formatDisplayName } = require("devtools/server/actors/frame");
+
 /**
  * Set breakpoints on all the given entry points with the given
  * BreakpointActor as the handler.
  *
  * @param BreakpointActor actor
  *        The actor handling the breakpoint hits.
  * @param Array entryPoints
  *        An array of objects of the form `{ script, offsets }`.
@@ -90,23 +92,26 @@ BreakpointActor.prototype = {
   // is associated with.
   _updateOptionsForScript(script, offsets, options) {
     // When replaying, logging breakpoints are handled using an API to get logged
     // messages from throughout the recording.
     if (this.threadActor.dbg.replaying && options.logValue) {
       for (const offset of offsets) {
         const { lineNumber, columnNumber } = script.getOffsetLocation(offset);
         script.replayVirtualConsoleLog(
-          offset, options.logValue, options.condition, (executionPoint, rv) => {
+          offset,
+          options.logValue,
+          options.condition,
+          (executionPoint, rv) => {
             const message = {
               filename: script.url,
               lineNumber,
               columnNumber,
               executionPoint,
-              "arguments": ["return" in rv ? rv.return : rv.throw],
+              arguments: ["return" in rv ? rv.return : rv.throw],
               logpointId: options.logGroupId,
             };
             this.threadActor._parent._consoleActor.onConsoleAPICall(message);
           }
         );
       }
     }
   },
@@ -169,32 +174,36 @@ BreakpointActor.prototype = {
     // black-boxed.
     const {
       generatedSourceActor,
       generatedLine,
       generatedColumn,
     } = this.threadActor.sources.getFrameLocation(frame);
     const url = generatedSourceActor.url;
 
-    if (this.threadActor.sources.isBlackBoxed(url, generatedLine, generatedColumn)
-        || this.threadActor.skipBreakpoints
-        || frame.onStep) {
+    if (
+      this.threadActor.sources.isBlackBoxed(url, generatedLine, generatedColumn) ||
+      this.threadActor.skipBreakpoints ||
+      frame.onStep
+    ) {
       return undefined;
     }
 
     // If we're trying to pop this frame, and we see a breakpoint at
     // the spot at which popping started, ignore it.  See bug 970469.
     const locationAtFinish = frame.onPop && frame.onPop.generatedLocation;
-    if (locationAtFinish &&
-        locationAtFinish.generatedLine === generatedLine &&
-        locationAtFinish.generatedColumn === generatedColumn) {
+    if (
+      locationAtFinish &&
+      locationAtFinish.generatedLine === generatedLine &&
+      locationAtFinish.generatedColumn === generatedColumn
+    ) {
       return undefined;
     }
 
-    const reason = { type: "breakpoint", actors: [ this.actorID ] };
+    const reason = { type: "breakpoint", actors: [this.actorID] };
     const { condition, logValue } = this.options || {};
 
     // When replaying, breakpoints with log values are handled via
     // _updateOptionsForScript.
     if (logValue && this.threadActor.dbg.replaying) {
       return undefined;
     }
 
@@ -207,17 +216,18 @@ BreakpointActor.prototype = {
           reason.message = message;
         }
       } else {
         return undefined;
       }
     }
 
     if (logValue) {
-      const completion = frame.eval(`[${logValue}]`);
+      const displayName = formatDisplayName(frame);
+      const completion = frame.evalWithBindings(`[${logValue}]`, { displayName });
       let value;
       if (!completion) {
         // The evaluation was killed (possibly by the slow script dialog).
         value = ["Log value evaluation incomplete"];
       } else if ("return" in completion) {
         value = completion.return;
       } else {
         value = [this.getThrownMessage(completion)];
@@ -226,17 +236,18 @@ BreakpointActor.prototype = {
       if (value && typeof value.unsafeDereference === "function") {
         value = value.unsafeDereference();
       }
 
       const message = {
         filename: url,
         lineNumber: generatedLine,
         columnNumber: generatedColumn,
-        "arguments": value,
+        level: "logPoint",
+        arguments: value,
       };
       this.threadActor._parent._consoleActor.onConsoleAPICall(message);
 
       // Never stop at log points.
       return undefined;
     }
 
     return this.threadActor._pauseAndRespond(frame, reason);
--- a/devtools/server/actors/frame.js
+++ b/devtools/server/actors/frame.js
@@ -78,31 +78,30 @@ const FrameActor = ActorClassWithSpec(fr
     return envActor.form();
   },
 
   /**
    * Returns a frame form for use in a protocol message.
    */
   form: function() {
     const threadActor = this.threadActor;
-    const form = { actor: this.actorID,
-                   type: this.frame.type };
+    const form = { actor: this.actorID, type: this.frame.type };
 
     // NOTE: ignoreFrameEnvironment lets the client explicitly avoid
     // populating form environments on pause.
-    if (
-      !this.threadActor._options.ignoreFrameEnvironment &&
-      this.frame.environment
-    ) {
+    if (!this.threadActor._options.ignoreFrameEnvironment && this.frame.environment) {
       form.environment = this.getEnvironment();
     }
 
     if (this.frame.type != "wasmcall") {
-      form.this = createValueGrip(this.frame.this, threadActor._pausePool,
-        threadActor.objectGrip);
+      form.this = createValueGrip(
+        this.frame.this,
+        threadActor._pausePool,
+        threadActor.objectGrip
+      );
     }
 
     form.displayName = formatDisplayName(this.frame);
     form.arguments = this._args();
     if (this.frame.script) {
       const generatedLocation = this.threadActor.sources.getFrameLocation(this.frame);
       form.where = {
         actor: generatedLocation.generatedSourceActor.actorID,
@@ -118,14 +117,16 @@ const FrameActor = ActorClassWithSpec(fr
     return form;
   },
 
   _args: function() {
     if (!this.frame.arguments) {
       return [];
     }
 
-    return this.frame.arguments.map(arg => createValueGrip(arg,
-      this.threadActor._pausePool, this.threadActor.objectGrip));
+    return this.frame.arguments.map(arg =>
+      createValueGrip(arg, this.threadActor._pausePool, this.threadActor.objectGrip)
+    );
   },
 });
 
 exports.FrameActor = FrameActor;
+exports.formatDisplayName = formatDisplayName;
--- a/devtools/shared/Loader.jsm
+++ b/devtools/shared/Loader.jsm
@@ -54,16 +54,17 @@ BuiltinProvider.prototype = {
     // But we have to keep using Promise.jsm for other loader to prevent
     // breaking unhandled promise rejection in tests.
     if (this.invisibleToDebugger) {
       paths.promise = "resource://gre/modules/Promise-backend.js";
     }
     this.loader = new Loader({
       paths,
       invisibleToDebugger: this.invisibleToDebugger,
+      freshCompartment: this.freshCompartment,
       sharedGlobal: true,
       sandboxName: "DevTools (Module loader)",
       requireHook: (id, require) => {
         if (id.startsWith("raw!") || id.startsWith("theme-loader!")) {
           return requireRawId(id, require);
         }
         return require(id);
       },
@@ -147,16 +148,17 @@ DevToolsLoader.prototype = {
     if (this._provider) {
       delete this.require;
       this._provider.unload("newprovider");
     }
     this._provider = provider;
 
     // Pass through internal loader settings specific to this loader instance
     this._provider.invisibleToDebugger = this.invisibleToDebugger;
+    this._provider.freshCompartment = this.freshCompartment;
 
     this._provider.load();
     this.require = Require(this._provider.loader, { id: "devtools" });
 
     // Fetch custom pseudo modules and globals
     const { modules, globals } = this.require("devtools/shared/builtin-modules");
 
     // When creating a Loader for the browser toolbox, we have to use
--- a/devtools/shared/base-loader.js
+++ b/devtools/shared/base-loader.js
@@ -97,16 +97,17 @@ function Sandbox(options) {
   options = {
     // Do not expose `Components` if you really need them (bad idea!) you
     // still can expose via prototype.
     wantComponents: false,
     sandboxName: options.name,
     sandboxPrototype: "prototype" in options ? options.prototype : {},
     invisibleToDebugger: "invisibleToDebugger" in options ?
                          options.invisibleToDebugger : false,
+    freshCompartment: options.freshCompartment || false,
   };
 
   const sandbox = Cu.Sandbox(systemPrincipal, options);
 
   delete sandbox.Components;
 
   return sandbox;
 }
@@ -572,16 +573,17 @@ function Loader(options) {
   // Create the unique sandbox we will be using for all modules,
   // so that we prevent creating a new compartment per module.
   // The side effect is that all modules will share the same
   // global objects.
   const sharedGlobalSandbox = Sandbox({
     name: options.sandboxName || "DevTools",
     invisibleToDebugger: options.invisibleToDebugger || false,
     prototype: options.sandboxPrototype || globals,
+    freshCompartment: options.freshCompartment,
   });
 
   if (options.sandboxPrototype) {
     // If we were given a sandboxPrototype, we have to define the globals on
     // the sandbox directly. Note that this will not work for callers who
     // depend on being able to add globals after the loader was created.
     for (const name of getOwnIdentifiers(globals)) {
       Object.defineProperty(sharedGlobalSandbox, name,
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -27,23 +27,21 @@ DIRS += [
     'security',
     'sprintfjs',
     'specs',
     'transport',
     'webconsole',
     'worker',
 ]
 
-# Only ship test helpers in local builds
-if not CONFIG['MOZILLA_OFFICIAL']:
-    DIRS += ['test-helpers']
-
 if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
     BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 
+BROWSER_CHROME_MANIFESTS += ['test-helpers/browser.ini']
+
 MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
 DevToolsModules(
     'async-storage.js',
     'async-utils.js',
new file mode 100644
--- /dev/null
+++ b/devtools/shared/test-helpers/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../.eslintrc.mochitests.js"
+};
--- a/devtools/shared/test-helpers/allocation-tracker.js
+++ b/devtools/shared/test-helpers/allocation-tracker.js
@@ -37,37 +37,83 @@
 
 const { Cu } = require("chrome");
 const ChromeUtils = require("ChromeUtils");
 
 const global = Cu.getGlobalForObject(this);
 const {addDebuggerToGlobal} = ChromeUtils.import("resource://gre/modules/jsdebugger.jsm");
 addDebuggerToGlobal(global);
 
-exports.allocationTracker = function() {
+/**
+ * Start recording JS object allocations.
+ *
+ * @param Object watchGlobal
+ *        One global object to observe. Only allocation made from this global
+ *        will be recorded.
+ * @param Boolean watchAllGlobals
+ *        If true, allocations from everywhere are going to be recorded.
+ * @param Boolean watchAllGlobals
+ *        If true, only allocations made from DevTools contexts are going to be recorded.
+ */
+exports.allocationTracker = function({
+  watchGlobal,
+  watchAllGlobals,
+  watchDevToolsGlobals,
+} = {}) {
   dump("DEVTOOLS ALLOCATION: Start logging allocations\n");
   let dbg = new global.Debugger();
 
   // Enable allocation site tracking, to have the stack for each allocation
   dbg.memory.trackingAllocationSites = true;
   // Force saving *all* the allocation sites
   dbg.memory.allocationSamplingProbability = 1.0;
   // Bumps the default buffer size, which may prevent recording all the test allocations
   dbg.memory.maxAllocationsLogLength = 5000000;
 
+  let acceptGlobal;
+  if (watchGlobal) {
+    acceptGlobal = () => false;
+    dbg.addDebuggee(watchGlobal);
+  } else if (watchAllGlobals) {
+    acceptGlobal = () => true;
+  } else if (watchDevToolsGlobals) {
+    // Only accept globals related to DevTools
+    acceptGlobal = g => {
+      // self-hosting-global crashes when trying to call unsafeDereference
+      if (g.class == "self-hosting-global") {
+        return false;
+      }
+      const ref = g.unsafeDereference();
+      const location = Cu.getRealmLocation(ref);
+      const accept = !!location.match(/devtools/i);
+      dump("TRACKER NEW GLOBAL: " + (accept ? "+" : "-") + " : " + location + "\n");
+      return accept;
+    };
+  }
+
   // Watch all globals
-  dbg.addAllGlobalsAsDebuggees();
+  if (watchAllGlobals || watchDevToolsGlobals) {
+    dbg.addAllGlobalsAsDebuggees();
+
+    for (const g of dbg.getDebuggees()) {
+      if (!acceptGlobal(g)) {
+        dbg.removeDebuggee(g);
+      }
+    }
+  }
 
   // Remove this global to ignore all its object/JS
   dbg.removeDebuggee(global);
 
   // addAllGlobalsAsDebuggees won't automatically track new ones,
   // so ensure tracking all new globals
   dbg.onNewGlobalObject = function(g) {
-    dbg.addDebuggee(g);
+    if (acceptGlobal(g)) {
+      dbg.addDebuggee(g);
+    }
   };
 
   return {
     get overflowed() {
       return dbg.memory.allocationsLogOverflowed;
     },
 
     /**
@@ -171,16 +217,21 @@ exports.allocationTracker = function() {
       const allocations = dbg.memory.drainAllocationsLog();
       return allocations.length;
     },
 
     flushAllocations() {
       dbg.memory.drainAllocationsLog();
     },
 
+    stillAllocatedObjects() {
+      const sensus = dbg.memory.takeCensus({ breakdown: { by: "count" } });
+      return sensus.count;
+    },
+
     stop() {
       dump("DEVTOOLS ALLOCATION: Stop logging allocations\n");
       dbg.onNewGlobalObject = undefined;
       dbg.removeAllDebuggees();
       dbg = null;
     },
   };
 };
new file mode 100644
--- /dev/null
+++ b/devtools/shared/test-helpers/browser.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  allocation-tracker.js
+
+[browser_allocation_tracker.js]
new file mode 100644
--- /dev/null
+++ b/devtools/shared/test-helpers/browser_allocation_tracker.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Load the tracker in a dedicated loader using invisibleToDebugger and freshCompartment
+// so that it can inspect any other module/compartment, even DevTools, chrome,
+// and this script!
+const { DevToolsLoader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
+const loader = new DevToolsLoader();
+loader.invisibleToDebugger = true;
+loader.freshCompartment = true;
+const { allocationTracker } =
+  loader.require("chrome://mochitests/content/browser/devtools/shared/test-helpers/allocation-tracker");
+
+add_task(async function() {
+  // Use a sandbox to allocate test javascript object in order to avoid any
+  // external noise
+  const global = Cu.Sandbox("http://example.com");
+
+  const tracker = allocationTracker({ watchGlobal: global });
+  const before = tracker.stillAllocatedObjects();
+
+  /* eslint-disable no-undef */
+  Cu.evalInSandbox("let list; new " + function() {
+    list = [];
+    for (let i = 0; i < 1000; i++) {
+      list.push({});
+    }
+  }, global, undefined, "test-file.js", 1);
+  /* eslint-enable no-undef */
+
+  const allocations = tracker.countAllocations();
+  ok(allocations > 1000,
+    `At least 1000 objects are reported as created (${allocations})`);
+
+  // Uncomment this and comment the call to `countAllocations` to debug the allocations.
+  // The call to `countAllocations` will reset the allocation record.
+  // tracker.logAllocationSites();
+
+  const afterCreation = tracker.stillAllocatedObjects();
+  ok(afterCreation - before > 1000,
+    `At least 1000 more objects are reported still allocated (${before}` +
+    ` + ${afterCreation - before} -> ${afterCreation})`);
+
+  Cu.evalInSandbox("list = null;", global, undefined, "test-file.js", 7);
+
+  Cu.forceGC();
+  Cu.forceCC();
+
+  const afterGC = tracker.stillAllocatedObjects();
+  ok(afterCreation - afterGC > 1000,
+    `At least 1000 less objects are reported still allocated (${afterCreation}` +
+    ` -(${afterGC - afterCreation})-> ${afterGC})`);
+
+  tracker.stop();
+});
deleted file mode 100644
--- a/devtools/shared/test-helpers/moz.build
+++ /dev/null
@@ -1,15 +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/.
-
-# This directory is only processed for local build
-# where MOZILLA_OFFICIAL isn't set
-
-DevToolsModules(
-    'allocation-tracker.js',
-)
-
-with Files('**'):
-    BUG_COMPONENT = ('DevTools', 'General')
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -126,22 +126,20 @@ already_AddRefed<BrowsingContext> Browsi
     context = new BrowsingContext(aParent, group, id, aType);
   }
 
   // The name and opener fields need to be explicitly initialized. Don't bother
   // using transactions to set them, as we haven't been attached yet.
   context->mName = aName;
   context->mOpenerId = aOpener ? aOpener->Id() : 0;
 
-  if (aParent) {
-    context->mCrossOriginPolicy = aParent->mCrossOriginPolicy;
-  } else if (aOpener) {
-    context->mCrossOriginPolicy = aOpener->mCrossOriginPolicy;
-  } else {
-    context->mCrossOriginPolicy = nsILoadInfo::CROSS_ORIGIN_POLICY_NULL;
+  BrowsingContext* inherit = aParent ? aParent : aOpener;
+  if (inherit) {
+    context->mOpenerPolicy = inherit->mOpenerPolicy;
+    context->mCrossOriginPolicy = inherit->mCrossOriginPolicy;
   }
 
   Register(context);
 
   // Attach the browsing context to the tree.
   context->Attach();
 
   return context.forget();
--- a/docshell/base/BrowsingContextFieldList.h
+++ b/docshell/base/BrowsingContextFieldList.h
@@ -12,16 +12,17 @@
 // validators.
 #ifndef MOZ_BC_FIELD_RACY
 #  define MOZ_BC_FIELD_RACY MOZ_BC_FIELD
 #endif
 
 MOZ_BC_FIELD_RACY(Name, nsString)
 MOZ_BC_FIELD_RACY(Closed, bool)
 MOZ_BC_FIELD(CrossOriginPolicy, nsILoadInfo::CrossOriginPolicy)
+MOZ_BC_FIELD(OpenerPolicy, nsILoadInfo::CrossOriginOpenerPolicy)
 
 // The current opener for this BrowsingContext. This is a weak reference, and
 // stored as the opener ID.
 MOZ_BC_FIELD(OpenerId, uint64_t)
 
 // Toplevel browsing contexts only. This field controls whether the browsing
 // context is currently considered to be activated by a gesture.
 MOZ_BC_FIELD_RACY(IsActivatedByUserGesture, bool)
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9617,17 +9617,20 @@ nsresult nsDocShell::DoURILoad(nsDocShel
                           &doesNotReturnData);
       if (doesNotReturnData) {
         bool popupBlocked = true;
 
         // Let's consider external protocols as popups and let's check if the
         // page is allowed to open them without abuse regardless of allowed
         // events
         if (PopupBlocker::GetPopupControlState() <= PopupBlocker::openBlocked) {
-          popupBlocked = !PopupBlocker::TryUsePopupOpeningToken();
+          nsCOMPtr<nsINode> loadingNode =
+              mScriptGlobal->AsOuter()->GetFrameElementInternal();
+          popupBlocked = !PopupBlocker::TryUsePopupOpeningToken(
+              loadingNode ? loadingNode->NodePrincipal() : nullptr);
         } else if (mIsActive &&
                    PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe()) {
           popupBlocked = false;
         } else {
           nsCOMPtr<nsINode> loadingNode =
               mScriptGlobal->AsOuter()->GetFrameElementInternal();
           if (loadingNode) {
             popupBlocked = !PopupBlocker::CanShowPopupByPermission(
@@ -9825,21 +9828,16 @@ nsresult nsDocShell::DoURILoad(nsDocShel
 
   // We have to do this in case our OriginAttributes are different from the
   // OriginAttributes of the parent document. Or in case there isn't a
   // parent document.
   bool isTopLevelDoc = mItemType == typeContent &&
                        (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
                         GetIsMozBrowser());
 
-  if (isTopLevelDoc && GetDocument() && GetDocument()->GetChannel()) {
-    nsCOMPtr<nsILoadInfo> oldLoadInfo = GetDocument()->GetChannel()->LoadInfo();
-    loadInfo->SetOpenerPolicy(oldLoadInfo->GetOpenerPolicy());
-  }
-
   OriginAttributes attrs;
 
   // Inherit origin attributes from PrincipalToInherit if inheritAttrs is
   // true. Otherwise we just use the origin attributes from docshell.
   if (inheritAttrs) {
     MOZ_ASSERT(aLoadState->PrincipalToInherit(),
                "We should have PrincipalToInherit here.");
     attrs = aLoadState->PrincipalToInherit()->OriginAttributesRef();
--- a/dom/base/PopupBlocker.cpp
+++ b/dom/base/PopupBlocker.cpp
@@ -131,24 +131,28 @@ bool PopupBlocker::CanShowPopupByPermiss
       return false;
     }
   }
 
   return !StaticPrefs::dom_disable_open_during_load();
 }
 
 /* static */
-bool PopupBlocker::TryUsePopupOpeningToken() {
+bool PopupBlocker::TryUsePopupOpeningToken(nsIPrincipal* aPrincipal) {
   MOZ_ASSERT(sPopupStatePusherCount);
 
   if (!sUnusedPopupToken) {
     sUnusedPopupToken = true;
     return true;
   }
 
+  if (aPrincipal && nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+    return true;
+  }
+
   return false;
 }
 
 /* static */
 bool PopupBlocker::IsPopupOpeningTokenUnused() { return sUnusedPopupToken; }
 
 /* static */
 void PopupBlocker::PopupStatePusherCreated() { ++sPopupStatePusherCount; }
--- a/dom/base/PopupBlocker.h
+++ b/dom/base/PopupBlocker.h
@@ -41,17 +41,19 @@ class PopupBlocker final {
 
   // This method checks if the principal is allowed by open popups by user
   // permissions. In this case, the caller should not block popups.
   static bool CanShowPopupByPermission(nsIPrincipal* aPrincipal);
 
   // This method returns true if the caller is allowed to show a popup, and it
   // consumes the popup token for the current event. There is just 1 popup
   // allowed per event.
-  static bool TryUsePopupOpeningToken();
+  // This method returns true if the token has been already consumed but
+  // aPrincipal is the system principal.
+  static bool TryUsePopupOpeningToken(nsIPrincipal* aPrincipal);
 
   static bool IsPopupOpeningTokenUnused();
 
   static PopupBlocker::PopupControlState GetEventPopupControlState(
       WidgetEvent* aEvent, Event* aDOMEvent = nullptr);
 
   // Returns if a external protocol iframe is allowed.
   static bool ConsumeTimerTokenForExternalProtocolIframe();
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -5608,17 +5608,17 @@ PopupBlocker::PopupControlState nsGlobal
       abuse = PopupBlocker::openOverridden;
   }
 
   // If this popup is allowed, let's block any other for this event, forcing
   // PopupBlocker::openBlocked state.
   if ((abuse == PopupBlocker::openAllowed ||
        abuse == PopupBlocker::openControlled) &&
       StaticPrefs::dom_block_multiple_popups() && !PopupWhitelisted() &&
-      !PopupBlocker::TryUsePopupOpeningToken()) {
+      !PopupBlocker::TryUsePopupOpeningToken(mDoc->NodePrincipal())) {
     abuse = PopupBlocker::openBlocked;
   }
 
   return abuse;
 }
 
 /* If a window open is blocked, fire the appropriate DOM events. */
 void nsGlobalWindowOuter::FireAbuseEvents(
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -649,17 +649,17 @@ bool HTMLInputElement::IsPopupBlocked() 
   nsCOMPtr<nsPIDOMWindowOuter> win = OwnerDoc()->GetWindow();
   MOZ_ASSERT(win, "window should not be null");
   if (!win) {
     return true;
   }
 
   // Check if page can open a popup without abuse regardless of allowed events
   if (PopupBlocker::GetPopupControlState() <= PopupBlocker::openBlocked) {
-    return !PopupBlocker::TryUsePopupOpeningToken();
+    return !PopupBlocker::TryUsePopupOpeningToken(OwnerDoc()->NodePrincipal());
   }
 
   return !PopupBlocker::CanShowPopupByPermission(OwnerDoc()->NodePrincipal());
 }
 
 nsresult HTMLInputElement::InitColorPicker() {
   if (mPickerRunning) {
     NS_WARNING("Just one nsIColorPicker is allowed");
--- a/dom/html/test/chrome.ini
+++ b/dom/html/test/chrome.ini
@@ -1,10 +1,9 @@
 [DEFAULT]
 support-files =
   file_anchor_ping.html
   image.png
 
 [test_anchor_ping.html]
 skip-if = os == 'android'
 [test_bug1414077.html]
-[test_multipleFilePicker.html]
 [test_external_protocol_iframe.html]
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -607,8 +607,9 @@ skip-if = toolkit == "android"
 [test_script_module.html]
 support-files =
   file_script_module.html
   file_script_nomodule.html
 [test_getElementsByName_after_mutation.html]
 [test_bug1279218.html]
 [test_set_input_files.html]
 [test_nestediframe.html]
+[test_multipleFilePicker.html]
--- a/dom/html/test/test_multipleFilePicker.html
+++ b/dom/html/test/test_multipleFilePicker.html
@@ -1,20 +1,21 @@
 <!DOCTYPE html>
 <html>
 <head>
   <title>Test for single filepicker per event</title>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
   <div id='foo'><a href='#'>Click here to test this issue</a></div>
   <script>
 
+SimpleTest.requestFlakyTimeout("Timeouts are needed to simulate user-interaction");
 SimpleTest.waitForExplicitFinish();
 
 let clickCount = 0;
 let foo = document.getElementById('foo');
 foo.addEventListener('click', _ => {
   if (++clickCount < 10) {
     let input = document.createElement('input');
     input.type = 'file';
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2051,60 +2051,24 @@ void ContentParent::LaunchSubprocessInte
   // the command line.
 
   SharedPreferenceSerializer prefSerializer;
   if (!prefSerializer.SerializeToSharedMemory()) {
     MarkAsDead();
     earlyReject();
     return;
   }
+  prefSerializer.AddSharedPrefCmdLineArgs(*mSubprocess, extraArgs);
 
   // Register ContentParent as an observer for changes to any pref
   // whose prefix matches the empty string, i.e. all of them.  The
   // observation starts here in order to capture pref updates that
   // happen during async launch.
   Preferences::AddStrongObserver(this, "");
 
-  // Formats a pointer or pointer-sized-integer as a string suitable for passing
-  // in an arguments list.
-  auto formatPtrArg = [](auto arg) {
-    return nsPrintfCString("%zu", uintptr_t(arg));
-  };
-
-#if defined(XP_WIN)
-  // Record the handle as to-be-shared, and pass it via a command flag. This
-  // works because Windows handles are system-wide.
-  HANDLE prefsHandle = prefSerializer.GetSharedMemoryHandle();
-  mSubprocess->AddHandleToShare(prefsHandle);
-  mSubprocess->AddHandleToShare(prefSerializer.GetPrefMapHandle().get());
-  extraArgs.push_back("-prefsHandle");
-  extraArgs.push_back(formatPtrArg(prefsHandle).get());
-  extraArgs.push_back("-prefMapHandle");
-  extraArgs.push_back(
-      formatPtrArg(prefSerializer.GetPrefMapHandle().get()).get());
-#else
-  // In contrast, Unix fds are per-process. So remap the fd to a fixed one that
-  // will be used in the child.
-  // XXX: bug 1440207 is about improving how fixed fds are used.
-  //
-  // Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel,
-  // and the fixed fd isn't used. However, we still need to mark it for
-  // remapping so it doesn't get closed in the child.
-  mSubprocess->AddFdToRemap(prefSerializer.GetSharedMemoryHandle().fd,
-                            kPrefsFileDescriptor);
-  mSubprocess->AddFdToRemap(prefSerializer.GetPrefMapHandle().get(),
-                            kPrefMapFileDescriptor);
-#endif
-
-  // Pass the lengths via command line flags.
-  extraArgs.push_back("-prefsLen");
-  extraArgs.push_back(formatPtrArg(prefSerializer.GetPrefLength()).get());
-  extraArgs.push_back("-prefMapSize");
-  extraArgs.push_back(formatPtrArg(prefSerializer.GetPrefMapSize()).get());
-
   if (gSafeMode) {
     extraArgs.push_back("-safeMode");
   }
 
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
   // If we're launching a middleman process for a
   // recording or replay, start the sandbox later.
   if (sEarlySandboxInit && IsContentSandboxEnabled() &&
--- a/dom/ipc/WindowGlobalChild.cpp
+++ b/dom/ipc/WindowGlobalChild.cpp
@@ -13,16 +13,17 @@
 #include "mozilla/dom/WindowGlobalParent.h"
 #include "mozilla/ipc/InProcessChild.h"
 #include "nsDocShell.h"
 #include "nsGlobalWindowInner.h"
 
 #include "mozilla/dom/JSWindowActorBinding.h"
 #include "mozilla/dom/JSWindowActorChild.h"
 #include "mozilla/dom/JSWindowActorService.h"
+#include "nsIHttpChannelInternal.h"
 
 using namespace mozilla::ipc;
 using namespace mozilla::dom::ipc;
 
 namespace mozilla {
 namespace dom {
 
 typedef nsRefPtrHashtable<nsUint64HashKey, WindowGlobalChild> WGCByIdMap;
@@ -41,16 +42,27 @@ already_AddRefed<WindowGlobalChild> Wind
   nsCOMPtr<nsIPrincipal> principal = aWindow->GetPrincipal();
   MOZ_ASSERT(principal);
 
   RefPtr<nsDocShell> docshell = nsDocShell::Cast(aWindow->GetDocShell());
   MOZ_ASSERT(docshell);
 
   // Initalize our WindowGlobalChild object.
   RefPtr<dom::BrowsingContext> bc = docshell->GetBrowsingContext();
+
+  // When creating a new window global child we also need to look at the
+  // channel's Cross-Origin-Opener-Policy and set it on the browsing context
+  // so it's available in the parent process.
+  nsCOMPtr<nsIHttpChannelInternal> chan =
+      do_QueryInterface(aWindow->GetDocument()->GetChannel());
+  nsILoadInfo::CrossOriginOpenerPolicy policy;
+  if (chan && NS_SUCCEEDED(chan->GetCrossOriginOpenerPolicy(&policy))) {
+    bc->SetOpenerPolicy(policy);
+  }
+
   RefPtr<WindowGlobalChild> wgc = new WindowGlobalChild(aWindow, bc);
 
   WindowGlobalInit init(principal, bc, wgc->mInnerWindowId,
                         wgc->mOuterWindowId);
 
   // Send the link constructor over PInProcessChild or PBrowser.
   if (XRE_IsParentProcess()) {
     InProcessChild* ipc = InProcessChild::Singleton();
--- a/dom/media/ipc/RDDProcessHost.cpp
+++ b/dom/media/ipc/RDDProcessHost.cpp
@@ -4,16 +4,17 @@
  * 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/. */
 #include "RDDProcessHost.h"
 
 #include "chrome/common/process_watcher.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPrefs.h"
 
+#include "ProcessUtils.h"
 #include "RDDChild.h"
 
 #if defined(XP_MACOSX) && defined(MOZ_SANDBOX)
 #  include "mozilla/Sandbox.h"
 #endif
 
 namespace mozilla {
 
@@ -46,16 +47,22 @@ RDDProcessHost::RDDProcessHost(Listener*
 }
 
 RDDProcessHost::~RDDProcessHost() { MOZ_COUNT_DTOR(RDDProcessHost); }
 
 bool RDDProcessHost::Launch(StringVector aExtraOpts) {
   MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched);
   MOZ_ASSERT(!mRDDChild);
 
+  mPrefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>();
+  if (!mPrefSerializer->SerializeToSharedMemory()) {
+    return false;
+  }
+  mPrefSerializer->AddSharedPrefCmdLineArgs(*this, aExtraOpts);
+
 #if defined(XP_WIN) && defined(MOZ_SANDBOX)
   mSandboxLevel = Preferences::GetInt("security.sandbox.rdd.level");
 #endif
 
   mLaunchPhase = LaunchPhase::Waiting;
   mLaunchTime = TimeStamp::Now();
 
   if (!GeckoChildProcessHost::AsyncLaunch(aExtraOpts)) {
@@ -134,16 +141,17 @@ void RDDProcessHost::OnChannelErrorTask(
 
 static uint64_t sRDDProcessTokenCounter = 0;
 
 void RDDProcessHost::InitAfterConnect(bool aSucceeded) {
   MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting);
   MOZ_ASSERT(!mRDDChild);
 
   mLaunchPhase = LaunchPhase::Complete;
+  mPrefSerializer = nullptr;
 
   if (aSucceeded) {
     mProcessToken = ++sRDDProcessTokenCounter;
     mRDDChild = MakeUnique<RDDChild>(this);
     DebugOnly<bool> rv =
         mRDDChild->Open(GetChannel(), base::GetProcId(GetChildProcessHandle()));
     MOZ_ASSERT(rv);
 
--- a/dom/media/ipc/RDDProcessHost.h
+++ b/dom/media/ipc/RDDProcessHost.h
@@ -8,16 +8,21 @@
 #define _include_dom_media_ipc_RDDProcessHost_h_
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 
 #include "mozilla/Maybe.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/ipc/TaskFactory.h"
 
+namespace mozilla {
+namespace ipc {
+class SharedPreferenceSerializer;
+}
+}
 class nsITimer;
 
 namespace mozilla {
 
 class RDDChild;
 
 // RDDProcessHost is the "parent process" container for a subprocess handle and
 // IPC connection. It owns the parent process IPDL actor, which in this case,
@@ -134,16 +139,18 @@ class RDDProcessHost final : public mozi
   mozilla::ipc::TaskFactory<RDDProcessHost> mTaskFactory;
 
   enum class LaunchPhase { Unlaunched, Waiting, Complete };
   LaunchPhase mLaunchPhase;
 
   UniquePtr<RDDChild> mRDDChild;
   uint64_t mProcessToken;
 
+  UniquePtr<ipc::SharedPreferenceSerializer> mPrefSerializer;
+
   bool mShutdownRequested;
   bool mChannelClosed;
 
   TimeStamp mLaunchTime;
 };
 
 }  // namespace mozilla
 
--- a/dom/media/ipc/RDDProcessImpl.cpp
+++ b/dom/media/ipc/RDDProcessImpl.cpp
@@ -6,38 +6,73 @@
 #include "RDDProcessImpl.h"
 
 #include "mozilla/ipc/IOThreadChild.h"
 
 #if defined(OS_WIN) && defined(MOZ_SANDBOX)
 #  include "mozilla/sandboxTarget.h"
 #endif
 
+#include "ProcessUtils.h"
+
 namespace mozilla {
 
 using namespace ipc;
 
 RDDProcessImpl::RDDProcessImpl(ProcessId aParentPid)
     : ProcessChild(aParentPid) {}
 
 RDDProcessImpl::~RDDProcessImpl() {}
 
 bool RDDProcessImpl::Init(int aArgc, char* aArgv[]) {
 #if defined(MOZ_SANDBOX) && defined(OS_WIN)
   mozilla::SandboxTarget::Instance()->StartSandbox();
 #endif
   char* parentBuildID = nullptr;
+  char* prefsHandle = nullptr;
+  char* prefMapHandle = nullptr;
+  char* prefsLen = nullptr;
+  char* prefMapSize = nullptr;
   for (int i = 1; i < aArgc; i++) {
     if (!aArgv[i]) {
       continue;
     }
     if (strcmp(aArgv[i], "-parentBuildID") == 0) {
       parentBuildID = aArgv[i + 1];
+
+#ifdef XP_WIN
+    } else if (strcmp(aArgv[i], "-prefsHandle") == 0) {
+      if (++i == aArgc) {
+        return false;
+      }
+      prefsHandle = aArgv[i];
+    } else if (strcmp(aArgv[i], "-prefMapHandle") == 0) {
+      if (++i == aArgc) {
+        return false;
+      }
+      prefMapHandle = aArgv[i];
+#endif
+    } else if (strcmp(aArgv[i], "-prefsLen") == 0) {
+      if (++i == aArgc) {
+        return false;
+      }
+      prefsLen = aArgv[i];
+    } else if (strcmp(aArgv[i], "-prefMapSize") == 0) {
+      if (++i == aArgc) {
+        return false;
+      }
+      prefMapSize = aArgv[i];
     }
   }
 
+  SharedPreferenceDeserializer deserializer;
+  if (!deserializer.DeserializeFromSharedMemory(prefsHandle, prefMapHandle,
+                                                prefsLen, prefMapSize)) {
+    return false;
+  }
+
   return mRDD.Init(ParentPid(), parentBuildID, IOThreadChild::message_loop(),
                    IOThreadChild::channel());
 }
 
 void RDDProcessImpl::CleanUp() { NS_ShutdownXPCOM(nullptr); }
 
 }  // namespace mozilla
--- a/dom/power/PowerManagerService.cpp
+++ b/dom/power/PowerManagerService.cpp
@@ -154,31 +154,31 @@ already_AddRefed<WakeLock> PowerManagerS
 NS_DEFINE_NAMED_CID(NS_POWERMANAGERSERVICE_CID);
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(
     nsIPowerManagerService,
     mozilla::dom::power::PowerManagerService::GetInstance)
 
 static const mozilla::Module::CIDEntry kPowerManagerCIDs[] = {
     // clang-format off
-  { &kNS_POWERMANAGERSERVICE_CID, false, nullptr, nsIPowerManagerServiceConstructor, mozilla::Module::ALLOW_IN_GPU_AND_SOCKET_PROCESS },
+  { &kNS_POWERMANAGERSERVICE_CID, false, nullptr, nsIPowerManagerServiceConstructor, mozilla::Module::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS },
   { nullptr }
     // clang-format on
 };
 
 static const mozilla::Module::ContractIDEntry kPowerManagerContracts[] = {
     // clang-format off
-  { POWERMANAGERSERVICE_CONTRACTID, &kNS_POWERMANAGERSERVICE_CID, mozilla::Module::ALLOW_IN_GPU_AND_SOCKET_PROCESS },
+  { POWERMANAGERSERVICE_CONTRACTID, &kNS_POWERMANAGERSERVICE_CID, mozilla::Module::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS },
   { nullptr }
     // clang-format on
 };
 
 // We mark the power module as being available in the GPU process because the
 // appshell depends on the power manager service.
 extern const mozilla::Module kPowerManagerModule = {
     mozilla::Module::kVersion,
     kPowerManagerCIDs,
     kPowerManagerContracts,
     nullptr,
     nullptr,
     nullptr,
     nullptr,
-    mozilla::Module::ALLOW_IN_GPU_AND_SOCKET_PROCESS};
+    mozilla::Module::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS};
--- a/dom/prototype/PrototypeDocumentContentSink.cpp
+++ b/dom/prototype/PrototypeDocumentContentSink.cpp
@@ -434,16 +434,17 @@ nsresult PrototypeDocumentContentSink::R
 
     while (mContextStack.Depth() > 0) {
       // Look at the top of the stack to determine what we're
       // currently working on.
       // This will always be a node already constructed and
       // inserted to the actual document.
       nsXULPrototypeElement* proto;
       nsCOMPtr<nsIContent> element;
+      nsCOMPtr<nsIContent> nodeToPushTo;
       int32_t indx;  // all children of proto before indx (not
                      // inclusive) have already been constructed
       rv = mContextStack.Peek(&proto, getter_AddRefs(element), &indx);
       if (NS_FAILED(rv)) return rv;
 
       if (indx >= (int32_t)proto->mChildren.Length()) {
         if (element) {
           // We've processed all of the prototype's children.
@@ -462,16 +463,25 @@ nsresult PrototypeDocumentContentSink::R
           }
         }
         // Now pop the context stack back up to the parent
         // element and continue the prototype walk.
         mContextStack.Pop();
         continue;
       }
 
+      nodeToPushTo = element;
+      // For template elements append the content to the template's document
+      // fragment.
+      if (element->IsHTMLElement(nsGkAtoms::_template)) {
+        HTMLTemplateElement* templateElement =
+            static_cast<HTMLTemplateElement*>(element.get());
+        nodeToPushTo = templateElement->Content();
+      }
+
       // Grab the next child, and advance the current context stack
       // to the next sibling to our right.
       nsXULPrototypeNode* childproto = proto->mChildren[indx];
       mContextStack.SetTopIndex(++indx);
 
       NS_ASSERTION(element, "no element on context stack");
 
       switch (childproto->mType) {
@@ -482,17 +492,17 @@ nsresult PrototypeDocumentContentSink::R
 
           RefPtr<Element> child;
 
           rv = CreateElementFromPrototype(protoele, getter_AddRefs(child),
                                           false);
           if (NS_FAILED(rv)) return rv;
 
           // ...and append it to the content model.
-          rv = element->AppendChildTo(child, false);
+          rv = nodeToPushTo->AppendChildTo(child, false);
           if (NS_FAILED(rv)) return rv;
 
           // If it has children, push the element onto the context
           // stack and begin to process them.
           if (protoele->mChildren.Length() > 0) {
             rv = mContextStack.Push(protoele, child);
             if (NS_FAILED(rv)) return rv;
           } else {
@@ -527,17 +537,17 @@ nsresult PrototypeDocumentContentSink::R
         case nsXULPrototypeNode::eType_Text: {
           // A simple text node.
           RefPtr<nsTextNode> text = new nsTextNode(mNodeInfoManager);
 
           nsXULPrototypeText* textproto =
               static_cast<nsXULPrototypeText*>(childproto);
           text->SetText(textproto->mValue, false);
 
-          rv = element->AppendChildTo(text, false);
+          rv = nodeToPushTo->AppendChildTo(text, false);
           NS_ENSURE_SUCCESS(rv, rv);
         } break;
 
         case nsXULPrototypeNode::eType_PI: {
           nsXULPrototypePI* piProto =
               static_cast<nsXULPrototypePI*>(childproto);
 
           // <?xml-stylesheet?> doesn't have an effect
--- a/dom/xul/test/chrome.ini
+++ b/dom/xul/test/chrome.ini
@@ -14,10 +14,11 @@ support-files =
 [test_bug418216.xul]
 [test_bug445177.xul]
 [test_bug449457.xul]
 [test_bug468176.xul]
 [test_bug583948.xul]
 [test_bug757137.xul]
 [test_bug775972.xul]
 [test_bug1070049_throw_from_script.xul]
+[test_html_template.xul]
 [test_import_xul_to_content.xul]
 [test_bug1290965.xul]
new file mode 100644
--- /dev/null
+++ b/dom/xul/test/test_html_template.xul
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="HTML template in XUL"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/AddTask.js" />
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <template id="template">Content<span>Content</span></template>
+<script type="application/javascript"><![CDATA[
+  add_task(async function test_template() {
+    let template = document.getElementById("template");
+    ok("content" in template, "Template has shadow root.");
+    is(template.childNodes.length, 0, "Template should have no children.");
+    is(template.content.childNodes.length, 2, "Template content should have two children.");
+  });
+]]></script>
+  </body>
+</window>
--- a/gfx/layers/LayerMetricsWrapper.h
+++ b/gfx/layers/LayerMetricsWrapper.h
@@ -277,16 +277,22 @@ class MOZ_STACK_CLASS LayerMetricsWrappe
     // mLayer->GetTransform() is only used at the bottom layer, we only
     // need to check GetTransformIsPerspective() at the bottom layer too.
     if (AtBottomLayer()) {
       return mLayer->GetTransformIsPerspective();
     }
     return false;
   }
 
+  bool Combines3DTransformWithAncestors() const {
+    MOZ_ASSERT(IsValid());
+
+    return mLayer->Combines3DTransformWithAncestors();
+  }
+
   EventRegions GetEventRegions() const {
     MOZ_ASSERT(IsValid());
 
     if (AtBottomLayer()) {
       return mLayer->GetEventRegions();
     }
     return EventRegions();
   }
--- a/gfx/layers/client/ClientTiledPaintedLayer.cpp
+++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp
@@ -64,18 +64,28 @@ void ClientTiledPaintedLayer::FillSpecif
 static Maybe<LayerRect> ApplyParentLayerToLayerTransform(
     const ParentLayerToLayerMatrix4x4& aTransform,
     const ParentLayerRect& aParentLayerRect, const LayerRect& aClip) {
   return UntransformBy(aTransform, aParentLayerRect, aClip);
 }
 
 static LayerToParentLayerMatrix4x4 GetTransformToAncestorsParentLayer(
     Layer* aStart, const LayerMetricsWrapper& aAncestor) {
+
+  // If the ancestor layer Combines3DTransformWithAncestors, then the
+  // scroll offset is contained in the transform of the layer at the
+  // root of the 3D context. So we must first find that layer, then
+  // calcuate the transform to its parent.
+  LayerMetricsWrapper root3dAncestor = aAncestor;
+  while (root3dAncestor.Combines3DTransformWithAncestors()) {
+    root3dAncestor = root3dAncestor.GetParent();
+  }
+
   gfx::Matrix4x4 transform;
-  const LayerMetricsWrapper& ancestorParent = aAncestor.GetParent();
+  const LayerMetricsWrapper& ancestorParent = root3dAncestor.GetParent();
   for (LayerMetricsWrapper iter(aStart, LayerMetricsWrapper::StartAt::BOTTOM);
        ancestorParent ? iter != ancestorParent : iter.IsValid();
        iter = iter.GetParent()) {
     transform = transform * iter.GetTransform();
 
     if (gfxPrefs::LayoutUseContainersForRootFrames()) {
       // When scrolling containers, layout adds a post-scale into the transform
       // of the displayport-ancestor (which we pick up in GetTransform() above)
--- a/gfx/wr/webrender/src/batch.rs
+++ b/gfx/wr/webrender/src/batch.rs
@@ -2824,18 +2824,22 @@ impl ClipBatcher {
         }
 
         // Get the world rect of the clip rectangle. If we can't transform it due
         // to the matrix, just fall back to drawing the entire clip mask.
         let local_clip_rect = LayoutRect::new(
             clip_instance.local_pos,
             clip_rect_size,
         );
+        let transform = clip_scroll_tree.get_relative_transform(
+            clip_instance.spatial_node_index,
+            ROOT_SPATIAL_NODE_INDEX,
+        );
         let world_clip_rect = match project_rect(
-            &clip_spatial_node.world_content_transform.to_transform(),
+            &transform.flattened.with_destination::<WorldPixel>(),
             &local_clip_rect,
             world_rect,
         ) {
             Some(rect) => rect,
             None => return false,
         };
 
         // Work out how many tiles to draw this clip mask in, stretched across the
--- a/gfx/wr/webrender/src/frame_builder.rs
+++ b/gfx/wr/webrender/src/frame_builder.rs
@@ -21,17 +21,16 @@ use prim_store::{PrimitiveStore, SpaceMa
 use prim_store::{PrimitiveStoreStats};
 use profiler::{FrameProfileCounters, GpuCacheProfileCounters, TextureCacheProfileCounters};
 use render_backend::{DataStores, FrameStamp};
 use render_task::{RenderTask, RenderTaskId, RenderTaskLocation, RenderTaskTree, RenderTaskTreeCounters};
 use resource_cache::{ResourceCache};
 use scene::{ScenePipeline, SceneProperties};
 use scene_builder::DocumentStats;
 use segment::SegmentBuilder;
-use spatial_node::SpatialNode;
 use std::{f32, mem};
 use std::sync::Arc;
 use tiling::{Frame, RenderPass, RenderPassKind, RenderTargetContext, RenderTarget};
 
 
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -197,33 +196,16 @@ pub struct PictureState {
     pub map_pic_to_world: SpaceMapper<PicturePixel, WorldPixel>,
     pub map_pic_to_raster: SpaceMapper<PicturePixel, RasterPixel>,
     pub map_raster_to_world: SpaceMapper<RasterPixel, WorldPixel>,
     /// If the plane splitter, the primitives get added to it instead of
     /// batching into their parent pictures.
     pub plane_splitter: Option<PlaneSplitter>,
 }
 
-pub struct PrimitiveContext<'a> {
-    pub spatial_node: &'a SpatialNode,
-    pub spatial_node_index: SpatialNodeIndex,
-}
-
-impl<'a> PrimitiveContext<'a> {
-    pub fn new(
-        spatial_node: &'a SpatialNode,
-        spatial_node_index: SpatialNodeIndex,
-    ) -> Self {
-        PrimitiveContext {
-            spatial_node,
-            spatial_node_index,
-        }
-    }
-}
-
 impl FrameBuilder {
     #[cfg(feature = "replay")]
     pub fn empty() -> Self {
         FrameBuilder {
             hit_testing_scene: Arc::new(HitTestingScene::new(&HitTestingSceneStats::empty())),
             prim_store: PrimitiveStore::new(&PrimitiveStoreStats::empty()),
             clip_store: ClipStore::new(),
             output_rect: DeviceIntRect::zero(),
--- a/gfx/wr/webrender/src/prim_store/mod.rs
+++ b/gfx/wr/webrender/src/prim_store/mod.rs
@@ -14,17 +14,17 @@ use clip::{ClipStore};
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, ClipScrollTree, SpatialNodeIndex, VisibleFace};
 use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem};
 use debug_colors;
 use debug_render::DebugItem;
 use display_list_flattener::{CreateShadow, IsVisible};
 use euclid::{SideOffsets2D, TypedTransform3D, TypedRect, TypedScale, TypedSize2D};
 use euclid::approxeq::ApproxEq;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
-use frame_builder::{PrimitiveContext, FrameVisibilityContext, FrameVisibilityState};
+use frame_builder::{FrameVisibilityContext, FrameVisibilityState};
 use glyph_rasterizer::GlyphKey;
 use gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest, ToGpuBlocks};
 use gpu_types::{BrushFlags, SnapOffsets};
 use image::{Repetition};
 use intern;
 use malloc_size_of::MallocSizeOf;
 use picture::{PictureCompositeMode, PicturePrimitive};
 use picture::{ClusterIndex, PrimitiveList, RecordedDirtyRegion, SurfaceIndex, RetainedTiles, RasterConfig};
@@ -267,16 +267,20 @@ impl<F, T> SpaceMapper<F, T> where F: fm
                 }
             }
         }
     }
 
     pub fn visible_face(&self) -> VisibleFace {
         self.visible_face
     }
+
+    pub fn get_conservative_local_bounds(&self) -> Option<TypedRect<f32, F>> {
+        self.unmap(&self.bounds)
+    }
 }
 
 /// For external images, it's not possible to know the
 /// UV coords of the image (or the image data itself)
 /// until the render thread receives the frame and issues
 /// callbacks to the client application. For external
 /// images that are visible, a DeferredResolve is created
 /// that is stored in the frame. This allows the render
@@ -1772,28 +1776,16 @@ impl PrimitiveStore {
                     prim_instance.id, pic_index);
             }
 
             // Get the cluster and see if is visible
             if !prim_list.clusters[prim_instance.cluster_index.0 as usize].is_visible {
                 continue;
             }
 
-            let spatial_node = &frame_context
-                .clip_scroll_tree
-                .spatial_nodes[prim_instance.spatial_node_index.0 as usize];
-
-            // TODO(gw): Although constructing these is cheap, they are often
-            //           the same for many consecutive primitives, so it may
-            //           be worth caching the most recent context.
-            let prim_context = PrimitiveContext::new(
-                spatial_node,
-                prim_instance.spatial_node_index,
-            );
-
             map_local_to_surface.set_target_spatial_node(
                 prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
             );
 
             let (is_passthrough, prim_local_rect) = match prim_instance.kind {
                 PrimitiveInstanceKind::Picture { pic_index, .. } => {
                     let is_composite = {
@@ -1926,17 +1918,17 @@ impl PrimitiveStore {
                 }
 
                 let clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
                         frame_state.clip_chain_stack.current_clips(),
                         local_rect,
                         prim_instance.local_clip_rect,
-                        prim_context.spatial_node_index,
+                        prim_instance.spatial_node_index,
                         &map_local_to_surface,
                         &map_surface_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         surface.device_pixel_scale,
                         &frame_context.screen_world_rect,
                         &mut frame_state.data_stores.clip,
@@ -2165,17 +2157,16 @@ impl PrimitiveStore {
         // the collapsed primitive will be drawn directly into the
         // parent picture.
         self.pictures[pic_index.0].requested_composite_mode = None;
     }
 
     pub fn prepare_prim_for_render(
         &mut self,
         prim_instance: &mut PrimitiveInstance,
-        prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         plane_split_anchor: usize,
         data_stores: &mut DataStores,
         scratch: &mut PrimitiveScratchBuffer,
     ) -> bool {
@@ -2269,17 +2260,16 @@ impl PrimitiveStore {
             }
         };
 
         if !is_passthrough {
             // Push the per-primitive clip chain onto the current active stack
             frame_state.clip_chain_stack.push_clip(prim_instance.clip_chain_id);
 
             prim_instance.update_clip_task(
-                prim_context,
                 pic_context.raster_spatial_node_index,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
                 self,
                 data_stores,
                 scratch,
@@ -2356,18 +2346,18 @@ impl PrimitiveStore {
             PrimitiveInstanceKind::ImageBorder { .. } |
             PrimitiveInstanceKind::YuvImage { .. } |
             PrimitiveInstanceKind::Image { .. } |
             PrimitiveInstanceKind::LinearGradient { .. } |
             PrimitiveInstanceKind::RadialGradient { .. } |
             PrimitiveInstanceKind::LineDecoration { .. } => {
                 self.prepare_interned_prim_for_render(
                     prim_instance,
-                    prim_context,
                     pic_context,
+                    pic_state,
                     frame_context,
                     frame_state,
                     data_stores,
                     scratch,
                 );
             }
         }
 
@@ -2427,36 +2417,23 @@ impl PrimitiveStore {
                     None => {
                         // Outside the overall dirty rect, so can be skipped.
                         prim_instance.visibility_info = PrimitiveVisibilityIndex::INVALID;
                         continue;
                     }
                 }
             }
 
-            let spatial_node = &frame_context
-                .clip_scroll_tree
-                .spatial_nodes[prim_instance.spatial_node_index.0 as usize];
-
-            // TODO(gw): Although constructing these is cheap, they are often
-            //           the same for many consecutive primitives, so it may
-            //           be worth caching the most recent context.
-            let prim_context = PrimitiveContext::new(
-                spatial_node,
-                prim_instance.spatial_node_index,
-            );
-
             pic_state.map_local_to_pic.set_target_spatial_node(
                 prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
             );
 
             if self.prepare_prim_for_render(
                 prim_instance,
-                &prim_context,
                 pic_context,
                 pic_state,
                 frame_context,
                 frame_state,
                 plane_split_anchor,
                 data_stores,
                 scratch,
             ) {
@@ -2466,18 +2443,18 @@ impl PrimitiveStore {
     }
 
     /// Prepare an interned primitive for rendering, by requesting
     /// resources, render tasks etc. This is equivalent to the
     /// prepare_prim_for_render_inner call for old style primitives.
     fn prepare_interned_prim_for_render(
         &mut self,
         prim_instance: &mut PrimitiveInstance,
-        prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
+        pic_state: &PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         data_stores: &mut DataStores,
         scratch: &mut PrimitiveScratchBuffer,
     ) {
         let is_chased = prim_instance.is_chased();
         let device_pixel_scale = frame_state.surfaces[pic_context.surface_index.0].device_pixel_scale;
 
@@ -2535,29 +2512,34 @@ impl PrimitiveStore {
                 let prim_data = &mut data_stores.text_run[*data_handle];
                 let run = &mut self.text_runs[*run_index];
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
                 prim_data.update(frame_state);
 
                 // The transform only makes sense for screen space rasterization
-                let transform = prim_context.spatial_node.world_content_transform.to_transform();
+                let relative_transform = frame_context
+                    .clip_scroll_tree
+                    .get_relative_transform(
+                        prim_instance.spatial_node_index,
+                        ROOT_SPATIAL_NODE_INDEX,
+                    );
                 let prim_offset = prim_instance.prim_origin.to_vector() - run.reference_frame_relative_offset;
 
                 // TODO(gw): This match is a bit untidy, but it should disappear completely
                 //           once the prepare_prims and batching are unified. When that
                 //           happens, we can use the cache handle immediately, and not need
                 //           to temporarily store it in the primitive instance.
                 run.prepare_for_render(
                     prim_offset,
                     &prim_data.font,
                     &prim_data.glyphs,
                     device_pixel_scale,
-                    &transform,
+                    &relative_transform.flattened.with_destination::<WorldPixel>(),
                     pic_context,
                     frame_state.resource_cache,
                     frame_state.gpu_cache,
                     frame_state.render_tasks,
                     scratch,
                 );
             }
             PrimitiveInstanceKind::Clear { data_handle, .. } => {
@@ -2571,24 +2553,30 @@ impl PrimitiveStore {
                 let prim_data = &mut data_stores.normal_border[*data_handle];
                 let common_data = &mut prim_data.common;
                 let border_data = &mut prim_data.kind;
 
                 // Update the template this instane references, which may refresh the GPU
                 // cache with any shared template data.
                 border_data.update(common_data, frame_state);
 
-                // TODO(gw): When drawing in screen raster mode, we should also incorporate a
-                //           scale factor from the world transform to get an appropriately
-                //           sized border task.
-                let transform = prim_context.spatial_node.world_content_transform.to_transform();
+                // TODO(gw): For now, the scale factors to rasterize borders at are
+                //           based on the true world transform of the primitive. When
+                //           raster roots with local scale are supported in future,
+                //           that will need to be accounted for here.
+                let relative_transform = frame_context
+                    .clip_scroll_tree
+                    .get_relative_transform(
+                        prim_instance.spatial_node_index,
+                        ROOT_SPATIAL_NODE_INDEX,
+                    );
 
                 // Scale factors are normalized to a power of 2 to reduce the number of
                 // resolution changes
-                let scale = scale_factors(&transform);
+                let scale = scale_factors(&relative_transform.flattened);
                 // For frames with a changing scale transform round scale factors up to
                 // nearest power-of-2 boundary so that we don't keep having to redraw
                 // the content as it scales up and down. Rounding up to nearest
                 // power-of-2 boundary ensures we never scale up, only down --- avoiding
                 // jaggies. It also ensures we never scale down by more than a factor of
                 // 2, avoiding bad downscaling quality.
                 let scale_width = clamp_to_scale_factor(scale.0, false);
                 let scale_height = clamp_to_scale_factor(scale.1, false);
@@ -2722,19 +2710,18 @@ impl PrimitiveStore {
                         common_data.prim_size,
                     );
                     let tight_clip_rect = prim_info
                         .combined_local_clip_rect
                         .intersection(&prim_rect).unwrap();
                     image_instance.tight_local_clip_rect = tight_clip_rect;
 
                     let visible_rect = compute_conservative_visible_rect(
-                        prim_context,
-                        &frame_state.current_dirty_region().combined.world_rect,
-                        &tight_clip_rect
+                        &tight_clip_rect,
+                        &pic_state.map_local_to_pic,
                     );
 
                     let base_edge_flags = edge_flags_for_tile_spacing(&image_data.tile_spacing);
 
                     let stride = image_data.stretch_size + image_data.tile_spacing;
 
                     let repetitions = ::image::repetitions(
                         &prim_rect,
@@ -2881,19 +2868,19 @@ impl PrimitiveStore {
                         prim_data.common.prim_size,
                     );
 
                     gradient.visible_tiles_range = decompose_repeated_primitive(
                         &prim_info.combined_local_clip_rect,
                         &prim_rect,
                         &prim_data.stretch_size,
                         &prim_data.tile_spacing,
-                        prim_context,
                         frame_state,
                         &mut scratch.gradient_tiles,
+                        &pic_state.map_local_to_pic,
                         &mut |_, mut request| {
                             request.push([
                                 prim_data.start_point.x,
                                 prim_data.start_point.y,
                                 prim_data.end_point.x,
                                 prim_data.end_point.y,
                             ]);
                             request.push([
@@ -2927,19 +2914,19 @@ impl PrimitiveStore {
                         prim_data.common.prim_size,
                     );
 
                     *visible_tiles_range = decompose_repeated_primitive(
                         &prim_info.combined_local_clip_rect,
                         &prim_rect,
                         &prim_data.stretch_size,
                         &prim_data.tile_spacing,
-                        prim_context,
                         frame_state,
                         &mut scratch.gradient_tiles,
+                        &pic_state.map_local_to_pic,
                         &mut |_, mut request| {
                             request.push([
                                 prim_data.center.x,
                                 prim_data.center.y,
                                 prim_data.params.start_radius,
                                 prim_data.params.end_radius,
                             ]);
                             request.push([
@@ -2992,34 +2979,32 @@ fn write_segment<F>(
     }
 }
 
 fn decompose_repeated_primitive(
     combined_local_clip_rect: &LayoutRect,
     prim_local_rect: &LayoutRect,
     stretch_size: &LayoutSize,
     tile_spacing: &LayoutSize,
-    prim_context: &PrimitiveContext,
     frame_state: &mut FrameBuildingState,
     gradient_tiles: &mut GradientTileStorage,
+    map_local_to_pic: &SpaceMapper<LayoutPixel, PicturePixel>,
     callback: &mut FnMut(&LayoutRect, GpuDataRequest),
 ) -> GradientTileRange {
     let mut visible_tiles = Vec::new();
-    let world_rect = frame_state.current_dirty_region().combined.world_rect;
 
     // Tighten the clip rect because decomposing the repeated image can
     // produce primitives that are partially covering the original image
     // rect and we want to clip these extra parts out.
     let tight_clip_rect = combined_local_clip_rect
         .intersection(prim_local_rect).unwrap();
 
     let visible_rect = compute_conservative_visible_rect(
-        prim_context,
-        &world_rect,
-        &tight_clip_rect
+        &tight_clip_rect,
+        map_local_to_pic,
     );
     let stride = *stretch_size + *tile_spacing;
 
     let repetitions = ::image::repetitions(prim_local_rect, &visible_rect, stride);
     for Repetition { origin, .. } in repetitions {
         let mut handle = GpuCacheHandle::new();
         let rect = LayoutRect {
             origin: origin,
@@ -3045,26 +3030,21 @@ fn decompose_repeated_primitive(
     if visible_tiles.is_empty() {
         GradientTileRange::empty()
     } else {
         gradient_tiles.extend(visible_tiles)
     }
 }
 
 fn compute_conservative_visible_rect(
-    prim_context: &PrimitiveContext,
-    world_rect: &WorldRect,
     local_clip_rect: &LayoutRect,
+    map_local_to_pic: &SpaceMapper<LayoutPixel, PicturePixel>,
 ) -> LayoutRect {
-    if let Some(layer_screen_rect) = prim_context
-        .spatial_node
-        .world_content_transform
-        .unapply(world_rect) {
-
-        return local_clip_rect.intersection(&layer_screen_rect).unwrap_or(LayoutRect::zero());
+    if let Some(local_bounds) = map_local_to_pic.get_conservative_local_bounds() {
+        return local_clip_rect.intersection(&local_bounds).unwrap_or(LayoutRect::zero())
     }
 
     *local_clip_rect
 }
 
 fn edge_flags_for_tile_spacing(tile_spacing: &LayoutSize) -> EdgeAaSegmentMask {
     let mut flags = EdgeAaSegmentMask::empty();
 
@@ -3331,17 +3311,16 @@ impl PrimitiveInstance {
             };
         }
     }
 
     fn update_clip_task_for_brush(
         &self,
         prim_info: &mut PrimitiveVisibility,
         root_spatial_node_index: SpatialNodeIndex,
-        prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         prim_store: &PrimitiveStore,
         data_stores: &mut DataStores,
         segments_store: &mut SegmentStorage,
         segment_instances_store: &mut SegmentInstanceStorage,
@@ -3472,17 +3451,17 @@ impl PrimitiveInstance {
                     .clip_store
                     .build_clip_chain_instance(
                         frame_state.clip_chain_stack.current_clips(),
                         segment.local_rect.translate(&LayoutVector2D::new(
                             self.prim_origin.x,
                             self.prim_origin.y,
                         )),
                         self.local_clip_rect,
-                        prim_context.spatial_node_index,
+                        self.spatial_node_index,
                         &pic_state.map_local_to_pic,
                         &pic_state.map_pic_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
                         device_pixel_scale,
                         &dirty_world_rect,
                         &mut data_stores.clip,
@@ -3505,17 +3484,16 @@ impl PrimitiveInstance {
             }
         }
 
         true
     }
 
     fn update_clip_task(
         &mut self,
-        prim_context: &PrimitiveContext,
         root_spatial_node_index: SpatialNodeIndex,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         prim_store: &mut PrimitiveStore,
         data_stores: &mut DataStores,
         scratch: &mut PrimitiveScratchBuffer,
@@ -3525,17 +3503,17 @@ impl PrimitiveInstance {
 
         if self.is_chased() {
             println!("\tupdating clip task with pic rect {:?}", prim_info.clip_chain.pic_clip_rect);
         }
 
         // Get the device space rect for the primitive if it was unclipped,
         // including any snap offsets applied to the corners.
         let (unclipped, prim_snap_offsets) = match get_unclipped_device_rect(
-            prim_context.spatial_node_index,
+            self.spatial_node_index,
             root_spatial_node_index,
             prim_info.clip_chain.pic_clip_rect,
             &pic_state.map_pic_to_raster,
             device_pixel_scale,
             frame_context,
             frame_state,
         ) {
             Some(info) => info,
@@ -3550,17 +3528,16 @@ impl PrimitiveInstance {
             &mut scratch.segments,
             &mut scratch.segment_instances,
         );
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
             prim_info,
             root_spatial_node_index,
-            prim_context,
             pic_context,
             pic_state,
             frame_context,
             frame_state,
             prim_store,
             data_stores,
             &mut scratch.segments,
             &mut scratch.segment_instances,
--- a/gfx/wr/webrender/src/util.rs
+++ b/gfx/wr/webrender/src/util.rs
@@ -747,28 +747,16 @@ impl<Src, Dst> FastTransform<Src, Dst> {
             FastTransform::Offset(offset) => {
                 let new_point = *point + offset;
                 Some(TypedPoint2D::from_untyped(&new_point.to_untyped()))
             }
             FastTransform::Transform { ref transform, .. } => transform.transform_point2d(point),
         }
     }
 
-    pub fn unapply(&self, rect: &TypedRect<f32, Dst>) -> Option<TypedRect<f32, Src>> {
-        match *self {
-            FastTransform::Offset(offset) =>
-                Some(TypedRect::from_untyped(&rect.to_untyped().translate(&-offset.to_untyped()))),
-            FastTransform::Transform { inverse: Some(ref inverse), is_2d: true, .. }  =>
-                inverse.transform_rect(rect),
-            FastTransform::Transform { ref transform, is_2d: false, .. } =>
-                transform.inverse_rect_footprint(rect),
-            FastTransform::Transform { inverse: None, .. }  => None,
-        }
-    }
-
     pub fn post_translate(&self, new_offset: TypedVector2D<f32, Dst>) -> Self {
         match *self {
             FastTransform::Offset(offset) => {
                 let offset = offset.to_untyped() + new_offset.to_untyped();
                 FastTransform::Offset(TypedVector2D::from_untyped(&offset))
             }
             FastTransform::Transform { ref transform, .. } => {
                 let transform = transform.post_translate(new_offset.to_3d());
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -529,18 +529,17 @@ nsresult LoadInfoToLoadInfoArgs(nsILoadI
       redirectChainIncludingInternalRedirects, redirectChain,
       ancestorPrincipals, aLoadInfo->AncestorOuterWindowIDs(), ipcClientInfo,
       ipcReservedClientInfo, ipcInitialClientInfo, ipcController,
       aLoadInfo->CorsUnsafeHeaders(), aLoadInfo->GetForcePreflight(),
       aLoadInfo->GetIsPreflight(), aLoadInfo->GetLoadTriggeredFromExternal(),
       aLoadInfo->GetServiceWorkerTaintingSynthesized(),
       aLoadInfo->GetDocumentHasUserInteracted(),
       aLoadInfo->GetDocumentHasLoaded(), cspNonce,
-      aLoadInfo->GetIsFromProcessingFrameAttributes(),
-      aLoadInfo->GetOpenerPolicy(), cookieSettingsArgs));
+      aLoadInfo->GetIsFromProcessingFrameAttributes(), cookieSettingsArgs));
 
   return NS_OK;
 }
 
 nsresult LoadInfoArgsToLoadInfo(
     const Maybe<LoadInfoArgs>& aOptionalLoadInfoArgs,
     nsILoadInfo** outLoadInfo) {
   if (aOptionalLoadInfoArgs.isNothing()) {
@@ -687,62 +686,57 @@ nsresult LoadInfoArgsToLoadInfo(
       loadInfoArgs.serviceWorkerTaintingSynthesized(),
       loadInfoArgs.documentHasUserInteracted(),
       loadInfoArgs.documentHasLoaded(), loadInfoArgs.cspNonce());
 
   if (loadInfoArgs.isFromProcessingFrameAttributes()) {
     loadInfo->SetIsFromProcessingFrameAttributes();
   }
 
-  loadInfo->SetOpenerPolicy(loadInfoArgs.openerPolicy());
-
   loadInfo.forget(outLoadInfo);
   return NS_OK;
 }
 
 void LoadInfoToParentLoadInfoForwarder(
     nsILoadInfo* aLoadInfo, ParentLoadInfoForwarderArgs* aForwarderArgsOut) {
   if (!aLoadInfo) {
     *aForwarderArgsOut = ParentLoadInfoForwarderArgs(
         false, Nothing(), nsILoadInfo::TAINTING_BASIC,
         false,  // serviceWorkerTaintingSynthesized
         false,  // documentHasUserInteracted
         false,  // documentHasLoaded
-        nsILoadInfo::OPENER_POLICY_NULL, Maybe<CookieSettingsArgs>());
+        Maybe<CookieSettingsArgs>());
     return;
   }
 
   Maybe<IPCServiceWorkerDescriptor> ipcController;
   Maybe<ServiceWorkerDescriptor> controller(aLoadInfo->GetController());
   if (controller.isSome()) {
     ipcController.emplace(controller.ref().ToIPC());
   }
 
   uint32_t tainting = nsILoadInfo::TAINTING_BASIC;
   Unused << aLoadInfo->GetTainting(&tainting);
 
-  nsILoadInfo::CrossOriginOpenerPolicy openerPolicy =
-      aLoadInfo->GetOpenerPolicy();
-
   Maybe<CookieSettingsArgs> cookieSettingsArgs;
 
   nsCOMPtr<nsICookieSettings> cookieSettings;
   nsresult rv = aLoadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
   CookieSettings* cs = static_cast<CookieSettings*>(cookieSettings.get());
   if (NS_SUCCEEDED(rv) && cookieSettings && cs->HasBeenChanged()) {
     CookieSettingsArgs args;
     cs->Serialize(args);
     cookieSettingsArgs = Some(args);
   }
 
   *aForwarderArgsOut = ParentLoadInfoForwarderArgs(
       aLoadInfo->GetAllowInsecureRedirectToDataURI(), ipcController, tainting,
       aLoadInfo->GetServiceWorkerTaintingSynthesized(),
       aLoadInfo->GetDocumentHasUserInteracted(),
-      aLoadInfo->GetDocumentHasLoaded(), openerPolicy, cookieSettingsArgs);
+      aLoadInfo->GetDocumentHasLoaded(), cookieSettingsArgs);
 }
 
 nsresult MergeParentLoadInfoForwarder(
     ParentLoadInfoForwarderArgs const& aForwarderArgs, nsILoadInfo* aLoadInfo) {
   if (!aLoadInfo) {
     return NS_OK;
   }
 
@@ -760,19 +754,16 @@ nsresult MergeParentLoadInfoForwarder(
 
   if (aForwarderArgs.serviceWorkerTaintingSynthesized()) {
     aLoadInfo->SynthesizeServiceWorkerTainting(
         static_cast<LoadTainting>(aForwarderArgs.tainting()));
   } else {
     aLoadInfo->MaybeIncreaseTainting(aForwarderArgs.tainting());
   }
 
-  MOZ_ALWAYS_SUCCEEDS(
-      aLoadInfo->SetOpenerPolicy(aForwarderArgs.openerPolicy()));
-
   MOZ_ALWAYS_SUCCEEDS(aLoadInfo->SetDocumentHasUserInteracted(
       aForwarderArgs.documentHasUserInteracted()));
   MOZ_ALWAYS_SUCCEEDS(
       aLoadInfo->SetDocumentHasLoaded(aForwarderArgs.documentHasLoaded()));
 
   const Maybe<CookieSettingsArgs>& cookieSettingsArgs =
       aForwarderArgs.cookieSettings();
   if (cookieSettingsArgs.isSome()) {
--- a/ipc/glue/ProcessUtils.h
+++ b/ipc/glue/ProcessUtils.h
@@ -8,16 +8,18 @@
 #define mozilla_ipc_ProcessUtils_h
 
 #include "FileDescriptor.h"
 #include "base/shared_memory.h"
 
 namespace mozilla {
 namespace ipc {
 
+class GeckoChildProcessHost;
+
 // You probably should call ContentChild::SetProcessName instead of calling
 // this directly.
 void SetThisProcessName(const char* aName);
 
 class SharedPreferenceSerializer final {
  public:
   SharedPreferenceSerializer();
   SharedPreferenceSerializer(SharedPreferenceSerializer&& aOther);
@@ -32,16 +34,19 @@ class SharedPreferenceSerializer final {
   const FileDescriptor::UniquePlatformHandle& GetPrefMapHandle() const {
     return mPrefMapHandle;
   }
 
   nsACString::size_type GetPrefLength() const { return mPrefs.Length(); }
 
   size_t GetPrefMapSize() const { return mPrefMapSize; }
 
+  void AddSharedPrefCmdLineArgs(GeckoChildProcessHost& procHost,
+                                std::vector<std::string>& aExtraOpts) const;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(SharedPreferenceSerializer);
   size_t mPrefMapSize;
   FileDescriptor::UniquePlatformHandle mPrefMapHandle;
   base::SharedMemory mShm;
   nsAutoCStringN<1024> mPrefs;
 };
 
--- a/ipc/glue/ProcessUtils_common.cpp
+++ b/ipc/glue/ProcessUtils_common.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=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/. */
 
 #include "ProcessUtils.h"
 
 #include "mozilla/Preferences.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
 
 namespace mozilla {
 namespace ipc {
 
 SharedPreferenceSerializer::SharedPreferenceSerializer() : mPrefMapSize(0) {
   MOZ_COUNT_CTOR(SharedPreferenceSerializer);
 }
 
@@ -46,16 +47,56 @@ bool SharedPreferenceSerializer::Seriali
   }
 
   // Copy the serialized prefs into the shared memory.
   memcpy(static_cast<char*>(mShm.memory()), mPrefs.get(), mPrefs.Length());
 
   return true;
 }
 
+void SharedPreferenceSerializer::AddSharedPrefCmdLineArgs(
+    mozilla::ipc::GeckoChildProcessHost& procHost,
+    std::vector<std::string>& aExtraOpts) const {
+  // Formats a pointer or pointer-sized-integer as a string suitable for passing
+  // in an arguments list.
+  auto formatPtrArg = [](auto arg) {
+    return nsPrintfCString("%zu", uintptr_t(arg));
+  };
+
+#if defined(XP_WIN)
+  // Record the handle as to-be-shared, and pass it via a command flag. This
+  // works because Windows handles are system-wide.
+  HANDLE prefsHandle = GetSharedMemoryHandle();
+  procHost.AddHandleToShare(prefsHandle);
+  procHost.AddHandleToShare(GetPrefMapHandle().get());
+  aExtraOpts.push_back("-prefsHandle");
+  aExtraOpts.push_back(formatPtrArg(prefsHandle).get());
+  aExtraOpts.push_back("-prefMapHandle");
+  aExtraOpts.push_back(
+      formatPtrArg(GetPrefMapHandle().get()).get());
+#else
+  // In contrast, Unix fds are per-process. So remap the fd to a fixed one that
+  // will be used in the child.
+  // XXX: bug 1440207 is about improving how fixed fds are used.
+  //
+  // Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel,
+  // and the fixed fd isn't used. However, we still need to mark it for
+  // remapping so it doesn't get closed in the child.
+  procHost.AddFdToRemap(GetSharedMemoryHandle().fd, kPrefsFileDescriptor);
+  procHost.AddFdToRemap(GetPrefMapHandle().get(), kPrefMapFileDescriptor);
+#endif
+
+  // Pass the lengths via command line flags.
+  aExtraOpts.push_back("-prefsLen");
+  aExtraOpts.push_back(formatPtrArg(GetPrefLength()).get());
+  aExtraOpts.push_back("-prefMapSize");
+  aExtraOpts.push_back(formatPtrArg(GetPrefMapSize()).get());
+}
+
+
 #ifdef ANDROID
 static int gPrefsFd = -1;
 static int gPrefMapFd = -1;
 
 void SetPrefsFd(int aFd) { gPrefsFd = aFd; }
 
 void SetPrefMapFd(int aFd) { gPrefMapFd = aFd; }
 #endif
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -730,34 +730,34 @@ void TextOverflow::PruneDisplayListConte
 
     nsDisplayList* wrapper = item->GetSameCoordinateSystemChildren();
     if (wrapper) {
       if (!itemFrame || GetSelfOrNearestBlock(itemFrame) == mBlock) {
         PruneDisplayListContents(wrapper, aFramesToHide, aInsideMarkersArea);
       }
     }
 
-    nsCharClipDisplayItem* charClip =
-        itemFrame ? nsCharClipDisplayItem::CheckCast(item) : nullptr;
-    if (charClip && GetSelfOrNearestBlock(itemFrame) == mBlock) {
+    nsDisplayText* textItem =
+        itemFrame ? nsDisplayText::CheckCast(item) : nullptr;
+    if (textItem && GetSelfOrNearestBlock(itemFrame) == mBlock) {
       LogicalRect rect =
           GetLogicalScrollableOverflowRectRelativeToBlock(itemFrame);
       if (mIStart.IsNeeded()) {
         nscoord istart =
             aInsideMarkersArea.IStart(mBlockWM) - rect.IStart(mBlockWM);
         if (istart > 0) {
-          (mBlockWM.IsBidiLTR() ? charClip->mVisIStartEdge
-                                : charClip->mVisIEndEdge) = istart;
+          (mBlockWM.IsBidiLTR() ? textItem->VisIStartEdge()
+                                : textItem->VisIEndEdge()) = istart;
         }
       }
       if (mIEnd.IsNeeded()) {
         nscoord iend = rect.IEnd(mBlockWM) - aInsideMarkersArea.IEnd(mBlockWM);
         if (iend > 0) {
-          (mBlockWM.IsBidiLTR() ? charClip->mVisIEndEdge
-                                : charClip->mVisIStartEdge) = iend;
+          (mBlockWM.IsBidiLTR() ? textItem->VisIEndEdge()
+                                : textItem->VisIStartEdge()) = iend;
         }
       }
     }
 
     saved.AppendToTop(item);
   }
   aList->AppendToTop(&saved);
 }
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -63,21 +63,16 @@
 #include "nsFrameSelection.h"
 #include "nsRange.h"
 #include "nsCSSRendering.h"
 #include "nsContentUtils.h"
 #include "nsLineBreaker.h"
 #include "nsIFrameInlines.h"
 #include "mozilla/intl/WordBreaker.h"
 #include "mozilla/ServoStyleSet.h"
-#include "mozilla/layers/LayersMessages.h"
-#include "mozilla/layers/RenderRootStateManager.h"
-#include "mozilla/layers/WebRenderBridgeChild.h"
-#include "mozilla/webrender/WebRenderAPI.h"
-#include "mozilla/layers/StackingContextHelper.h"
 
 #include <algorithm>
 #include <limits>
 #ifdef ACCESSIBILITY
 #  include "nsAccessibilityService.h"
 #endif
 
 #include "nsPrintfCString.h"
@@ -100,17 +95,16 @@
 
 #ifdef DrawText
 #  undef DrawText
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
-using namespace mozilla::layers;
 
 typedef mozilla::layout::TextDrawTarget TextDrawTarget;
 
 struct TabWidth {
   TabWidth(uint32_t aOffset, uint32_t aWidth)
       : mOffset(aOffset), mWidth(float(aWidth)) {}
 
   uint32_t mOffset;  // DOM offset relative to the current frame's offset.
@@ -4844,284 +4838,23 @@ nsresult nsTextFrame::CharacterDataChang
       textFrame->ClearTextRuns();
       textFrame = textFrame->GetNextContinuation();
     }
   }
 
   return NS_OK;
 }
 
-class nsDisplayText final : public nsCharClipDisplayItem {
- public:
-  nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame,
-                const Maybe<bool>& aIsSelected);
-#ifdef NS_BUILD_REFCNT_LOGGING
-  virtual ~nsDisplayText() { MOZ_COUNT_DTOR(nsDisplayText); }
-#endif
-
-  void RestoreState() final {
-    nsCharClipDisplayItem::RestoreState();
-    mOpacity = 1.0f;
-  }
-
-  nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final {
-    *aSnap = false;
-    return mBounds;
-  }
-  void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
-               HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) final {
-    if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) {
-      aOutFrames->AppendElement(mFrame);
-    }
-  }
-  bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
-                               mozilla::wr::IpcResourceUpdateQueue& aResources,
-                               const StackingContextHelper& aSc,
-                               RenderRootStateManager* aManager,
-                               nsDisplayListBuilder* aDisplayListBuilder) final;
-  void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final;
-  NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
-
-  nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const final {
-    if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
-      // On OS X, web authors can turn off subpixel text rendering using the
-      // CSS property -moz-osx-font-smoothing. If they do that, we don't need
-      // to use component alpha layers for the affected text.
-      if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
-        return nsRect();
-      }
-    }
-    bool snap;
-    return GetBounds(aBuilder, &snap);
-  }
-
-  nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) final;
-
-  void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
-                                 const nsDisplayItemGeometry* aGeometry,
-                                 nsRegion* aInvalidRegion) const final;
-
-  void RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder,
-                       bool aIsRecording = false);
-
-  bool CanApplyOpacity() const final {
-    if (IsSelected()) {
-      return false;
-    }
-
-    nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
-    const nsStyleText* textStyle = f->StyleText();
-    if (textStyle->mTextShadow) {
-      return false;
-    }
-
-    nsTextFrame::TextDecorations decorations;
-    f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
-                          decorations);
-    if (decorations.HasDecorationLines()) {
-      return false;
-    }
-
-    return true;
-  }
-
-  void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity,
-                    const DisplayItemClipChain* aClip) final {
-    NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
-    mOpacity = aOpacity;
-    IntersectClip(aBuilder, aClip, false);
-  }
-
-  void WriteDebugInfo(std::stringstream& aStream) final {
-#ifdef DEBUG
-    aStream << " (\"";
-
-    nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
-    nsCString buf;
-    int32_t totalContentLength;
-    f->ToCString(buf, &totalContentLength);
-
-    aStream << buf.get() << "\")";
-#endif
-  }
-
-  nsRect mBounds;
-  float mOpacity;
-};
-
-class nsDisplayTextGeometry : public nsCharClipGeometry {
- public:
-  nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder)
-      : nsCharClipGeometry(aItem, aBuilder), mOpacity(aItem->mOpacity) {
-    nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame());
-    f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
-                          mDecorations);
-  }
-
-  /**
-   * We store the computed text decorations here since they are
-   * computed using style data from parent frames. Any changes to these
-   * styles will only invalidate the parent frame and not this frame.
-   */
-  nsTextFrame::TextDecorations mDecorations;
-  float mOpacity;
-};
-
-nsDisplayItemGeometry* nsDisplayText::AllocateGeometry(
-    nsDisplayListBuilder* aBuilder) {
-  return new nsDisplayTextGeometry(this, aBuilder);
-}
-
-void nsDisplayText::ComputeInvalidationRegion(
-    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
-    nsRegion* aInvalidRegion) const {
-  const nsDisplayTextGeometry* geometry =
-      static_cast<const nsDisplayTextGeometry*>(aGeometry);
-  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
-
-  nsTextFrame::TextDecorations decorations;
-  f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
-                        decorations);
-
-  bool snap;
-  nsRect newRect = geometry->mBounds;
-  nsRect oldRect = GetBounds(aBuilder, &snap);
-  if (decorations != geometry->mDecorations ||
-      mVisIStartEdge != geometry->mVisIStartEdge ||
-      mVisIEndEdge != geometry->mVisIEndEdge ||
-      !oldRect.IsEqualInterior(newRect) ||
-      !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) ||
-      mOpacity != geometry->mOpacity) {
-    aInvalidRegion->Or(oldRect, newRect);
-  }
-}
-
 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TextCombineScaleFactorProperty, float)
 
-static float GetTextCombineScaleFactor(nsTextFrame* aFrame) {
+float nsTextFrame::GetTextCombineScaleFactor(nsTextFrame* aFrame) {
   float factor = aFrame->GetProperty(TextCombineScaleFactorProperty());
   return factor ? factor : 1.0f;
 }
 
-nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder,
-                             nsTextFrame* aFrame,
-                             const Maybe<bool>& aIsSelected)
-    : nsCharClipDisplayItem(aBuilder, aFrame), mOpacity(1.0f) {
-  MOZ_COUNT_CTOR(nsDisplayText);
-  mIsFrameSelected = aIsSelected;
-  mBounds = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
-  // Bug 748228
-  mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
-}
-
-void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
-  AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS);
-
-  DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
-                                                    mDisableSubpixelAA);
-  RenderToContext(aCtx, aBuilder);
-}
-
-bool nsDisplayText::CreateWebRenderCommands(
-    mozilla::wr::DisplayListBuilder& aBuilder,
-    mozilla::wr::IpcResourceUpdateQueue& aResources,
-    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
-    nsDisplayListBuilder* aDisplayListBuilder) {
-  if (mBounds.IsEmpty()) {
-    return true;
-  }
-
-  auto appUnitsPerDevPixel = Frame()->PresContext()->AppUnitsPerDevPixel();
-  gfx::Point deviceOffset =
-      LayoutDevicePoint::FromAppUnits(mBounds.TopLeft(), appUnitsPerDevPixel)
-          .ToUnknownPoint();
-
-  nsRect visible = GetPaintRect();
-  visible.Inflate(3 * appUnitsPerDevPixel);
-
-  visible = visible.Intersect(mBounds);
-
-  RefPtr<gfxContext> textDrawer = aBuilder.GetTextContext(
-      aResources, aSc, aManager, this, visible, deviceOffset);
-
-  RenderToContext(textDrawer, aDisplayListBuilder, true);
-
-  return textDrawer->GetTextDrawer()->Finish();
-}
-
-void nsDisplayText::RenderToContext(gfxContext* aCtx,
-                                    nsDisplayListBuilder* aBuilder,
-                                    bool aIsRecording) {
-  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
-
-  // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
-  // antialiased pixels beyond the measured text extents.
-  // This is temporary until we do this in the actual calculation of text
-  // extents.
-  auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
-  LayoutDeviceRect extraVisible =
-      LayoutDeviceRect::FromAppUnits(GetPaintRect(), A2D);
-  extraVisible.Inflate(1);
-
-  gfxRect pixelVisible(extraVisible.x, extraVisible.y, extraVisible.width,
-                       extraVisible.height);
-  pixelVisible.Inflate(2);
-  pixelVisible.RoundOut();
-
-  bool willClip = !aBuilder->IsForGenerateGlyphMask() && !aIsRecording;
-  if (willClip) {
-    aCtx->NewPath();
-    aCtx->Rectangle(pixelVisible);
-    aCtx->Clip();
-  }
-
-  NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge");
-  NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge");
-
-  gfxContextMatrixAutoSaveRestore matrixSR;
-
-  nsPoint framePt = ToReferenceFrame();
-  if (f->Style()->IsTextCombined()) {
-    float scaleFactor = GetTextCombineScaleFactor(f);
-    if (scaleFactor != 1.0f) {
-      if (auto* textDrawer = aCtx->GetTextDrawer()) {
-        // WebRender doesn't support scaling text like this yet
-        textDrawer->FoundUnsupportedFeature();
-        return;
-      }
-      matrixSR.SetContext(aCtx);
-      // Setup matrix to compress text for text-combine-upright if
-      // necessary. This is done here because we want selection be
-      // compressed at the same time as text.
-      gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D);
-      gfxMatrix mat = aCtx->CurrentMatrixDouble()
-                          .PreTranslate(pt)
-                          .PreScale(scaleFactor, 1.0)
-                          .PreTranslate(-pt);
-      aCtx->SetMatrixDouble(mat);
-    }
-  }
-  nsTextFrame::PaintTextParams params(aCtx);
-  params.framePt = gfx::Point(framePt.x, framePt.y);
-  params.dirtyRect = extraVisible;
-
-  if (aBuilder->IsForGenerateGlyphMask()) {
-    params.state = nsTextFrame::PaintTextParams::GenerateTextMask;
-  } else {
-    params.state = nsTextFrame::PaintTextParams::PaintText;
-  }
-
-  f->PaintText(params, mVisIStartEdge, mVisIEndEdge, ToReferenceFrame(),
-               IsSelected(), mOpacity);
-
-  if (willClip) {
-    aCtx->PopClip();
-  }
-}
-
 void nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                    const nsDisplayListSet& aLists) {
   if (!IsVisibleForPainting()) return;
 
   DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
 
   const nsStyleText* st = StyleText();
   bool isTextTransparent =
@@ -6202,17 +5935,17 @@ void nsTextFrame::PaintOneShadow(const P
 }
 
 // Paints selection backgrounds and text in the correct colors. Also computes
 // aAllTypes, the union of all selection types that are applying to this text.
 bool nsTextFrame::PaintTextWithSelectionColors(
     const PaintTextSelectionParams& aParams,
     const UniquePtr<SelectionDetails>& aDetails,
     SelectionTypeMask* aAllSelectionTypeMask,
-    const nsCharClipDisplayItem::ClipEdges& aClipEdges) {
+    const nsDisplayText::ClipEdges& aClipEdges) {
   const gfxTextRun::Range& contentRange = aParams.contentRange;
 
   // Figure out which selections control the colors to use for each character.
   // Note: prevailingSelectionsBuffer is keeping extra raw pointers to
   // uniquely-owned resources, but it's safe because it's temporary and the
   // resources are owned by the caller. Therefore, they'll outlive this object.
   AutoTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer;
   SelectionDetails** prevailingSelections =
@@ -6467,17 +6200,17 @@ void nsTextFrame::PaintTextSelectionDeco
                                aParams.callbacks, verticalRun, kDecoration);
     }
     iterator.UpdateWithAdvance(advance);
   }
 }
 
 bool nsTextFrame::PaintTextWithSelection(
     const PaintTextSelectionParams& aParams,
-    const nsCharClipDisplayItem::ClipEdges& aClipEdges) {
+    const nsDisplayText::ClipEdges& aClipEdges) {
   NS_ASSERTION(GetContent()->IsSelectionDescendant(), "wrong paint path");
 
   UniquePtr<SelectionDetails> details = GetSelectionDetails();
   if (!details) {
     return false;
   }
 
   SelectionTypeMask allSelectionTypeMask;
@@ -6822,18 +6555,18 @@ void nsTextFrame::PaintText(const PaintT
                               &snappedEndEdge)) {
     return;
   }
   if (verticalRun) {
     textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge;
   } else {
     textBaselinePt.x += reversed ? -snappedEndEdge : snappedStartEdge;
   }
-  nsCharClipDisplayItem::ClipEdges clipEdges(this, aToReferenceFrame,
-                                             snappedStartEdge, snappedEndEdge);
+  nsDisplayText::ClipEdges clipEdges(this, aToReferenceFrame, snappedStartEdge,
+                                     snappedEndEdge);
   nsTextPaintStyle textPaintStyle(this);
   textPaintStyle.SetResolveColors(!aParams.callbacks);
 
   // Fork off to the (slower) paint-with-selection path if necessary.
   if (aIsSelected) {
     MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!");
     gfxSkipCharsIterator tmp(provider.GetStart());
     Range contentRange(
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -28,19 +28,16 @@
 #  undef DrawText
 #endif
 
 class nsTextPaintStyle;
 class PropertyProvider;
 struct SelectionDetails;
 class nsTextFragment;
 
-class nsDisplayTextGeometry;
-class nsDisplayText;
-
 namespace mozilla {
 class SVGContextPaint;
 };
 
 class nsTextFrame : public nsFrame {
   typedef mozilla::LayoutDeviceRect LayoutDeviceRect;
   typedef mozilla::SelectionTypeMask SelectionTypeMask;
   typedef mozilla::SelectionType SelectionType;
@@ -58,18 +55,16 @@ class nsTextFrame : public nsFrame {
         mNextContinuation(nullptr),
         mContentOffset(0),
         mContentLengthHint(0),
         mAscent(0) {}
 
   NS_DECL_FRAMEARENA_HELPERS(nsTextFrame)
 
   friend class nsContinuingTextFrame;
-  friend class nsDisplayTextGeometry;
-  friend class nsDisplayText;
 
   // nsQueryFrame
   NS_DECL_QUERYFRAME
 
   // nsIFrame
   void BuildDisplayList(nsDisplayListBuilder* aBuilder,
                         const nsDisplayListSet& aLists) final;
 
@@ -441,45 +436,44 @@ class nsTextFrame : public nsFrame {
     bool drawSoftHyphen = false;
     explicit DrawTextRunParams(gfxContext* aContext) : context(aContext) {}
   };
 
   struct DrawTextParams : DrawTextRunParams {
     mozilla::gfx::Point framePt;
     LayoutDeviceRect dirtyRect;
     const nsTextPaintStyle* textStyle = nullptr;
-    const nsCharClipDisplayItem::ClipEdges* clipEdges = nullptr;
+    const nsDisplayText::ClipEdges* clipEdges = nullptr;
     const nscolor* decorationOverrideColor = nullptr;
     explicit DrawTextParams(gfxContext* aContext)
         : DrawTextRunParams(aContext) {}
   };
 
   // Primary frame paint method called from nsDisplayText.  Can also be used
   // to generate paths rather than paint the frame's text by passing a callback
   // object.  The private DrawText() is what applies the text to a graphics
   // context.
   void PaintText(const PaintTextParams& aParams, const nscoord aVisIStartEdge,
                  const nscoord aVisIEndEdge, const nsPoint& aToReferenceFrame,
                  const bool aIsSelected, float aOpacity = 1.0f);
   // helper: paint text frame when we're impacted by at least one selection.
   // Return false if the text was not painted and we should continue with
   // the fast path.
-  bool PaintTextWithSelection(
-      const PaintTextSelectionParams& aParams,
-      const nsCharClipDisplayItem::ClipEdges& aClipEdges);
+  bool PaintTextWithSelection(const PaintTextSelectionParams& aParams,
+                              const nsDisplayText::ClipEdges& aClipEdges);
   // helper: paint text with foreground and background colors determined
   // by selection(s). Also computes a mask of all selection types applying to
   // our text, returned in aAllSelectionTypeMask.
   // Return false if the text was not painted and we should continue with
   // the fast path.
   bool PaintTextWithSelectionColors(
       const PaintTextSelectionParams& aParams,
       const mozilla::UniquePtr<SelectionDetails>& aDetails,
       SelectionTypeMask* aAllSelectionTypeMask,
-      const nsCharClipDisplayItem::ClipEdges& aClipEdges);
+      const nsDisplayText::ClipEdges& aClipEdges);
   // helper: paint text decorations for text selected by aSelectionType
   void PaintTextSelectionDecorations(
       const PaintTextSelectionParams& aParams,
       const mozilla::UniquePtr<SelectionDetails>& aDetails,
       SelectionType aSelectionType);
 
   void DrawEmphasisMarks(gfxContext* aContext, mozilla::WritingMode aWM,
                          const mozilla::gfx::Point& aTextBaselinePt,
@@ -613,16 +607,19 @@ class nsTextFrame : public nsFrame {
   void SetInflatedFontMetrics(nsFontMetrics* aMetrics) {
     mFontMetrics = aMetrics;
   }
   nsFontMetrics* InflatedFontMetrics() const { return mFontMetrics; }
 
  protected:
   virtual ~nsTextFrame();
 
+  friend class nsDisplayTextGeometry;
+  friend class nsDisplayText;
+
   RefPtr<nsFontMetrics> mFontMetrics;
   RefPtr<gfxTextRun> mTextRun;
   nsTextFrame* mNextContinuation;
   // The key invariant here is that mContentOffset never decreases along
   // a next-continuation chain. And of course mContentOffset is always <= the
   // the text node's content length, and the mContentOffset for the first frame
   // is always 0. Furthermore the text mapped by a frame is determined by
   // GetContentOffset() and GetContentLength()/GetContentEnd(), which get
@@ -658,17 +655,17 @@ class nsTextFrame : public nsFrame {
 
   struct PaintShadowParams {
     gfxTextRun::Range range;
     LayoutDeviceRect dirtyRect;
     mozilla::gfx::Point framePt;
     mozilla::gfx::Point textBaselinePt;
     gfxContext* context;
     nscolor foregroundColor = NS_RGBA(0, 0, 0, 0);
-    const nsCharClipDisplayItem::ClipEdges* clipEdges = nullptr;
+    const nsDisplayText::ClipEdges* clipEdges = nullptr;
     PropertyProvider* provider = nullptr;
     nscoord leftSideOffset = 0;
     explicit PaintShadowParams(const PaintTextParams& aParams)
         : dirtyRect(aParams.dirtyRect),
           framePt(aParams.framePt),
           context(aParams.context) {}
   };
 
@@ -793,16 +790,18 @@ class nsTextFrame : public nsFrame {
    */
   static gfxFloat ComputeSelectionUnderlineHeight(
       nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics,
       SelectionType aSelectionType);
 
   ContentOffsets GetCharacterOffsetAtFramePointInternal(
       const nsPoint& aPoint, bool aForInsertionPoint);
 
+  static float GetTextCombineScaleFactor(nsTextFrame* aFrame);
+
   void ClearFrameOffsetCache();
 
   void ClearMetrics(ReflowOutput& aMetrics);
 
   /**
    * UpdateIteratorFromOffset() updates the iterator from a given offset.
    * Also, aInOffset may be updated to cluster start if aInOffset isn't
    * the offset of cluster start.
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -8897,47 +8897,231 @@ bool nsDisplayPerspective::CreateWebRend
                            params);
 
   aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList(
       GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources);
 
   return true;
 }
 
-nsDisplayItemGeometry* nsCharClipDisplayItem::AllocateGeometry(
-    nsDisplayListBuilder* aBuilder) {
-  return new nsCharClipGeometry(this, aBuilder);
-}
-
-void nsCharClipDisplayItem::ComputeInvalidationRegion(
-    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
-    nsRegion* aInvalidRegion) const {
-  auto* geometry = static_cast<const nsCharClipGeometry*>(aGeometry);
-
-  bool snap;
-  nsRect newRect = geometry->mBounds;
-  nsRect oldRect = GetBounds(aBuilder, &snap);
-  if (mVisIStartEdge != geometry->mVisIStartEdge ||
-      mVisIEndEdge != geometry->mVisIEndEdge ||
-      !oldRect.IsEqualInterior(newRect) ||
-      !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
-    aInvalidRegion->Or(oldRect, newRect);
-  }
-}
-
-bool nsCharClipDisplayItem::IsSelected() const {
+nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder,
+                             nsTextFrame* aFrame,
+                             const Maybe<bool>& aIsSelected)
+    : nsDisplayItem(aBuilder, aFrame),
+      mOpacity(1.0f),
+      mVisIStartEdge(0),
+      mVisIEndEdge(0) {
+  MOZ_COUNT_CTOR(nsDisplayText);
+  mIsFrameSelected = aIsSelected;
+  mBounds = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
+  // Bug 748228
+  mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
+}
+
+bool nsDisplayText::CanApplyOpacity() const {
+  if (IsSelected()) {
+    return false;
+  }
+
+  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
+  const nsStyleText* textStyle = f->StyleText();
+  if (textStyle->mTextShadow) {
+    return false;
+  }
+
+  nsTextFrame::TextDecorations decorations;
+  f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
+                        decorations);
+  if (decorations.HasDecorationLines()) {
+    return false;
+  }
+
+  return true;
+}
+
+void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
+  AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS);
+
+  DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
+                                                    mDisableSubpixelAA);
+  RenderToContext(aCtx, aBuilder);
+}
+
+bool nsDisplayText::CreateWebRenderCommands(
+    mozilla::wr::DisplayListBuilder& aBuilder,
+    mozilla::wr::IpcResourceUpdateQueue& aResources,
+    const StackingContextHelper& aSc, RenderRootStateManager* aManager,
+    nsDisplayListBuilder* aDisplayListBuilder) {
+  if (mBounds.IsEmpty()) {
+    return true;
+  }
+
+  auto appUnitsPerDevPixel = Frame()->PresContext()->AppUnitsPerDevPixel();
+  gfx::Point deviceOffset =
+      LayoutDevicePoint::FromAppUnits(mBounds.TopLeft(), appUnitsPerDevPixel)
+          .ToUnknownPoint();
+
+  nsRect visible = GetPaintRect();
+  visible.Inflate(3 * appUnitsPerDevPixel);
+
+  visible = visible.Intersect(mBounds);
+
+  RefPtr<gfxContext> textDrawer = aBuilder.GetTextContext(
+      aResources, aSc, aManager, this, visible, deviceOffset);
+
+  RenderToContext(textDrawer, aDisplayListBuilder, true);
+
+  return textDrawer->GetTextDrawer()->Finish();
+}
+
+void nsDisplayText::RenderToContext(gfxContext* aCtx,
+                                    nsDisplayListBuilder* aBuilder,
+                                    bool aIsRecording) {
+  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
+
+  // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
+  // antialiased pixels beyond the measured text extents.
+  // This is temporary until we do this in the actual calculation of text
+  // extents.
+  auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
+  LayoutDeviceRect extraVisible =
+      LayoutDeviceRect::FromAppUnits(GetPaintRect(), A2D);
+  extraVisible.Inflate(1);
+
+  gfxRect pixelVisible(extraVisible.x, extraVisible.y, extraVisible.width,
+                       extraVisible.height);
+  pixelVisible.Inflate(2);
+  pixelVisible.RoundOut();
+
+  bool willClip = !aBuilder->IsForGenerateGlyphMask() && !aIsRecording;
+  if (willClip) {
+    aCtx->NewPath();
+    aCtx->Rectangle(pixelVisible);
+    aCtx->Clip();
+  }
+
+  NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge");
+  NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge");
+
+  gfxContextMatrixAutoSaveRestore matrixSR;
+
+  nsPoint framePt = ToReferenceFrame();
+  if (f->Style()->IsTextCombined()) {
+    float scaleFactor = nsTextFrame::GetTextCombineScaleFactor(f);
+    if (scaleFactor != 1.0f) {
+      if (auto* textDrawer = aCtx->GetTextDrawer()) {
+        // WebRender doesn't support scaling text like this yet
+        textDrawer->FoundUnsupportedFeature();
+        return;
+      }
+      matrixSR.SetContext(aCtx);
+      // Setup matrix to compress text for text-combine-upright if
+      // necessary. This is done here because we want selection be
+      // compressed at the same time as text.
+      gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D);
+      gfxMatrix mat = aCtx->CurrentMatrixDouble()
+                          .PreTranslate(pt)
+                          .PreScale(scaleFactor, 1.0)
+                          .PreTranslate(-pt);
+      aCtx->SetMatrixDouble(mat);
+    }
+  }
+  nsTextFrame::PaintTextParams params(aCtx);
+  params.framePt = gfx::Point(framePt.x, framePt.y);
+  params.dirtyRect = extraVisible;
+
+  if (aBuilder->IsForGenerateGlyphMask()) {
+    params.state = nsTextFrame::PaintTextParams::GenerateTextMask;
+  } else {
+    params.state = nsTextFrame::PaintTextParams::PaintText;
+  }
+
+  f->PaintText(params, mVisIStartEdge, mVisIEndEdge, ToReferenceFrame(),
+               IsSelected(), mOpacity);
+
+  if (willClip) {
+    aCtx->PopClip();
+  }
+}
+
+bool nsDisplayText::IsSelected() const {
   if (mIsFrameSelected.isNothing()) {
     MOZ_ASSERT((nsTextFrame*)do_QueryFrame(mFrame));
     auto* f = static_cast<nsTextFrame*>(mFrame);
     mIsFrameSelected.emplace(f->IsSelected());
   }
 
   return mIsFrameSelected.value();
 }
 
+class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry {
+ public:
+  nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder)
+      : nsDisplayItemGenericGeometry(aItem, aBuilder),
+        mOpacity(aItem->Opacity()),
+        mVisIStartEdge(aItem->VisIStartEdge()),
+        mVisIEndEdge(aItem->VisIEndEdge()) {
+    nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame());
+    f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
+                          mDecorations);
+  }
+
+  /**
+   * We store the computed text decorations here since they are
+   * computed using style data from parent frames. Any changes to these
+   * styles will only invalidate the parent frame and not this frame.
+   */
+  nsTextFrame::TextDecorations mDecorations;
+  float mOpacity;
+  nscoord mVisIStartEdge;
+  nscoord mVisIEndEdge;
+};
+
+nsDisplayItemGeometry* nsDisplayText::AllocateGeometry(
+    nsDisplayListBuilder* aBuilder) {
+  return new nsDisplayTextGeometry(this, aBuilder);
+}
+
+void nsDisplayText::ComputeInvalidationRegion(
+    nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry,
+    nsRegion* aInvalidRegion) const {
+  const nsDisplayTextGeometry* geometry =
+      static_cast<const nsDisplayTextGeometry*>(aGeometry);
+  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
+
+  nsTextFrame::TextDecorations decorations;
+  f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors,
+                        decorations);
+
+  bool snap;
+  const nsRect& newRect = geometry->mBounds;
+  nsRect oldRect = GetBounds(aBuilder, &snap);
+  if (decorations != geometry->mDecorations ||
+      mVisIStartEdge != geometry->mVisIStartEdge ||
+      mVisIEndEdge != geometry->mVisIEndEdge ||
+      !oldRect.IsEqualInterior(newRect) ||
+      !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) ||
+      mOpacity != geometry->mOpacity) {
+    aInvalidRegion->Or(oldRect, newRect);
+  }
+}
+
+void nsDisplayText::WriteDebugInfo(std::stringstream& aStream) {
+#ifdef DEBUG
+  aStream << " (\"";
+
+  nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
+  nsCString buf;
+  int32_t totalContentLength;
+  f->ToCString(buf, &totalContentLength);
+
+  aStream << buf.get() << "\")";
+#endif
+}
+
 nsDisplayEffectsBase::nsDisplayEffectsBase(
     nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList,
     const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain)
     : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot,
                         aClearClipChain),
       mHandleOpacity(false) {
   MOZ_COUNT_CTOR(nsDisplayEffectsBase);
 }
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -7024,39 +7024,101 @@ class nsDisplayPerspective : public nsDi
   }
 
  private:
   mutable RetainedDisplayList mList;
 };
 
 /**
  * This class adds basic support for limiting the rendering (in the inline axis
- * of the writing mode) to the part inside the specified edges.  It's a base
- * class for the display item classes that do the actual work.
+ * of the writing mode) to the part inside the specified edges.
  * The two members, mVisIStartEdge and mVisIEndEdge, are relative to the edges
  * of the frame's scrollable overflow rectangle and are the amount to suppress
  * on each side.
  *
  * Setting none, both or only one edge is allowed.
  * The values must be non-negative.
  * The default value for both edges is zero, which means everything is painted.
  */
-class nsCharClipDisplayItem : public nsDisplayItem {
+class nsDisplayText final : public nsDisplayItem {
  public:
-  nsCharClipDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
-      : nsDisplayItem(aBuilder, aFrame), mVisIStartEdge(0), mVisIEndEdge(0) {}
-
-  void RestoreState() override { nsDisplayItem::RestoreState(); }
-
-  nsDisplayItemGeometry* AllocateGeometry(
-      nsDisplayListBuilder* aBuilder) override;
+  nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame,
+                const mozilla::Maybe<bool>& aIsSelected);
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplayText() { MOZ_COUNT_DTOR(nsDisplayText); }
+#endif
+
+  void RestoreState() final {
+    mIsFrameSelected.reset();
+    mOpacity = 1.0f;
+
+    nsDisplayItem::RestoreState();
+  }
+
+  nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const final {
+    *aSnap = false;
+    return mBounds;
+  }
+
+  void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
+               HitTestState* aState, nsTArray<nsIFrame*>* aOutFrames) final {
+    if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) {
+      aOutFrames->AppendElement(mFrame);
+    }
+  }
+
+  bool CreateWebRenderCommands(
+      mozilla::wr::DisplayListBuilder& aBuilder,
+      mozilla::wr::IpcResourceUpdateQueue& aResources,
+      const StackingContextHelper& aSc,
+      mozilla::layers::RenderRootStateManager* aManager,
+      nsDisplayListBuilder* aDisplayListBuilder) final;
+  void Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) final;
+  NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
+
+  nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const final {
+    if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
+      // On OS X, web authors can turn off subpixel text rendering using the
+      // CSS property -moz-osx-font-smoothing. If they do that, we don't need
+      // to use component alpha layers for the affected text.
+      if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
+        return nsRect();
+      }
+    }
+    bool snap;
+    return GetBounds(aBuilder, &snap);
+  }
+
+  nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) final;
 
   void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
                                  const nsDisplayItemGeometry* aGeometry,
-                                 nsRegion* aInvalidRegion) const override;
+                                 nsRegion* aInvalidRegion) const final;
+
+  void RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder,
+                       bool aIsRecording = false);
+
+  bool CanApplyOpacity() const final;
+
+  void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity,
+                    const DisplayItemClipChain* aClip) final {
+    NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
+    mOpacity = aOpacity;
+    IntersectClip(aBuilder, aClip, false);
+  }
+
+  void WriteDebugInfo(std::stringstream& aStream) final;
+
+  static nsDisplayText* CheckCast(nsDisplayItem* aItem) {
+    return (aItem->GetType() == DisplayItemType::TYPE_TEXT)
+               ? static_cast<nsDisplayText*>(aItem)
+               : nullptr;
+  }
+
+  bool IsSelected() const;
 
   struct ClipEdges {
     ClipEdges(const nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
               nscoord aVisIStartEdge, nscoord aVisIEndEdge) {
       nsRect r = aFrame->GetScrollableOverflowRect() + aToReferenceFrame;
       if (aFrame->GetWritingMode().IsVertical()) {
         mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN;
         mVisIEnd = aVisIEndEdge > 0
@@ -7075,29 +7137,30 @@ class nsCharClipDisplayItem : public nsD
       *aVisIStart = std::max(*aVisIStart, mVisIStart);
       *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0);
     }
 
     nscoord mVisIStart;
     nscoord mVisIEnd;
   };
 
-  static nsCharClipDisplayItem* CheckCast(nsDisplayItem* aItem) {
-    return (aItem->GetType() == DisplayItemType::TYPE_TEXT)
-               ? static_cast<nsCharClipDisplayItem*>(aItem)
-               : nullptr;
-  }
-
-  bool IsSelected() const;
+  nscoord& VisIStartEdge() { return mVisIStartEdge; }
+  nscoord& VisIEndEdge() { return mVisIEndEdge; }
+  float Opacity() const { return mOpacity; }
+
+ private:
+  nsRect mBounds;
+  float mOpacity;
 
   // Lengths measured from the visual inline start and end sides
   // (i.e. left and right respectively in horizontal writing modes,
   // regardless of bidi directionality; top and bottom in vertical modes).
   nscoord mVisIStartEdge;
   nscoord mVisIEndEdge;
+
   // Cached result of mFrame->IsSelected().  Only initialized when needed.
   mutable mozilla::Maybe<bool> mIsFrameSelected;
 };
 
 /**
  * A display item that for webrender to handle SVG
  */
 class nsDisplaySVGWrapper : public nsDisplayWrapList {
--- a/layout/painting/nsDisplayListInvalidation.cpp
+++ b/layout/painting/nsDisplayListInvalidation.cpp
@@ -109,20 +109,14 @@ nsDisplayMasksAndClipPathsGeometry::nsDi
       nsImageGeometryMixin(aItem, aBuilder),
       mDestRects(aItem->GetDestRects()) {}
 
 nsDisplayFiltersGeometry::nsDisplayFiltersGeometry(
     nsDisplayFilters* aItem, nsDisplayListBuilder* aBuilder)
     : nsDisplaySVGEffectGeometry(aItem, aBuilder),
       nsImageGeometryMixin(aItem, aBuilder) {}
 
-nsCharClipGeometry::nsCharClipGeometry(nsCharClipDisplayItem* aItem,
-                                       nsDisplayListBuilder* aBuilder)
-    : nsDisplayItemGenericGeometry(aItem, aBuilder),
-      mVisIStartEdge(aItem->mVisIStartEdge),
-      mVisIEndEdge(aItem->mVisIEndEdge) {}
-
 nsDisplayTableItemGeometry::nsDisplayTableItemGeometry(
     nsDisplayTableItem* aItem, nsDisplayListBuilder* aBuilder,
     const nsPoint& aFrameOffsetToViewport)
     : nsDisplayItemGenericGeometry(aItem, aBuilder),
       nsImageGeometryMixin(aItem, aBuilder),
       mFrameOffsetToViewport(aFrameOffsetToViewport) {}
--- a/layout/painting/nsDisplayListInvalidation.h
+++ b/layout/painting/nsDisplayListInvalidation.h
@@ -299,25 +299,16 @@ class nsDisplayFiltersGeometry
   nsDisplayFiltersGeometry(nsDisplayFilters* aItem,
                            nsDisplayListBuilder* aBuilder);
 
   bool InvalidateForSyncDecodeImages() const override {
     return ShouldInvalidateToSyncDecodeImages();
   }
 };
 
-class nsCharClipGeometry : public nsDisplayItemGenericGeometry {
- public:
-  nsCharClipGeometry(nsCharClipDisplayItem* aItem,
-                     nsDisplayListBuilder* aBuilder);
-
-  nscoord mVisIStartEdge;
-  nscoord mVisIEndEdge;
-};
-
 class nsDisplayTableItemGeometry
     : public nsDisplayItemGenericGeometry,
       public nsImageGeometryMixin<nsDisplayTableItemGeometry> {
  public:
   nsDisplayTableItemGeometry(nsDisplayTableItem* aItem,
                              nsDisplayListBuilder* aBuilder,
                              const nsPoint& aFrameOffsetToViewport);
 
--- a/layout/xul/nsMenuPopupFrame.cpp
+++ b/layout/xul/nsMenuPopupFrame.cpp
@@ -158,16 +158,29 @@ void nsMenuPopupFrame::Init(nsIContent* 
     mPopupType = ePopupTypeTooltip;
   }
 
   nsCOMPtr<nsIDocShellTreeItem> dsti = PresContext()->GetDocShell();
   if (dsti && dsti->ItemType() == nsIDocShellTreeItem::typeChrome) {
     mInContentShell = false;
   }
 
+  // Support incontentshell=false attribute to allow popups to be displayed outside of the
+  // content shell. Chrome only.
+  if (aContent->NodePrincipal()->IsSystemPrincipal()) {
+    if (aContent->AsElement()->AttrValueIs(kNameSpaceID_None,
+                                           nsGkAtoms::incontentshell,
+                                           nsGkAtoms::_true, eCaseMatters)) {
+      mInContentShell = true;
+    } else if (aContent->AsElement()->AttrValueIs(
+                   kNameSpaceID_None, nsGkAtoms::incontentshell,
+                   nsGkAtoms::_false, eCaseMatters)) {
+      mInContentShell = false;
+    }
+  }
   // To improve performance, create the widget for the popup only if it is not
   // a leaf. Leaf popups such as menus will create their widgets later when
   // the popup opens.
   if (!IsLeaf() && !ourView->HasWidget()) {
     CreateWidgetForView(ourView);
   }
 
   if (aContent->NodeInfo()->Equals(nsGkAtoms::tooltip, kNameSpaceID_XUL) &&
--- a/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/AnnotationProcessor.java
+++ b/mobile/android/annotations/src/main/java/org/mozilla/gecko/annotationProcessors/AnnotationProcessor.java
@@ -165,16 +165,31 @@ public class AnnotationProcessor {
 
     private static String getHeaderGuardName(final String name) {
         return name.replaceAll("\\W", "_");
     }
 
     private static int writeOutputFile(final String name, final StringBuilder content) {
         final byte[] contentBytes = content.toString().getBytes(StandardCharsets.UTF_8);
 
+        try {
+            final byte[] existingBytes = Files.readAllBytes(new File(name).toPath());
+            if (Arrays.equals(contentBytes, existingBytes)) {
+                return 0;
+            }
+        } catch (FileNotFoundException e) {
+            // Pass.
+        } catch (NoSuchFileException e) {
+            // Pass.
+        } catch (IOException e) {
+            System.err.println("Unable to read " + name + ". Perhaps a permissions issue?");
+            e.printStackTrace(System.err);
+            return 1;
+        }
+
         try (FileOutputStream outStream = new FileOutputStream(name)) {
             outStream.write(contentBytes);
         } catch (IOException e) {
             System.err.println("Unable to write " + name + ". Perhaps a permissions issue?");
             e.printStackTrace(System.err);
             return 1;
         }
 
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -377,17 +377,19 @@ android.applicationVariants.all { varian
     // as necessary, smoothing out the edit-compile-test development cycle.
     // They do what they say on the tin!
     if ((variant.productFlavors*.name).contains('withGeckoBinaries')) {
         configureVariantWithGeckoBinaries(variant)
     }
 }
 
 android.applicationVariants.all { variant ->
-    configureApplicationVariantWithJNIWrappers(variant, "Fennec")
+    if (variant.name == mozconfig.substs.GRADLE_ANDROID_APP_VARIANT_NAME) {
+        configureApplicationVariantWithJNIWrappers(variant, "Fennec")
+    }
 }
 
 if (gradle.startParameter.taskNames.any { it.endsWith('UnitTest') }) {
     // Approach cribbed from https://github.com/rwinch/jce-checker.
     int maxKeyLen = javax.crypto.Cipher.getMaxAllowedKeyLength("AES")
     if (maxKeyLen <= 128) {
         throw new GradleException(
             "Android unit tests require " +
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/main/java/org/mozilla/geckoview/GeckoViewBridge.java
@@ -0,0 +1,19 @@
+/* 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 android.support.annotation.AnyThread;
+import android.support.annotation.NonNull;
+
+import org.mozilla.gecko.EventDispatcher;
+
+/** This class exists so that Fennec can have access to getEventDispatcher without giving access to
+ * it to any other GV consumer. */
+public class GeckoViewBridge {
+    @AnyThread
+    public static @NonNull EventDispatcher getEventDispatcher(GeckoView geckoView) {
+        return geckoView.getEventDispatcher();
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ActionBarTextSelection.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.util.ResourceDrawableUtils;
 import org.mozilla.gecko.text.TextSelection;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.geckoview.GeckoView;
+import org.mozilla.geckoview.GeckoViewBridge;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v7.view.ActionMode;
 import android.view.Menu;
@@ -60,31 +61,31 @@ public class ActionBarTextSelection impl
     public ActionBarTextSelection(@NonNull final GeckoView geckoView,
                                   @Nullable final ActionModePresenter presenter) {
         this.geckoView = geckoView;
         this.presenter = presenter;
     }
 
     @Override
     public void create() {
-        geckoView.getEventDispatcher().registerUiThreadListener(this,
+        GeckoViewBridge.getEventDispatcher(geckoView).registerUiThreadListener(this,
                 "TextSelection:ActionbarInit",
                 "TextSelection:ActionbarStatus",
                 "TextSelection:ActionbarUninit");
     }
 
     @Override
     public boolean dismiss() {
         // We do not call endActionMode() here because this is already handled by the activity.
         return false;
     }
 
     @Override
     public void destroy() {
-        geckoView.getEventDispatcher().unregisterUiThreadListener(this,
+        GeckoViewBridge.getEventDispatcher(geckoView).unregisterUiThreadListener(this,
                 "TextSelection:ActionbarInit",
                 "TextSelection:ActionbarStatus",
                 "TextSelection:ActionbarUninit");
     }
 
     @Override
     public void handleMessage(final String event, final GeckoBundle message,
                               final EventCallback callback) {
@@ -195,24 +196,24 @@ public class ActionBarTextSelection impl
             return true;
         }
 
         @Override
         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
             final GeckoBundle obj = mItems[item.getItemId()];
             final GeckoBundle data = new GeckoBundle(1);
             data.putString("id", obj.getString("id", ""));
-            geckoView.getEventDispatcher().dispatch("TextSelection:Action", data);
+            GeckoViewBridge.getEventDispatcher(geckoView).dispatch("TextSelection:Action", data);
             return true;
         }
 
         // Called when the user exits the action mode
         @Override
         public void onDestroyActionMode(ActionMode mode) {
             mActionMode = null;
             mCallback = null;
 
             final GeckoBundle data = new GeckoBundle(1);
             data.putInt("selectionID", selectionID);
-            geckoView.getEventDispatcher().dispatch("TextSelection:End", data);
+            GeckoViewBridge.getEventDispatcher(geckoView).dispatch("TextSelection:End", data);
         }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
@@ -9,16 +9,17 @@ import org.mozilla.gecko.animation.ViewH
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.widget.SwipeDismissListViewTouchListener;
 import org.mozilla.gecko.widget.SwipeDismissListViewTouchListener.OnDismissCallback;
 import org.mozilla.geckoview.GeckoView;
+import org.mozilla.geckoview.GeckoViewBridge;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -90,24 +91,24 @@ public class FormAssistPopup extends Rel
         mAnimation = AnimationUtils.loadAnimation(context, R.anim.grow_fade_in);
         mAnimation.setDuration(75);
 
         setFocusable(false);
     }
 
     public void create(final GeckoView view) {
         mGeckoView = view;
-        mGeckoView.getEventDispatcher().registerUiThreadListener(this,
+        GeckoViewBridge.getEventDispatcher(mGeckoView).registerUiThreadListener(this,
             "FormAssist:AutoCompleteResult",
             "FormAssist:ValidationMessage",
             "FormAssist:Hide");
     }
 
     public void destroy() {
-        mGeckoView.getEventDispatcher().unregisterUiThreadListener(this,
+        GeckoViewBridge.getEventDispatcher(mGeckoView).unregisterUiThreadListener(this,
             "FormAssist:AutoCompleteResult",
             "FormAssist:ValidationMessage",
             "FormAssist:Hide");
         mGeckoView = null;
     }
 
     @Override // BundleEventListener
     public void handleMessage(final String event, final GeckoBundle message,
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -90,16 +90,17 @@ import android.widget.ListView;
 import android.widget.RelativeLayout;
 import android.widget.SimpleAdapter;
 import android.widget.TextView;
 import android.widget.Toast;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
+import org.mozilla.geckoview.GeckoViewBridge;
 import org.mozilla.mozstumbler.service.mainthread.SafeReceiver;
 
 import java.io.File;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Locale;
@@ -1748,17 +1749,17 @@ public abstract class GeckoApp extends G
     }
 
     @RobocopTarget
     public @NonNull EventDispatcher getAppEventDispatcher() {
         if (mLayerView == null) {
             throw new IllegalStateException("Must not call getAppEventDispatcher() until after onCreate()");
         }
 
-        return mLayerView.getEventDispatcher();
+        return GeckoViewBridge.getEventDispatcher(mLayerView);
     }
 
     protected static GeckoProfile getProfile() {
         return GeckoThread.getActiveProfile();
     }
 
     /**
      * Check whether we've crashed during the last browsing session.
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -57,16 +57,17 @@ import org.mozilla.gecko.util.PackageUti
 import org.mozilla.gecko.webapps.WebApps;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
 import org.mozilla.geckoview.AllowOrDeny;
 import org.mozilla.geckoview.GeckoResult;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
+import org.mozilla.geckoview.GeckoViewBridge;
 import org.mozilla.geckoview.WebRequestError;
 
 import java.util.List;
 
 public class CustomTabsActivity extends AppCompatActivity
                                 implements ActionModePresenter,
                                            GeckoMenu.Callback,
                                            GeckoSession.ContentDelegate,
@@ -129,18 +130,19 @@ public class CustomTabsActivity extends 
                 .build();
         mGeckoSession = new GeckoSession(settings);
         mGeckoSession.setNavigationDelegate(this);
         mGeckoSession.setProgressDelegate(this);
         mGeckoSession.setContentDelegate(this);
 
         mGeckoView.setSession(mGeckoSession, GeckoApplication.ensureRuntime(this));
 
-        mPromptService = new PromptService(this, mGeckoView.getEventDispatcher());
-        mDoorHangerPopup = new DoorHangerPopup(this, mGeckoView.getEventDispatcher());
+        mPromptService = new PromptService(this, GeckoViewBridge.getEventDispatcher(mGeckoView));
+        mDoorHangerPopup = new DoorHangerPopup(this,
+                                               GeckoViewBridge.getEventDispatcher(mGeckoView));
 
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.form_assist_popup);
         mFormAssistPopup.create(mGeckoView);
 
         mTextSelection = TextSelection.Factory.create(mGeckoView, this);
         mTextSelection.create();
 
         if (intent != null && !TextUtils.isEmpty(intent.getDataString())) {
--- a/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java
+++ b/mobile/android/base/java/org/mozilla/gecko/text/FloatingActionModeCallback.java
@@ -9,16 +9,17 @@ import android.graphics.Rect;
 import android.os.Build;
 import android.view.ActionMode;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
 
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.util.GeckoBundle;
+import org.mozilla.geckoview.GeckoViewBridge;
 
 import java.util.List;
 
 @TargetApi(Build.VERSION_CODES.M)
 public class FloatingActionModeCallback extends ActionMode.Callback2 {
     private FloatingToolbarTextSelection textSelection;
     private List<TextAction> actions;
 
@@ -49,17 +50,17 @@ public class FloatingActionModeCallback 
     }
 
     @Override
     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
         final TextAction action = actions.get(item.getItemId());
 
         final GeckoBundle data = new GeckoBundle(1);
         data.putString("id", action.getId());
-        textSelection.geckoView.getEventDispatcher().dispatch("TextSelection:Action", data);
+        GeckoViewBridge.getEventDispatcher(textSelection.geckoView).dispatch("TextSelection:Action", data);
 
         return true;
     }
 
     @Override
     public void onDestroyActionMode(ActionMode mode) {}
 
     @Override
--- a/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
+++ b/mobile/android/base/java/org/mozilla/gecko/text/FloatingToolbarTextSelection.java
@@ -18,16 +18,17 @@ import org.mozilla.gecko.EventDispatcher
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.geckoview.GeckoView;
+import org.mozilla.geckoview.GeckoViewBridge;
 
 import java.util.List;
 
 /**
  * Floating toolbar for text selection actions. Only on Android 6+.
  */
 @TargetApi(Build.VERSION_CODES.M)
 public class FloatingToolbarTextSelection implements TextSelection, BundleEventListener {
@@ -66,39 +67,39 @@ public class FloatingToolbarTextSelectio
 
     private void endTextSelection() {
         if (selectionID == 0) {
             return;
         }
 
         final GeckoBundle data = new GeckoBundle(1);
         data.putInt("selectionID", selectionID);
-        geckoView.getEventDispatcher().dispatch("TextSelection:End", data);
+        GeckoViewBridge.getEventDispatcher(geckoView).dispatch("TextSelection:End", data);
     }
 
     @Override
     public void create() {
         registerForEvents();
     }
 
     @Override
     public void destroy() {
         unregisterFromEvents();
     }
 
     private void registerForEvents() {
-        geckoView.getEventDispatcher().registerUiThreadListener(this,
+        GeckoViewBridge.getEventDispatcher(geckoView).registerUiThreadListener(this,
                 "TextSelection:ActionbarInit",
                 "TextSelection:ActionbarStatus",
                 "TextSelection:ActionbarUninit",
                 "TextSelection:Visibility");
     }
 
     private void unregisterFromEvents() {
-        geckoView.getEventDispatcher().unregisterUiThreadListener(this,
+        GeckoViewBridge.getEventDispatcher(geckoView).unregisterUiThreadListener(this,
                 "TextSelection:ActionbarInit",
                 "TextSelection:ActionbarStatus",
                 "TextSelection:ActionbarUninit",
                 "TextSelection:Visibility");
     }
 
     @Override // BundleEventListener
     public void handleMessage(final String event, final GeckoBundle message,
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -39,16 +39,17 @@ import org.mozilla.gecko.text.TextSelect
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.geckoview.AllowOrDeny;
 import org.mozilla.geckoview.GeckoResult;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
+import org.mozilla.geckoview.GeckoViewBridge;
 import org.mozilla.geckoview.WebRequestError;
 
 public class WebAppActivity extends AppCompatActivity
                             implements ActionModePresenter,
                                        GeckoSession.ContentDelegate,
                                        GeckoSession.NavigationDelegate {
     private static final String LOGTAG = "WebAppActivity";
 
@@ -153,18 +154,19 @@ public class WebAppActivity extends AppC
                     if (security.isException) {
                         message = R.string.identity_connection_insecure;
                         fallbackToFennec(getString(message));
                     }
                 }
             }
         });
 
-        mPromptService = new PromptService(this, mGeckoView.getEventDispatcher());
-        mDoorHangerPopup = new DoorHangerPopup(this, mGeckoView.getEventDispatcher());
+        mPromptService = new PromptService(this, GeckoViewBridge.getEventDispatcher(mGeckoView));
+        mDoorHangerPopup = new DoorHangerPopup(this,
+                                               GeckoViewBridge.getEventDispatcher(mGeckoView));
 
         mFormAssistPopup = (FormAssistPopup) findViewById(R.id.pwa_form_assist_popup);
         mFormAssistPopup.create(mGeckoView);
 
         mTextSelection = TextSelection.Factory.create(mGeckoView, this);
         mTextSelection.create();
 
         try {
--- a/mobile/android/geckoview/api.txt
+++ b/mobile/android/geckoview/api.txt
@@ -14,25 +14,25 @@ package org.mozilla.geckoview {
     method public void enableExternalActions(boolean);
     method public void onGetContentRect(@android.support.annotation.Nullable android.view.ActionMode, @android.support.annotation.Nullable android.view.View, @android.support.annotation.NonNull android.graphics.Rect);
     method protected void clearSelection();
     method @android.support.annotation.NonNull protected java.lang.String[] getAllActions();
     method protected boolean isActionAvailable(@android.support.annotation.NonNull java.lang.String);
     method protected boolean performAction(@android.support.annotation.NonNull java.lang.String, @android.support.annotation.NonNull android.view.MenuItem);
     method protected void prepareAction(@android.support.annotation.NonNull java.lang.String, @android.support.annotation.NonNull android.view.MenuItem);
     field protected static final java.lang.String ACTION_PROCESS_TEXT = "android.intent.action.PROCESS_TEXT";
-    field protected android.view.ActionMode mActionMode;
-    field protected java.util.List<java.lang.String> mActions;
-    field protected final android.app.Activity mActivity;
+    field @android.support.annotation.Nullable protected android.view.ActionMode mActionMode;
+    field @android.support.annotation.Nullable protected java.util.List<java.lang.String> mActions;
+    field @android.support.annotation.NonNull protected final android.app.Activity mActivity;
     field protected boolean mRepopulatedMenu;
-    field protected org.mozilla.geckoview.GeckoResponse<java.lang.String> mResponse;
-    field protected org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.Selection mSelection;
-    field protected org.mozilla.geckoview.GeckoSession mSession;
-    field protected final android.graphics.Matrix mTempMatrix;
-    field protected final android.graphics.RectF mTempRect;
+    field @android.support.annotation.Nullable protected org.mozilla.geckoview.GeckoResponse<java.lang.String> mResponse;
+    field @android.support.annotation.Nullable protected org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.Selection mSelection;
+    field @android.support.annotation.Nullable protected org.mozilla.geckoview.GeckoSession mSession;
+    field @android.support.annotation.NonNull protected final android.graphics.Matrix mTempMatrix;
+    field @android.support.annotation.NonNull protected final android.graphics.RectF mTempRect;
     field protected final boolean mUseFloatingToolbar;
   }
 
   @android.support.annotation.UiThread public final class CompositorController {
     method public void addDrawCallback(@android.support.annotation.NonNull java.lang.Runnable);
     method public int getClearColor();
     method @android.support.annotation.Nullable public java.lang.Runnable getFirstPaintCallback();
     method public void removeDrawCallback(@android.support.annotation.NonNull java.lang.Runnable);
@@ -87,22 +87,22 @@ package org.mozilla.geckoview {
     method @android.support.annotation.NonNull public org.mozilla.geckoview.ContentBlocking.Settings setCookieLifetime(int);
   }
 
   @android.support.annotation.AnyThread public static class ContentBlocking.Settings.Builder extends org.mozilla.geckoview.RuntimeSettings.Builder<Settings extends org.mozilla.geckoview.RuntimeSettings> {
     ctor public Builder();
     method @android.support.annotation.NonNull public org.mozilla.geckoview.ContentBlocking.Settings.Builder categories(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.ContentBlocking.Settings.Builder cookieBehavior(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.ContentBlocking.Settings.Builder cookieLifetime(int);
-    method @android.support.annotation.NonNull protected org.mozilla.geckoview.ContentBlocking.Settings newSettings(org.mozilla.geckoview.ContentBlocking.Settings);
+    method @android.support.annotation.NonNull protected org.mozilla.geckoview.ContentBlocking.Settings newSettings(@android.support.annotation.Nullable org.mozilla.geckoview.ContentBlocking.Settings);
   }
 
   public class CrashReporter {
     ctor public CrashReporter();
-    method @android.support.annotation.AnyThread public static org.mozilla.geckoview.GeckoResult<java.lang.String> sendCrashReport(@android.support.annotation.NonNull android.content.Context, @android.support.annotation.NonNull android.content.Intent, @android.support.annotation.NonNull java.lang.String);
+    method @android.support.annotation.AnyThread @android.support.annotation.NonNull public static org.mozilla.geckoview.GeckoResult<java.lang.String> sendCrashReport(@android.support.annotation.NonNull android.content.Context, @android.support.annotation.NonNull android.content.Intent, @android.support.annotation.NonNull java.lang.String);
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull public static org.mozilla.geckoview.GeckoResult<java.lang.String> sendCrashReport(@android.support.annotation.NonNull android.content.Context, @android.support.annotation.NonNull android.os.Bundle, @android.support.annotation.NonNull java.lang.String);
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull public static org.mozilla.geckoview.GeckoResult<java.lang.String> sendCrashReport(@android.support.annotation.NonNull android.content.Context, @android.support.annotation.NonNull java.io.File, @android.support.annotation.NonNull java.io.File, boolean, @android.support.annotation.NonNull java.lang.String);
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull public static org.mozilla.geckoview.GeckoResult<java.lang.String> sendCrashReport(@android.support.annotation.NonNull android.content.Context, @android.support.annotation.NonNull java.io.File, @android.support.annotation.NonNull java.util.Map<java.lang.String,java.lang.String>, boolean, @android.support.annotation.NonNull java.lang.String);
   }
 
   @android.support.annotation.UiThread public final class DynamicToolbarAnimator {
     method @android.support.annotation.Nullable public org.mozilla.geckoview.DynamicToolbarAnimator.ToolbarChromeProxy getToolbarChromeProxy();
     method public void hideToolbar(boolean);
@@ -249,52 +249,51 @@ package org.mozilla.geckoview {
 
   @android.support.annotation.AnyThread public static final class GeckoRuntimeSettings.Builder extends org.mozilla.geckoview.RuntimeSettings.Builder<Settings extends org.mozilla.geckoview.RuntimeSettings> {
     ctor public Builder();
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder arguments(@android.support.annotation.NonNull java.lang.String[]);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder automaticFontSizeAdjustment(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder autoplayDefault(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder consoleOutput(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder contentBlocking(@android.support.annotation.NonNull org.mozilla.geckoview.ContentBlocking.Settings);
-    method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder crashHandler(java.lang.Class<?>);
+    method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder crashHandler(@android.support.annotation.Nullable java.lang.Class<?>);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder displayDensityOverride(float);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder displayDpiOverride(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder extras(@android.support.annotation.NonNull android.os.Bundle);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder fontInflation(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder fontSizeFactor(float);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder javaScriptEnabled(boolean);
-    method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder locales(java.lang.String[]);
+    method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder locales(@android.support.annotation.Nullable java.lang.String[]);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder pauseForDebugger(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder preferredColorScheme(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder remoteDebuggingEnabled(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder screenSizeOverride(int, int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder useContentProcessHint(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder useMaxScreenDepth(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoRuntimeSettings.Builder webFontsEnabled(boolean);
-    method @android.support.annotation.NonNull protected org.mozilla.geckoview.GeckoRuntimeSettings newSettings(org.mozilla.geckoview.GeckoRuntimeSettings);
+    method @android.support.annotation.NonNull protected org.mozilla.geckoview.GeckoRuntimeSettings newSettings(@android.support.annotation.Nullable org.mozilla.geckoview.GeckoRuntimeSettings);
   }
 
   public class GeckoSession implements android.os.Parcelable {
     ctor public GeckoSession();
     ctor public GeckoSession(@android.support.annotation.Nullable org.mozilla.geckoview.GeckoSessionSettings);
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoDisplay acquireDisplay();
     method @android.support.annotation.UiThread public void close();
-    method @android.support.annotation.AnyThread public static java.lang.String createDataUri(@android.support.annotation.NonNull byte[], @android.support.annotation.Nullable java.lang.String);
-    method @android.support.annotation.AnyThread public static java.lang.String createDataUri(@android.support.annotation.NonNull java.lang.String, @android.support.annotation.Nullable java.lang.String);
+    method @android.support.annotation.AnyThread @android.support.annotation.NonNull public static java.lang.String createDataUri(@android.support.annotation.NonNull byte[], @android.support.annotation.Nullable java.lang.String);
+    method @android.support.annotation.AnyThread @android.support.annotation.NonNull public static java.lang.String createDataUri(@android.support.annotation.NonNull java.lang.String, @android.support.annotation.Nullable java.lang.String);
     method @android.support.annotation.AnyThread public void exitFullScreen();
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.SessionAccessibility getAccessibility();
     method @android.support.annotation.UiThread public void getClientBounds(@android.support.annotation.NonNull android.graphics.RectF);
     method @android.support.annotation.UiThread public void getClientToScreenMatrix(@android.support.annotation.NonNull android.graphics.Matrix);
     method @android.support.annotation.UiThread public void getClientToSurfaceMatrix(@android.support.annotation.NonNull android.graphics.Matrix);
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.CompositorController getCompositorController();
     method @android.support.annotation.AnyThread @android.support.annotation.Nullable public org.mozilla.geckoview.ContentBlocking.Delegate getContentBlockingDelegate();
     method @android.support.annotation.UiThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession.ContentDelegate getContentDelegate();
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull public static java.lang.String getDefaultUserAgent();
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.DynamicToolbarAnimator getDynamicToolbarAnimator();
-    method @android.support.annotation.AnyThread @android.support.annotation.NonNull public org.mozilla.gecko.EventDispatcher getEventDispatcher();
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull public org.mozilla.geckoview.SessionFinder getFinder();
     method @android.support.annotation.AnyThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession.HistoryDelegate getHistoryDelegate();
     method @android.support.annotation.AnyThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession.MediaDelegate getMediaDelegate();
     method @android.support.annotation.UiThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession.NavigationDelegate getNavigationDelegate();
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.OverscrollEdgeEffect getOverscrollEdgeEffect();
     method @android.support.annotation.UiThread public void getPageToScreenMatrix(@android.support.annotation.NonNull android.graphics.Matrix);
     method @android.support.annotation.UiThread public void getPageToSurfaceMatrix(@android.support.annotation.NonNull android.graphics.Matrix);
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.PanZoomController getPanZoomController();
@@ -346,17 +345,17 @@ package org.mozilla.geckoview {
     field public static final int FINDER_FIND_MATCH_CASE = 2;
     field public static final int FINDER_FIND_WHOLE_WORD = 4;
     field public static final int LOAD_FLAGS_ALLOW_POPUPS = 8;
     field public static final int LOAD_FLAGS_BYPASS_CACHE = 1;
     field public static final int LOAD_FLAGS_BYPASS_CLASSIFIER = 16;
     field public static final int LOAD_FLAGS_BYPASS_PROXY = 2;
     field public static final int LOAD_FLAGS_EXTERNAL = 4;
     field public static final int LOAD_FLAGS_NONE = 0;
-    field protected org.mozilla.geckoview.GeckoSession.Window mWindow;
+    field @android.support.annotation.Nullable protected org.mozilla.geckoview.GeckoSession.Window mWindow;
   }
 
   public static interface GeckoSession.ContentDelegate {
     method @android.support.annotation.UiThread default public void onCloseRequest(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
     method @android.support.annotation.UiThread default public void onContextMenu(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, int, int, @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.ContentDelegate.ContextElement);
     method @android.support.annotation.UiThread default public void onCrash(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
     method @android.support.annotation.UiThread default public void onExternalResponse(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.WebResponseInfo);
     method @android.support.annotation.UiThread default public void onFirstComposite(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
@@ -461,19 +460,19 @@ package org.mozilla.geckoview {
     field public static final int SOURCE_BROWSER = 4;
     field public static final int SOURCE_CAMERA = 0;
     field public static final int SOURCE_MICROPHONE = 5;
     field public static final int SOURCE_OTHER = 7;
     field public static final int SOURCE_SCREEN = 1;
     field public static final int SOURCE_WINDOW = 3;
     field public static final int TYPE_AUDIO = 1;
     field public static final int TYPE_VIDEO = 0;
-    field public final java.lang.String id;
-    field public final java.lang.String name;
-    field public final java.lang.String rawId;
+    field @android.support.annotation.NonNull public final java.lang.String id;
+    field @android.support.annotation.Nullable public final java.lang.String name;
+    field @android.support.annotation.NonNull public final java.lang.String rawId;
     field public final int source;
     field public final int type;
   }
 
   public static interface GeckoSession.PermissionDelegate.Permission implements java.lang.annotation.Annotation {
   }
 
   public static interface GeckoSession.ProgressDelegate {
@@ -487,27 +486,27 @@ package org.mozilla.geckoview {
   public static class GeckoSession.ProgressDelegate.SecurityInformation {
     ctor protected SecurityInformation();
     field public static final int CONTENT_BLOCKED = 1;
     field public static final int CONTENT_LOADED = 2;
     field public static final int CONTENT_UNKNOWN = 0;
     field public static final int SECURITY_MODE_IDENTIFIED = 1;
     field public static final int SECURITY_MODE_UNKNOWN = 0;
     field public static final int SECURITY_MODE_VERIFIED = 2;
-    field public final java.lang.String host;
+    field @android.support.annotation.NonNull public final java.lang.String host;
     field public final boolean isException;
     field public final boolean isSecure;
-    field public final java.lang.String issuerCommonName;
-    field public final java.lang.String issuerOrganization;
+    field @android.support.annotation.NonNull public final java.lang.String issuerCommonName;
+    field @android.support.annotation.NonNull public final java.lang.String issuerOrganization;
     field public final int mixedModeActive;
     field public final int mixedModePassive;
-    field public final java.lang.String organization;
-    field public final java.lang.String origin;
+    field @android.support.annotation.NonNull public final java.lang.String organization;
+    field @android.support.annotation.Nullable public final java.lang.String origin;
     field public final int securityMode;
-    field public final java.lang.String subjectName;
+    field @android.support.annotation.NonNull public final java.lang.String subjectName;
   }
 
   public static interface GeckoSession.PromptDelegate {
     method @android.support.annotation.UiThread default public void onAlert(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable java.lang.String, @android.support.annotation.Nullable java.lang.String, @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.PromptDelegate.AlertCallback);
     method @android.support.annotation.UiThread default public void onAuthPrompt(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable java.lang.String, @android.support.annotation.Nullable java.lang.String, @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthOptions, @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.PromptDelegate.AuthCallback);
     method @android.support.annotation.UiThread default public void onButtonPrompt(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable java.lang.String, @android.support.annotation.Nullable java.lang.String, @android.support.annotation.Nullable java.lang.String[], @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.PromptDelegate.ButtonCallback);
     method @android.support.annotation.UiThread default public void onChoicePrompt(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable java.lang.String, @android.support.annotation.Nullable java.lang.String, int, @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice[], @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.PromptDelegate.ChoiceCallback);
     method @android.support.annotation.UiThread default public void onColorPrompt(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable java.lang.String, @android.support.annotation.Nullable java.lang.String, @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.PromptDelegate.TextCallback);
@@ -547,35 +546,35 @@ package org.mozilla.geckoview {
     field public static final int AUTH_FLAG_ONLY_PASSWORD = 8;
     field public static final int AUTH_FLAG_PREVIOUS_FAILED = 16;
     field public static final int AUTH_FLAG_PROXY = 2;
     field public static final int AUTH_LEVEL_NONE = 0;
     field public static final int AUTH_LEVEL_PW_ENCRYPTED = 1;
     field public static final int AUTH_LEVEL_SECURE = 2;
     field public int flags;
     field public int level;
-    field public java.lang.String password;
-    field public java.lang.String uri;
-    field public java.lang.String username;
+    field @android.support.annotation.Nullable public java.lang.String password;
+    field @android.support.annotation.Nullable public java.lang.String uri;
+    field @android.support.annotation.Nullable public java.lang.String username;
   }
 
   public static interface GeckoSession.PromptDelegate.ButtonCallback implements org.mozilla.geckoview.GeckoSession.PromptDelegate.AlertCallback {
     method @android.support.annotation.UiThread default public void confirm(int);
   }
 
   public static class GeckoSession.PromptDelegate.Choice {
     ctor protected Choice();
     field public static final int CHOICE_TYPE_MENU = 1;
     field public static final int CHOICE_TYPE_MULTIPLE = 3;
     field public static final int CHOICE_TYPE_SINGLE = 2;
     field public final boolean disabled;
-    field public final java.lang.String icon;
-    field public final java.lang.String id;
-    field public final org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice[] items;
-    field public final java.lang.String label;
+    field @android.support.annotation.Nullable public final java.lang.String icon;
+    field @android.support.annotation.NonNull public final java.lang.String id;
+    field @android.support.annotation.Nullable public final org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice[] items;
+    field @android.support.annotation.NonNull public final java.lang.String label;
     field public final boolean selected;
     field public final boolean separator;
   }
 
   public static interface GeckoSession.PromptDelegate.ChoiceCallback implements org.mozilla.geckoview.GeckoSession.PromptDelegate.AlertCallback {
     method @android.support.annotation.UiThread default public void confirm(@android.support.annotation.Nullable java.lang.String);
     method @android.support.annotation.UiThread default public void confirm(@android.support.annotation.NonNull java.lang.String[]);
     method @android.support.annotation.UiThread default public void confirm(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.PromptDelegate.Choice);
@@ -598,17 +597,17 @@ package org.mozilla.geckoview {
   }
 
   public static interface GeckoSession.ScrollDelegate {
     method @android.support.annotation.UiThread default public void onScrollChanged(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, int, int);
   }
 
   public static interface GeckoSession.SelectionActionDelegate {
     method @android.support.annotation.UiThread default public void onHideAction(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, int);
-    method @android.support.annotation.UiThread default public void onShowActionRequest(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.Selection, java.lang.String[], @android.support.annotation.NonNull org.mozilla.geckoview.GeckoResponse<java.lang.String>);
+    method @android.support.annotation.UiThread default public void onShowActionRequest(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.SelectionActionDelegate.Selection, @android.support.annotation.NonNull java.lang.String[], @android.support.annotation.NonNull org.mozilla.geckoview.GeckoResponse<java.lang.String>);
     field public static final java.lang.String ACTION_COLLAPSE_TO_END = "org.mozilla.geckoview.COLLAPSE_TO_END";
     field public static final java.lang.String ACTION_COLLAPSE_TO_START = "org.mozilla.geckoview.COLLAPSE_TO_START";
     field public static final java.lang.String ACTION_COPY = "org.mozilla.geckoview.COPY";
     field public static final java.lang.String ACTION_CUT = "org.mozilla.geckoview.CUT";
     field public static final java.lang.String ACTION_DELETE = "org.mozilla.geckoview.DELETE";
     field public static final java.lang.String ACTION_HIDE = "org.mozilla.geckoview.HIDE";
     field public static final java.lang.String ACTION_PASTE = "org.mozilla.geckoview.PASTE";
     field public static final java.lang.String ACTION_SELECT_ALL = "org.mozilla.geckoview.SELECT_ALL";
@@ -628,26 +627,25 @@ package org.mozilla.geckoview {
   public static interface GeckoSession.SelectionActionDelegate.Flag implements java.lang.annotation.Annotation {
   }
 
   public static interface GeckoSession.SelectionActionDelegate.HideReason implements java.lang.annotation.Annotation {
   }
 
   public static class GeckoSession.SelectionActionDelegate.Selection {
     ctor protected Selection();
-    field public final android.graphics.RectF clientRect;
+    field @android.support.annotation.Nullable public final android.graphics.RectF clientRect;
     field public final int flags;
-    field public final java.lang.String text;
+    field @android.support.annotation.NonNull public final java.lang.String text;
   }
 
   @android.support.annotation.AnyThread public static class GeckoSession.SessionState implements android.os.Parcelable {
     ctor public SessionState(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession.SessionState);
     method @android.support.annotation.NonNull public static org.mozilla.geckoview.GeckoSession.SessionState fromString(@android.support.annotation.NonNull java.lang.String);
     method public void readFromParcel(@android.support.annotation.NonNull android.os.Parcel);
-    method public void updateSessionState(@android.support.annotation.NonNull org.mozilla.gecko.util.GeckoBundle);
     field public static final android.os.Parcelable.Creator<org.mozilla.geckoview.GeckoSession.SessionState> CREATOR;
   }
 
   public static interface GeckoSession.TextInputDelegate {
     method @android.support.annotation.UiThread default public void hideSoftInput(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
     method @android.support.annotation.UiThread default public void notifyAutoFill(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, int, int);
     method @android.support.annotation.UiThread default public void restartInput(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, int);
     method @android.support.annotation.UiThread default public void showSoftInput(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
@@ -717,17 +715,17 @@ package org.mozilla.geckoview {
     field public static final int VIEWPORT_MODE_MOBILE = 0;
   }
 
   @android.support.annotation.AnyThread public static final class GeckoSessionSettings.Builder {
     ctor public Builder();
     ctor public Builder(org.mozilla.geckoview.GeckoSessionSettings);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder allowJavascript(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings build();
-    method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder chromeUri(java.lang.String);
+    method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder chromeUri(@android.support.annotation.NonNull java.lang.String);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder displayMode(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder fullAccessibilityTree(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder screenId(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder suspendMediaWhenInactive(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder useMultiprocess(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder usePrivateMode(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder useTrackingProtection(boolean);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoSessionSettings.Builder userAgentMode(int);
@@ -743,34 +741,33 @@ package org.mozilla.geckoview {
   }
 
   @android.support.annotation.UiThread public class GeckoView extends android.widget.FrameLayout {
     ctor public GeckoView(android.content.Context);
     ctor public GeckoView(android.content.Context, android.util.AttributeSet);
     method @android.support.annotation.UiThread @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoResult<android.graphics.Bitmap> capturePixels();
     method public void coverUntilFirstPaint(int);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.DynamicToolbarAnimator getDynamicToolbarAnimator();
-    method @android.support.annotation.AnyThread @android.support.annotation.NonNull public org.mozilla.gecko.EventDispatcher getEventDispatcher();
     method @android.support.annotation.NonNull public org.mozilla.geckoview.PanZoomController getPanZoomController();
     method @android.support.annotation.AnyThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession getSession();
     method @android.support.annotation.UiThread @android.support.annotation.Nullable public org.mozilla.geckoview.GeckoSession releaseSession();
     method @android.support.annotation.UiThread public void setSession(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession);
     method @android.support.annotation.UiThread public void setSession(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoSession, @android.support.annotation.Nullable org.mozilla.geckoview.GeckoRuntime);
     method public boolean shouldPinOnScreen();
-    field protected final org.mozilla.geckoview.GeckoView.Display mDisplay;
-    field protected org.mozilla.geckoview.GeckoRuntime mRuntime;
-    field protected org.mozilla.geckoview.GeckoSession mSession;
-    field protected android.view.SurfaceView mSurfaceView;
+    field @android.support.annotation.NonNull protected final org.mozilla.geckoview.GeckoView.Display mDisplay;
+    field @android.support.annotation.Nullable protected org.mozilla.geckoview.GeckoRuntime mRuntime;
+    field @android.support.annotation.Nullable protected org.mozilla.geckoview.GeckoSession mSession;
+    field @android.support.annotation.Nullable protected android.view.SurfaceView mSurfaceView;
   }
 
   @android.support.annotation.AnyThread public class GeckoWebExecutor {
     ctor public GeckoWebExecutor(@android.support.annotation.NonNull org.mozilla.geckoview.GeckoRuntime);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoResult<org.mozilla.geckoview.WebResponse> fetch(@android.support.annotation.NonNull org.mozilla.geckoview.WebRequest);
     method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoResult<org.mozilla.geckoview.WebResponse> fetch(@android.support.annotation.NonNull org.mozilla.geckoview.WebRequest, int);
-    method public org.mozilla.geckoview.GeckoResult<java.net.InetAddress[]> resolve(@android.support.annotation.NonNull java.lang.String);
+    method @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoResult<java.net.InetAddress[]> resolve(@android.support.annotation.NonNull java.lang.String);
     method public void speculativeConnect(@android.support.annotation.NonNull java.lang.String);
     field public static final int FETCH_FLAGS_ANONYMOUS = 1;
     field public static final int FETCH_FLAGS_NONE = 0;
     field public static final int FETCH_FLAGS_NO_REDIRECTS = 2;
   }
 
   public static interface GeckoWebExecutor.FetchFlags implements java.lang.annotation.Annotation {
   }
@@ -800,18 +797,18 @@ package org.mozilla.geckoview {
     field public static final int MEDIA_STATE_PAUSE = 2;
     field public static final int MEDIA_STATE_PLAY = 0;
     field public static final int MEDIA_STATE_PLAYING = 1;
     field public static final int MEDIA_STATE_SEEKED = 5;
     field public static final int MEDIA_STATE_SEEKING = 4;
     field public static final int MEDIA_STATE_STALLED = 6;
     field public static final int MEDIA_STATE_SUSPEND = 7;
     field public static final int MEDIA_STATE_WAITING = 8;
-    field protected org.mozilla.geckoview.MediaElement.Delegate mDelegate;
-    field protected final org.mozilla.geckoview.GeckoSession mSession;
+    field @android.support.annotation.Nullable protected org.mozilla.geckoview.MediaElement.Delegate mDelegate;
+    field @android.support.annotation.NonNull protected final org.mozilla.geckoview.GeckoSession mSession;
     field protected final long mVideoId;
   }
 
   public static interface MediaElement.Delegate {
     method @android.support.annotation.UiThread default public void onError(@android.support.annotation.NonNull org.mozilla.geckoview.MediaElement, int);
     method @android.support.annotation.UiThread default public void onFullscreenChange(@android.support.annotation.NonNull org.mozilla.geckoview.MediaElement, boolean);
     method @android.support.annotation.UiThread default public void onLoadProgress(@android.support.annotation.NonNull org.mozilla.geckoview.MediaElement, @android.support.annotation.NonNull org.mozilla.geckoview.MediaElement.LoadProgressInfo);
     method @android.support.annotation.UiThread default public void onMetadataChange(@android.support.annotation.NonNull org.mozilla.geckoview.MediaElement, @android.support.annotation.NonNull org.mozilla.geckoview.MediaElement.Metadata);
@@ -833,32 +830,32 @@ package org.mozilla.geckoview {
     ctor protected TimeRange(double, double);
     field public final double end;
     field public final double start;
   }
 
   public static class MediaElement.Metadata {
     ctor protected Metadata();
     field public final int audioTrackCount;
-    field public final java.lang.String currentSource;
+    field @android.support.annotation.Nullable public final java.lang.String currentSource;
     field public final double duration;
     field public final long height;
     field public final boolean isSeekable;
     field public final int videoTrackCount;
     field public final long width;
   }
 
   @android.support.annotation.UiThread public final class OverscrollEdgeEffect {
     method public void draw(@android.support.annotation.NonNull android.graphics.Canvas);
     method @android.support.annotation.Nullable public java.lang.Runnable getInvalidationCallback();
     method public void setInvalidationCallback(@android.support.annotation.Nullable java.lang.Runnable);
     method public void setTheme(@android.support.annotation.NonNull android.content.Context);
   }
 
-  @android.support.annotation.UiThread public class PanZoomController extends org.mozilla.gecko.mozglue.JNIObject {
+  @android.support.annotation.UiThread public class PanZoomController {
     ctor protected PanZoomController(org.mozilla.geckoview.GeckoSession);
     method public float getScrollFactor();
     method public boolean onMotionEvent(@android.support.annotation.NonNull android.view.MotionEvent);
     method public boolean onMouseEvent(@android.support.annotation.NonNull android.view.MotionEvent);
     method public boolean onTouchEvent(@android.support.annotation.NonNull android.view.MotionEvent);
     method @android.support.annotation.UiThread public void scrollBy(@android.support.annotation.NonNull org.mozilla.geckoview.ScreenLength, @android.support.annotation.NonNull org.mozilla.geckoview.ScreenLength);
     method @android.support.annotation.UiThread public void scrollBy(@android.support.annotation.NonNull org.mozilla.geckoview.ScreenLength, @android.support.annotation.NonNull org.mozilla.geckoview.ScreenLength, int);
     method @android.support.annotation.UiThread public void scrollTo(@android.support.annotation.NonNull org.mozilla.geckoview.ScreenLength, @android.support.annotation.NonNull org.mozilla.geckoview.ScreenLength);
@@ -881,17 +878,17 @@ package org.mozilla.geckoview {
   public abstract static class RuntimeSettings.Builder<Settings extends org.mozilla.geckoview.RuntimeSettings> {
     ctor public Builder();
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull public Settings build();
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull protected Settings getSettings();
     method @android.support.annotation.AnyThread @android.support.annotation.NonNull protected abstract Settings newSettings(@android.support.annotation.Nullable Settings);
   }
 
   public final class RuntimeTelemetry {
-    method @android.support.annotation.AnyThread @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoResult<org.mozilla.gecko.util.GeckoBundle> getSnapshots(boolean);
+    method @android.support.annotation.AnyThread @android.support.annotation.NonNull public org.mozilla.geckoview.GeckoResult<org.json.JSONObject> getSnapshots(boolean);
   }
 
   public class ScreenLength {
     method @android.support.annotation.NonNull @android.support.annotation.AnyThread public static org.mozilla.geckoview.ScreenLength bottom();
     method @android.support.annotation.NonNull @android.support.annotation.AnyThread public static org.mozilla.geckoview.ScreenLength fromPixels(double);
     method @android.support.annotation.NonNull @android.support.annotation.AnyThread public static org.mozilla.geckoview.ScreenLength fromViewportHeight(double);
     method @android.support.annotation.NonNull @android.support.annotation.AnyThread public static org.mozilla.geckoview.ScreenLength fromViewportWidth(double);
     method @android.support.annotation.AnyThread public int getType();
--- a/mobile/android/geckoview/build.gradle
+++ b/mobile/android/geckoview/build.gradle
@@ -304,17 +304,19 @@ android.libraryVariants.all { variant ->
         configFile file("checkstyle.xml")
         // TODO: cleanup and include all sources
         source = ['src/main/java/']
         include '**/*.java'
     }
 }
 
 android.libraryVariants.all { variant ->
-    configureLibraryVariantWithJNIWrappers(variant, "Generated")
+    if (variant.name == mozconfig.substs.GRADLE_ANDROID_GECKOVIEW_VARIANT_NAME) {
+        configureLibraryVariantWithJNIWrappers(variant, "Generated")
+    }
 }
 
 apply plugin: 'maven-publish'
 
 version = computeVersionNumber()
 
 publishing {
     publications {
@@ -452,9 +454,15 @@ task("generateSDKBindings", type: JavaEx
 apply plugin: 'org.mozilla.apilint'
 
 apiLint {
     // TODO: Change this to `org` after hiding org.mozilla.gecko
     packageFilter = 'org.mozilla.geckoview'
     changelogFileName = 'src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md'
     skipClassesRegex = ['^org.mozilla.geckoview.BuildConfig$']
     lintFilters = ['GV']
+    allowedPackages = [
+        'java',
+        'android',
+        'org.json',
+        'org.mozilla.geckoview',
+    ]
 }
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SelectionActionDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SelectionActionDelegateTest.kt
@@ -378,17 +378,17 @@ class SelectionActionDelegateTest : Base
         mainSession.forCallbacksDuringWait(object : Callbacks.SelectionActionDelegate {
             @AssertCalled(count = 1)
             override fun onShowActionRequest(session: GeckoSession, selection: GeckoSession.SelectionActionDelegate.Selection, actions: Array<out String>, response: GeckoResponse<String>) {
                 assertThat("Selection text should be valid",
                            selection.text, equalTo(it.initialContent))
                 assertThat("Selection flags should be valid",
                            selection.flags, equalTo(expectedFlags))
                 assertThat("Selection rect should be valid",
-                           selection.clientRect.isEmpty, equalTo(false))
+                           selection.clientRect!!.isEmpty, equalTo(false))
                 assertThat("Actions must be valid", actions,
                            arrayContainingInAnyOrder(*expectedActions))
             }
         })
     }
 
     private fun copiesText() = { it: SelectedContent ->
         sessionRule.waitUntilCalled(ClipboardManager.OnPrimaryClipChangedListener {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/BasicSelectionActionDelegate.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/BasicSelectionActionDelegate.java
@@ -53,28 +53,28 @@ public class BasicSelectionActionDelegat
 
     private static final String[] FLOATING_TOOLBAR_ACTIONS = new String[] {
         ACTION_CUT, ACTION_COPY, ACTION_PASTE, ACTION_SELECT_ALL, ACTION_PROCESS_TEXT
     };
     private static final String[] FIXED_TOOLBAR_ACTIONS = new String[] {
         ACTION_SELECT_ALL, ACTION_CUT, ACTION_COPY, ACTION_PASTE
     };
 
-    protected final Activity mActivity;
+    protected final @NonNull Activity mActivity;
     protected final boolean mUseFloatingToolbar;
-    protected final Matrix mTempMatrix = new Matrix();
-    protected final RectF mTempRect = new RectF();
+    protected final @NonNull Matrix mTempMatrix = new Matrix();
+    protected final @NonNull RectF mTempRect = new RectF();
 
     private boolean mExternalActionsEnabled;
 
-    protected ActionMode mActionMode;
-    protected GeckoSession mSession;
-    protected Selection mSelection;
-    protected List<String> mActions;
-    protected GeckoResponse<String> mResponse;
+    protected @Nullable ActionMode mActionMode;
+    protected @Nullable GeckoSession mSession;
+    protected @Nullable Selection mSelection;
+    protected @Nullable List<String> mActions;
+    protected @Nullable GeckoResponse<String> mResponse;
     protected boolean mRepopulatedMenu;
 
     @TargetApi(Build.VERSION_CODES.M)
     private class Callback2Wrapper extends ActionMode.Callback2 {
         @Override
         public boolean onCreateActionMode(final ActionMode actionMode, final Menu menu) {
             return BasicSelectionActionDelegate.this.onCreateActionMode(actionMode, menu);
         }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentBlocking.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentBlocking.java
@@ -24,17 +24,17 @@ import org.mozilla.gecko.util.GeckoBundl
 @AnyThread
 public class ContentBlocking {
     @AnyThread
     public static class Settings extends RuntimeSettings {
         @AnyThread
         public static class Builder
                 extends RuntimeSettings.Builder<Settings> {
             @Override
-            protected @NonNull Settings newSettings(final Settings settings) {
+            protected @NonNull Settings newSettings(final @Nullable Settings settings) {
                 return new Settings(settings);
             }
 
             /**
              * Set content blocking categories.
              *
              * @param cat The categories of resources that should be blocked.
              *            Use one or more of the
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CrashReporter.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CrashReporter.java
@@ -67,19 +67,19 @@ public class CrashReporter {
      * @param appName A human-readable app name.
      * @throws IOException This can be thrown if there was a networking error while sending the report.
      * @throws URISyntaxException This can be thrown if the crash server URI from the extra data was invalid.
      * @return A GeckoResult containing the crash ID as a String.
      * @see GeckoRuntimeSettings.Builder#crashHandler(Class)
      * @see GeckoRuntime#ACTION_CRASHED
      */
     @AnyThread
-    public static GeckoResult<String> sendCrashReport(@NonNull final Context context,
-                                                      @NonNull final Intent intent,
-                                                      @NonNull final String appName)
+    public static @NonNull GeckoResult<String> sendCrashReport(@NonNull final Context context,
+                                                               @NonNull final Intent intent,
+                                                               @NonNull final String appName)
             throws IOException, URISyntaxException {
         return sendCrashReport(context, intent.getExtras(), appName);
     }
 
     /**
      * Sends a crash report to the Mozilla  <a href="https://wiki.mozilla.org/Socorro">Socorro</a>
      * crash report server.
      * <br>
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntimeSettings.java
@@ -27,17 +27,17 @@ public final class GeckoRuntimeSettings 
     /**
      * Settings builder used to construct the settings object.
      */
     @AnyThread
     public static final class Builder
             extends RuntimeSettings.Builder<GeckoRuntimeSettings> {
         @Override
         protected @NonNull GeckoRuntimeSettings newSettings(
-                final GeckoRuntimeSettings settings) {
+                final @Nullable GeckoRuntimeSettings settings) {
             return new GeckoRuntimeSettings(settings);
         }
 
         /**
          * Set the content process hint flag.
          *
          * @param use If true, this will reload the content process for future use.
          *            Default is false.
@@ -275,28 +275,28 @@ public final class GeckoRuntimeSettings 
          * on how this data will be handled.
          *
          * @param handler The class for the crash handler Service.
          * @return This builder instance.
          *
          * @see <a href="https://developer.android.com/about/versions/oreo/background">Android Background Execution Limits</a>
          * @see GeckoRuntime#ACTION_CRASHED
          */
-        public @NonNull Builder crashHandler(final Class<? extends Service> handler) {
+        public @NonNull Builder crashHandler(final @Nullable Class<? extends Service> handler) {
             getSettings().mCrashHandler = handler;
             return this;
         }
 
         /**
          * Set the locale.
          *
          * @param requestedLocales List of locale codes in Gecko format ("en" or "en-US").
          * @return The builder instance.
          */
-        public @NonNull Builder locales(final String[] requestedLocales) {
+        public @NonNull Builder locales(final @Nullable String[] requestedLocales) {
             getSettings().mRequestedLocales = requestedLocales;
             return this;
         }
 
         public @NonNull Builder contentBlocking(
                 final @NonNull ContentBlocking.Settings cb) {
             getSettings().mContentBlocking = cb;
             return this;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -182,17 +182,17 @@ public class GeckoSession implements Par
             // Clear out any pending calls on the UI thread.
             GeckoSession.this.onCompositorDetached();
         }
 
         @WrapForJNI(dispatchTo = "gecko")
         @Override protected native void disposeNative();
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
-        public native void attachNPZC(PanZoomController npzc);
+        public native void attachNPZC(PanZoomController.NativeProvider npzc);
 
         @WrapForJNI(calledFrom = "ui", dispatchTo = "gecko")
         public native void onBoundsChanged(int left, int top, int width, int height);
 
         // Gecko thread pauses compositor; blocks UI thread.
         @WrapForJNI(calledFrom = "ui", dispatchTo = "current")
         public native void syncPauseCompositor();
 
@@ -1117,17 +1117,17 @@ public class GeckoSession implements Par
             if ("GeckoView:PinOnScreen".equals(event)) {
                 GeckoSession.this.setShouldPinOnScreen(message.getBoolean("pinned"));
             } else if ("GeckoView:Prompt".equals(event)) {
                 handlePromptEvent(GeckoSession.this, message, callback);
             }
         }
     }
 
-    protected Window mWindow;
+    protected @Nullable Window mWindow;
     private GeckoSessionSettings mSettings;
 
     public GeckoSession() {
         this(null);
     }
 
     public GeckoSession(final @Nullable GeckoSessionSettings settings) {
         mSettings = new GeckoSessionSettings(settings, this);
@@ -1542,29 +1542,31 @@ public class GeckoSession implements Par
 
     /**
      * Creates a data URI of of the form "data:&lt;mime type&gt;,&lt;base64-encoded data&gt;"
      * @param bytes the bytes that should be contained in the URL
      * @param mimeType optional mime type, e.g. text/plain
      * @return a URI String
      */
     @AnyThread
-    public static String createDataUri(@NonNull final byte[] bytes, @Nullable final String mimeType) {
+    public static @NonNull String createDataUri(@NonNull final byte[] bytes,
+                                                @Nullable final String mimeType) {
         return String.format("data:%s;base64,%s", mimeType != null ? mimeType : "",
                              Base64.encodeToString(bytes, Base64.NO_WRAP));
     }
 
     /**
      * Creates a data URI of of the form "data:&lt;mime type&gt;,&lt;base64-encoded data&gt;"
      * @param data the String data that should be contained in the URL
      * @param mimeType optional mime type, e.g. text/plain
      * @return a URI String
      */
     @AnyThread
-    public static String createDataUri(@NonNull final String data, @Nullable final String mimeType) {
+    public static @NonNull String createDataUri(@NonNull final String data,
+                                                @Nullable final String mimeType) {
         return String.format("data:%s,%s", mimeType != null ? mimeType : "", data);
     }
 
     /**
     * Reload the current URI.
     */
     @AnyThread
     public void reload() {
@@ -1741,17 +1743,17 @@ public class GeckoSession implements Par
         private SessionState(final @NonNull GeckoBundle state) {
             mState = new GeckoBundle(state);
         }
 
         public SessionState(final @NonNull SessionState state) {
             mState = new GeckoBundle(state.mState);
         }
 
-        public void updateSessionState(final @NonNull GeckoBundle updateData) {
+        /* package */ void updateSessionState(final @NonNull GeckoBundle updateData) {
             if (updateData == null) {
                 Log.w(LOGTAG, "Session state update has no data field.");
                 return;
             }
 
             final GeckoBundle history = updateData.getBundle("historychange");
             final GeckoBundle scroll = updateData.getBundle("scroll");
             final GeckoBundle formdata = updateData.getBundle("formdata");
@@ -2511,17 +2513,17 @@ public class GeckoSession implements Par
     }
 
     /* package */ boolean shouldPinOnScreen() {
         ThreadUtils.assertOnUiThread();
         return mShouldPinOnScreen;
     }
 
     @AnyThread
-    public @NonNull EventDispatcher getEventDispatcher() {
+    /* package */ @NonNull EventDispatcher getEventDispatcher() {
         return mEventDispatcher;
     }
 
     public interface ProgressDelegate {
         /**
          * Class representing security information for a site.
          */
         public class SecurityInformation {
@@ -2545,37 +2547,37 @@ public class GeckoSession implements Par
             public final boolean isSecure;
             /**
              * Indicates whether or not the site is a security exception.
              */
             public final boolean isException;
             /**
              * Contains the origin of the certificate.
              */
-            public final String origin;
+            public final @Nullable String origin;
             /**
              * Contains the host associated with the certificate.
              */
-            public final String host;
+            public final @NonNull String host;
             /**
              * Contains the human-readable name of the certificate subject.
              */
-            public final String organization;
+            public final @NonNull String organization;
             /**
              * Contains the full name of the certificate subject, including location.
              */
-            public final String subjectName;
+            public final @NonNull String subjectName;
             /**
              * Contains the common name of the issuing authority.
              */
-            public final String issuerCommonName;
+            public final @NonNull String issuerCommonName;
             /**
              * Contains the full/proper name of the issuing authority.
              */
-            public final String issuerOrganization;
+            public final @NonNull String issuerOrganization;
             /**
              * Indicates the security level of the site; possible values are SECURITY_MODE_UNKNOWN,
              * SECURITY_MODE_IDENTIFIED, and SECURITY_MODE_VERIFIED. SECURITY_MODE_IDENTIFIED
              * indicates domain validation only, while SECURITY_MODE_VERIFIED indicates extended validation.
              */
             public final @SecurityMode int securityMode;
             /**
              * Indicates the presence of passive mixed content; possible values are
@@ -2964,24 +2966,24 @@ public class GeckoSession implements Par
              * of the {@link #FLAG_IS_COLLAPSED FLAG_*} constants.
              */
             public final @Flag int flags;
 
             /**
              * Text content of the current selection. An empty string indicates the selection
              * is collapsed or the selection cannot be represented as plain text.
              */
-            public final String text;
+            public final @NonNull String text;
 
             /**
              * The bounds of the current selection in client coordinates. Use {@link
              * GeckoSession#getClientToScreenMatrix} to perform transformation to screen
              * coordinates.
              */
-            public final RectF clientRect;
+            public final @Nullable RectF clientRect;
 
             /* package */ Selection(final GeckoBundle bundle) {
                 flags = (bundle.getBoolean("collapsed") ?
                          SelectionActionDelegate.FLAG_IS_COLLAPSED : 0) |
                         (bundle.getBoolean("editable") ?
                          SelectionActionDelegate.FLAG_IS_EDITABLE : 0) |
                         (bundle.getBoolean("password") ?
                          SelectionActionDelegate.FLAG_IS_PASSWORD : 0);
@@ -3028,17 +3030,18 @@ public class GeckoSession implements Par
          * @param actions Array of built-in actions available; possible values
          * come from the {@link #ACTION_HIDE ACTION_*} constants.
          * @param response Callback object for performing built-in actions. For example,
          * {@code response.respond(actions[0])} performs the first action. May be used
          * multiple times to perform multiple actions at once.
          */
         @UiThread
         default void onShowActionRequest(@NonNull GeckoSession session, @NonNull Selection selection,
-                                         @Action String[] actions, @NonNull GeckoResponse<String> response) {}
+                                         @NonNull @Action String[] actions,
+                                         @NonNull GeckoResponse<String> response) {}
 
         @Retention(RetentionPolicy.SOURCE)
         @IntDef({HIDE_REASON_NO_SELECTION,
                  HIDE_REASON_INVISIBLE_SELECTION,
                  HIDE_REASON_ACTIVE_SELECTION,
                  HIDE_REASON_ACTIVE_SCROLL})
         /* package */ @interface HideReason {}
 
@@ -3444,32 +3447,32 @@ public class GeckoSession implements Par
             /**
              * An int bit-field of AUTH_FLAG_* flags.
              */
             public @AuthFlag int flags;
 
             /**
              * A string containing the URI for the auth request or null if unknown.
              */
-            public String uri;
+            public @Nullable String uri;
 
             /**
              * An int, one of AUTH_LEVEL_*, indicating level of encryption.
              */
             public @AuthLevel int level;
 
             /**
              * A string containing the initial username or null if password-only.
              */
-            public String username;
+            public @Nullable String username;
 
             /**
              * A string containing the initial password.
              */
-            public String password;
+            public @Nullable String password;
 
             /* package */ AuthOptions(final GeckoBundle options) {
                 flags = options.getInt("flags");
                 uri = options.getString("uri");
                 level = options.getInt("level");
                 username = options.getString("username");
                 password = options.getString("password");
             }
@@ -3527,32 +3530,32 @@ public class GeckoSession implements Par
              * selectable if this is true.
              */
             public final boolean disabled;
 
             /**
              * A String giving the URI of the item icon, or null if none exists
              * (only valid for menus)
              */
-            public final String icon;
+            public final @Nullable String icon;
 
             /**
              * A String giving the ID of the item or group
              */
-            public final String id;
+            public final @NonNull String id;
 
             /**
              * A Choice array of sub-items in a group, or null if not a group
              */
-            public final Choice[] items;
+            public final @Nullable Choice[] items;
 
             /**
              * A string giving the label for displaying the item or group
              */
-            public final String label;
+            public final @NonNull String label;
 
             /**
              * A boolean indicating if the item should be pre-selected
              * (pre-checked for menu items)
              */
             public final boolean selected;
 
             /**
@@ -4103,29 +4106,29 @@ public class GeckoSession implements Par
             /**
              * The media type is audio.
              */
             public static final int TYPE_AUDIO = 1;
 
             /**
              * A string giving the origin-specific source identifier.
              */
-            public final String id;
+            public final @NonNull String id;
 
             /**
              * A string giving the non-origin-specific source identifier.
              */
-            public final String rawId;
+            public final @NonNull String rawId;
 
             /**
              * A string giving the name of the video source from the system
              * (for example, "Camera 0, Facing back, Orientation 90").
              * May be empty.
              */
-            public final String name;
+            public final @Nullable String name;
 
             /**
              * An int giving the media source type.
              * Possible values for a video source are:
              * SOURCE_CAMERA, SOURCE_SCREEN, SOURCE_APPLICATION, SOURCE_WINDOW, SOURCE_BROWSER, and SOURCE_OTHER.
              * Possible values for an audio source are:
              * SOURCE_MICROPHONE, SOURCE_AUDIOCAPTURE, and SOURCE_OTHER.
              */
@@ -4441,17 +4444,17 @@ public class GeckoSession implements Par
     }
 
     /* package */ void onCompositorAttached() {
         if (DEBUG) {
             ThreadUtils.assertOnUiThread();
         }
 
         mAttachedCompositor = true;
-        mCompositor.attachNPZC(mPanZoomController);
+        mCompositor.attachNPZC(mPanZoomController.mNative);
 
         if (mSurface != null) {
             // If we have a valid surface, create the compositor now that we're attached.
             // Leave mSurface alone because we'll need it later for onCompositorReady.
             onSurfaceChanged(mSurface, mOffsetX, mOffsetY, mWidth, mHeight);
         }
 
         mCompositor.sendToolbarAnimatorMessage(IS_COMPOSITOR_CONTROLLER_OPEN);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSessionSettings.java
@@ -46,17 +46,17 @@ public final class GeckoSessionSettings 
 
         /**
          * Set the chrome URI.
          *
          * @param uri The URI to set the Chrome URI to.
          * @return This Builder instance.
 
          */
-        public @NonNull  Builder chromeUri(final String uri) {
+        public @NonNull Builder chromeUri(final @NonNull String uri) {
             mSettings.setChromeUri(uri);
             return this;
         }
 
         /**
          * Set the screen id.
          *
          * @param id The screen id.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -47,22 +47,22 @@ import android.view.inputmethod.InputCon
 import android.view.inputmethod.InputMethodManager;
 import android.widget.FrameLayout;
 
 @UiThread
 public class GeckoView extends FrameLayout {
     private static final String LOGTAG = "GeckoView";
     private static final boolean DEBUG = false;
 
-    protected final Display mDisplay = new Display();
-    protected GeckoSession mSession;
-    protected GeckoRuntime mRuntime;
+    protected final @NonNull Display mDisplay = new Display();
+    protected @Nullable GeckoSession mSession;
+    protected @Nullable GeckoRuntime mRuntime;
     private boolean mStateSaved;
 
-    protected SurfaceView mSurfaceView;
+    protected @Nullable SurfaceView mSurfaceView;
 
     private boolean mIsResettingFocus;
 
     private GeckoSession.SelectionActionDelegate mSelectionActionDelegate;
 
     private static class SavedState extends BaseSavedState {
         public final GeckoSession session;
 
@@ -405,17 +405,17 @@ public class GeckoView extends FrameLayo
     }
 
     @AnyThread
     public @Nullable GeckoSession getSession() {
         return mSession;
     }
 
     @AnyThread
-    public @NonNull EventDispatcher getEventDispatcher() {
+    /* package */ @NonNull EventDispatcher getEventDispatcher() {
         return mSession.getEventDispatcher();
     }
 
     public @NonNull PanZoomController getPanZoomController() {
         ThreadUtils.assertOnUiThread();
         return mSession.getPanZoomController();
     }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoWebExecutor.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoWebExecutor.java
@@ -138,17 +138,17 @@ public class GeckoWebExecutor {
     /**
      * Resolves the specified host name.
      *
      * @param host An Internet host name, e.g. mozilla.org.
      * @return A {@link GeckoResult} which will be fulfilled with a {@link List}
      *         of {@link InetAddress}. In case of failure, the {@link GeckoResult}
      *         will be completed exceptionally with a {@link java.net.UnknownHostException}.
      */
-    public GeckoResult<InetAddress[]> resolve(final @NonNull String host) {
+    public @NonNull GeckoResult<InetAddress[]> resolve(final @NonNull String host) {
         final GeckoResult<InetAddress[]> result = new GeckoResult<>();
 
         if (GeckoThread.isStateAtLeast(GeckoThread.State.PROFILE_READY)) {
             nativeResolve(host, result);
         } else {
             GeckoThread.queueNativeCallUntil(GeckoThread.State.PROFILE_READY, this,
                     "nativeResolve", String.class, host,
                     GeckoResult.class, result);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaElement.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/MediaElement.java
@@ -147,17 +147,17 @@ public class MediaElement {
 
     /**
      * Data class with the Metadata associated to a Media Element.
      **/
     public static class Metadata {
         /**
          * Contains the current media source URI.
          */
-        public final String currentSource;
+        public final @Nullable String currentSource;
 
         /**
          * Indicates the duration of the media in seconds.
          */
         public final double duration;
 
         /**
          * Indicates the width of the video in device pixels.
@@ -569,12 +569,12 @@ public class MediaElement {
         return bundle;
     }
 
     /* package */ MediaElement(final long videoId, final GeckoSession session) {
         mVideoId = videoId;
         mSession = session;
     }
 
-    final protected GeckoSession mSession;
+    final protected @NonNull GeckoSession mSession;
     final protected long mVideoId;
-    protected MediaElement.Delegate mDelegate;
+    protected @Nullable MediaElement.Delegate mDelegate;
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PanZoomController.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/PanZoomController.java
@@ -21,17 +21,17 @@ import android.view.MotionEvent;
 import android.view.InputDevice;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
 import java.util.ArrayList;
 
 @UiThread
-public class PanZoomController extends JNIObject {
+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 final GeckoSession mSession;
     private final Rect mTempRect = new Rect();
     private boolean mAttached;
@@ -52,32 +52,75 @@ public class PanZoomController extends J
     public static final int SCROLL_BEHAVIOR_AUTO = 1;
 
     private SynthesizedEventState mPointerState;
 
     private ArrayList<Pair<Integer, MotionEvent>> mQueuedEvents;
 
     private boolean mSynthesizedEvent = false;
 
-    @WrapForJNI(calledFrom = "ui")
-    private native boolean handleMotionEvent(
-            int action, int actionIndex, long time, int metaState,  float screenX, float screenY,
-            int pointerId[], float x[], float y[], float orientation[], float pressure[],
-            float toolMajor[], float toolMinor[]);
+    /* package */ final class NativeProvider extends JNIObject {
+        @Override // JNIObject
+        protected void disposeNative() {
+            // Disposal happens in native code.
+            throw new UnsupportedOperationException();
+        }
+
+        @WrapForJNI(calledFrom = "ui")
+        public native boolean handleMotionEvent(
+               int action, int actionIndex, long time, int metaState,  float screenX, float screenY,
+               int pointerId[], float x[], float y[], float orientation[], float pressure[],
+               float toolMajor[], float toolMinor[]);
+
+        @WrapForJNI(calledFrom = "ui")
+        private native boolean handleScrollEvent(
+                long time, int metaState,
+                float x, float y,
+                float hScroll, float vScroll);
+
+        @WrapForJNI(calledFrom = "ui")
+        private native boolean handleMouseEvent(
+                int action, long time, int metaState,
+                float x, float y, int buttons);
+
+        @WrapForJNI(stubName = "SetIsLongpressEnabled") // Called from test thread.
+        private native void nativeSetIsLongpressEnabled(boolean isLongpressEnabled);
 
-    @WrapForJNI(calledFrom = "ui")
-    private native boolean handleScrollEvent(
-            long time, int metaState,
-            float x, float y,
-            float hScroll, float vScroll);
+        @WrapForJNI(calledFrom = "ui")
+        private void synthesizeNativeTouchPoint(final int pointerId, final int eventType,
+                                                final int clientX, final int clientY,
+                                                final double pressure, final int orientation) {
+            if (pointerId == PointerInfo.RESERVED_MOUSE_POINTER_ID) {
+                throw new IllegalArgumentException("Pointer ID reserved for mouse");
+            }
+            synthesizeNativePointer(InputDevice.SOURCE_TOUCHSCREEN, pointerId,
+                    eventType, clientX, clientY, pressure, orientation);
+        }
 
-    @WrapForJNI(calledFrom = "ui")
-    private native boolean handleMouseEvent(
-            int action, long time, int metaState,
-            float x, float y, int buttons);
+        @WrapForJNI(calledFrom = "ui")
+        private void synthesizeNativeMouseEvent(final int eventType, final int clientX,
+                                                final int clientY) {
+            synthesizeNativePointer(InputDevice.SOURCE_MOUSE,
+                    PointerInfo.RESERVED_MOUSE_POINTER_ID,
+                    eventType, clientX, clientY, 0, 0);
+        }
+
+        @WrapForJNI(calledFrom = "ui")
+        private void setAttached(final boolean attached) {
+            if (attached) {
+                mAttached = true;
+                flushEventQueue();
+            } else if (mAttached) {
+                mAttached = false;
+                enableEventQueue();
+            }
+        }
+    }
+
+    /* package */ final NativeProvider mNative = new NativeProvider();
 
     private boolean handleMotionEvent(final MotionEvent event) {
         if (!mAttached) {
             mQueuedEvents.add(new Pair<>(EVENT_SOURCE_MOTION, event));
             return false;
         }
 
         final int action = event.getActionMasked();
@@ -119,19 +162,19 @@ public class PanZoomController extends J
 
         // Take this opportunity to update screen origin of session. This gets
         // dispatched to the gecko thread, so we also pass the new screen x/y directly to apz.
         // If this is a synthesized touch, the screen offset is bogus so ignore it.
         if (!mSynthesizedEvent) {
             mSession.onScreenOriginChanged((int)screenX, (int)screenY);
         }
 
-        return handleMotionEvent(action, event.getActionIndex(), event.getEventTime(),
-                event.getMetaState(), screenX, screenY, pointerId, x, y, orientation, pressure,
-                toolMajor, toolMinor);
+        return mNative.handleMotionEvent(action, event.getActionIndex(), event.getEventTime(),
+                                         event.getMetaState(), screenX, screenY, pointerId, x, y,
+                                         orientation, pressure, toolMajor, toolMinor);
     }
 
     private boolean handleScrollEvent(final MotionEvent event) {
         if (!mAttached) {
             mQueuedEvents.add(new Pair<>(EVENT_SOURCE_SCROLL, event));
             return false;
         }
 
@@ -149,18 +192,18 @@ public class PanZoomController extends J
         final float x = coords.x - mTempRect.left;
         final float y = coords.y - mTempRect.top;
 
         final float hScroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL) *
                               mPointerScrollFactor;
         final float vScroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL) *
                               mPointerScrollFactor;
 
-        return handleScrollEvent(event.getEventTime(), event.getMetaState(), x, y,
-                                 hScroll, vScroll);
+        return mNative.handleScrollEvent(event.getEventTime(), event.getMetaState(), x, y,
+                                         hScroll, vScroll);
     }
 
     private boolean handleMouseEvent(final MotionEvent event) {
         if (!mAttached) {
             mQueuedEvents.add(new Pair<>(EVENT_SOURCE_MOUSE, event));
             return false;
         }
 
@@ -173,18 +216,18 @@ public class PanZoomController extends J
         final MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
         event.getPointerCoords(0, coords);
 
         // Translate surface origin to client origin for mouse events.
         mSession.getSurfaceBounds(mTempRect);
         final float x = coords.x - mTempRect.left;
         final float y = coords.y - mTempRect.top;
 
-        return handleMouseEvent(event.getActionMasked(), event.getEventTime(),
-                                event.getMetaState(), x, y, event.getButtonState());
+        return mNative.handleMouseEvent(event.getActionMasked(), event.getEventTime(),
+                                        event.getMetaState(), x, y, event.getButtonState());
     }
 
     protected PanZoomController(final GeckoSession session) {
         mSession = session;
         enableEventQueue();
     }
 
     /**
@@ -235,17 +278,17 @@ public class PanZoomController extends J
         if (event.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
             return handleMouseEvent(event);
         }
         return handleMotionEvent(event);
     }
 
     @Override
     protected void finalize() throws Throwable {
-        setAttached(false);
+        mNative.setAttached(false);
     }
 
     /**
      * Process a non-touch motion event through the pan-zoom controller. Currently, hover
      * and scroll events are supported. Pointer coordinates should be relative to the
      * display surface.
      *
      * @param event MotionEvent to process.
@@ -296,46 +339,26 @@ public class PanZoomController extends J
                     break;
                 case EVENT_SOURCE_MOUSE:
                     handleMouseEvent(pair.second);
                     break;
             }
         }
     }
 
-    @WrapForJNI(calledFrom = "ui")
-    private void setAttached(final boolean attached) {
-        if (attached) {
-            mAttached = true;
-            flushEventQueue();
-        } else if (mAttached) {
-            mAttached = false;
-            enableEventQueue();
-        }
-    }
-
-    @Override // JNIObject
-    protected void disposeNative() {
-        // Disposal happens in native code.
-        throw new UnsupportedOperationException();
-    }
-
-    @WrapForJNI(stubName = "SetIsLongpressEnabled") // Called from test thread.
-    private native void nativeSetIsLongpressEnabled(boolean isLongpressEnabled);
-
     /**
      * Set whether Gecko should generate long-press events.
      *
      * @param isLongpressEnabled True if Gecko should generate long-press events.
      */
     public void setIsLongpressEnabled(final boolean isLongpressEnabled) {
         ThreadUtils.assertOnUiThread();
 
         if (mAttached) {
-            nativeSetIsLongpressEnabled(isLongpressEnabled);
+            mNative.nativeSetIsLongpressEnabled(isLongpressEnabled);
         }
     }
 
     private static class PointerInfo {
         // We reserve one pointer ID for the mouse, so that tests don't have
         // to worry about tracking pointer IDs if they just want to test mouse
         // event synthesization. If somebody tries to use this ID for a
         // synthesized touch event we'll throw an exception.
@@ -535,35 +558,16 @@ public class PanZoomController extends J
         if (eventType == MotionEvent.ACTION_POINTER_UP ||
             eventType == MotionEvent.ACTION_UP ||
             eventType == MotionEvent.ACTION_CANCEL ||
             eventType == MotionEvent.ACTION_HOVER_MOVE) {
             mPointerState.pointers.remove(pointerIndex);
         }
     }
 
-    @WrapForJNI(calledFrom = "ui")
-    private void synthesizeNativeTouchPoint(final int pointerId, final int eventType,
-                                            final int clientX, final int clientY,
-                                            final double pressure, final int orientation) {
-        if (pointerId == PointerInfo.RESERVED_MOUSE_POINTER_ID) {
-            throw new IllegalArgumentException("Pointer ID reserved for mouse");
-        }
-        synthesizeNativePointer(InputDevice.SOURCE_TOUCHSCREEN, pointerId,
-                                eventType, clientX, clientY, pressure, orientation);
-    }
-
-    @WrapForJNI(calledFrom = "ui")
-    private void synthesizeNativeMouseEvent(final int eventType, final int clientX,
-                                            final int clientY) {
-        synthesizeNativePointer(InputDevice.SOURCE_MOUSE,
-                                PointerInfo.RESERVED_MOUSE_POINTER_ID,
-                                eventType, clientX, clientY, 0, 0);
-    }
-
     /**
      * Scroll the document body by an offset from the current scroll position.
      * Uses {@link #SCROLL_BEHAVIOR_SMOOTH}.
      *
      * @param width {@link ScreenLength} offset to scroll along X axis.
      * @param height {@link ScreenLength} offset to scroll along Y axis.
      */
     @UiThread
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/RuntimeTelemetry.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/RuntimeTelemetry.java
@@ -4,31 +4,28 @@
  * 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 android.support.annotation.AnyThread;
 import android.support.annotation.NonNull;
 
+import org.json.JSONException;
+import org.json.JSONObject;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.util.GeckoBundle;
 
 /**
  * The telemetry API gives access to telemetry data of the Gecko runtime.
  */
 public final class RuntimeTelemetry {
-    private final static String LOGTAG = "GeckoViewTelemetry";
-    private final static boolean DEBUG = false;
-
-    private final GeckoRuntime mRuntime;
     private final EventDispatcher mEventDispatcher;
 
     /* package */ RuntimeTelemetry(final @NonNull GeckoRuntime runtime) {
-        mRuntime = runtime;
         mEventDispatcher = EventDispatcher.getInstance();
     }
 
     /**
      * Retrieve all telemetry snapshots.
      * The response bundle will contain following snapshots:
      * <ul>
      * <li>histograms</li>
@@ -36,25 +33,29 @@ public final class RuntimeTelemetry {
      * <li>scalars</li>
      * <li>keyedScalars</li>
      * </ul>
      *
      * @param clear Whether the retrieved snapshots should be cleared.
      * @return A {@link GeckoResult} with the GeckoBundle snapshot results.
      */
     @AnyThread
-    public @NonNull GeckoResult<GeckoBundle> getSnapshots(final boolean clear) {
+    public @NonNull GeckoResult<JSONObject> getSnapshots(final boolean clear) {
         final GeckoBundle msg = new GeckoBundle(1);
         msg.putBoolean("clear", clear);
 
-        final GeckoSession.CallbackResult<GeckoBundle> result =
-            new GeckoSession.CallbackResult<GeckoBundle>() {
+        final GeckoSession.CallbackResult<JSONObject> result =
+            new GeckoSession.CallbackResult<JSONObject>() {
                 @Override
                 public void sendSuccess(final Object value) {
-                    complete((GeckoBundle) value);
+                    try {
+                        complete(((GeckoBundle) value).toJSONObject());
+                    } catch (JSONException ex) {
+                        completeExceptionally(ex);
+                    }
                 }
             };
 
         mEventDispatcher.dispatch("GeckoView:TelemetrySnapshots", msg, result);
 
         return result;
     }
 }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/doc-files/CHANGELOG.md
@@ -37,16 +37,24 @@ exclude: true
 
 [68.8]: ../GeckoSession.SessionState.html#fromString-java.lang.String-
 
 - Added [`GeckoRuntimeSettings#setPreferredColorScheme`][68.9] to override
   the default color theme for web content ("light" or "dark").
 
 [68.9]: ../GeckoRuntimeSettings.html#setPreferredColorScheme-int-
 
+- Added [`@NonNull`][66.1] or [`@Nullable`][66.2] to all fields.
+
+- [`RuntimeTelemetry#getSnapshots`][68.10] returns a [`JSONObject`][67.22] now.
+
+[68.10]: ../RuntimeTelemetry.html#getSnapshots-boolean-
+
+- Removed all `org.mozilla.gecko` references in the API.
+
 ## v67
 - Added [`setAutomaticFontSizeAdjustment`][67.2] to
   [`GeckoRuntimeSettings`][67.3] for automatically adjusting font size settings
   depending on the OS-level font size setting.
 
 [67.2]: ../GeckoRuntimeSettings.html#setAutomaticFontSizeAdjustment-boolean-
 [67.3]: ../GeckoRuntimeSettings.html
 
@@ -243,9 +251,9 @@ exclude: true
 [65.23]: ../GeckoSession.FinderResult.html
 
 - Update [`CrashReporter#sendCrashReport`][65.24] to return the crash ID as a
   [`GeckoResult<String>`][65.25].
 
 [65.24]: ../CrashReporter.html#sendCrashReport-android.content.Context-android.os.Bundle-java.lang.String-
 [65.25]: ../GeckoResult.html
 
-[api-version]: 053d9b4164690ff13996be9e7288dd183e2a1db4
+[api-version]: a5ffe8ea42f0210fc7c64b742ae6d81d03a93e06
--- a/mobile/android/modules/geckoview/.eslintrc.js
+++ b/mobile/android/modules/geckoview/.eslintrc.js
@@ -2,10 +2,26 @@
 
 module.exports = {
   "globals": {
     "debug": false,
     "warn": false,
   },
   "rules": {
     "prefer-const": "error",
+    "indent": [
+      "error",
+      2,
+      {
+        "FunctionExpression": {
+          "parameters": "first"
+        },
+        "CallExpression": {
+          "arguments": "first"
+        },
+        "ObjectExpression": "first",
+        "MemberExpression": "off",
+        "ArrayExpression": "first",
+        "SwitchCase": 1,
+      }
+    ],
   },
 };
--- a/mobile/android/modules/geckoview/GeckoViewAutoFill.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewAutoFill.jsm
@@ -54,17 +54,17 @@ class GeckoViewAutoFill {
       if (aFromDeferredTask) {
         // Canceled before we could run the task.
         debug `Auto-fill task canceled`;
         return;
       }
       // Start a new task so we can coalesce adding elements in one batch.
       debug `Deferring auto-fill task`;
       task = new DeferredTask(
-          () => this._addElement(aFormLike, true), 100);
+        () => this._addElement(aFormLike, true), 100);
       task.arm();
       if (!this._autoFillTasks) {
         this._autoFillTasks = new WeakMap();
       }
       this._autoFillTasks.set(aFormLike.rootElement, task);
       return;
     }
 
@@ -90,18 +90,19 @@ class GeckoViewAutoFill {
         parent,
         root,
         tag: element.tagName,
         type: element instanceof window.HTMLInputElement ? element.type : null,
         editable: (element instanceof window.HTMLInputElement) &&
                   ["color", "date", "datetime-local", "email", "month",
                    "number", "password", "range", "search", "tel", "text",
                    "time", "url", "week"].includes(element.type),
-        disabled: element instanceof window.HTMLInputElement ? element.disabled
-                                                             : null,
+        disabled: element instanceof window.HTMLInputElement
+          ? element.disabled
+          : null,
         attributes: Object.assign({}, ...Array.from(element.attributes)
             .filter(attr => attr.localName !== "value")
             .map(attr => ({[attr.localName]: attr.value}))),
         origin: element.ownerDocument.location.origin,
         autofillhint: "",
         bounds: {
           left: bounds.left,
           top: bounds.top,
@@ -147,19 +148,21 @@ class GeckoViewAutoFill {
           if (element instanceof window.HTMLInputElement &&
               !element.disabled && element.parentElement) {
             element.setUserInput(value);
             if (winUtils && element.value === value) {
               // Add highlighting for autofilled fields.
               winUtils.addManuallyManagedState(element, AUTOFILL_STATE);
 
               // Remove highlighting when the field is changed.
-              element.addEventListener("input", _ =>
-                  winUtils.removeManuallyManagedState(element, AUTOFILL_STATE),
-                  { mozSystemGroup: true, once: true });
+              element.addEventListener(
+                "input",
+                _ => winUtils.removeManuallyManagedState(element,
+                                                         AUTOFILL_STATE),
+                { mozSystemGroup: true, once: true });
             }
           } else if (element) {
             warn `Don't know how to auto-fill ${element.tagName}`;
           }
         }
       },
       onError: error => {
         warn `Cannot perform autofill ${error}`;
@@ -211,23 +214,21 @@ class GeckoViewAutoFill {
    */
   scanDocument(aDoc) {
     // Add forms first; only check forms with password inputs.
     const inputs = aDoc.querySelectorAll("input[type=password]");
     let inputAdded = false;
     for (let i = 0; i < inputs.length; i++) {
       if (inputs[i].form) {
         // Let _addAutoFillElement coalesce multiple calls for the same form.
-        this._addElement(
-            FormLikeFactory.createFromForm(inputs[i].form));
+        this._addElement(FormLikeFactory.createFromForm(inputs[i].form));
       } else if (!inputAdded) {
         // Treat inputs without forms as one unit, and process them only once.
         inputAdded = true;
-        this._addElement(
-            FormLikeFactory.createFromField(inputs[i]));
+        this._addElement(FormLikeFactory.createFromField(inputs[i]));
       }
     }
 
     // Finally add frames.
     const frames = aDoc.defaultView.frames;
     for (let i = 0; i < frames.length; i++) {
       this.scanDocument(frames[i].document);
     }
--- a/mobile/android/modules/geckoview/GeckoViewContent.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContent.jsm
@@ -11,46 +11,46 @@ const {XPCOMUtils} = ChromeUtils.import(
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   Services: "resource://gre/modules/Services.jsm",
 });
 
 class GeckoViewContent extends GeckoViewModule {
   onInit() {
     this.registerListener([
-        "GeckoViewContent:ExitFullScreen",
-        "GeckoView:ClearMatches",
-        "GeckoView:DisplayMatches",
-        "GeckoView:FindInPage",
-        "GeckoView:RestoreState",
-        "GeckoView:SetActive",
-        "GeckoView:SetFocused",
-        "GeckoView:ZoomToInput",
-        "GeckoView:ScrollBy",
-        "GeckoView:ScrollTo",
+      "GeckoViewContent:ExitFullScreen",
+      "GeckoView:ClearMatches",
+      "GeckoView:DisplayMatches",
+      "GeckoView:FindInPage",
+      "GeckoView:RestoreState",
+      "GeckoView:SetActive",
+      "GeckoView:SetFocused",
+      "GeckoView:ZoomToInput",
+      "GeckoView:ScrollBy",
+      "GeckoView:ScrollTo",
     ]);
   }
 
   onEnable() {
     this.window.addEventListener("MozDOMFullscreen:Entered", this,
-                                 /* capture */ true, /* untrusted */ false);
+      /* capture */ true, /* untrusted */ false);
     this.window.addEventListener("MozDOMFullscreen:Exited", this,
-                                 /* capture */ true, /* untrusted */ false);
+      /* capture */ true, /* untrusted */ false);
 
     this.messageManager.addMessageListener("GeckoView:DOMFullscreenExit", this);
     this.messageManager.addMessageListener("GeckoView:DOMFullscreenRequest", this);
 
     Services.obs.addObserver(this, "oop-frameloader-crashed");
   }
 
   onDisable() {
     this.window.removeEventListener("MozDOMFullscreen:Entered", this,
-                                    /* capture */ true);
+      /* capture */ true);
     this.window.removeEventListener("MozDOMFullscreen:Exited", this,
-                                    /* capture */ true);
+      /* capture */ true);
 
     this.messageManager.removeMessageListener("GeckoView:DOMFullscreenExit", this);
     this.messageManager.removeMessageListener("GeckoView:DOMFullscreenRequest", this);
 
     Services.obs.removeObserver(this, "oop-frameloader-crashed");
   }
 
   // Bundle event handler.
@@ -151,18 +151,18 @@ class GeckoViewContent extends GeckoView
         const browser = aSubject.ownerElement;
         if (!browser || browser != this.browser) {
           return;
         }
 
         this.eventDispatcher.sendRequest({
           type: "GeckoView:ContentCrash",
         });
+        break;
       }
-      break;
     }
   }
 
   _findInPage(aData, aCallback) {
     debug `findInPage: data=${aData} callback=${aCallback && "non-null"}`;
 
     let finder;
     try {
--- a/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewNavigation.jsm
@@ -82,22 +82,22 @@ class GeckoViewNavigation extends GeckoV
 
         if (this.settings.useMultiprocess) {
           this.moduleManager.updateRemoteTypeForURI(uri);
         }
 
         let parsedUri;
         let triggeringPrincipal;
         try {
-            parsedUri = Services.io.newURI(uri);
-            if (parsedUri.schemeIs("about") || parsedUri.schemeIs("data") ||
-                parsedUri.schemeIs("file") || parsedUri.schemeIs("resource")) {
-              // Only allow privileged loading for certain URIs.
-              triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
-            }
+          parsedUri = Services.io.newURI(uri);
+          if (parsedUri.schemeIs("about") || parsedUri.schemeIs("data") ||
+              parsedUri.schemeIs("file") || parsedUri.schemeIs("resource")) {
+            // Only allow privileged loading for certain URIs.
+            triggeringPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+          }
         } catch (ignored) {
         }
         if (!triggeringPrincipal) {
           triggeringPrincipal = Services.scriptSecurityManager.createNullPrincipal({});
         }
 
         this.browser.loadURI(parsedUri ? parsedUri.spec : uri, {
           flags: navFlags,
@@ -190,17 +190,17 @@ class GeckoViewNavigation extends GeckoV
     }).then(window => {
       browser = (window && window.browser) || null;
     }, () => {
       browser = null;
     });
 
     // Wait indefinitely for app to respond with a browser or null
     Services.tm.spinEventLoopUntil(() =>
-        this.window.closed || browser !== undefined);
+      this.window.closed || browser !== undefined);
     return browser || null;
   }
 
   // nsIBrowserDOMWindow.
   createContentWindow(aUri, aOpener, aWhere, aFlags, aTriggeringPrincipal) {
     debug `createContentWindow: uri=${aUri && aUri.spec}
                                 where=${aWhere} flags=${aFlags}`;
 
--- a/mobile/android/modules/geckoview/GeckoViewProgress.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewProgress.jsm
@@ -5,21 +5,22 @@
 "use strict";
 
 var EXPORTED_SYMBOLS = ["GeckoViewProgress"];
 
 const {GeckoViewModule} = ChromeUtils.import("resource://gre/modules/GeckoViewModule.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-XPCOMUtils.defineLazyServiceGetter(this, "OverrideService",
-  "@mozilla.org/security/certoverride;1", "nsICertOverrideService");
+XPCOMUtils.defineLazyServiceGetter(
+  this, "OverrideService", "@mozilla.org/security/certoverride;1",
+  "nsICertOverrideService");
 
-XPCOMUtils.defineLazyServiceGetter(this, "IDNService",
-  "@mozilla.org/network/idn-service;1", "nsIIDNService");
+XPCOMUtils.defineLazyServiceGetter(
+  this, "IDNService", "@mozilla.org/network/idn-service;1", "nsIIDNService");
 
 var IdentityHandler = {
   // The definitions below should be kept in sync with those in GeckoView.ProgressListener.SecurityInformation
   // No trusted identity information. No site identity icon is shown.
   IDENTITY_MODE_UNKNOWN: 0,
 
   // Domain-Validation SSL CA-signed domain verification (DV).
   IDENTITY_MODE_IDENTIFIED: 1,
@@ -51,21 +52,21 @@ var IdentityHandler = {
       return this.IDENTITY_MODE_IDENTIFIED;
     }
 
     return this.IDENTITY_MODE_UNKNOWN;
   },
 
   getMixedDisplayMode: function getMixedDisplayMode(aState) {
     if (aState & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT) {
-        return this.MIXED_MODE_CONTENT_LOADED;
+      return this.MIXED_MODE_CONTENT_LOADED;
     }
 
     if (aState & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT) {
-        return this.MIXED_MODE_CONTENT_BLOCKED;
+      return this.MIXED_MODE_CONTENT_BLOCKED;
     }
 
     return this.MIXED_MODE_UNKNOWN;
   },
 
   getMixedActiveMode: function getActiveDisplayMode(aState) {
     // Only show an indicator for loaded mixed content if the pref to block it is enabled
     if ((aState & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) &&
@@ -126,17 +127,17 @@ var IdentityHandler = {
 
     result.organization = cert.organization;
     result.subjectName = cert.subjectName;
     result.issuerOrganization = cert.issuerOrganization;
     result.issuerCommonName = cert.issuerCommonName;
 
     try {
       result.securityException = OverrideService.hasMatchingOverride(
-          uri.host, uri.port, cert, {}, {});
+        uri.host, uri.port, cert, {}, {});
     } catch (e) {
     }
 
     return result;
   },
 };
 
 class GeckoViewProgress extends GeckoViewModule {
--- a/mobile/android/modules/geckoview/GeckoViewRemoteDebugger.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewRemoteDebugger.jsm
@@ -50,17 +50,17 @@ var GeckoViewRemoteDebugger = {
 
     // This lets Marionette start listening (when it's enabled).  Both
     // GeckoView and Marionette do most of their initialization in
     // "profile-after-change", and there is no order enforced between
     // them.  Therefore we defer asking Marionette to startup until
     // after all "profile-after-change" handlers (including this one)
     // have completed.
     Services.tm.dispatchToMainThread(() => {
-        Services.obs.notifyObservers(null, "marionette-startup-requested");
+      Services.obs.notifyObservers(null, "marionette-startup-requested");
     });
   },
 
   onEnable() {
     if (this._isEnabled) {
       return;
     }
 
--- a/mobile/android/modules/geckoview/GeckoViewUtils.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewUtils.jsm
@@ -149,17 +149,17 @@ var GeckoViewUtils = {
 
   _addLazyListeners: function(events, handler, scope, name, addFn, handleFn) {
     if (!handler) {
       handler = (_ => Array.isArray(name) ? name.map(n => scope[n]) : scope[name]);
     }
     const listener = (...args) => {
       let handlers = handler(...args);
       if (!handlers) {
-          return;
+        return;
       }
       if (!Array.isArray(handlers)) {
         handlers = [handlers];
       }
       handleFn(handlers, listener, args);
     };
     if (Array.isArray(events)) {
       addFn(events, listener);
@@ -178,17 +178,18 @@ var GeckoViewUtils = {
    *                actual event handler as an object or an array of objects.
    *                If handler is not specified, the actual event handler is
    *                specified using the scope and name pair.
    * @param scope   See handler.
    * @param name    See handler.
    * @param options Options for addEventListener.
    */
   addLazyEventListener: function(target, events, {handler, scope, name, options}) {
-    this._addLazyListeners(events, handler, scope, name,
+    this._addLazyListeners(
+      events, handler, scope, name,
       (events, listener) => {
         events.forEach(event => target.addEventListener(event, listener, options));
       },
       (handlers, listener, args) => {
         if (!options || !options.once) {
           target.removeEventListener(args[0].type, listener, options);
           handlers.forEach(handler =>
             target.addEventListener(args[0].type, handler, options));
@@ -210,17 +211,18 @@ var GeckoViewUtils = {
    * @param scope   See handler.
    * @param name    See handler.
    * @param once    If true, only listen to the specified events once.
    */
   registerLazyWindowEventListener: function(window, events,
                                             {handler, scope, name, once}) {
     const dispatcher = this.getDispatcherForWindow(window);
 
-    this._addLazyListeners(events, handler, scope, name,
+    this._addLazyListeners(
+      events, handler, scope, name,
       (events, listener) => {
         dispatcher.registerListener(listener, events);
       },
       (handlers, listener, args) => {
         if (!once) {
           dispatcher.unregisterListener(listener, args[0]);
           handlers.forEach(handler =>
             dispatcher.registerListener(handler, args[0]));
@@ -241,17 +243,18 @@ var GeckoViewUtils = {
    *                actual event handler as an object or an array of objects.
    *                If handler is not specified, the actual event handler is
    *                specified using the scope and name pair.
    * @param scope   See handler.
    * @param name    See handler.
    * @param once    If true, only observe the specified prefs once.
    */
   addLazyPrefObserver: function(aPrefs, {handler, scope, name, once}) {
-    this._addLazyListeners(aPrefs, handler, scope, name,
+    this._addLazyListeners(
+      aPrefs, handler, scope, name,
       (prefs, observer) => {
         prefs.forEach(pref => Services.prefs.addObserver(pref.name, observer));
         prefs.forEach(pref => {
           if (pref.default === undefined) {
             return;
           }
           let value;
           switch (typeof pref.default) {
@@ -378,17 +381,17 @@ var GeckoViewUtils = {
     aScope = aScope || {};
     const tag = "GeckoView." + aTag.replace(/^GeckoView\.?/, "");
 
     // Only provide two levels for simplicity.
     // For "info", use "debug" instead.
     // For "error", throw an actual JS error instead.
     for (const level of ["DEBUG", "WARN"]) {
       const log = (strings, ...exprs) =>
-          this._log(log.logger, level, strings, exprs);
+        this._log(log.logger, level, strings, exprs);
 
       XPCOMUtils.defineLazyGetter(log, "logger", _ => {
         const logger = Log.repository.getLogger(tag);
         logger.parent = this.rootLogger;
         return logger;
       });
 
       aScope[level.toLowerCase()] = new Proxy(log, {
@@ -453,9 +456,9 @@ var GeckoViewUtils = {
       }
     }
 
     return aLogger[aLevel.toLowerCase()](strs, ...aExprs);
   },
 };
 
 XPCOMUtils.defineLazyGetter(GeckoViewUtils, "IS_PARENT_PROCESS", _ =>
-    Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT);
+  Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT);
--- a/mobile/android/modules/geckoview/LoadURIDelegate.jsm
+++ b/mobile/android/modules/geckoview/LoadURIDelegate.jsm
@@ -21,18 +21,18 @@ const LoadURIDelegate = {
   load: function(aWindow, aEventDispatcher, aUri, aWhere, aFlags,
                  aTriggeringPrincipal) {
     if (!aWindow) {
       return false;
     }
 
     const triggerUri = aTriggeringPrincipal &&
                        (aTriggeringPrincipal.isNullPrincipal
-                        ? null
-                        : aTriggeringPrincipal.URI);
+                         ? null
+                         : aTriggeringPrincipal.URI);
 
     const message = {
       type: "GeckoView:OnLoadRequest",
       uri: aUri ? aUri.displaySpec : "",
       where: aWhere,
       flags: aFlags,
       triggerUri: triggerUri && triggerUri.displaySpec,
     };
@@ -41,27 +41,27 @@ const LoadURIDelegate = {
     aEventDispatcher.sendRequestForResult(message).then(response => {
       handled = response;
     }, () => {
       // There was an error or listener was not registered in GeckoSession,
       // treat as unhandled.
       handled = false;
     });
     Services.tm.spinEventLoopUntil(() =>
-        aWindow.closed || handled !== undefined);
+      aWindow.closed || handled !== undefined);
 
     return handled || false;
   },
 
   handleLoadError: function(aWindow, aEventDispatcher, aUri, aError,
                             aErrorModule) {
     let errorClass = 0;
     try {
       const nssErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
-                               .getService(Ci.nsINSSErrorsService);
+        .getService(Ci.nsINSSErrorsService);
       errorClass = nssErrorsService.getErrorClass(aError);
     } catch (e) {}
 
     const msg = {
       type: "GeckoView:OnLoadError",
       uri: aUri && aUri.spec,
       error: aError,
       errorModule: aErrorModule,
@@ -77,17 +77,17 @@ const LoadURIDelegate = {
         errorPageURI = null;
         Components.returnCode = Cr.NS_ERROR_ABORT;
       }
     }, e => {
       errorPageURI = null;
       Components.returnCode = Cr.NS_ERROR_ABORT;
     });
     Services.tm.spinEventLoopUntil(() =>
-        aWindow.closed || errorPageURI !== undefined);
+      aWindow.closed || errorPageURI !== undefined);
 
     return errorPageURI;
   },
 
   isSafeBrowsingError(aError) {
     return aError === Cr.NS_ERROR_PHISHING_URI ||
            aError === Cr.NS_ERROR_MALWARE_URI ||
            aError === Cr.NS_ERROR_HARMFUL_URI ||
--- a/mobile/android/modules/geckoview/Messaging.jsm
+++ b/mobile/android/modules/geckoview/Messaging.jsm
@@ -18,17 +18,17 @@ const IS_PARENT_PROCESS = (Services.appi
 function DispatcherDelegate(aDispatcher, aMessageManager) {
   this._dispatcher = aDispatcher;
   this._messageManager = aMessageManager;
 
   if (!aDispatcher) {
     // Child process.
     this._replies = new Map();
     (aMessageManager || Services.cpmm).addMessageListener(
-        "GeckoView:MessagingReply", this);
+      "GeckoView:MessagingReply", this);
   }
 }
 
 DispatcherDelegate.prototype = {
   /**
    * Register a listener to be notified of event(s).
    *
    * @param aListener Target listener implementing nsIAndroidEventListener.
--- a/modules/libpref/components.conf
+++ b/modules/libpref/components.conf
@@ -13,16 +13,16 @@ UnloadFunc = 'mozilla::UnloadPrefsModule
 Classes = [
     {
         'cid': '{91ca2441-050f-4f7c-9df8-75b40ea40156}',
         'contract_ids': ['@mozilla.org/preferences-service;1'],
         'singleton': True,
         'type': 'mozilla::Preferences',
         'headers': ['mozilla/Preferences.h'],
         'constructor': 'mozilla::Preferences::GetInstanceForService',
-        'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS,
+        'processes': ProcessSelector.ALLOW_IN_RDD_AND_SOCKET_PROCESS,
     },
     {
         'cid': '{064d9cee-1dd2-11b2-83e3-d25ab0193c26}',
         'contract_ids': ['@mozilla.org/pref-localizedstring;1'],
         'type': 'nsPrefLocalizedString',
     },
 ]
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -91,18 +91,17 @@ LoadInfo::LoadInfo(
       mIsDocshellReload(false),
       mSendCSPViolationEvents(true),
       mForcePreflight(false),
       mIsPreflight(false),
       mLoadTriggeredFromExternal(false),
       mServiceWorkerTaintingSynthesized(false),
       mDocumentHasUserInteracted(false),
       mDocumentHasLoaded(false),
-      mIsFromProcessingFrameAttributes(false),
-      mOpenerPolicy(nsILoadInfo::OPENER_POLICY_NULL) {
+      mIsFromProcessingFrameAttributes(false) {
   MOZ_ASSERT(mLoadingPrincipal);
   MOZ_ASSERT(mTriggeringPrincipal);
 
 #ifdef DEBUG
   // TYPE_DOCUMENT loads initiated by javascript tests will go through
   // nsIOService and use the wrong constructor.  Don't enforce the
   // !TYPE_DOCUMENT check in those cases
   bool skipContentTypeCheck = false;
@@ -351,18 +350,17 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* a
       mIsDocshellReload(false),
       mSendCSPViolationEvents(true),
       mForcePreflight(false),
       mIsPreflight(false),
       mLoadTriggeredFromExternal(false),
       mServiceWorkerTaintingSynthesized(false),
       mDocumentHasUserInteracted(false),
       mDocumentHasLoaded(false),
-      mIsFromProcessingFrameAttributes(false),
-      mOpenerPolicy(nsILoadInfo::OPENER_POLICY_NULL) {
+      mIsFromProcessingFrameAttributes(false) {
   // Top-level loads are never third-party
   // Grab the information we can out of the window.
   MOZ_ASSERT(aOuterWindow);
   MOZ_ASSERT(mTriggeringPrincipal);
 
   // if the load is sandboxed, we can not also inherit the principal
   if (mSecurityFlags & nsILoadInfo::SEC_SANDBOXED) {
     mForceInheritPrincipalDropped =
@@ -466,18 +464,17 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
       mIsPreflight(rhs.mIsPreflight),
       mLoadTriggeredFromExternal(rhs.mLoadTriggeredFromExternal),
       // mServiceWorkerTaintingSynthesized must be handled specially during
       // redirect
       mServiceWorkerTaintingSynthesized(false),
       mDocumentHasUserInteracted(rhs.mDocumentHasUserInteracted),
       mDocumentHasLoaded(rhs.mDocumentHasLoaded),
       mCspNonce(rhs.mCspNonce),
-      mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes),
-      mOpenerPolicy(rhs.mOpenerPolicy) {}
+      mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes) {}
 
 LoadInfo::LoadInfo(
     nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
     nsIPrincipal* aPrincipalToInherit, nsIPrincipal* aSandboxedLoadingPrincipal,
     nsIPrincipal* aTopLevelPrincipal,
     nsIPrincipal* aTopLevelStorageAreaPrincipal, nsIURI* aResultPrincipalURI,
     nsICookieSettings* aCookieSettings, const Maybe<ClientInfo>& aClientInfo,
     const Maybe<ClientInfo>& aReservedClientInfo,
@@ -548,18 +545,17 @@ LoadInfo::LoadInfo(
       mCorsUnsafeHeaders(aCorsUnsafeHeaders),
       mForcePreflight(aForcePreflight),
       mIsPreflight(aIsPreflight),
       mLoadTriggeredFromExternal(aLoadTriggeredFromExternal),
       mServiceWorkerTaintingSynthesized(aServiceWorkerTaintingSynthesized),
       mDocumentHasUserInteracted(aDocumentHasUserInteracted),
       mDocumentHasLoaded(aDocumentHasLoaded),
       mCspNonce(aCspNonce),
-      mIsFromProcessingFrameAttributes(false),
-      mOpenerPolicy(nsILoadInfo::OPENER_POLICY_NULL) {
+      mIsFromProcessingFrameAttributes(false) {
   // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
   MOZ_ASSERT(mLoadingPrincipal ||
              aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
   MOZ_ASSERT(mTriggeringPrincipal);
 
   mRedirectChainIncludingInternalRedirects.SwapElements(
       aRedirectChainIncludingInternalRedirects);
 
@@ -1436,22 +1432,10 @@ LoadInfo::GetCspEventListener(nsICSPEven
 }
 
 NS_IMETHODIMP
 LoadInfo::SetCspEventListener(nsICSPEventListener* aCSPEventListener) {
   mCSPEventListener = aCSPEventListener;
   return NS_OK;
 }
 
-NS_IMETHODIMP
-LoadInfo::GetOpenerPolicy(nsILoadInfo::CrossOriginOpenerPolicy* aOpenerPolicy) {
-  *aOpenerPolicy = mOpenerPolicy;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-LoadInfo::SetOpenerPolicy(nsILoadInfo::CrossOriginOpenerPolicy aOpenerPolicy) {
-  mOpenerPolicy = aOpenerPolicy;
-  return NS_OK;
-}
-
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -205,16 +205,14 @@ class LoadInfo final : public nsILoadInf
   bool mDocumentHasUserInteracted;
   bool mDocumentHasLoaded;
   nsString mCspNonce;
 
   // Is true if this load was triggered by processing the attributes of the
   // browsing context container.
   // See nsILoadInfo.isFromProcessingFrameAttributes
   bool mIsFromProcessingFrameAttributes;
-
-  nsILoadInfo::CrossOriginOpenerPolicy mOpenerPolicy;
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // mozilla_LoadInfo_h
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -1087,16 +1087,14 @@ interface nsILoadInfo : nsISupports
     OPENER_POLICY_NULL           = 0,
     OPENER_POLICY_SAME_ORIGIN    = 1,
     OPENER_POLICY_SAME_SITE      = 2,
     OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG = 0x80,
     OPENER_POLICY_SAME_ORIGIN_ALLOW_OUTGOING = OPENER_POLICY_SAME_ORIGIN | OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG,
     OPENER_POLICY_SAME_SITE_ALLOW_OUTGOING = OPENER_POLICY_SAME_SITE | OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG
   };
 
-  [infallible] attribute nsILoadInfo_CrossOriginOpenerPolicy openerPolicy;
-
   cenum CrossOriginPolicy : 8 {
     CROSS_ORIGIN_POLICY_NULL            = 0,
     CROSS_ORIGIN_POLICY_ANONYMOUS       = 1,
     CROSS_ORIGIN_POLICY_USE_CREDENTIALS = 2
   };
 };
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -16,17 +16,16 @@ include IPCStream;
 include PBackgroundSharedTypes;
 include DOMTypes;
 
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 using RequestHeaderTuples from "mozilla/net/PHttpChannelParams.h";
 using struct nsHttpAtom from "nsHttp.h";
 using class mozilla::net::nsHttpResponseHead from "nsHttpResponseHead.h";
 using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
-using nsILoadInfo::CrossOriginOpenerPolicy from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace net {
 
 //-----------------------------------------------------------------------------
 // CookieSettings IPDL structs
 //-----------------------------------------------------------------------------
 
@@ -136,17 +135,16 @@ struct LoadInfoArgs
   bool                        forcePreflight;
   bool                        isPreflight;
   bool                        loadTriggeredFromExternal;
   bool                        serviceWorkerTaintingSynthesized;
   bool                        documentHasUserInteracted;
   bool                        documentHasLoaded;
   nsString                    cspNonce;
   bool                        isFromProcessingFrameAttributes;
-  CrossOriginOpenerPolicy     openerPolicy;
 
   CookieSettingsArgs cookieSettings;
 };
 
 /**
  * This structure is used to carry selected properties of a LoadInfo
  * object to child processes to merge LoadInfo changes from the parent
  * process.  We don't want to use LoadInfoArgs for that since it's
@@ -170,18 +168,16 @@ struct ParentLoadInfoForwarderArgs
 
   // We must also note that the tainting value was explicitly set
   // by the service worker.
   bool serviceWorkerTaintingSynthesized;
 
   bool documentHasUserInteracted;
   bool documentHasLoaded;
 
-  CrossOriginOpenerPolicy openerPolicy;
-
   CookieSettingsArgs? cookieSettings;
 
   // IMPORTANT: when you add new properites here you must also update
   // LoadInfoToParentLoadInfoForwarder and MergeParentLoadInfoForwarder
   // in BackgroundUtils.cpp/.h!
 };
 
 /**
@@ -219,17 +215,16 @@ struct HttpChannelOpenArgs
   // set originalURI != uri (about:credits?); also not clear if
   // chrome channel would ever need to know.  Get rid of next arg?
   URIParams?                  original;
   URIParams?                  doc;
   URIParams?                  originalReferrer;
   uint32_t                    referrerPolicy;
   URIParams?                  apiRedirectTo;
   URIParams?                  topWindowURI;
-  nsIPrincipal                topWindowPrincipal;
   uint32_t                    loadFlags;
   RequestHeaderTuples         requestHeaders;
   nsCString                   requestMethod;
   IPCStream?                  uploadStream;
   bool                        uploadStreamHasHeaders;
   int16_t                     priority;
   uint32_t                    classOfService;
   uint8_t                     redirectionLimit;
--- a/netwerk/ipc/SocketProcessHost.cpp
+++ b/netwerk/ipc/SocketProcessHost.cpp
@@ -106,51 +106,17 @@ bool SocketProcessHost::Launch() {
   nsAutoCString parentBuildID(mozilla::PlatformBuildID());
   extraArgs.push_back("-parentBuildID");
   extraArgs.push_back(parentBuildID.get());
 
   SharedPreferenceSerializer prefSerializer;
   if (!prefSerializer.SerializeToSharedMemory()) {
     return false;
   }
-
-  // Formats a pointer or pointer-sized-integer as a string suitable for passing
-  // in an arguments list.
-  auto formatPtrArg = [](auto arg) {
-    return nsPrintfCString("%zu", uintptr_t(arg));
-  };
-
-#if defined(XP_WIN)
-  // Record the handle as to-be-shared, and pass it via a command flag. This
-  // works because Windows handles are system-wide.
-  HANDLE prefsHandle = prefSerializer.GetSharedMemoryHandle();
-  AddHandleToShare(prefsHandle);
-  AddHandleToShare(prefSerializer.GetPrefMapHandle().get());
-  extraArgs.push_back("-prefsHandle");
-  extraArgs.push_back(formatPtrArg(prefsHandle).get());
-  extraArgs.push_back("-prefMapHandle");
-  extraArgs.push_back(
-      formatPtrArg(prefSerializer.GetPrefMapHandle().get()).get());
-#else
-  // In contrast, Unix fds are per-process. So remap the fd to a fixed one that
-  // will be used in the child.
-  // XXX: bug 1440207 is about improving how fixed fds are used.
-  //
-  // Note: on Android, AddFdToRemap() sets up the fd to be passed via a Parcel,
-  // and the fixed fd isn't used. However, we still need to mark it for
-  // remapping so it doesn't get closed in the child.
-  AddFdToRemap(prefSerializer.GetSharedMemoryHandle().fd, kPrefsFileDescriptor);
-  AddFdToRemap(prefSerializer.GetPrefMapHandle().get(), kPrefMapFileDescriptor);
-#endif
-
-  // Pass the lengths via command line flags.
-  extraArgs.push_back("-prefsLen");
-  extraArgs.push_back(formatPtrArg(prefSerializer.GetPrefLength()).get());
-  extraArgs.push_back("-prefMapSize");
-  extraArgs.push_back(formatPtrArg(prefSerializer.GetPrefMapSize()).get());
+  prefSerializer.AddSharedPrefCmdLineArgs(*this, extraArgs);
 
   mLaunchPhase = LaunchPhase::Waiting;
   if (!GeckoChildProcessHost::LaunchAndWaitForProcessHandle(extraArgs)) {
     mLaunchPhase = LaunchPhase::Complete;
     return false;
   }
 
   return true;
--- a/netwerk/protocol/http/ClassifierDummyChannel.cpp
+++ b/netwerk/protocol/http/ClassifierDummyChannel.cpp
@@ -546,22 +546,16 @@ ClassifierDummyChannel::GetTopWindowURI(
 }
 
 NS_IMETHODIMP
 ClassifierDummyChannel::SetTopWindowURIIfUnknown(nsIURI* aTopWindowURI) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
-ClassifierDummyChannel::SetTopWindowPrincipal(
-    nsIPrincipal* aTopWindowPrincipal) {
-  return NS_ERROR_NOT_IMPLEMENTED;
-}
-
-NS_IMETHODIMP
 ClassifierDummyChannel::GetProxyURI(nsIURI** aProxyURI) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 void ClassifierDummyChannel::SetCorsPreflightParameters(
     const nsTArray<nsCString>& aUnsafeHeaders) {}
 
 void ClassifierDummyChannel::SetAltDataForChild(bool aIsForChild) {}
@@ -619,10 +613,15 @@ NS_IMETHODIMP
 ClassifierDummyChannel::CancelByChannelClassifier(nsresult aErrorCode) {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 void ClassifierDummyChannel::SetIPv4Disabled() {}
 
 void ClassifierDummyChannel::SetIPv6Disabled() {}
 
+NS_IMETHODIMP ClassifierDummyChannel::GetCrossOriginOpenerPolicy(
+    nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) {
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -2323,26 +2323,16 @@ HttpBaseChannel::SetTopWindowURIIfUnknow
     return NS_ERROR_FAILURE;
   }
 
   mTopWindowURI = aTopWindowURI;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-HttpBaseChannel::SetTopWindowPrincipal(nsIPrincipal* aTopWindowPrincipal) {
-  if (!aTopWindowPrincipal) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  mTopWindowPrincipal = aTopWindowPrincipal;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 HttpBaseChannel::GetTopWindowURI(nsIURI** aTopWindowURI) {
   nsCOMPtr<nsIURI> uriBeingLoaded =
       AntiTrackingCommon::MaybeGetDocumentURIBeingLoaded(this);
   return GetTopWindowURI(uriBeingLoaded, aTopWindowURI);
 }
 
 nsresult HttpBaseChannel::GetTopWindowURI(nsIURI* aURIBeingLoaded,
                                           nsIURI** aTopWindowURI) {
@@ -2370,28 +2360,16 @@ nsresult HttpBaseChannel::GetTopWindowUR
       }
 #endif
     }
   }
   NS_IF_ADDREF(*aTopWindowURI = mTopWindowURI);
   return rv;
 }
 
-nsresult HttpBaseChannel::GetTopWindowPrincipal(
-    nsIPrincipal** aTopWindowPrincipal) {
-  nsCOMPtr<mozIThirdPartyUtil> util = services::GetThirdPartyUtil();
-  nsCOMPtr<mozIDOMWindowProxy> win;
-  nsresult rv =
-      util->GetTopWindowForChannel(this, nullptr, getter_AddRefs(win));
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-  return util->GetPrincipalFromWindow(win, getter_AddRefs(mTopWindowPrincipal));
-}
-
 NS_IMETHODIMP
 HttpBaseChannel::GetDocumentURI(nsIURI** aDocumentURI) {
   NS_ENSURE_ARG_POINTER(aDocumentURI);
   *aDocumentURI = mDocumentURI;
   NS_IF_ADDREF(*aDocumentURI);
   return NS_OK;
 }
 
@@ -4559,10 +4537,64 @@ HttpBaseChannel::CancelByChannelClassifi
       UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
   return Cancel(aErrorCode);
 }
 
 void HttpBaseChannel::SetIPv4Disabled() { mCaps |= NS_HTTP_DISABLE_IPV4; }
 
 void HttpBaseChannel::SetIPv6Disabled() { mCaps |= NS_HTTP_DISABLE_IPV6; }
 
+NS_IMETHODIMP HttpBaseChannel::GetCrossOriginOpenerPolicy(
+    nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) {
+  if (!mResponseHead) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsAutoCString openerPolicy;
+  Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Opener_Policy,
+                                     openerPolicy);
+
+  // Cross-Origin-Opener-Policy = sameness [ RWS outgoing ]
+  // sameness = %s"same-origin" / %s"same-site" ; case-sensitive
+  // outgoing = %s"unsafe-allow-outgoing" ; case-sensitive
+
+  Tokenizer t(openerPolicy);
+  nsAutoCString sameness;
+  nsAutoCString outgoing;
+
+  // The return value will be true if we find any whitespace. If there is
+  // whitespace, then it must be followed by "unsafe-allow-outgoing" otherwise
+  // this is a malformed header value.
+  bool allowOutgoing = t.ReadUntil(Tokenizer::Token::Whitespace(), sameness);
+  if (allowOutgoing) {
+    t.SkipWhites();
+    bool foundEOF = t.ReadUntil(Tokenizer::Token::EndOfFile(), outgoing);
+    if (!foundEOF) {
+      // Malformed response. There should be no text after the second token.
+      *aPolicy = nsILoadInfo::OPENER_POLICY_NULL;
+      return NS_OK;
+    }
+    if (!outgoing.EqualsLiteral("unsafe-allow-outgoing")) {
+      // Malformed response. Only one allowed value for the second token.
+      *aPolicy = nsILoadInfo::OPENER_POLICY_NULL;
+      return NS_OK;
+    }
+  }
+
+  nsILoadInfo::CrossOriginOpenerPolicy policy = nsILoadInfo::OPENER_POLICY_NULL;
+  if (sameness.EqualsLiteral("same-origin")) {
+    policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN;
+    if (allowOutgoing) {
+      policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_OUTGOING;
+    }
+  } else if (sameness.EqualsLiteral("same-site")) {
+    policy = nsILoadInfo::OPENER_POLICY_SAME_SITE;
+    if (allowOutgoing) {
+      policy = nsILoadInfo::OPENER_POLICY_SAME_SITE_ALLOW_OUTGOING;
+    }
+  }
+
+  *aPolicy = policy;
+  return NS_OK;
+}
+
 }  // namespace net
 }  // namespace mozilla
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -296,32 +296,33 @@ class HttpBaseChannel : public nsHashPro
   NS_IMETHOD GetCorsMode(uint32_t *aCorsMode) override;
   NS_IMETHOD SetCorsMode(uint32_t aCorsMode) override;
   NS_IMETHOD GetRedirectMode(uint32_t *aRedirectMode) override;
   NS_IMETHOD SetRedirectMode(uint32_t aRedirectMode) override;
   NS_IMETHOD GetFetchCacheMode(uint32_t *aFetchCacheMode) override;
   NS_IMETHOD SetFetchCacheMode(uint32_t aFetchCacheMode) override;
   NS_IMETHOD GetTopWindowURI(nsIURI **aTopWindowURI) override;
   NS_IMETHOD SetTopWindowURIIfUnknown(nsIURI *aTopWindowURI) override;
-  NS_IMETHOD SetTopWindowPrincipal(nsIPrincipal *aTopWindowPrincipal) override;
   NS_IMETHOD GetProxyURI(nsIURI **proxyURI) override;
   virtual void SetCorsPreflightParameters(
       const nsTArray<nsCString> &unsafeHeaders) override;
   virtual void SetAltDataForChild(bool aIsForChild) override;
   NS_IMETHOD GetConnectionInfoHashKey(
       nsACString &aConnectionInfoHashKey) override;
   NS_IMETHOD GetIntegrityMetadata(nsAString &aIntegrityMetadata) override;
   NS_IMETHOD SetIntegrityMetadata(const nsAString &aIntegrityMetadata) override;
   NS_IMETHOD GetLastRedirectFlags(uint32_t *aValue) override;
   NS_IMETHOD SetLastRedirectFlags(uint32_t aValue) override;
   NS_IMETHOD GetNavigationStartTimeStamp(TimeStamp *aTimeStamp) override;
   NS_IMETHOD SetNavigationStartTimeStamp(TimeStamp aTimeStamp) override;
   NS_IMETHOD CancelByChannelClassifier(nsresult aErrorCode) override;
   virtual void SetIPv4Disabled(void) override;
   virtual void SetIPv6Disabled(void) override;
+  NS_IMETHOD GetCrossOriginOpenerPolicy(
+      nsILoadInfo::CrossOriginOpenerPolicy *aPolicy) override;
 
   inline void CleanRedirectCacheChainIfNecessary() {
     mRedirectedCachekeys = nullptr;
   }
   NS_IMETHOD HTTPUpgrade(const nsACString &aProtocolName,
                          nsIHttpUpgradeListener *aListener) override;
 
   // nsISupportsPriority
@@ -459,17 +460,16 @@ class HttpBaseChannel : public nsHashPro
 
   MOZ_MUST_USE nsresult SetTopWindowURI(nsIURI *aTopWindowURI) {
     mTopWindowURI = aTopWindowURI;
     return NS_OK;
   }
 
  protected:
   nsresult GetTopWindowURI(nsIURI *aURIBeingLoaded, nsIURI **aTopWindowURI);
-  nsresult GetTopWindowPrincipal(nsIPrincipal **aTopWindowPrincipal);
 
   // Handle notifying listener, removing from loadgroup if request failed.
   void DoNotifyListener();
   virtual void DoNotifyListenerCleanup() = 0;
 
   // drop reference to listener, its callbacks, and the progress sink
   virtual void ReleaseListeners();
 
@@ -560,17 +560,16 @@ class HttpBaseChannel : public nsHashPro
   // channel is a tracking third-party resource or not.
   nsCOMPtr<nsIURI> mOriginalReferrer;
   nsCOMPtr<nsIURI> mReferrer;
   nsCOMPtr<nsIApplicationCache> mApplicationCache;
   nsCOMPtr<nsIURI> mAPIRedirectToURI;
   nsCOMPtr<nsIURI> mProxyURI;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIURI> mTopWindowURI;
-  nsCOMPtr<nsIPrincipal> mTopWindowPrincipal;
   nsCOMPtr<nsIStreamListener> mListener;
   // An instance of nsHTTPCompressConv
   nsCOMPtr<nsIStreamListener> mCompressListener;
 
  private:
   // Proxy release all members above on main thread.
   void ReleaseMainThreadOnlyReferences();
 
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -2696,21 +2696,16 @@ nsresult HttpChannelChild::ContinueAsync
   GetClientSetCorsPreflightParameters(optionalCorsPreflightArgs);
 
   // NB: This call forces us to cache mTopWindowURI if we haven't already.
   nsCOMPtr<nsIURI> uri;
   GetTopWindowURI(mURI, getter_AddRefs(uri));
 
   SerializeURI(mTopWindowURI, openArgs.topWindowURI());
 
-  if (!mTopWindowPrincipal) {
-    GetTopWindowPrincipal(getter_AddRefs(mTopWindowPrincipal));
-  }
-  openArgs.topWindowPrincipal() = mTopWindowPrincipal;
-
   openArgs.preflightArgs() = optionalCorsPreflightArgs;
 
   openArgs.uploadStreamHasHeaders() = mUploadStreamHasHeaders;
   openArgs.priority() = mPriority;
   openArgs.classOfService() = mClassOfService;
   openArgs.redirectionLimit() = mRedirectionLimit;
   openArgs.allowSTS() = mAllowSTS;
   openArgs.thirdPartyFlags() = mThirdPartyFlags;
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -130,19 +130,19 @@ bool HttpChannelParent::Init(const HttpC
   LOG(("HttpChannelParent::Init [this=%p]\n", this));
   AUTO_PROFILER_LABEL("HttpChannelParent::Init", NETWORK);
   switch (aArgs.type()) {
     case HttpChannelCreationArgs::THttpChannelOpenArgs: {
       const HttpChannelOpenArgs& a = aArgs.get_HttpChannelOpenArgs();
       return DoAsyncOpen(
           a.uri(), a.original(), a.doc(), a.originalReferrer(),
           a.referrerPolicy(), a.apiRedirectTo(), a.topWindowURI(),
-          a.topWindowPrincipal(), a.loadFlags(), a.requestHeaders(),
-          a.requestMethod(), a.uploadStream(), a.uploadStreamHasHeaders(),
-          a.priority(), a.classOfService(), a.redirectionLimit(), a.allowSTS(),
+          a.loadFlags(), a.requestHeaders(), a.requestMethod(),
+          a.uploadStream(), a.uploadStreamHasHeaders(), a.priority(),
+          a.classOfService(), a.redirectionLimit(), a.allowSTS(),
           a.thirdPartyFlags(), a.resumeAt(), a.startPos(), a.entityID(),
           a.chooseApplicationCache(), a.appCacheClientID(), a.allowSpdy(),
           a.allowAltSvc(), a.beConservative(), a.tlsFlags(), a.loadInfo(),
           a.synthesizedResponseHead(), a.synthesizedSecurityInfoSerialization(),
           a.cacheKey(), a.requestContextID(), a.preflightArgs(),
           a.initialRwin(), a.blockAuthPrompt(),
           a.suspendAfterSynthesizeResponse(), a.allowStaleCacheContent(),
           a.contentTypeHint(), a.corsMode(), a.redirectMode(), a.channelId(),
@@ -381,23 +381,23 @@ void HttpChannelParent::InvokeAsyncOpen(
   }
 }
 
 bool HttpChannelParent::DoAsyncOpen(
     const URIParams& aURI, const Maybe<URIParams>& aOriginalURI,
     const Maybe<URIParams>& aDocURI,
     const Maybe<URIParams>& aOriginalReferrerURI,
     const uint32_t& aReferrerPolicy, const Maybe<URIParams>& aAPIRedirectToURI,
-    const Maybe<URIParams>& aTopWindowURI, nsIPrincipal* aTopWindowPrincipal,
-    const uint32_t& aLoadFlags, const RequestHeaderTuples& requestHeaders,
-    const nsCString& requestMethod, const Maybe<IPCStream>& uploadStream,
-    const bool& uploadStreamHasHeaders, const int16_t& priority,
-    const uint32_t& classOfService, const uint8_t& redirectionLimit,
-    const bool& allowSTS, const uint32_t& thirdPartyFlags,
-    const bool& doResumeAt, const uint64_t& startPos, const nsCString& entityID,
+    const Maybe<URIParams>& aTopWindowURI, const uint32_t& aLoadFlags,
+    const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
+    const Maybe<IPCStream>& uploadStream, const bool& uploadStreamHasHeaders,
+    const int16_t& priority, const uint32_t& classOfService,
+    const uint8_t& redirectionLimit, const bool& allowSTS,
+    const uint32_t& thirdPartyFlags, const bool& doResumeAt,
+    const uint64_t& startPos, const nsCString& entityID,
     const bool& chooseApplicationCache, const nsCString& appCacheClientID,
     const bool& allowSpdy, const bool& allowAltSvc, const bool& beConservative,
     const uint32_t& tlsFlags, const Maybe<LoadInfoArgs>& aLoadInfoArgs,
     const Maybe<nsHttpResponseHead>& aSynthesizedResponseHead,
     const nsCString& aSecurityInfoSerialization, const uint32_t& aCacheKey,
     const uint64_t& aRequestContextID,
     const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
     const uint32_t& aInitialRwin, const bool& aBlockAuthPrompt,
@@ -488,18 +488,16 @@ bool HttpChannelParent::DoAsyncOpen(
     MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
   if (apiRedirectToUri) httpChannel->RedirectTo(apiRedirectToUri);
   if (topWindowUri) {
     rv = httpChannel->SetTopWindowURI(topWindowUri);
     MOZ_ASSERT(NS_SUCCEEDED(rv));
   }
 
-  httpChannel->SetTopWindowPrincipal(aTopWindowPrincipal);
-
   if (aLoadFlags != nsIRequest::LOAD_NORMAL)
     httpChannel->SetLoadFlags(aLoadFlags);
 
   if (aForceMainDocumentChannel) {
     httpChannel->SetIsMainDocumentChannel(true);
   }
 
   for (uint32_t i = 0; i < requestHeaders.Length(); i++) {
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -138,27 +138,27 @@ class HttpChannelParent final : public n
                                    const bool& shouldIntercept);
 
   MOZ_MUST_USE bool DoAsyncOpen(
       const URIParams& uri, const Maybe<URIParams>& originalUri,
       const Maybe<URIParams>& docUri,
       const Maybe<URIParams>& originalReferrerUri,
       const uint32_t& referrerPolicy,
       const Maybe<URIParams>& internalRedirectUri,
-      const Maybe<URIParams>& topWindowUri, nsIPrincipal* aTopWindowPrincipal,
-      const uint32_t& loadFlags, const RequestHeaderTuples& requestHeaders,
-      const nsCString& requestMethod, const Maybe<IPCStream>& uploadStream,
-      const bool& uploadStreamHasHeaders, const int16_t& priority,
-      const uint32_t& classOfService, const uint8_t& redirectionLimit,
-      const bool& allowSTS, const uint32_t& thirdPartyFlags,
-      const bool& doResumeAt, const uint64_t& startPos,
-      const nsCString& entityID, const bool& chooseApplicationCache,
-      const nsCString& appCacheClientID, const bool& allowSpdy,
-      const bool& allowAltSvc, const bool& beConservative,
-      const uint32_t& tlsFlags, const Maybe<LoadInfoArgs>& aLoadInfoArgs,
+      const Maybe<URIParams>& topWindowUri, const uint32_t& loadFlags,
+      const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
+      const Maybe<IPCStream>& uploadStream, const bool& uploadStreamHasHeaders,
+      const int16_t& priority, const uint32_t& classOfService,
+      const uint8_t& redirectionLimit, const bool& allowSTS,
+      const uint32_t& thirdPartyFlags, const bool& doResumeAt,
+      const uint64_t& startPos, const nsCString& entityID,
+      const bool& chooseApplicationCache, const nsCString& appCacheClientID,
+      const bool& allowSpdy, const bool& allowAltSvc,
+      const bool& beConservative, const uint32_t& tlsFlags,
+      const Maybe<LoadInfoArgs>& aLoadInfoArgs,
       const Maybe<nsHttpResponseHead>& aSynthesizedResponseHead,
       const nsCString& aSecurityInfoSerialization, const uint32_t& aCacheKey,
       const uint64_t& aRequestContextID,
       const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
       const uint32_t& aInitialRwin, const bool& aBlockAuthPrompt,
       const bool& aSuspendAfterSynthesizeResponse,
       const bool& aAllowStaleCacheContent, const nsCString& aContentTypeHint,
       const uint32_t& aCorsMode, const uint32_t& aRedirectMode,
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -121,16 +121,18 @@
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ServiceWorkerUtils.h"
 #include "mozilla/net/AsyncUrlChannelClassifier.h"
 #include "mozilla/net/NeckoChannelParams.h"
 #include "mozilla/net/UrlClassifierFeatureFactory.h"
 #include "nsIWebNavigation.h"
 #include "HttpTrafficAnalyzer.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
 
 #ifdef MOZ_TASK_TRACER
 #  include "GeckoTaskTracer.h"
 #endif
 
 #ifdef MOZ_GECKO_PROFILER
 #  include "ProfilerMarkerPayload.h"
 #endif
@@ -7325,65 +7327,16 @@ nsresult nsHttpChannel::StartCrossProces
                                         mCrossProcessRedirectIdentifier);
 
   // This will suspend the channel
   rv = WaitForRedirectCallback();
 
   return rv;
 }
 
-static nsILoadInfo::CrossOriginOpenerPolicy GetCrossOriginOpenerPolicy(
-    nsHttpResponseHead *responseHead) {
-  MOZ_ASSERT(responseHead);
-
-  nsAutoCString openerPolicy;
-  Unused << responseHead->GetHeader(nsHttp::Cross_Origin_Opener_Policy,
-                                    openerPolicy);
-
-  // Cross-Origin-Opener-Policy = sameness [ RWS outgoing ]
-  // sameness = %s"same-origin" / %s"same-site" ; case-sensitive
-  // outgoing = %s"unsafe-allow-outgoing" ; case-sensitive
-
-  Tokenizer t(openerPolicy);
-  nsAutoCString sameness;
-  nsAutoCString outgoing;
-
-  // The return value will be true if we find any whitespace. If there is
-  // whitespace, then it must be followed by "unsafe-allow-outgoing" otherwise
-  // this is a malformed header value.
-  bool allowOutgoing = t.ReadUntil(Tokenizer::Token::Whitespace(), sameness);
-  if (allowOutgoing) {
-    t.SkipWhites();
-    bool foundEOF = t.ReadUntil(Tokenizer::Token::EndOfFile(), outgoing);
-    if (!foundEOF) {
-      // Malformed response. There should be no text after the second token.
-      return nsILoadInfo::OPENER_POLICY_NULL;
-    }
-    if (!outgoing.EqualsLiteral("unsafe-allow-outgoing")) {
-      // Malformed response. Only one allowed value for the second token.
-      return nsILoadInfo::OPENER_POLICY_NULL;
-    }
-  }
-
-  nsILoadInfo::CrossOriginOpenerPolicy policy = nsILoadInfo::OPENER_POLICY_NULL;
-  if (sameness.EqualsLiteral("same-origin")) {
-    policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN;
-    if (allowOutgoing) {
-      policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_OUTGOING;
-    }
-  } else if (sameness.EqualsLiteral("same-site")) {
-    policy = nsILoadInfo::OPENER_POLICY_SAME_SITE;
-    if (allowOutgoing) {
-      policy = nsILoadInfo::OPENER_POLICY_SAME_SITE_ALLOW_OUTGOING;
-    }
-  }
-
-  return policy;
-}
-
 static bool CompareCrossOriginOpenerPolicies(
     nsILoadInfo::CrossOriginOpenerPolicy documentPolicy,
     nsIPrincipal *documentOrigin,
     nsILoadInfo::CrossOriginOpenerPolicy resultPolicy,
     nsIPrincipal *resultOrigin) {
   if (documentPolicy == nsILoadInfo::OPENER_POLICY_NULL &&
       resultPolicy == nsILoadInfo::OPENER_POLICY_NULL) {
     return true;
@@ -7401,16 +7354,18 @@ static bool CompareCrossOriginOpenerPoli
   }
 
   if (documentPolicy & nsILoadInfo::OPENER_POLICY_SAME_SITE) {
     nsAutoCString siteOriginA;
     nsAutoCString siteOriginB;
 
     documentOrigin->GetSiteOrigin(siteOriginA);
     resultOrigin->GetSiteOrigin(siteOriginB);
+    LOG(("Comparing origin doc:[%s] with result:[%s]\n", siteOriginA.get(),
+         siteOriginB.get()));
     if (siteOriginA == siteOriginB) {
       return true;
     }
   }
 
   return false;
 }
 
@@ -7430,37 +7385,55 @@ nsHttpChannel::HasCrossOriginOpenerPolic
 
   // Maybe the channel failed and we have no response head?
   nsHttpResponseHead *head =
       mResponseHead ? mResponseHead : mCachedResponseHead;
   if (!head) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  RefPtr<mozilla::dom::BrowsingContext> ctx;
+  mLoadInfo->GetBrowsingContext(getter_AddRefs(ctx));
+
   // Get the policy of the active document, and the policy for the result.
-  nsILoadInfo::CrossOriginOpenerPolicy documentPolicy =
-      mLoadInfo->GetOpenerPolicy();
+  nsILoadInfo::CrossOriginOpenerPolicy documentPolicy = ctx->GetOpenerPolicy();
   nsILoadInfo::CrossOriginOpenerPolicy resultPolicy =
-      GetCrossOriginOpenerPolicy(head);
-
-  mLoadInfo->SetOpenerPolicy(resultPolicy);
+      nsILoadInfo::OPENER_POLICY_NULL;
+  GetCrossOriginOpenerPolicy(&resultPolicy);
+
+  if (!ctx->Canonical()->GetCurrentWindowGlobal()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
 
   // We use the top window principal as the documentOrigin
-  if (!mTopWindowPrincipal) {
-    GetTopWindowPrincipal(getter_AddRefs(mTopWindowPrincipal));
-  }
-
-  nsCOMPtr<nsIPrincipal> documentOrigin = mTopWindowPrincipal;
+  nsCOMPtr<nsIPrincipal> documentOrigin =
+      ctx->Canonical()->GetCurrentWindowGlobal()->DocumentPrincipal();
   nsCOMPtr<nsIPrincipal> resultOrigin;
 
   nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
       this, getter_AddRefs(resultOrigin));
 
-  if (!CompareCrossOriginOpenerPolicies(documentPolicy, documentOrigin,
-                                        resultPolicy, resultOrigin)) {
+  bool compareResult = CompareCrossOriginOpenerPolicies(
+      documentPolicy, documentOrigin, resultPolicy, resultOrigin);
+
+  if (LOG_ENABLED()) {
+    LOG(
+        ("nsHttpChannel::HasCrossOriginOpenerPolicyMismatch - "
+         "doc:%d result:%d - compare:%d\n",
+         documentPolicy, resultPolicy, compareResult));
+    nsAutoCString docOrigin;
+    nsCOMPtr<nsIURI> uri = documentOrigin->GetURI();
+    uri->GetSpec(docOrigin);
+    nsAutoCString resOrigin;
+    uri = resultOrigin->GetURI();
+    uri->GetSpec(resOrigin);
+    LOG(("doc origin:%s - res origin: %s\n", docOrigin.get(), resOrigin.get()));
+  }
+
+  if (!compareResult) {
     // If one of the following is false:
     //   - doc is the initial about:blank document
     //   - document's unsafe-allow-outgoing is true
     //   - resultPolicy is null
     // then we have a mismatch.
     if (!documentOrigin->GetIsNullPrincipal() ||
         !(documentPolicy &
           nsILoadInfo::OPENER_POLICY_UNSAFE_ALLOW_OUTGOING_FLAG) ||
--- a/netwerk/protocol/http/nsIHttpChannel.idl
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -483,16 +483,26 @@ interface nsIHttpChannel : nsIChannel
 
     /**
      * Unique ID of the channel, shared between parent and child. Needed if
      * the channel activity needs to be monitored across process boundaries,
      * like in devtools net monitor. See bug 1274556.
      */
     [must_use] attribute uint64_t channelId;
 
+%{ C++
+  inline uint64_t ChannelId()
+  {
+    uint64_t value = 0;
+    if (NS_SUCCEEDED(GetChannelId(&value))) {
+      return value;
+    }
+    return 0;
+  }
+%}
     /**
      * ID of the top-level document's inner window.  Identifies the content
      * this channels is being load in.
      */
     [must_use] attribute uint64_t topLevelContentWindowId;
 
     /**
      * Returns the classification flags if the channel has been processed by
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -1,25 +1,26 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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/. */
 
 #include "nsISupports.idl"
+#include "nsILoadInfo.idl"
 
 %{C++
 #include "nsStringFwd.h"
 #include "nsTArrayForwardDeclare.h"
 template<class T> class nsCOMArray;
 namespace mozilla {
 class TimeStamp;
 }
 %}
 [ptr] native StringArray(nsTArray<nsCString>);
-[ref] native StringArrayRef(const nsTArray<nsCString>);
+[ref] native CStringArrayRef(const nsTArray<nsCString>);
 [ref] native securityMessagesArray(nsCOMArray<nsISecurityConsoleMessage>);
 
 native TimeStamp(mozilla::TimeStamp);
 
 interface nsIAsyncInputStream;
 interface nsIAsyncOutputStream;
 interface nsIPrincipal;
 interface nsIProxyInfo;
@@ -300,35 +301,27 @@ interface nsIHttpChannelInternal : nsISu
     /**
      * Set top-level window URI to this channel only when the topWindowURI
      * is null and there is no window associated to this channel.
      * Note that the current usage of this method is only for xpcshell test.
      */
     [must_use] void setTopWindowURIIfUnknown(in nsIURI topWindowURI);
 
     /**
-     * Set top-level window Principal for this channel.
-     * This method is used in HttpChannelParent to pass the window principal
-     * from the child to nsHttpChannel.
-     * It's also called directly in xpcshell-tests
-     */
-    [must_use] void setTopWindowPrincipal(in nsIPrincipal topWindowPrincipal);
-
-    /**
      * Read the proxy URI, which, if non-null, will be used to resolve
      * proxies for this channel.
      */
     [must_use] readonly attribute nsIURI proxyURI;
 
     /**
      * Make cross-origin CORS loads happen with a CORS preflight, and specify
      * the CORS preflight parameters.
      */
     [noscript, notxpcom, nostdcall]
-    void setCorsPreflightParameters(in StringArrayRef unsafeHeaders);
+    void setCorsPreflightParameters(in CStringArrayRef unsafeHeaders);
 
     [noscript, notxpcom, nostdcall]
     void setAltDataForChild(in boolean aIsForChild);
 
     /**
      * When set to true, the channel will not pop any authentication prompts up
      * to the user.  When provided or cached credentials lead to an
      * authentication failure, that failure will be propagated to the channel
@@ -372,9 +365,12 @@ interface nsIHttpChannelInternal : nsISu
     [noscript, notxpcom, nostdcall]
     void setIPv4Disabled();
 
     /**
      * The channel will be loaded over IPv4, disabling IPv6.
      */
     [noscript, notxpcom, nostdcall]
     void setIPv6Disabled();
+
+    [noscript]
+    nsILoadInfo_CrossOriginOpenerPolicy getCrossOriginOpenerPolicy();
 };
deleted file mode 100644
--- a/netwerk/test/unit/test_crossOriginOpenerPolicy.js
+++ /dev/null
@@ -1,223 +0,0 @@
-"use strict";
-
-const {HttpServer} = ChromeUtils.import("resource://testing-common/httpd.js");
-const {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
-var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-let testServer = null;
-
-function inChildProcess() {
-  return Cc["@mozilla.org/xre/app-info;1"]
-           .getService(Ci.nsIXULRuntime)
-           .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-}
-
-class OnStartListener {
-  constructor(onStartCallback) {
-    this.callback = onStartCallback;
-    this.done = new Promise(resolve => { this.resolve = resolve; });
-  }
-
-  onStartRequest(request) {
-    this.callback(request.QueryInterface(Ci.nsIHttpChannel));
-  }
-
-  onDataAvailable(request, stream, offset, count) {
-    let string = NetUtil.readInputStreamToString(stream, count);
-  }
-
-  onStopRequest(request, status) { this.resolve(); }
-}
-
-
-function handler_null(metadata, response)
-{
-  info("request_null");
-  response.setStatusLine(metadata.httpVersion, 200, "OK");
-  response.setHeader("Content-Type", "text/plain", false);
-  let body = "foo";
-  response.bodyOutputStream.write(body, body.length);
-}
-
-function handler_same_origin(metadata, response)
-{
-  info("request_same_origin");
-  response.setStatusLine(metadata.httpVersion, 200, "OK");
-  response.setHeader("Content-Type", "text/plain", false);
-  response.setHeader("Cross-Origin-Opener-Policy", "same-origin", false);
-  let body = "foo";
-  response.bodyOutputStream.write(body, body.length);
-}
-
-function handler_same_origin_allow_outgoing(metadata, response)
-{
-  info("request_same_origin");
-  response.setStatusLine(metadata.httpVersion, 200, "OK");
-  response.setHeader("Content-Type", "text/plain", false);
-  response.setHeader("Cross-Origin-Opener-Policy", "same-origin unsafe-allow-outgoing", false);
-  let body = "foo";
-  response.bodyOutputStream.write(body, body.length);
-}
-
-function handler_same_site(metadata, response)
-{
-  info("request_same_site");
-  response.setStatusLine(metadata.httpVersion, 200, "OK");
-  response.setHeader("Content-Type", "text/plain", false);
-  response.setHeader("Cross-Origin-Opener-Policy", "same-site", false);
-  let body = "foo";
-  response.bodyOutputStream.write(body, body.length);
-}
-
-function handler_same_site_allow_outgoing(metadata, response)
-{
-  info("request_same_site");
-  response.setStatusLine(metadata.httpVersion, 200, "OK");
-  response.setHeader("Content-Type", "text/plain", false);
-  response.setHeader("Cross-Origin-Opener-Policy", "same-site unsafe-allow-outgoing", false);
-  let body = "foo";
-  response.bodyOutputStream.write(body, body.length);
-}
-
-add_task(async function test_setup() {
-  if (!inChildProcess()) {
-    Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com, example.com, example.org");
-    registerCleanupFunction(function() {
-      Services.prefs.clearUserPref("network.dns.localDomains");
-    });
-  }
-
-  testServer = new HttpServer();
-  testServer.start(-1);
-  registerCleanupFunction(() => testServer.stop(() => {}));
-  testServer.registerPathHandler("/null", handler_null);
-  testServer.registerPathHandler("/same-origin", handler_same_origin);
-  testServer.registerPathHandler("/same-origin_allow-outgoing", handler_same_origin_allow_outgoing);
-  testServer.registerPathHandler("/same-site", handler_same_site);
-  testServer.registerPathHandler("/same-site_allow-outgoing", handler_same_site_allow_outgoing);
-  testServer.identity.setPrimary("http", "example.com", testServer.identity.primaryPort);
-});
-
-// policyA, originA, policyB, originB
-async function generate_test(policyA, originA, policyB, originB, expectedResult, contentPolicyType = Ci.nsIContentPolicy.TYPE_DOCUMENT) {
-  let listener = new OnStartListener((channel) => {
-    if (!inChildProcess()) {
-      equal(channel.hasCrossOriginOpenerPolicyMismatch(), expectedResult, `check for mismatch testing ${policyA}, ${originA}, ${policyB}, ${originB}, ${expectedResult}`);
-    }
-  });
-
-  let chan = NetUtil.newChannel({
-    uri: `${originB}:${testServer.identity.primaryPort}/${policyB}`,
-    loadUsingSystemPrincipal: true,
-    contentPolicyType: contentPolicyType,
-  }).QueryInterface(Ci.nsIHttpChannel);
-
-  if (policyA == "null") {
-    chan.loadInfo.openerPolicy = Ci.nsILoadInfo.OPENER_POLICY_NULL;
-  } else if (policyA == "same-origin") {
-    chan.loadInfo.openerPolicy = Ci.nsILoadInfo.OPENER_POLICY_SAME_ORIGIN;
-  } else if (policyA == "same-origin_allow-outgoing") {
-    chan.loadInfo.openerPolicy = Ci.nsILoadInfo.OPENER_POLICY_SAME_ORIGIN_ALLOW_OUTGOING;
-  } else if (policyA == "same-site") {
-    chan.loadInfo.openerPolicy = Ci.nsILoadInfo.OPENER_POLICY_SAME_SITE;
-  } else if (policyA == "same-site_allow-outgoing") {
-    chan.loadInfo.openerPolicy = Ci.nsILoadInfo.OPENER_POLICY_SAME_SITE_ALLOW_OUTGOING;
-  }
-
-  let principalA = Services.scriptSecurityManager.createNullPrincipal({});
-  if (originA != "about:blank") {
-    principalA = Services.scriptSecurityManager.createCodebasePrincipal(Services.io.newURI(`${originA}:${testServer.identity.primaryPort}/${policyA}`), {});
-  }
-
-  chan.QueryInterface(Ci.nsIHttpChannelInternal).setTopWindowPrincipal(principalA);
-  equal(chan.loadInfo.externalContentPolicyType, contentPolicyType);
-
-  if (inChildProcess()) {
-    do_send_remote_message("prepare-test", {"channelId": chan.channelId,
-                                            "policyA": policyA,
-                                            "originA": `${originA}`,
-                                            "policyB": policyB,
-                                            "originB": `${originB}`,
-                                            "expectedResult": expectedResult});
-    await do_await_remote_message("test-ready");
-  }
-
-  chan.asyncOpen(listener);
-  return listener.done;
-}
-
-add_task(async function test_policies() {
-  // Note: This test only verifies that the result of
-  // nsIHttpChannel.hasCrossOriginOpenerPolicyMismatch() is correct. It does not
-  // check that the header is honored for auxiliary browsing contexts, how it
-  // affects a top-level browsing context opened from a sandboxed iframe,
-  // whether it affects the browsing context name, etc
-
-  await generate_test("null", "http://example.com", "null", "http://example.com", false);
-  await generate_test("null", "http://example.com", "same-origin", "http://example.com", true);
-  await generate_test("null", "http://example.com", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("null", "http://example.com", "same-site", "http://example.com", true);
-  await generate_test("null", "http://example.com", "same-site_allow-outgoing", "http://example.com", true);
-  await generate_test("null", "about:blank", "null", "http://example.com", false);
-  await generate_test("null", "about:blank", "same-origin", "http://example.com", true);
-  await generate_test("null", "about:blank", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("null", "about:blank", "same-site", "http://example.com", true);
-  await generate_test("null", "about:blank", "same-site_allow-outgoing", "http://example.com", true);
-
-  await generate_test("same-origin", "http://example.com", "null", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "http://example.com", "null", "http://example.com", true);
-  await generate_test("same-origin", "about:blank", "null", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "about:blank", "null", "http://example.com", false);
-  await generate_test("same-origin", "http://example.com", "same-origin", "http://example.com", false);
-  await generate_test("same-origin_allow-outgoing", "http://example.com", "same-origin", "http://example.com", true);
-  await generate_test("same-origin", "http://example.com", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "http://example.com", "same-origin_allow-outgoing", "http://example.com", false);
-  await generate_test("same-origin", "https://example.com", "same-origin", "http://example.com", true);
-  // true because policyB is not null
-  await generate_test("same-origin", "about:blank", "same-origin", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "about:blank", "same-origin", "http://example.com", true);
-  await generate_test("same-origin", "about:blank", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "about:blank", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("same-origin", "http://foo.example.com", "same-origin", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "http://foo.example.com", "same-origin", "http://example.com", true);
-  await generate_test("same-origin", "http://foo.example.com", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "http://foo.example.com", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("same-origin", "http://example.org", "same-origin", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "http://example.org", "same-origin", "http://example.com", true);
-  await generate_test("same-origin", "http://example.org", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "http://example.org", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("same-origin", "http://example.com", "same-site", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "http://example.com", "same-site", "http://example.com", true);
-  await generate_test("same-origin", "http://example.com", "same-site_allow-outgoing", "http://example.com", true);
-  await generate_test("same-origin_allow-outgoing", "http://example.com", "same-site_allow-outgoing", "http://example.com", true);
-
-  await generate_test("same-site", "http://example.com", "null", "http://example.com", true);
-  await generate_test("same-site_allow-outgoing", "http://example.com", "null", "http://example.com", true);
-  await generate_test("same-site", "about:blank", "null", "http://example.com", true);
-  await generate_test("same-site_allow-outgoing", "about:blank", "null", "http://example.com", false);
-  await generate_test("same-site", "http://example.com", "same-origin", "http://example.com", true);
-  await generate_test("same-site_allow-outgoing", "http://example.com", "same-origin", "http://example.com", true);
-  await generate_test("same-site", "http://example.com", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("same-site_allow-outgoing", "http://example.com", "same-origin_allow-outgoing", "http://example.com", true);
-  // true because policyB is not null
-  await generate_test("same-site", "about:blank", "same-origin", "http://example.com", true);
-  await generate_test("same-site_allow-outgoing", "about:blank", "same-origin", "http://example.com", true);
-  await generate_test("same-site", "about:blank", "same-origin_allow-outgoing", "http://example.com", true);
-  await generate_test("same-site_allow-outgoing", "about:blank", "same-origin_allow-outgoing", "http://example.com", true);
-  // true because they have different schemes
-  await generate_test("same-site", "https://example.com", "same-site", "http://example.com", true);
-  await generate_test("same-site_allow-outgoing", "https://example.com", "same-site", "http://example.com", true);
-  await generate_test("same-site", "https://example.com", "same-site_allow-outgoing", "http://example.com", true);
-  await generate_test("same-site_allow-outgoing", "https://example.com", "same-site_allow-outgoing", "http://example.com", true);
-
-  // These return false because the contentPolicyType is not TYPE_DOCUMENT
-  await generate_test("null", "http://example.com", "same-origin", "http://example.com", false, Ci.nsIContentPolicy.TYPE_OTHER);
-  await generate_test("same-origin", "http://example.com", "null", "http://example.com", false, Ci.nsIContentPolicy.TYPE_OTHER);
-
-  if (inChildProcess()) {
-    // send one message with no payload to clear the listener
-    info("finishing");
-    do_send_remote_message("prepare-test");
-    await do_await_remote_message("test-ready");
-  }
-});
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -424,10 +424,9 @@ skip-if = os == "android" # CP service i
 run-sequentially = node server exceptions dont replay well
 [test_esni_dns_fetch.js]
 # http2-using tests require node available
 skip-if = os == "android" || (os == "win" && processor == "aarch64") # bug 1536211
 [test_network_connectivity_service.js]
 skip-if = os == "android" # DNSv6 issues on android
 [test_suspend_channel_on_authRetry.js]
 [test_suspend_channel_on_examine_merged_response.js]
-[test_crossOriginOpenerPolicy.js]
 [test_bug1527293.js]
deleted file mode 100644
--- a/netwerk/test/unit_ipc/test_crossOriginOpenerPolicy_wrap.js
+++ /dev/null
@@ -1,47 +0,0 @@
-"use strict";
-
-const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-// This holds information about the test that is currently running.
-// We can only call nsIHttpChannel.crossOriginOpenerPolicyMismatch
-// in the main process, so for each channel opened in the child process we need
-// to perform the check in the parent. We intercept it during onStartRequest
-// with the "http-on-examine-response" observer notification.
-let gData = null;
-
-function observer(subject, topic, state) {
-  info("observer called with " + topic);
-  let chan = subject.QueryInterface(Ci.nsIHttpChannel);
-  equal(chan.channelId, gData.channelId);
-  equal(chan.hasCrossOriginOpenerPolicyMismatch(), gData.expectedResult, `check for mismatch testing ${gData.policyA}, ${gData.originA}, ${gData.policyB}, ${gData.originB}, ${gData.expectedResult}`);
-}
-
-function waitForTest() {
-  do_await_remote_message("prepare-test").then((data) => {
-    info(`prepare test: ${data}`);
-    gData = data;
-    do_send_remote_message("test-ready");
-
-    // So we don't hang, the child must send a final message with no data
-    // so we can clear the listener. Otherwise we call waitForTest again.
-    if (!data) {
-      info("parent test finishing");
-      return;
-    }
-    waitForTest();
-  });
-}
-
-function run_test() {
-  Services.obs.addObserver(observer, "http-on-examine-response");
-  Services.prefs.setCharPref("network.dns.localDomains", "foo.example.com, example.com, example.org");
-
-  registerCleanupFunction(function() {
-    Services.prefs.clearUserPref("network.dns.localDomains");
-    Services.obs.removeObserver(observer, "http-on-examine-response");
-  });
-
-  waitForTest();
-
-  run_test_in_child("../unit/test_crossOriginOpenerPolicy.js");
-}
--- a/netwerk/test/unit_ipc/xpcshell.ini
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -53,17 +53,16 @@ support-files =
   !/netwerk/test/unit/data/signed_win.exe
   !/netwerk/test/unit/test_alt-data_simple.js
   !/netwerk/test/unit/test_alt-data_stream.js
   !/netwerk/test/unit/test_alt-data_closeWithStatus.js
   !/netwerk/test/unit/test_channel_priority.js
   !/netwerk/test/unit/test_multipart_streamconv.js
   !/netwerk/test/unit/test_original_sent_received_head.js
   !/netwerk/test/unit/test_alt-data_cross_process.js
-  !/netwerk/test/unit/test_crossOriginOpenerPolicy.js
   child_cookie_header.js
 
 [test_bug528292_wrap.js]
 [test_cookie_header_stripped.js]
 [test_cacheflags_wrap.js]
 [test_cache-entry-id_wrap.js]
 [test_cache_jar_wrap.js]
 [test_channel_close_wrap.js]
@@ -102,9 +101,8 @@ skip-if = true
 [test_alt-data_closeWithStatus_wrap.js]
 [test_original_sent_received_head_wrap.js]
 [test_channel_id.js]
 [test_trackingProtection_annotateChannels_wrap1.js]
 [test_trackingProtection_annotateChannels_wrap2.js]
 [test_channel_priority_wrap.js]
 [test_multipart_streamconv_wrap.js]
 [test_alt-data_cross_process_wrap.js]
-[test_crossOriginOpenerPolicy_wrap.js]
--- a/parser/htmlparser/components.conf
+++ b/parser/htmlparser/components.conf
@@ -3,16 +3,17 @@
 # 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/.
 
 Headers = [
     'nsHTMLTags.h',
 ]
 
+InitFunc = 'nsParserInitialize'
 UnloadFunc = 'nsHTMLTags::ReleaseTable'
 
 Classes = [
     {
         'cid': '{2ce606b0-bee6-11d1-aad9-00805f8a3e14}',
         'contract_ids': [],
         'type': 'nsParser',
         'headers': ['/parser/htmlparser/nsParser.h'],
--- a/parser/htmlparser/nsParser.h
+++ b/parser/htmlparser/nsParser.h
@@ -380,9 +380,11 @@ class nsParser final : public nsIParser,
   nsString mUnusedInput;
   NotNull<const Encoding*> mCharset;
   nsCString mCommandStr;
 
   bool mProcessingNetworkData;
   bool mIsAboutBlank;
 };
 
+nsresult nsParserInitialize();
+
 #endif
--- a/parser/htmlparser/nsParserModule.cpp
+++ b/parser/htmlparser/nsParserModule.cpp
@@ -1,40 +1,18 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
-#include "mozilla/ModuleUtils.h"
-#include "nsParser.h"
-#include "nsParserCIID.h"
 #include "nsHTMLTags.h"
 
-//----------------------------------------------------------------------
-
-NS_GENERIC_FACTORY_CONSTRUCTOR(nsParser)
-
-NS_DEFINE_NAMED_CID(NS_PARSER_CID);
-
-static const mozilla::Module::CIDEntry kParserCIDs[] = {
-    {&kNS_PARSER_CID, false, nullptr, nsParserConstructor}, {nullptr}};
-
-static nsresult Initialize() {
+nsresult nsParserInitialize() {
   nsresult rv = nsHTMLTags::AddRefTable();
   NS_ENSURE_SUCCESS(rv, rv);
 
 #ifdef DEBUG
   CheckElementTable();
   nsHTMLTags::TestTagTable();
 #endif
 
   return rv;
 }
-
-static void Shutdown() { nsHTMLTags::ReleaseTable(); }
-
-extern const mozilla::Module kParserModule = {mozilla::Module::kVersion,
-                                              kParserCIDs,
-                                              nullptr,
-                                              nullptr,
-                                              nullptr,
-                                              Initialize,
-                                              Shutdown};
--- a/python/mozversioncontrol/mozversioncontrol/__init__.py
+++ b/python/mozversioncontrol/mozversioncontrol/__init__.py
@@ -343,17 +343,18 @@ class HgRepository(Repository):
             args.append(b'--ignored')
 
         # If output is empty, there are no entries of requested status, which
         # means we are clean.
         return not len(self._run(*args).strip())
 
     def push_to_try(self, message):
         try:
-            subprocess.check_call((self._tool, 'push-to-try', '-m', message), cwd=self.path)
+            subprocess.check_call((self._tool, 'push-to-try', '-m', message), cwd=self.path,
+                                  env=self._env)
         except subprocess.CalledProcessError:
             try:
                 self._run('showconfig', 'extensions.push-to-try')
             except subprocess.CalledProcessError:
                 raise MissingVCSExtension('push-to-try')
             raise
         finally:
             self._run('revert', '-a')
--- a/security/manager/ssl/LocalCertService.cpp
+++ b/security/manager/ssl/LocalCertService.cpp
@@ -445,30 +445,9 @@ LocalCertService::GetLoginPromptRequired
     }
   }
 
   *aRequired =
       PK11_NeedLogin(slot.get()) && !PK11_IsLoggedIn(slot.get(), nullptr);
   return NS_OK;
 }
 
-#define LOCALCERTSERVICE_CID                         \
-  {                                                  \
-    0x47402be2, 0xe653, 0x45d0, {                    \
-      0x8d, 0xaa, 0x9f, 0x0d, 0xce, 0x0a, 0xc1, 0x48 \
-    }                                                \
-  }
-
-NS_GENERIC_FACTORY_CONSTRUCTOR(LocalCertService)
-
-NS_DEFINE_NAMED_CID(LOCALCERTSERVICE_CID);
-
-static const Module::CIDEntry kLocalCertServiceCIDs[] = {
-    {&kLOCALCERTSERVICE_CID, false, nullptr, LocalCertServiceConstructor},
-    {nullptr}};
-
-static const Module::ContractIDEntry kLocalCertServiceContracts[] = {
-    {LOCALCERTSERVICE_CONTRACTID, &kLOCALCERTSERVICE_CID}, {nullptr}};
-
-extern const Module kLocalCertServiceModule = {
-    Module::kVersion, kLocalCertServiceCIDs, kLocalCertServiceContracts};
-
 }  // namespace mozilla
--- a/testing/mochitest/static/chromexul.template.txt
+++ b/testing/mochitest/static/chromexul.template.txt
@@ -1,18 +1,16 @@
 <?xml version="1.0"?>
 <?xml-stylesheet type="text/css" href="chrome://global/skin"?>
 <?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
 <window title="TODO: Insert title here"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/AddTask.js" />
-  <script type="application/javascript"><![CDATA[
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+  <script src="chrome://mochikit/content/tests/SimpleTest/AddTask.js" />
+  <script><![CDATA[
     add_task(async function test_TODO() {
       ok(true, "TODO: implement the test");
     });
   ]]></script>
 
   <body xmlns="http://www.w3.org/1999/xhtml">
   </body>
 </window>
--- a/testing/mochitest/static/plainxul.template.txt
+++ b/testing/mochitest/static/plainxul.template.txt
@@ -1,12 +1,12 @@
 <?xml version="1.0"?>
 <?xml-stylesheet type="text/css" href="chrome://global/skin"?>
 <?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
 <window title="TODO: Insert title here"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript"><![CDATA[
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script><![CDATA[
     ok(true, "TODO: implement the test");
   ]]></script>
   <body xmlns="http://www.w3.org/1999/xhtml">
   </body>
 </window>
--- a/testing/mozharness/configs/android/androidx86_7_0.py
+++ b/testing/mozharness/configs/android/androidx86_7_0.py
@@ -5,20 +5,20 @@
 
 config = {
     "tooltool_manifest_path": "testing/config/tooltool-manifests/androidx86_7_0/releng.manifest",
     "emulator_manifest": """
         [
         {
             "algorithm": "sha512",
             "visibility": "internal",
-            "filename": "android-sdk_r27.3.10-linux-x86emu.tar.gz",
+            "filename": "android-sdk_r28.0.25.0-linux-x86emu.tar.gz",
             "unpack": true,
-            "digest": "1e226ec63fbfd9e33b2522da0f35960773cbfd6ab310167c49a5d5caf8333ac44a692cf2b20e00acceda02a3dca731883d65c4401a0d3eb79fdf11007ec04e8e",
-            "size": 131714575
+            "digest": "e62acc91f41ccef65a4937a2672fcb56362e9946b806bacc25854035b57d5bd2d525a9c7d660a643ab6381ae2e3b660be7fea70e302ed314c4b07880b2328e18",
+            "size": 241459387
         }
         ] """,
     "emulator_avd_name": "test-1",
     "emulator_process_name": "emulator64-x86",
     "emulator_extra_args": "-gpu swiftshader_indirect -skip-adb-auth -verbose -show-kernel -use-system-libs -ranchu -selinux permissive -memory 3072 -cores 4",
     "exes": {
         'adb': '%(abs_work_dir)s/android-sdk-linux/platform-tools/adb',
     },
--- a/testing/mozharness/mozharness/mozilla/testing/android.py
+++ b/testing/mozharness/mozharness/mozilla/testing/android.py
@@ -158,16 +158,22 @@ class AndroidMixin(object):
         else:
             sdk_path = os.path.join(self.abs_dirs['abs_work_dir'], 'android-sdk-linux')
         if os.path.exists(sdk_path):
             env['ANDROID_SDK_HOME'] = sdk_path
             self.info("Found sdk at %s" % sdk_path)
         else:
             self.warning("Android sdk missing? Not found at %s" % sdk_path)
 
+        # enable EGL 3.0 in advancedFeatures.ini
+        AF_FILE = os.path.join(sdk_path, "advancedFeatures.ini")
+        with open(AF_FILE, 'w') as f:
+            f.write("GLESDynamicVersion=on\n")
+        self.info("set GLESDynamicVersion=on in %s" % AF_FILE)
+
         # extra diagnostics for kvm acceleration
         emu = self.config.get('emulator_process_name')
         if os.path.exists('/dev/kvm') and emu and 'x86' in emu:
             try:
                 self.run_command(['ls', '-l', '/dev/kvm'])
                 self.run_command(['kvm-ok'])
                 self.run_command(["emulator", "-accel-check"], env=env)
             except Exception as e:
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/cross-origin-opener/new_window_same_site.html.ini
@@ -0,0 +1,2 @@
+[new_window_same_site.html]
+  prefs: [browser.tabs.remote.useCrossOriginOpenerPolicy:true]
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener/new_window_same_site.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script src="/common/get-host-info.sub.js"></script>
+
+<script>
+
+const CHANNEL_NAME = "new_window_same_site";
+
+async_test(t => {
+  let w = window.open(`${get_host_info().HTTP_REMOTE_ORIGIN}/html/cross-origin-opener/resources/window.sub.html?channel=${CHANNEL_NAME}`, "window_name", "height=200,width=250");
+
+  // w will be closed by its postback iframe. When out of process,
+  // window.close() does not work.
+  t.add_cleanup(() => w.close());
+
+  let bc = new BroadcastChannel(CHANNEL_NAME);
+  bc.onmessage = t.step_func_done((event) => {
+    let payload = event.data;
+    assert_equals(payload.name, "window_name");
+    assert_equals(payload.opener, true);
+  });
+}, "same-site policy works");
+
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener/new_window_same_site.html.headers
@@ -0,0 +1,1 @@
+Cross-Origin-Opener-Policy: same-site
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener/resources/postback.sub.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script>
+
+let channelName = new URL(location).searchParams.get("channel");
+let bc = new BroadcastChannel(channelName);
+window.addEventListener("message", (event) => {
+  bc.postMessage(event.data);
+  window.parent.close();
+}, false);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener/resources/window.sub.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<meta charset=utf-8>
+
+<script src="/common/get-host-info.sub.js"></script>
+<div id="status"> </div>
+
+<script type="text/javascript">
+
+  let iframe = document.createElement("iframe");
+  iframe.onload = () => {
+    let payload = { name: self.name, opener: !!self.opener };
+    iframe.contentWindow.postMessage(payload, "*");
+  };
+  let channelName = new URL(location).searchParams.get("channel");
+  iframe.src = `${get_host_info().HTTP_ORIGIN}/html/cross-origin-opener/resources/postback.sub.html?channel=${channelName}`;
+  document.body.appendChild(iframe);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/cross-origin-opener/resources/window.sub.html.headers
@@ -0,0 +1,1 @@
+Cross-Origin-Opener-Policy: same-site
new file mode 100644
--- /dev/null
+++ b/third_party/python/biplist/AUTHORS
@@ -0,0 +1,3 @@
+Andrew Wooster (andrew@planetaryscale.com)
+
+Ported to Python 3 by Kevin Kelley (kelleyk@kelleyk.net)
new file mode 100644
--- /dev/null
+++ b/third_party/python/biplist/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2010, Andrew Wooster
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of biplist nor the names of its contributors may be
+      used to endorse or promote products derived from this software without 
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
new file mode 100644
--- /dev/null
+++ b/third_party/python/biplist/MANIFEST.in
@@ -0,0 +1,4 @@
+include LICENSE
+include AUTHORS
+include README.md
+recursive-include tests *.py *.plist
new file mode 100644
--- /dev/null
+++ b/third_party/python/biplist/PKG-INFO
@@ -0,0 +1,24 @@
+Metadata-Version: 1.1
+Name: biplist
+Version: 1.0.3
+Summary: biplist is a library for reading/writing binary plists.
+Home-page: https://bitbucket.org/wooster/biplist
+Author: Andrew Wooster
+Author-email: andrew@planetaryscale.com
+License: BSD
+Download-URL: https://bitbucket.org/wooster/biplist/downloads/biplist-1.0.3.tar.gz
+Description: `biplist` is a binary plist parser/generator for Python.
+        
+        Binary Property List (plist) files provide a faster and smaller serialization
+        format for property lists on OS X. This is a library for generating binary
+        plists which can be read by OS X, iOS, or other clients.
+        
+        This module requires Python 2.6 or higher or Python 3.4 or higher.
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: BSD License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Text Processing :: Markup
new file mode 100644
--- /dev/null
+++ b/third_party/python/biplist/README.md
@@ -0,0 +1,62 @@
+biplist
+=======
+`biplist` is a binary plist parser/generator for Python.
+
+## About
+
+Binary Property List (plist) files provide a faster and smaller serialization
+format for property lists on OS X. This is a library for generating binary
+plists which can be read by OS X, iOS, or other clients.
+
+## API
+
+The API models the `plistlib` API, and will call through to plistlib when
+XML serialization or deserialization is required.
+
+To generate plists with UID values, wrap the values with the `Uid` object. The
+value must be an int.
+
+To generate plists with `NSData`/`CFData` values, wrap the values with the
+`Data` object. The value must be a string.
+
+Date values can only be `datetime.datetime` objects.
+
+The exceptions `InvalidPlistException` and `NotBinaryPlistException` may be 
+thrown to indicate that the data cannot be serialized or deserialized as
+a binary plist.
+
+## Installation
+
+To install the latest release version:
+
+`sudo easy_install biplist`
+
+## Examples
+
+Plist generation example:
+
+```python
+from biplist import *
+from datetime import datetime
+plist = {'aKey':'aValue',
+         '0':1.322,
+         'now':datetime.now(),
+         'list':[1,2,3],
+         'tuple':('a','b','c')
+         }
+try:
+    writePlist(plist, "example.plist")
+except (InvalidPlistException, NotBinaryPlistException), e:
+    print "Something bad happened:", e
+```
+
+Plist parsing example:
+
+```python
+from biplist import *
+try:
+    plist = readPlist("example.plist")
+    print plist
+except (InvalidPlistException, NotBinaryPlistException), e:
+    print "Not a plist:", e
+```
new file mode 100644
--- /dev/null
+++ b/third_party/python/biplist/biplist/__init__.py
@@ -0,0 +1,977 @@
+"""biplist -- a library for reading and writing binary property list files.
+
+Binary Property List (plist) files provide a faster and smaller serialization
+format for property lists on OS X. This is a library for generating binary
+plists which can be read by OS X, iOS, or other clients.
+
+The API models the plistlib API, and will call through to plistlib when
+XML serialization or deserialization is required.
+
+To generate plists with UID values, wrap the values with the Uid object. The
+value must be an int.
+
+To generate plists with NSData/CFData values, wrap the values with the
+Data object. The value must be a string.
+
+Date values can only be datetime.datetime objects.
+
+The exceptions InvalidPlistException and NotBinaryPlistException may be 
+thrown to indicate that the data cannot be serialized or deserialized as
+a binary plist.
+
+Plist generation example:
+    
+    from biplist import *
+    from datetime import datetime
+    plist = {'aKey':'aValue',
+             '0':1.322,
+             'now':datetime.now(),
+             'list':[1,2,3],
+             'tuple':('a','b','c')
+             }
+    try:
+        writePlist(plist, "example.plist")
+    except (InvalidPlistException, NotBinaryPlistException), e:
+        print "Something bad happened:", e
+
+Plist parsing example:
+
+    from biplist import *
+    try:
+        plist = readPlist("example.plist")
+        print plist
+    except (InvalidPlistException, NotBinaryPlistException), e:
+        print "Not a plist:", e
+"""
+
+from collections import namedtuple
+import datetime
+import io
+import math
+import plistlib
+from struct import pack, unpack, unpack_from
+from struct import error as struct_error
+import sys
+import time
+
+try:
+    unicode
+    unicodeEmpty = r''
+except NameError:
+    unicode = str
+    unicodeEmpty = ''
+try:
+    long
+except NameError:
+    long = int
+try:
+    {}.iteritems
+    iteritems = lambda x: x.iteritems()
+except AttributeError:
+    iteritems = lambda x: x.items()
+
+__all__ = [
+    'Uid', 'Data', 'readPlist', 'writePlist', 'readPlistFromString',
+    'writePlistToString', 'InvalidPlistException', 'NotBinaryPlistException'
+]
+
+# Apple uses Jan 1, 2001 as a base for all plist date/times.
+apple_reference_date = datetime.datetime.utcfromtimestamp(978307200)
+
+class Uid(object):
+    """Wrapper around integers for representing UID values. This
+       is used in keyed archiving."""
+    integer = 0
+    def __init__(self, integer):
+        self.integer = integer
+    
+    def __repr__(self):
+        return "Uid(%d)" % self.integer
+    
+    def __eq__(self, other):
+        if isinstance(self, Uid) and isinstance(other, Uid):
+            return self.integer == other.integer
+        return False
+    
+    def __cmp__(self, other):
+        return self.integer - other.integer
+    
+    def __lt__(self, other):
+        return self.integer < other.integer
+    
+    def __hash__(self):
+        return self.integer
+    
+    def __int__(self):
+        return int(self.integer)
+
+class Data(bytes):
+    """Wrapper around bytes to distinguish Data values."""
+
+class InvalidPlistException(Exception):
+    """Raised when the plist is incorrectly formatted."""
+
+class NotBinaryPlistException(Exception):
+    """Raised when a binary plist was expected but not encountered."""
+
+def readPlist(pathOrFile):
+    """Raises NotBinaryPlistException, InvalidPlistException"""
+    didOpen = False
+    result = None
+    if isinstance(pathOrFile, (bytes, unicode)):
+        pathOrFile = open(pathOrFile, 'rb')
+        didOpen = True
+    try:
+        reader = PlistReader(pathOrFile)
+        result = reader.parse()
+    except NotBinaryPlistException as e:
+        try:
+            pathOrFile.seek(0)
+            result = None
+            if hasattr(plistlib, 'loads'):
+                contents = None
+                if isinstance(pathOrFile, (bytes, unicode)):
+                    with open(pathOrFile, 'rb') as f:
+                        contents = f.read()
+                else:
+                    contents = pathOrFile.read()
+                result = plistlib.loads(contents)
+            else:
+                result = plistlib.readPlist(pathOrFile)
+            result = wrapDataObject(result, for_binary=True)
+        except Exception as e:
+            raise InvalidPlistException(e)
+    finally:
+        if didOpen:
+            pathOrFile.close()
+    return result
+
+def wrapDataObject(o, for_binary=False):
+    if isinstance(o, Data) and not for_binary:
+        v = sys.version_info
+        if not (v[0] >= 3 and v[1] >= 4):
+            o = plistlib.Data(o)
+    elif isinstance(o, (bytes, plistlib.Data)) and for_binary:
+        if hasattr(o, 'data'):
+            o = Data(o.data)
+    elif isinstance(o, tuple):
+        o = wrapDataObject(list(o), for_binary)
+        o = tuple(o)
+    elif isinstance(o, list):
+        for i in range(len(o)):
+            o[i] = wrapDataObject(o[i], for_binary)
+    elif isinstance(o, dict):
+        for k in o:
+            o[k] = wrapDataObject(o[k], for_binary)
+    return o
+
+def writePlist(rootObject, pathOrFile, binary=True):
+    if not binary:
+        rootObject = wrapDataObject(rootObject, binary)
+        if hasattr(plistlib, "dump"):
+            if isinstance(pathOrFile, (bytes, unicode)):
+                with open(pathOrFile, 'wb') as f:
+                    return plistlib.dump(rootObject, f)
+            else:
+                return plistlib.dump(rootObject, pathOrFile)
+        else:
+            return plistlib.writePlist(rootObject, pathOrFile)
+    else:
+        didOpen = False
+        if isinstance(pathOrFile, (bytes, unicode)):
+            pathOrFile = open(pathOrFile, 'wb')
+            didOpen = True
+        writer = PlistWriter(pathOrFile)
+        result = writer.writeRoot(rootObject)
+        if didOpen:
+            pathOrFile.close()
+        return result
+
+def readPlistFromString(data):
+    return readPlist(io.BytesIO(data))
+
+def writePlistToString(rootObject, binary=True):
+    if not binary:
+        rootObject = wrapDataObject(rootObject, binary)
+        if hasattr(plistlib, "dumps"):
+            return plistlib.dumps(rootObject)
+        elif hasattr(plistlib, "writePlistToBytes"):
+            return plistlib.writePlistToBytes(rootObject)
+        else:
+            return plistlib.writePlistToString(rootObject)
+    else:
+        ioObject = io.BytesIO()
+        writer = PlistWriter(ioObject)
+        writer.writeRoot(rootObject)
+        return ioObject.getvalue()
+
+def is_stream_binary_plist(stream):
+    stream.seek(0)
+    header = stream.read(7)
+    if header == b'bplist0':
+        return True
+    else:
+        return False
+
+PlistTrailer = namedtuple('PlistTrailer', 'offsetSize, objectRefSize, offsetCount, topLevelObjectNumber, offsetTableOffset')
+PlistByteCounts = namedtuple('PlistByteCounts', 'nullBytes, boolBytes, intBytes, realBytes, dateBytes, dataBytes, stringBytes, uidBytes, arrayBytes, setBytes, dictBytes')
+
+class PlistReader(object):
+    file = None
+    contents = ''
+    offsets = None
+    trailer = None
+    currentOffset = 0
+    # Used to detect recursive object references.
+    offsetsStack = []
+    
+    def __init__(self, fileOrStream):
+        """Raises NotBinaryPlistException."""
+        self.reset()
+        self.file = fileOrStream
+    
+    def parse(self):
+        return self.readRoot()
+    
+    def reset(self):
+        self.trailer = None
+        self.contents = ''
+        self.offsets = []
+        self.currentOffset = 0
+        self.offsetsStack = []
+    
+    def readRoot(self):
+        result = None
+        self.reset()
+        # Get the header, make sure it's a valid file.
+        if not is_stream_binary_plist(self.file):
+            raise NotBinaryPlistException()
+        self.file.seek(0)
+        self.contents = self.file.read()
+        if len(self.contents) < 32:
+            raise InvalidPlistException("File is too short.")
+        trailerContents = self.contents[-32:]
+        try:
+            self.trailer = PlistTrailer._make(unpack("!xxxxxxBBQQQ", trailerContents))
+            
+            if pow(2, self.trailer.offsetSize*8) < self.trailer.offsetTableOffset:
+                raise InvalidPlistException("Offset size insufficient to reference all objects.")
+            
+            if pow(2, self.trailer.objectRefSize*8) < self.trailer.offsetCount:
+                raise InvalidPlistException("Too many offsets to represent in size of object reference representation.")
+            
+            offset_size = self.trailer.offsetSize * self.trailer.offsetCount
+            offset = self.trailer.offsetTableOffset
+            
+            if offset + offset_size > pow(2, 64):
+                raise InvalidPlistException("Offset table is excessively long.")
+            
+            if self.trailer.offsetSize > 16:
+                raise InvalidPlistException("Offset size is greater than maximum integer size.")
+            
+            if self.trailer.objectRefSize == 0:
+                raise InvalidPlistException("Object reference size is zero.")
+            
+            if offset >= len(self.contents) - 32:
+                raise InvalidPlistException("Offset table offset is too large.")
+            
+            if offset < len("bplist00x"):
+                raise InvalidPlistException("Offset table offset is too small.")
+            
+            if self.trailer.topLevelObjectNumber >= self.trailer.offsetCount:
+                raise InvalidPlistException("Top level object number is larger than the number of objects.")
+            
+            offset_contents = self.contents[offset:offset+offset_size]
+            offset_i = 0
+            offset_table_length = len(offset_contents)
+            
+            while offset_i < self.trailer.offsetCount:
+                begin = self.trailer.offsetSize*offset_i
+                end = begin+self.trailer.offsetSize
+                if end > offset_table_length:
+                    raise InvalidPlistException("End of object is at invalid offset %d in offset table of length %d" % (end, offset_table_length))
+                tmp_contents = offset_contents[begin:end]
+                tmp_sized = self.getSizedInteger(tmp_contents, self.trailer.offsetSize)
+                self.offsets.append(tmp_sized)
+                offset_i += 1
+            self.setCurrentOffsetToObjectNumber(self.trailer.topLevelObjectNumber)
+            result = self.readObject()
+        except TypeError as e:
+            raise InvalidPlistException(e)
+        return result
+    
+    def setCurrentOffsetToObjectNumber(self, objectNumber):
+        if objectNumber > len(self.offsets) - 1:
+            raise InvalidPlistException("Invalid offset number: %d" % objectNumber)
+        self.currentOffset = self.offsets[objectNumber]
+        if self.currentOffset in self.offsetsStack:
+            raise InvalidPlistException("Recursive data structure detected in object: %d" % objectNumber)
+    
+    def beginOffsetProtection(self):
+        self.offsetsStack.append(self.currentOffset)
+        return self.currentOffset
+    
+    def endOffsetProtection(self, offset):
+        try:
+            index = self.offsetsStack.index(offset)
+            self.offsetsStack = self.offsetsStack[:index]
+        except ValueError as e:
+            pass
+    
+    def readObject(self):
+        protection = self.beginOffsetProtection()
+        result = None
+        tmp_byte = self.contents[self.currentOffset:self.currentOffset+1]
+        if len(tmp_byte) != 1:
+            raise InvalidPlistException("No object found at offset: %d" % self.currentOffset)
+        marker_byte = unpack("!B", tmp_byte)[0]
+        format = (marker_byte >> 4) & 0x0f
+        extra = marker_byte & 0x0f
+        self.currentOffset += 1
+        
+        def proc_extra(extra):
+            if extra == 0b1111:
+                extra = self.readObject()
+            return extra
+        
+        # bool, null, or fill byte
+        if format == 0b0000:
+            if extra == 0b0000:
+                result = None
+            elif extra == 0b1000:
+                result = False
+            elif extra == 0b1001:
+                result = True
+            elif extra == 0b1111:
+                pass # fill byte
+            else:
+                raise InvalidPlistException("Invalid object found at offset: %d" % (self.currentOffset - 1))
+        # int
+        elif format == 0b0001:
+            result = self.readInteger(pow(2, extra))
+        # real
+        elif format == 0b0010:
+            result = self.readReal(extra)
+        # date
+        elif format == 0b0011 and extra == 0b0011:
+            result = self.readDate()
+        # data
+        elif format == 0b0100:
+            extra = proc_extra(extra)
+            result = self.readData(extra)
+        # ascii string
+        elif format == 0b0101:
+            extra = proc_extra(extra)
+            result = self.readAsciiString(extra)
+        # Unicode string
+        elif format == 0b0110:
+            extra = proc_extra(extra)
+            result = self.readUnicode(extra)
+        # uid
+        elif format == 0b1000:
+            result = self.readUid(extra)
+        # array
+        elif format == 0b1010:
+            extra = proc_extra(extra)
+            result = self.readArray(extra)
+        # set
+        elif format == 0b1100:
+            extra = proc_extra(extra)
+            result = set(self.readArray(extra))
+        # dict
+        elif format == 0b1101:
+            extra = proc_extra(extra)
+            result = self.readDict(extra)
+        else:    
+            raise InvalidPlistException("Invalid object found: {format: %s, extra: %s}" % (bin(format), bin(extra)))
+        self.endOffsetProtection(protection)
+        return result
+    
+    def readContents(self, length, description="Object contents"):
+        end = self.currentOffset + length
+        if end >= len(self.contents) - 32:
+            raise InvalidPlistException("%s extends into trailer" % description)
+        elif length < 0:
+            raise InvalidPlistException("%s length is less than zero" % length)
+        data = self.contents[self.currentOffset:end]
+        return data
+    
+    def readInteger(self, byteSize):
+        data = self.readContents(byteSize, "Integer")
+        self.currentOffset = self.currentOffset + byteSize
+        return self.getSizedInteger(data, byteSize, as_number=True)
+    
+    def readReal(self, length):
+        to_read = pow(2, length)
+        data = self.readContents(to_read, "Real")
+        if length == 2: # 4 bytes
+            result = unpack('>f', data)[0]
+        elif length == 3: # 8 bytes
+            result = unpack('>d', data)[0]
+        else:
+            raise InvalidPlistException("Unknown Real of length %d bytes" % to_read)
+        return result
+    
+    def readRefs(self, count):    
+        refs = []
+        i = 0
+        while i < count:
+            fragment = self.readContents(self.trailer.objectRefSize, "Object reference")
+            ref = self.getSizedInteger(fragment, len(fragment))
+            refs.append(ref)
+            self.currentOffset += self.trailer.objectRefSize
+            i += 1
+        return refs
+    
+    def readArray(self, count):
+        if not isinstance(count, (int, long)):
+            raise InvalidPlistException("Count of entries in dict isn't of integer type.")
+        result = []
+        values = self.readRefs(count)
+        i = 0
+        while i < len(values):
+            self.setCurrentOffsetToObjectNumber(values[i])
+            value = self.readObject()
+            result.append(value)
+            i += 1
+        return result
+    
+    def readDict(self, count):
+        if not isinstance(count, (int, long)):
+            raise InvalidPlistException("Count of keys/values in dict isn't of integer type.")
+        result = {}
+        keys = self.readRefs(count)
+        values = self.readRefs(count)
+        i = 0
+        while i < len(keys):
+            self.setCurrentOffsetToObjectNumber(keys[i])
+            key = self.readObject()
+            self.setCurrentOffsetToObjectNumber(values[i])
+            value = self.readObject()
+            result[key] = value
+            i += 1
+        return result
+    
+    def readAsciiString(self, length):
+        if not isinstance(length, (int, long)):
+            raise InvalidPlistException("Length of ASCII string isn't of integer type.")
+        data = self.readContents(length, "ASCII string")
+        result = unpack("!%ds" % length, data)[0]
+        self.currentOffset += length
+        return str(result.decode('ascii'))
+    
+    def readUnicode(self, length):
+        if not isinstance(length, (int, long)):
+            raise InvalidPlistException("Length of Unicode string isn't of integer type.")
+        actual_length = length*2
+        data = self.readContents(actual_length, "Unicode string")
+        self.currentOffset += actual_length
+        return data.decode('utf_16_be')
+    
+    def readDate(self):
+        data = self.readContents(8, "Date")
+        x = unpack(">d", data)[0]
+        if math.isnan(x):
+            raise InvalidPlistException("Date is NaN")
+        # Use timedelta to workaround time_t size limitation on 32-bit python.
+        try:
+            result = datetime.timedelta(seconds=x) + apple_reference_date
+        except OverflowError:
+            if x > 0:
+                result = datetime.datetime.max
+            else:
+                result = datetime.datetime.min
+        self.currentOffset += 8
+        return result
+    
+    def readData(self, length):
+        if not isinstance(length, (int, long)):
+            raise InvalidPlistException("Length of data isn't of integer type.")
+        result = self.readContents(length, "Data")
+        self.currentOffset += length
+        return Data(result)
+    
+    def readUid(self, length):
+        if not isinstance(length, (int, long)):
+            raise InvalidPlistException("Uid length isn't of integer type.")
+        return Uid(self.readInteger(length+1))
+    
+    def getSizedInteger(self, data, byteSize, as_number=False):
+        """Numbers of 8 bytes are signed integers when they refer to numbers, but unsigned otherwise."""
+        result = 0
+        if byteSize == 0:
+            raise InvalidPlistException("Encountered integer with byte size of 0.")
+        # 1, 2, and 4 byte integers are unsigned
+        elif byteSize == 1:
+            result = unpack('>B', data)[0]
+        elif byteSize == 2:
+            result = unpack('>H', data)[0]
+        elif byteSize == 4:
+            result = unpack('>L', data)[0]
+        elif byteSize == 8:
+            if as_number:
+                result = unpack('>q', data)[0]
+            else:
+                result = unpack('>Q', data)[0]
+        elif byteSize <= 16:
+            # Handle odd-sized or integers larger than 8 bytes
+            # Don't naively go over 16 bytes, in order to prevent infinite loops.
+            result = 0
+            if hasattr(int, 'from_bytes'):
+                result = int.from_bytes(data, 'big')
+            else:
+                for byte in data:
+                    if not isinstance(byte, int): # Python3.0-3.1.x return ints, 2.x return str
+                        byte = unpack_from('>B', byte)[0]
+                    result = (result << 8) | byte
+        else:
+            raise InvalidPlistException("Encountered integer longer than 16 bytes.")
+        return result
+
+class HashableWrapper(object):
+    def __init__(self, value):
+        self.value = value
+    def __repr__(self):
+        return "<HashableWrapper: %s>" % [self.value]
+
+class BoolWrapper(object):
+    def __init__(self, value):
+        self.value = value
+    def __repr__(self):
+        return "<BoolWrapper: %s>" % self.value
+
+class FloatWrapper(object):
+    _instances = {}
+    def __new__(klass, value):
+        # Ensure FloatWrapper(x) for a given float x is always the same object
+        wrapper = klass._instances.get(value)
+        if wrapper is None:
+            wrapper = object.__new__(klass)
+            wrapper.value = value
+            klass._instances[value] = wrapper
+        return wrapper
+    def __repr__(self):
+        return "<FloatWrapper: %s>" % self.value
+
+class StringWrapper(object):
+    __instances = {}
+