Merge inbound to mozilla-central. a=merge
authorshindli <shindli@mozilla.com>
Wed, 05 Dec 2018 23:33:31 +0200
changeset 449445 643a4a6bbfe98aacb7f285b8f9e0a001794a9f8e
parent 449444 89fd36f5fbe5e90bf16563342819fa6700431ccb (current diff)
parent 449416 7d32febb06fde6818e60f875f2e39f98e9107d7a (diff)
child 449446 0bbffdf069f140e02e098b0f5e63e8c450d7a1e7
child 449467 0bcaabfc8a1d39d96314a881d3ea9989c025a8fa
push id110371
push usershindli@mozilla.com
push dateWed, 05 Dec 2018 21:41:36 +0000
treeherdermozilla-inbound@0bbffdf069f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
643a4a6bbfe9 / 65.0a1 / 20181205213623 / files
nightly linux64
643a4a6bbfe9 / 65.0a1 / 20181205213623 / files
nightly mac
643a4a6bbfe9 / 65.0a1 / 20181205213623 / files
nightly win32
643a4a6bbfe9 / 65.0a1 / 20181205213623 / files
nightly win64
643a4a6bbfe9 / 65.0a1 / 20181205213623 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/browser/components/preferences/in-content/containers.xul
+++ b/browser/components/preferences/in-content/containers.xul
@@ -12,17 +12,17 @@
       data-category="paneContainers">
   <label class="text-link" id="backContainersLink" data-l10n-id="containers-back-link"/>
 </hbox>
 
 <hbox id="header-containers"
       class="header"
       hidden="true"
       data-category="paneContainers">
-  <label class="header-name" flex="1" data-l10n-id="containers-header"/>
+  <html:h1 data-l10n-id="containers-header"/>
 </hbox>
 
 <!-- Containers -->
 <groupbox id="browserContainersGroupPane" data-category="paneContainers" hidden="true"
           data-hidden-from-search="true" data-subpanel="true">
   <vbox id="browserContainersbox">
     <richlistbox id="containersView"/>
   </vbox>
--- a/browser/components/preferences/in-content/home.xul
+++ b/browser/components/preferences/in-content/home.xul
@@ -6,17 +6,17 @@
 
 <script type="application/javascript"
         src="chrome://browser/content/preferences/in-content/home.js"/>
 
 <hbox id="firefoxHomeCategory"
       class="subcategory"
       hidden="true"
       data-category="paneHome">
-  <label class="header-name" data-l10n-id="pane-home-title" flex="1"/>
+  <html:h1 style="-moz-box-flex: 1;" data-l10n-id="pane-home-title"/>
   <button id="restoreDefaultHomePageBtn"
           class="homepage-button check-home-page-controlled"
           data-preference-related="browser.startup.homepage"
           data-l10n-id="home-restore-defaults"
           preference="pref.browser.homepage.disable_button.restore_default"/>
 </hbox>
 
 <groupbox id="homepageGroup"
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -15,17 +15,17 @@
         src="chrome://mozapps/content/preferences/fontbuilder.js"/>
 
 <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences.properties"/>
 
 <hbox id="generalCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-      <label class="header-name" flex="1" data-l10n-id="pane-general-title"/>
+  <html:h1 data-l10n-id="pane-general-title"/>
 </hbox>
 
 <!-- Startup -->
 <groupbox id="startupGroup"
           data-category="paneGeneral"
           hidden="true">
   <label><html:h2 data-l10n-id="startup-header"/></label>
 
@@ -134,17 +134,17 @@
       </hbox>
     </vbox>
 </groupbox>
 
 <hbox id="languageAndAppearanceCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="language-and-appearance-header"/>
+  <html:h1 data-l10n-id="language-and-appearance-header"/>
 </hbox>
 
 <!-- Fonts and Colors -->
 <groupbox id="fontsGroup" data-category="paneGeneral" hidden="true">
   <label><html:h2 data-l10n-id="fonts-and-colors-header"/></label>
 
   <hbox id="fontSettings">
     <hbox align="center" flex="1">
@@ -349,17 +349,17 @@
           preference="layout.spellcheckDefault"/>
 </groupbox>
 
 <!-- Files and Applications -->
 <hbox id="filesAndApplicationsCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="files-and-applications-title"/>
+  <html:h1 data-l10n-id="files-and-applications-title"/>
 </hbox>
 
 <!--Downloads-->
 <groupbox id="downloadsGroup" data-category="paneGeneral" hidden="true">
   <label><html:h2 data-l10n-id="download-header"/></label>
 
   <radiogroup id="saveWhere"
               preference="browser.download.useDownloadDir"
@@ -431,17 +431,17 @@
   <stringbundle id="bundleShell" src="chrome://browser/locale/shellservice.properties"/>
   <stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/>
 #endif
 
 <hbox id="updatesCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="update-application-title"/>
+  <html:h1 data-l10n-id="update-application-title"/>
 </hbox>
 
 <!-- Update -->
 <groupbox id="updateApp" data-category="paneGeneral" hidden="true">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="update-application-title"/></label>
 
   <label data-l10n-id="update-application-description"/>
   <hbox align="center">
@@ -577,17 +577,17 @@
             data-l10n-id="update-enable-search-update"
             preference="browser.search.update"/>
 </groupbox>
 
 <hbox id="performanceCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="performance-title"/>
+  <html:h1 data-l10n-id="performance-title"/>
 </hbox>
 
 <!-- Performance -->
 <groupbox id="performanceGroup" data-category="paneGeneral" hidden="true">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="performance-title"/></label>
 
   <hbox align="center">
     <checkbox id="useRecommendedPerformanceSettings"
@@ -623,17 +623,17 @@
     </description>
   </vbox>
 </groupbox>
 
 <hbox id="browsingCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="browsing-title"/>
+  <html:h1 data-l10n-id="browsing-title"/>
 </hbox>
 
 <!-- Browsing -->
 <groupbox id="browsingGroup" data-category="paneGeneral" hidden="true">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="browsing-title"/></label>
 
   <checkbox id="useAutoScroll"
             data-l10n-id="browsing-use-autoscroll"
@@ -662,17 +662,17 @@
     <label id="cfrLearnMore" class="learnMore text-link" data-l10n-id="browsing-cfr-recommendations-learn-more"/>
   </hbox>
 </groupbox>
 
 <hbox id="networkProxyCategory"
       class="subcategory"
       hidden="true"
       data-category="paneGeneral">
-  <label class="header-name" flex="1" data-l10n-id="network-settings-title"/>
+  <html:h1 data-l10n-id="network-settings-title"/>
 </hbox>
 
 <!-- Network Settings-->
 <groupbox id="connectionGroup" data-category="paneGeneral" hidden="true">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="network-settings-title"/></label>
 
   <hbox align="center">
     <hbox align="center" flex="1">
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -8,17 +8,17 @@
         src="chrome://browser/content/preferences/in-content/privacy.js"/>
 <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
 <stringbundle id="signonBundle" src="chrome://passwordmgr/locale/passwordmgr.properties"/>
 
 <hbox id="browserPrivacyCategory"
       class="subcategory"
       hidden="true"
       data-category="panePrivacy">
-  <label class="header-name" flex="1" data-l10n-id="privacy-header"/>
+  <html:h1 data-l10n-id="privacy-header"/>
 </hbox>
 
 <!-- Tracking / Content Blocking -->
 <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" aria-describedby="contentBlockingDescription">
   <label id="contentBlockingHeader"><html:h2 data-l10n-id="content-blocking-header"/></label>
   <vbox data-subcategory="trackingprotection">
     <hbox align="start">
       <image id="trackingProtectionShield"/>
@@ -435,17 +435,17 @@
   <label class="text-link" id="openSearchEnginePreferences"
          data-l10n-id="addressbar-suggestions-settings"/>
 </groupbox>
 
 <hbox id="permissionsCategory"
       class="subcategory"
       hidden="true"
       data-category="panePrivacy">
-  <label class="header-name" flex="1" data-l10n-id="permissions-header"/>
+  <html:h1 data-l10n-id="permissions-header"/>
 </hbox>
 
 <!-- Permissions -->
 <groupbox id="permissionsGroup" data-category="panePrivacy" hidden="true" data-subcategory="permissions">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="permissions-header"/></label>
 
   <grid>
     <columns>
@@ -666,17 +666,17 @@
 </groupbox>
 
 <!-- Firefox Data Collection and Use -->
 #ifdef MOZ_DATA_REPORTING
 <hbox id="dataCollectionCategory"
       class="subcategory"
       hidden="true"
       data-category="panePrivacy">
-  <label class="header-name" flex="1" data-l10n-id="collection-header"/>
+  <html:h1 data-l10n-id="collection-header"/>
 </hbox>
 
 <groupbox id="dataCollectionGroup" data-category="panePrivacy" hidden="true">
   <label class="search-header" hidden="true"><html:h2 data-l10n-id="collection-header"/></label>
 
   <description>
     <label class="tail-with-learn-more" data-l10n-id="collection-description"/>
     <label id="dataCollectionPrivacyNotice"
@@ -745,17 +745,17 @@
   </vbox>
 </groupbox>
 #endif
 
 <hbox id="securityCategory"
       class="subcategory"
       hidden="true"
       data-category="panePrivacy">
-  <label class="header-name" flex="1" data-l10n-id="security-header"/>
+  <html:h1 data-l10n-id="security-header"/>
 </hbox>
 
 <!-- addons, forgery (phishing) UI Security -->
 <groupbox id="browsingProtectionGroup" data-category="panePrivacy" hidden="true">
   <label><html:h2 data-l10n-id="security-browsing-protection"/></label>
   <hbox align = "center">
     <checkbox id="enableSafeBrowsing"
               data-l10n-id="security-enable-safe-browsing"
--- a/browser/components/preferences/in-content/search.xul
+++ b/browser/components/preferences/in-content/search.xul
@@ -1,16 +1,16 @@
     <script type="application/javascript"
             src="chrome://browser/content/preferences/in-content/search.js"/>
 
     <hbox id="searchCategory"
           class="subcategory"
           hidden="true"
           data-category="paneSearch">
-      <label class="header-name" flex="1" data-l10n-id="pane-search-title" />
+      <html:h1 data-l10n-id="pane-search-title"/>
     </hbox>
 
     <groupbox id="searchbarGroup" data-category="paneSearch">
       <label control="searchBarVisibleGroup"><html:h2 data-l10n-id="search-bar-header"/></label>
       <radiogroup id="searchBarVisibleGroup" preference="browser.search.widget.inNavBar">
         <radio id="searchBarHiddenRadio" value="false" data-l10n-id="search-bar-hidden"/>
         <image class="searchBarImage searchBarHiddenImage" role="presentation"/>
         <radio id="searchBarShownRadio" value="true" data-l10n-id="search-bar-shown"/>
--- a/browser/components/preferences/in-content/searchResults.xul
+++ b/browser/components/preferences/in-content/searchResults.xul
@@ -2,17 +2,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/. -->
 
 <hbox id="header-searchResults"
       class="subcategory"
       hidden="true"
       data-hidden-from-search="true"
       data-category="paneSearchResults">
-  <label class="header-name" flex="1" data-l10n-id="search-results-header" />
+  <html:h1 data-l10n-id="search-results-header"/>
 </hbox>
 
 <groupbox id="no-results-message"
           data-hidden-from-search="true"
           data-category="paneSearchResults"
           hidden="true">
   <vbox class="no-results-container">
     <label id="sorry-message" data-l10n-id="search-results-empty-message">
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -6,17 +6,17 @@
 
 <script type="application/javascript"
         src="chrome://browser/content/preferences/in-content/sync.js"/>
 
 <hbox id="firefoxAccountCategory"
       class="subcategory"
       hidden="true"
       data-category="paneSync">
-  <label class="header-name" flex="1" data-l10n-id="pane-sync-title" />
+  <html:h1 data-l10n-id="pane-sync-title"/>
 </hbox>
 
 <deck id="weavePrefsDeck" data-category="paneSync" hidden="true"
       data-hidden-from-search="true">
   <groupbox id="noFxaAccount">
     <hbox>
       <vbox flex="1">
         <label id="noFxaCaption"><html:h2 data-l10n-id="sync-signedout-caption"/></label>
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -23,26 +23,29 @@
   width: 100%;
   padding: 0;
 }
 
 groupbox[data-category] {
   margin: 0 0 32px;
 }
 
+html|h1 {
+  margin: 0 0 8px;
+  font-size: 1.46em;
+  font-weight: 300;
+  line-height: 1.3em;
+}
+
 html|h2 {
   margin: 16px 0 4px;
   font-size: 1.14em;
   line-height: normal;
 }
 
-.header-name {
-  margin-bottom: 8px;
-}
-
 description.indent,
 .indent > description {
   color: #737373;
 }
 
 button,
 treecol,
 html|option {
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "BrowsingContext.h"
+#include "mozilla/dom/BrowsingContext.h"
 
 #include "mozilla/dom/ChromeBrowsingContext.h"
 #include "mozilla/dom/BrowsingContextBinding.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Logging.h"
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
-#ifndef BrowsingContext_h
-#define BrowsingContext_h
+#ifndef mozilla_dom_BrowsingContext_h
+#define mozilla_dom_BrowsingContext_h
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsString.h"
@@ -107,18 +107,18 @@ class BrowsingContext : public nsWrapper
   BrowsingContext* GetOpener() { return mOpener; }
 
   void SetOpener(BrowsingContext* aOpener);
 
   static void GetRootBrowsingContexts(
       nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts);
 
   nsISupports* GetParentObject() const;
-  virtual JSObject* WrapObject(JSContext* aCx,
-                               JS::Handle<JSObject*> aGivenProto) override;
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override;
 
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(BrowsingContext)
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(BrowsingContext)
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(BrowsingContext)
 
   using Children = AutoCleanLinkedList<RefPtr<BrowsingContext>>;
 
  protected:
@@ -138,9 +138,10 @@ class BrowsingContext : public nsWrapper
   Children mChildren;
   WeakPtr<BrowsingContext> mOpener;
   nsCOMPtr<nsIDocShell> mDocShell;
   nsString mName;
 };
 
 }  // namespace dom
 }  // namespace mozilla
-#endif
+
+#endif  // !defined(mozilla_dom_BrowsingContext_h)
--- a/docshell/base/ChromeBrowsingContext.cpp
+++ b/docshell/base/ChromeBrowsingContext.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "ChromeBrowsingContext.h"
+#include "mozilla/dom/ChromeBrowsingContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
 
 namespace mozilla {
 namespace dom {
 
 ChromeBrowsingContext::ChromeBrowsingContext(BrowsingContext* aParent,
                                              BrowsingContext* aOpener,
                                              const nsAString& aName,
                                              uint64_t aBrowsingContextId,
@@ -51,10 +52,58 @@ ChromeBrowsingContext::ChromeBrowsingCon
 }
 
 /* static */ const ChromeBrowsingContext* ChromeBrowsingContext::Cast(
     const BrowsingContext* aContext) {
   MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
   return static_cast<const ChromeBrowsingContext*>(aContext);
 }
 
+void ChromeBrowsingContext::GetWindowGlobals(
+    nsTArray<RefPtr<WindowGlobalParent>>& aWindows) {
+  aWindows.SetCapacity(mWindowGlobals.Count());
+  for (auto iter = mWindowGlobals.Iter(); !iter.Done(); iter.Next()) {
+    aWindows.AppendElement(iter.Get()->GetKey());
+  }
+}
+
+void ChromeBrowsingContext::RegisterWindowGlobal(WindowGlobalParent* aGlobal) {
+  MOZ_ASSERT(!mWindowGlobals.Contains(aGlobal), "Global already registered!");
+  mWindowGlobals.PutEntry(aGlobal);
+}
+
+void ChromeBrowsingContext::UnregisterWindowGlobal(
+    WindowGlobalParent* aGlobal) {
+  MOZ_ASSERT(mWindowGlobals.Contains(aGlobal), "Global not registered!");
+  mWindowGlobals.RemoveEntry(aGlobal);
+
+  // Our current window global should be in our mWindowGlobals set. If it's not
+  // anymore, clear that reference.
+  if (aGlobal == mCurrentWindowGlobal) {
+    mCurrentWindowGlobal = nullptr;
+  }
+}
+
+void ChromeBrowsingContext::SetCurrentWindowGlobal(
+    WindowGlobalParent* aGlobal) {
+  MOZ_ASSERT(mWindowGlobals.Contains(aGlobal), "Global not registered!");
+
+  // TODO: This should probably assert that the processes match.
+  mCurrentWindowGlobal = aGlobal;
+}
+
+JSObject* ChromeBrowsingContext::WrapObject(JSContext* aCx,
+                                            JS::Handle<JSObject*> aGivenProto) {
+  return ChromeBrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void ChromeBrowsingContext::Traverse(nsCycleCollectionTraversalCallback& cb) {
+  ChromeBrowsingContext* tmp = this;
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowGlobals);
+}
+
+void ChromeBrowsingContext::Unlink() {
+  ChromeBrowsingContext* tmp = this;
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowGlobals);
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/docshell/base/ChromeBrowsingContext.h
+++ b/docshell/base/ChromeBrowsingContext.h
@@ -1,53 +1,77 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/. */
 
-#ifndef ChromeBrowsingContext_h
-#define ChromeBrowsingContext_h
+#ifndef mozilla_dom_ChromeBrowsingContext_h
+#define mozilla_dom_ChromeBrowsingContext_h
 
 #include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/RefPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
 
 class nsIDocShell;
 
 namespace mozilla {
 namespace dom {
 
+class WindowGlobalParent;
+
 // ChromeBrowsingContext is a BrowsingContext living in the parent
 // process, with whatever extra data that a BrowsingContext in the
 // parent needs.
 class ChromeBrowsingContext final : public BrowsingContext {
  public:
   static void CleanupContexts(uint64_t aProcessId);
   static already_AddRefed<ChromeBrowsingContext> Get(uint64_t aId);
   static ChromeBrowsingContext* Cast(BrowsingContext* aContext);
   static const ChromeBrowsingContext* Cast(const BrowsingContext* aContext);
 
   bool IsOwnedByProcess(uint64_t aProcessId) const {
     return mProcessId == aProcessId;
   }
 
+  void GetWindowGlobals(nsTArray<RefPtr<WindowGlobalParent>>& aWindows);
+
+  // Called by WindowGlobalParent to register and unregister window globals.
+  void RegisterWindowGlobal(WindowGlobalParent* aGlobal);
+  void UnregisterWindowGlobal(WindowGlobalParent* aGlobal);
+
+  // The current active WindowGlobal.
+  WindowGlobalParent* GetCurrentWindowGlobal() const {
+    return mCurrentWindowGlobal;
+  }
+  void SetCurrentWindowGlobal(WindowGlobalParent* aGlobal);
+
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override;
+
  protected:
-  void Traverse(nsCycleCollectionTraversalCallback& cb) {}
-  void Unlink() {}
+  void Traverse(nsCycleCollectionTraversalCallback& cb);
+  void Unlink();
 
   using Type = BrowsingContext::Type;
   ChromeBrowsingContext(BrowsingContext* aParent, BrowsingContext* aOpener,
                         const nsAString& aName, uint64_t aBrowsingContextId,
                         uint64_t aProcessId, Type aType = Type::Chrome);
 
  private:
   friend class BrowsingContext;
 
   // XXX(farre): Store a ContentParent pointer here rather than mProcessId?
   // Indicates which process owns the docshell.
   uint64_t mProcessId;
+
+  // All live window globals within this browsing context.
+  nsTHashtable<nsRefPtrHashKey<WindowGlobalParent>> mWindowGlobals;
+  RefPtr<WindowGlobalParent> mCurrentWindowGlobal;
 };
 
 }  // namespace dom
 }  // namespace mozilla
-#endif
+
+#endif  // !defined(mozilla_dom_ChromeBrowsingContext_h)
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -3188,16 +3188,32 @@ nsDocShell::SetTreeOwner(nsIDocShellTree
     nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(iter.GetNext());
     NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);
 
     if (child->ItemType() == mItemType) {
       child->SetTreeOwner(aTreeOwner);
     }
   }
 
+  // If we're in the content process and have had a TreeOwner set on us, extract
+  // our TabChild actor. If we've already had our TabChild set, assert that it
+  // hasn't changed.
+  if (mTreeOwner && XRE_IsContentProcess()) {
+    nsCOMPtr<nsITabChild> newTabChild = do_GetInterface(mTreeOwner);
+    MOZ_ASSERT(newTabChild, "No TabChild actor for tree owner in Content!");
+
+    if (mTabChild) {
+      nsCOMPtr<nsITabChild> oldTabChild = do_QueryReferent(mTabChild);
+      MOZ_RELEASE_ASSERT(oldTabChild == newTabChild,
+                         "Cannot cahnge TabChild during nsDocShell lifetime!");
+    } else {
+      mTabChild = do_GetWeakReference(newTabChild);
+    }
+  }
+
   // Our tree owner has changed. Recompute scriptability.
   //
   // Note that this is near-redundant with the recomputation in
   // SetDocLoaderParent(), but not so for the root DocShell, where the call to
   // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(),
   // and we never set another parent. Given that this is neither expensive nor
   // performance-critical, let's be safe and unconditionally recompute this
   // state whenever dependent state changes.
@@ -5040,16 +5056,18 @@ nsDocShell::Destroy() {
     mSessionHistory->EvictLocalContentViewers();
     mSessionHistory = nullptr;
   }
 
   mBrowsingContext->Detach();
 
   SetTreeOwner(nullptr);
 
+  mTabChild = nullptr;
+
   mOnePermittedSandboxedNavigator = nullptr;
 
   // required to break ref cycle
   mSecurityUI = nullptr;
 
   // Cancel any timers that were set for this docshell; this is needed
   // to break the cycle between us and the timers.
   CancelRefreshURITimers();
@@ -13316,18 +13334,17 @@ nsDocShell::GetEditingSession(nsIEditing
 
 NS_IMETHODIMP
 nsDocShell::GetScriptableTabChild(nsITabChild** aTabChild) {
   *aTabChild = GetTabChild().take();
   return *aTabChild ? NS_OK : NS_ERROR_FAILURE;
 }
 
 already_AddRefed<nsITabChild> nsDocShell::GetTabChild() {
-  nsCOMPtr<nsIDocShellTreeOwner> owner(mTreeOwner);
-  nsCOMPtr<nsITabChild> tc = do_GetInterface(owner);
+  nsCOMPtr<nsITabChild> tc = do_QueryReferent(mTabChild);
   return tc.forget();
 }
 
 nsICommandManager* nsDocShell::GetCommandManager() {
   NS_ENSURE_SUCCESS(EnsureCommandHandler(), nullptr);
   return mCommandManager;
 }
 
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -916,16 +916,19 @@ class nsDocShell final : public nsDocLoa
   uint64_t mContentWindowID;
   nsCOMPtr<nsIContentViewer> mContentViewer;
   nsCOMPtr<nsIWidget> mParentWidget;
   RefPtr<mozilla::dom::ChildSHistory> mSessionHistory;
   nsCOMPtr<nsIWebBrowserFind> mFind;
   nsCOMPtr<nsICommandManager> mCommandManager;
   RefPtr<mozilla::dom::BrowsingContext> mBrowsingContext;
 
+  // Weak reference to our TabChild actor.
+  nsWeakPtr mTabChild;
+
   // Dimensions of the docshell
   nsIntRect mBounds;
 
   /**
    * Content-Type Hint of the most-recently initiated load. Used for
    * session history entries.
    */
   nsCString mContentTypeHint;
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -230,16 +230,17 @@
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/TabGroup.h"
 #include "nsIWebNavigationInfo.h"
 #include "nsPluginHost.h"
 #include "nsIBrowser.h"
 #include "mozilla/HangAnnotations.h"
 #include "mozilla/Encoding.h"
 #include "nsXULElement.h"
+#include "mozilla/RecordReplay.h"
 
 #include "nsIBidiKeyboard.h"
 
 #if defined(XP_WIN)
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
@@ -10091,16 +10092,22 @@ static const uint64_t kIdBits = 64 - kId
 
   MOZ_RELEASE_ASSERT(processId < (uint64_t(1) << kIdProcessBits));
   uint64_t processBits = processId & ((uint64_t(1) << kIdProcessBits) - 1);
 
   uint64_t id = aId;
   MOZ_RELEASE_ASSERT(id < (uint64_t(1) << kIdBits));
   uint64_t bits = id & ((uint64_t(1) << kIdBits) - 1);
 
+  // Set the high bit for middleman processes so it doesn't conflict with the
+  // content process's generated IDs.
+  if (recordreplay::IsMiddleman()) {
+    bits |= uint64_t(1) << (kIdBits - 1);
+  }
+
   return (processBits << kIdBits) | bits;
 }
 
 // Tab ID is composed in a similar manner of Window ID.
 static uint64_t gNextTabId = 0;
 
 /* static */ uint64_t nsContentUtils::GenerateTabId() {
   return GenerateProcessSpecificId(++gNextTabId);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -266,16 +266,18 @@
 #include "nsXULCommandDispatcher.h"
 #include "nsXULPopupManager.h"
 #include "nsIDocShellTreeOwner.h"
 #endif
 #include "nsIPresShellInlines.h"
 
 #include "mozilla/DocLoadingTimelineMarker.h"
 
+#include "mozilla/dom/WindowGlobalChild.h"
+
 #include "nsISpeculativeConnect.h"
 
 #include "mozilla/MediaManager.h"
 
 #include "nsIURIClassifier.h"
 #include "nsIURIMutator.h"
 #include "mozilla/DocumentStyleRootIterator.h"
 #include "mozilla/PendingFullscreenEvent.h"
@@ -2892,16 +2894,23 @@ void nsIDocument::SetDocumentURI(nsIURI*
   // document's original URI.
   if (!mOriginalURI) mOriginalURI = mDocumentURI;
 
   // If changing the document's URI changed the base URI of the document, we
   // need to refresh the hrefs of all the links on the page.
   if (!equalBases) {
     RefreshLinkHrefs();
   }
+
+  // Tell our WindowGlobalParent that the document's URI has been changed.
+  nsPIDOMWindowInner* inner = GetInnerWindow();
+  WindowGlobalChild* wgc = inner ? inner->GetWindowGlobalChild() : nullptr;
+  if (wgc) {
+    Unused << wgc->SendUpdateDocumentURI(mDocumentURI);
+  }
 }
 
 static void GetFormattedTimeString(PRTime aTime,
                                    nsAString& aFormattedTimeString) {
   PRExplodedTime prtime;
   PR_ExplodeTime(aTime, PR_LocalTimeParameters, &prtime);
   // "MM/DD/YYYY hh:mm:ss"
   char formatedTime[24];
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -95,16 +95,17 @@
 #include "mozilla/dom/CustomEvent.h"
 
 #include "mozilla/dom/ipc/StructuredCloneData.h"
 #include "mozilla/WebBrowserPersistLocalDocument.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/dom/ParentSHistory.h"
 #include "mozilla/dom/ChildSHistory.h"
+#include "mozilla/dom/ChromeBrowsingContext.h"
 
 #include "mozilla/dom/HTMLBodyElement.h"
 
 #include "mozilla/ContentPrincipal.h"
 
 #ifdef XP_WIN
 #include "mozilla/plugins/PPluginWidgetParent.h"
 #include "../plugins/ipc/PluginWidgetParent.h"
@@ -3017,16 +3018,26 @@ already_AddRefed<nsILoadContext> nsFrame
   if (IsRemoteFrame() && (mRemoteBrowser || TryRemoteBrowser())) {
     loadContext = mRemoteBrowser->GetLoadContext();
   } else {
     loadContext = do_GetInterface(GetDocShell(IgnoreErrors()));
   }
   return loadContext.forget();
 }
 
+already_AddRefed<BrowsingContext> nsFrameLoader::GetBrowsingContext() {
+  RefPtr<BrowsingContext> browsingContext;
+  if (IsRemoteFrame() && (mRemoteBrowser || TryRemoteBrowser())) {
+    browsingContext = mRemoteBrowser->GetBrowsingContext();
+  } else if (GetDocShell(IgnoreErrors())) {
+    browsingContext = nsDocShell::Cast(mDocShell)->GetBrowsingContext();
+  }
+  return browsingContext.forget();
+}
+
 void nsFrameLoader::InitializeBrowserAPI() {
   if (!OwnerIsMozBrowserFrame()) {
     return;
   }
   if (!IsRemoteFrame()) {
     nsresult rv = EnsureMessageManager();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -43,16 +43,17 @@ class nsIPrintSettings;
 class nsIWebBrowserPersistDocumentReceiver;
 class nsIWebProgressListener;
 
 namespace mozilla {
 
 class OriginAttributes;
 
 namespace dom {
+class BrowsingContext;
 class ChromeMessageSender;
 class ContentParent;
 class InProcessTabChildMessageManager;
 class MessageSender;
 class PBrowserParent;
 class ProcessMessageManager;
 class Promise;
 class TabParent;
@@ -118,16 +119,18 @@ class nsFrameLoader final : public nsStu
   // WebIDL methods
 
   nsIDocShell* GetDocShell(mozilla::ErrorResult& aRv);
 
   already_AddRefed<nsITabParent> GetTabParent();
 
   already_AddRefed<nsILoadContext> LoadContext();
 
+  already_AddRefed<mozilla::dom::BrowsingContext> GetBrowsingContext();
+
   /**
    * Start loading the frame. This method figures out what to load
    * from the owner content in the frame loader.
    */
   void LoadFrame(bool aOriginalSrc);
 
   /**
    * Loads the specified URI in this frame. Behaves identically to loadFrame,
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -255,16 +255,18 @@
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
 #endif
 
 #include "mozilla/dom/ClientManager.h"
 #include "mozilla/dom/ClientSource.h"
 #include "mozilla/dom/ClientState.h"
 
+#include "mozilla/dom/WindowGlobalChild.h"
+
 // Apple system headers seem to have a check() macro.  <sigh>
 #ifdef check
 class nsIScriptTimeoutHandler;
 #undef check
 #endif  // check
 #include "AccessCheck.h"
 
 #ifdef ANDROID
@@ -1261,16 +1263,21 @@ void nsGlobalWindowInner::FreeInnerObjec
 
   if (mCleanMessageManager) {
     MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
     if (mChromeFields.mMessageManager) {
       mChromeFields.mMessageManager->Disconnect();
     }
   }
 
+  if (mWindowGlobalChild && !mWindowGlobalChild->IsClosed()) {
+    mWindowGlobalChild->Send__delete__(mWindowGlobalChild);
+  }
+  mWindowGlobalChild = nullptr;
+
   mIntlUtils = nullptr;
 }
 
 //*****************************************************************************
 // nsGlobalWindowInner::nsISupports
 //*****************************************************************************
 
 // QueryInterface implementation for nsGlobalWindowInner
@@ -1630,16 +1637,25 @@ void nsGlobalWindowInner::InnerSetNewDoc
   mPerformance = nullptr;
 
   // This must be called after nullifying the internal objects because here we
   // could recreate them, calling the getter methods, and store them into the JS
   // slots. If we nullify them after, the slot values and the objects will be
   // out of sync.
   ClearDocumentDependentSlots(aCx);
 
+  // FIXME: Currently, devtools can crete a fallback webextension window global
+  // in the content process which does not have a corresponding TabChild actor.
+  // This means we have no actor to be our parent. (Bug 1498293)
+  MOZ_DIAGNOSTIC_ASSERT(!mWindowGlobalChild,
+                        "Shouldn't have created WindowGlobalChild yet!");
+  if (XRE_IsParentProcess() || mTabChild) {
+    mWindowGlobalChild = WindowGlobalChild::Create(this);
+  }
+
 #ifdef DEBUG
   mLastOpenedURI = aDocument->GetDocumentURI();
 #endif
 
   Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
                         mMutationBits ? 1 : 0);
   Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_TEXT_EVENT_LISTENERS,
                         mMayHaveTextEventListenerInDefaultGroup ? 1 : 0);
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -63,16 +63,17 @@
 #include "WindowNamedPropertiesHandler.h"
 #include "nsFrameSelection.h"
 #include "nsNetUtil.h"
 #include "nsVariant.h"
 #include "nsPrintfCString.h"
 #include "mozilla/intl/LocaleService.h"
 #include "WindowDestroyedEvent.h"
 #include "nsDocShellLoadState.h"
+#include "mozilla/dom/WindowGlobalChild.h"
 
 // Helper Classes
 #include "nsJSUtils.h"
 #include "jsapi.h"
 #include "js/Wrapper.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsReadableUtils.h"
 #include "nsJSEnvironment.h"
@@ -1957,16 +1958,20 @@ nsresult nsGlobalWindowOuter::SetNewDocu
   }
 
   // Handle any document.open() logic after we setup the new inner window
   // so that any bound DETH objects can see the top window, document, etc.
   if (handleDocumentOpen) {
     newInnerWindow->MigrateStateForDocumentOpen(currentInner);
   }
 
+  // Tell the WindowGlobalParent that it should become the current window global
+  // for our BrowsingContext if it isn't already.
+  mInnerWindow->GetWindowGlobalChild()->SendBecomeCurrentWindowGlobal();
+
   // We no longer need the old inner window.  Start its destruction if
   // its not being reused and clear our reference.
   if (doomCurrentInner) {
     currentInner->FreeInnerObjects(handleDocumentOpen);
   }
   currentInner = nullptr;
 
   // Ask the JS engine to assert that it's valid to access our DocGroup whenever
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -63,16 +63,17 @@ class Performance;
 class Report;
 class ReportBody;
 class ReportingObserver;
 class Selection;
 class ServiceWorker;
 class ServiceWorkerDescriptor;
 class Timeout;
 class TimeoutManager;
+class WindowGlobalChild;
 class CustomElementRegistry;
 enum class CallerType : uint32_t;
 }  // namespace dom
 }  // namespace mozilla
 
 // Popup control state enum. The values in this enum must go from most
 // permissive to least permissive so that it's safe to push state in
 // all situations. Pushing popup state onto the stack never makes the
@@ -373,16 +374,20 @@ class nsPIDOMWindowInner : public mozIDO
 
   nsIDocument* GetDoc() {
     if (!mDoc) {
       MaybeCreateDoc();
     }
     return mDoc;
   }
 
+  mozilla::dom::WindowGlobalChild* GetWindowGlobalChild() {
+    return mWindowGlobalChild;
+  }
+
   virtual PopupControlState GetPopupControlState() const = 0;
 
   // Determine if the window is suspended or frozen.  Outer windows
   // will forward this call to the inner window for convenience.  If
   // there is no inner window then the outer window is considered
   // suspended and frozen by default.
   virtual bool IsSuspended() const = 0;
   virtual bool IsFrozen() const = 0;
@@ -690,16 +695,22 @@ class nsPIDOMWindowInner : public mozIDO
   // List of Report objects for ReportingObservers.
   nsTArray<RefPtr<mozilla::dom::ReportingObserver>> mReportingObservers;
   nsTArray<RefPtr<mozilla::dom::Report>> mReportRecords;
 
   // This is a list of storage access granted for the current window. These are
   // also set as permissions, but it could happen that we need to access them
   // synchronously in this context, and for this, we need a copy here.
   nsTArray<nsCString> mStorageAccessGranted;
+
+  // The WindowGlobalChild actor for this window.
+  //
+  // This will be non-null during the full lifetime of the window, initialized
+  // during SetNewDocument, and cleared during FreeInnerObjects.
+  RefPtr<mozilla::dom::WindowGlobalChild> mWindowGlobalChild;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsPIDOMWindowInner, NS_PIDOMWINDOWINNER_IID)
 
 class nsPIDOMWindowOuter : public mozIDOMWindowProxy {
  protected:
   explicit nsPIDOMWindowOuter(uint64_t aWindowID);
 
--- a/dom/chrome-webidl/BrowsingContext.webidl
+++ b/dom/chrome-webidl/BrowsingContext.webidl
@@ -12,8 +12,15 @@ interface BrowsingContext {
   sequence<BrowsingContext> getChildren();
 
   readonly attribute nsIDocShell? docShell;
 
   readonly attribute unsigned long long id;
 
   readonly attribute BrowsingContext? opener;
 };
+
+[Exposed=Window, ChromeOnly]
+interface ChromeBrowsingContext : BrowsingContext {
+  sequence<WindowGlobalParent> getWindowGlobals();
+
+  readonly attribute WindowGlobalParent? currentWindowGlobal;
+};
new file mode 100644
--- /dev/null
+++ b/dom/chrome-webidl/WindowGlobalActors.webidl
@@ -0,0 +1,46 @@
+/* -*- Mode: IDL; 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/. */
+
+interface Principal;
+interface URI;
+interface nsIDocShell;
+
+[Exposed=Window, ChromeOnly]
+interface WindowGlobalParent {
+  readonly attribute boolean isClosed;
+  readonly attribute boolean isInProcess;
+  readonly attribute ChromeBrowsingContext browsingContext;
+
+  readonly attribute boolean isCurrentGlobal;
+
+  readonly attribute unsigned long long innerWindowId;
+  readonly attribute unsigned long long outerWindowId;
+
+  readonly attribute FrameLoader? rootFrameLoader; // Embedded (browser) only
+
+  readonly attribute WindowGlobalChild? childActor; // in-process only
+
+  // Information about the currently loaded document.
+  readonly attribute Principal documentPrincipal;
+  readonly attribute URI? documentURI;
+
+  static WindowGlobalParent? getByInnerWindowId(unsigned long long innerWindowId);
+};
+
+[Exposed=Window, ChromeOnly]
+interface WindowGlobalChild {
+  readonly attribute boolean isClosed;
+  readonly attribute boolean isInProcess;
+  readonly attribute BrowsingContext browsingContext;
+
+  readonly attribute boolean isCurrentGlobal;
+
+  readonly attribute unsigned long long innerWindowId;
+  readonly attribute unsigned long long outerWindowId;
+
+  readonly attribute WindowGlobalParent? parentActor; // in-process only
+
+  static WindowGlobalChild? getByInnerWindowId(unsigned long long innerWIndowId);
+};
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -49,16 +49,17 @@ WEBIDL_FILES = [
     'MozStorageStatementParams.webidl',
     'MozStorageStatementRow.webidl',
     'PrecompiledScript.webidl',
     'PromiseDebugging.webidl',
     'StructuredCloneHolder.webidl',
     'TelemetryStopwatch.webidl',
     'WebExtensionContentScript.webidl',
     'WebExtensionPolicy.webidl',
+    'WindowGlobalActors.webidl',
     'XULFrameElement.webidl',
     'XULMenuElement.webidl',
     'XULScrollElement.webidl',
     'XULTextElement.webidl'
 ]
 
 if CONFIG['MOZ_PLACES']:
     WEBIDL_FILES += [
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -885,34 +885,37 @@ nsresult ContentChild::ProvideWindowComm
     tabGroup = aTabOpener->TabGroup();
   } else {
     tabGroup = new TabGroup();
   }
 
   TabContext newTabContext = aTabOpener ? *aTabOpener : TabContext();
   RefPtr<TabChild> newChild =
       new TabChild(this, tabId, tabGroup, newTabContext, aChromeFlags);
-  if (NS_FAILED(newChild->Init(aParent))) {
-    return NS_ERROR_ABORT;
-  }
 
   if (aTabOpener) {
     MOZ_ASSERT(ipcContext->type() == IPCTabContext::TPopupIPCTabContext);
     ipcContext->get_PopupIPCTabContext().opener() = aTabOpener;
   }
 
   nsCOMPtr<nsIEventTarget> target =
       tabGroup->EventTargetFor(TaskCategory::Other);
   SetEventTargetForActor(newChild, target);
 
   Unused << SendPBrowserConstructor(
       // We release this ref in DeallocPBrowserChild
       RefPtr<TabChild>(newChild).forget().take(), tabId, TabId(0), *ipcContext,
       aChromeFlags, GetID(), IsForBrowser());
 
+  // Now that |newChild| has had its IPC link established, call |Init| to set it
+  // up.
+  if (NS_FAILED(newChild->Init(aParent))) {
+    return NS_ERROR_ABORT;
+  }
+
   nsCOMPtr<nsPIDOMWindowInner> parentTopInnerWindow;
   if (aParent) {
     nsCOMPtr<nsPIDOMWindowOuter> parentTopWindow =
         nsPIDOMWindowOuter::From(aParent)->GetTop();
     if (parentTopWindow) {
       parentTopInnerWindow = parentTopWindow->GetCurrentInnerWindow();
     }
   }
@@ -2947,16 +2950,23 @@ uint64_t NextWindowID() {
       processID & ((uint64_t(1) << kWindowIDProcessBits) - 1);
 
   // Make sure no actual window ends up with mWindowID == 0.
   uint64_t windowID = ++gNextWindowID;
 
   MOZ_RELEASE_ASSERT(windowID < (uint64_t(1) << kWindowIDWindowBits));
   uint64_t windowBits = windowID & ((uint64_t(1) << kWindowIDWindowBits) - 1);
 
+  // Make sure that the middleman process doesn't generate WindowIDs which
+  // conflict with the process it's wrapping (which shares a ContentParentID
+  // with it).
+  if (recordreplay::IsMiddleman()) {
+    windowBits |= uint64_t(1) << (kWindowIDWindowBits - 1);
+  }
+
   return (processBits << kWindowIDWindowBits) | windowBits;
 }
 
 mozilla::ipc::IPCResult ContentChild::RecvInvokeDragSession(
     nsTArray<IPCDataTransfer>&& aTransfers, const uint32_t& aAction) {
   nsCOMPtr<nsIDragService> dragService =
       do_GetService("@mozilla.org/widget/dragservice;1");
   if (dragService) {
--- a/dom/ipc/DOMTypes.ipdlh
+++ b/dom/ipc/DOMTypes.ipdlh
@@ -21,16 +21,18 @@ using LayoutDeviceIntRect from "Units.h"
 using DesktopIntRect from "Units.h";
 using DesktopToLayoutDeviceScale from "Units.h";
 using CSSToLayoutDeviceScale from "Units.h";
 using CSSRect from "Units.h";
 using CSSSize from "Units.h";
 using mozilla::LayoutDeviceIntPoint from "Units.h";
 using hal::ScreenOrientation from "mozilla/HalScreenConfiguration.h";
 using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h";
+using refcounted class nsIPrincipal from "mozilla/dom/PermissionMessageUtils.h";
+using mozilla::dom::BrowsingContextId from "mozilla/dom/ipc/IdType.h";
 
 
 namespace mozilla {
 namespace dom {
 
 struct MessagePortIdentifier
 {
   nsID uuid;
@@ -178,10 +180,18 @@ struct PerformanceInfo
   // True if the document window is the top window
   bool isTopLevel;
   // Memory
   PerformanceMemoryInfo memory;
   // Counters per category. For workers, a single entry
   CategoryDispatch[] items;
 };
 
+struct WindowGlobalInit
+{
+  nsIPrincipal principal;
+  BrowsingContextId browsingContextId;
+  uint64_t innerWindowId;
+  uint64_t outerWindowId;
+};
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -13,16 +13,17 @@ include protocol PFilePicker;
 include protocol PIndexedDBPermissionRequest;
 include protocol PPluginWidget;
 include protocol PRemotePrintJob;
 include protocol PChildToParentStream;
 include protocol PParentToChildStream;
 include protocol PFileDescriptorSet;
 include protocol PIPCBlobInputStream;
 include protocol PPaymentRequest;
+include protocol PWindowGlobal;
 
 include DOMTypes;
 include IPCBlob;
 include IPCStream;
 include JavaScriptTypes;
 include URIParams;
 include PPrintingTypes;
 include PTabContext;
@@ -81,16 +82,17 @@ using mozilla::EventMessage from "mozill
 using nsEventStatus from "mozilla/EventForwards.h";
 using mozilla::Modifiers from "mozilla/EventForwards.h";
 using nsSizeMode from "nsIWidgetListener.h";
 using mozilla::widget::CandidateWindowPosition from "ipc/nsGUIEventIPC.h";
 using class mozilla::NativeEventData from "ipc/nsGUIEventIPC.h";
 using mozilla::FontRange from "ipc/nsGUIEventIPC.h";
 using mozilla::a11y::IAccessibleHolder from "mozilla/a11y/IPCTypes.h";
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
+using mozilla::dom::BrowsingContextId from "mozilla/dom/ipc/IdType.h";
 
 namespace mozilla {
 namespace dom {
 
 struct ShowInfo
 {
   nsString name;
   bool fullscreenAllowed;
@@ -113,16 +115,17 @@ nested(upto inside_cpow) sync protocol P
     manager PContent or PContentBridge;
 
     manages PColorPicker;
     manages PDocAccessible;
     manages PFilePicker;
     manages PIndexedDBPermissionRequest;
     manages PPluginWidget;
     manages PPaymentRequest;
+    manages PWindowGlobal;
 
 both:
     async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                        Principal aPrincipal, ClonedMessageData aData);
 
 parent:
     /**
      * Tell the parent process a new accessible document has been created.
@@ -140,16 +143,22 @@ parent:
      * in e10s mode. This is always initiated from the child in response
      * to windowed plugin creation.
      */
     sync PPluginWidget();
 
     async PPaymentRequest();
 
     /**
+     * Construct a new WindowGlobal actor for a window global in the given
+     * BrowsingContext and with the given principal.
+     */
+    async PWindowGlobal(WindowGlobalInit init);
+
+    /**
      * Sends an NS_NATIVE_CHILD_OF_SHAREABLE_WINDOW to be adopted by the
      * widget's shareable window on the chrome side. Only used on Windows.
      */
     async SetNativeChildOfShareableWindow(uintptr_t childWindow);
 
     /**
      * When content moves focus from a native plugin window that's a child
      * of the native browser window we need to move native focus to the
@@ -574,16 +583,18 @@ parent:
     async ShowCanvasPermissionPrompt(nsCString aFirstPartyURI);
 
     sync SetSystemFont(nsCString aFontName);
     sync GetSystemFont() returns (nsCString retval);
 
     sync SetPrefersReducedMotionOverrideForTest(bool aValue);
     sync ResetPrefersReducedMotionOverrideForTest();
 
+    async RootBrowsingContext(BrowsingContextId aId);
+
 child:
     /**
      * Notify the remote browser that it has been Show()n on this
      * side, with the given |visibleRect|.  This message is expected
      * to trigger creation of the remote browser's "widget".
      *
      * |Show()| and |Move()| take IntSizes rather than Rects because
      * content processes always render to a virtual <0, 0> top-left
new file mode 100644
--- /dev/null
+++ b/dom/ipc/PWindowGlobal.ipdl
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* 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 protocol PBrowser;
+include protocol PInProcess;
+
+include DOMTypes;
+
+using refcounted class nsIURI from "mozilla/ipc/URIUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+/**
+ * A PWindowGlobal actor has a lifetime matching that of a single Window Global,
+ * specifically a |nsGlobalWindowInner|. These actors will form a parent/child
+ * link either between the chrome/content process, or will be in-process, for
+ * documents which are loaded in the chrome process.
+ */
+async protocol PWindowGlobal
+{
+  manager PBrowser or PInProcess;
+
+parent:
+  /// Update the URI of the document in this WindowGlobal.
+  async UpdateDocumentURI(nsIURI aUri);
+
+  /// Notify the parent that this PWindowGlobal is now the current global.
+  async BecomeCurrentWindowGlobal();
+
+  async __delete__();
+};
+
+} // namespace dom
+} // namespace mozilla
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -117,16 +117,17 @@
 #include "nsQueryObject.h"
 #include "nsIHttpChannel.h"
 #include "mozilla/dom/DocGroup.h"
 #include "nsString.h"
 #include "nsISupportsPrimitives.h"
 #include "mozilla/Telemetry.h"
 #include "nsDocShellLoadState.h"
 #include "nsWebBrowser.h"
+#include "mozilla/dom/WindowGlobalChild.h"
 
 #ifdef XP_WIN
 #include "mozilla/plugins/PluginWidgetChild.h"
 #endif
 
 #ifdef NS_PRINTING
 #include "nsIPrintSession.h"
 #include "nsIPrintSettings.h"
@@ -532,16 +533,21 @@ nsresult TabChild::Init(mozIDOMWindowPro
   docShell->SetAffectPrivateSessionLifetime(
       mChromeFlags & nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME);
   nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(WebNavigation());
   MOZ_ASSERT(loadContext);
   loadContext->SetPrivateBrowsing(OriginAttributesRef().mPrivateBrowsingId > 0);
   loadContext->SetRemoteTabs(mChromeFlags &
                              nsIWebBrowserChrome::CHROME_REMOTE_WINDOW);
 
+  // Send our browsing context to the parent process.
+  RefPtr<BrowsingContext> browsingContext =
+      nsDocShell::Cast(docShell)->GetBrowsingContext();
+  SendRootBrowsingContext(BrowsingContextId(browsingContext->Id()));
+
   // Few lines before, baseWindow->Create() will end up creating a new
   // window root in nsGlobalWindow::SetDocShell.
   // Then this chrome event handler, will be inherited to inner windows.
   // We want to also set it to the docshell so that inner windows
   // and any code that has access to the docshell
   // can all listen to the same chrome event handler.
   // XXX: ideally, we would set a chrome event handler earlier,
   // and all windows, even the root one, will use the docshell one.
@@ -3132,16 +3138,27 @@ PPaymentRequestChild* TabChild::AllocPPa
   return nullptr;
 }
 
 bool TabChild::DeallocPPaymentRequestChild(PPaymentRequestChild* actor) {
   delete actor;
   return true;
 }
 
+PWindowGlobalChild* TabChild::AllocPWindowGlobalChild(const WindowGlobalInit&) {
+  MOZ_CRASH("We should never be manually allocating PWindowGlobalChild actors");
+  return nullptr;
+}
+
+bool TabChild::DeallocPWindowGlobalChild(PWindowGlobalChild* aActor) {
+  // This reference was added in WindowGlobalChild::Create.
+  static_cast<WindowGlobalChild*>(aActor)->Release();
+  return true;
+}
+
 ScreenIntSize TabChild::GetInnerSize() {
   LayoutDeviceIntSize innerSize =
       RoundedToInt(mUnscaledInnerSize * mPuppetWidget->GetDefaultScale());
   return ViewAs<ScreenPixel>(
       innerSize, PixelCastJustification::LayoutDeviceIsScreenForTabDims);
 };
 
 ScreenIntRect TabChild::GetOuterRect() {
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -652,16 +652,21 @@ class TabChild final : public TabChildBa
   static const nsTHashtable<nsPtrHashKey<TabChild>>& GetVisibleTabs() {
     MOZ_ASSERT(HasVisibleTabs());
     return *sVisibleTabs;
   }
 
  protected:
   virtual ~TabChild();
 
+  virtual PWindowGlobalChild* AllocPWindowGlobalChild(
+      const WindowGlobalInit& aInit) override;
+
+  virtual bool DeallocPWindowGlobalChild(PWindowGlobalChild* aActor) override;
+
   virtual mozilla::ipc::IPCResult RecvDestroy() override;
 
   virtual mozilla::ipc::IPCResult RecvSetDocShellIsActive(
       const bool& aIsActive) override;
 
   virtual mozilla::ipc::IPCResult RecvRenderLayers(
       const bool& aEnabled, const bool& aForce,
       const layers::LayersObserverEpoch& aEpoch) override;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -95,16 +95,18 @@
 #include "ImageOps.h"
 #include "UnitTransforms.h"
 #include <algorithm>
 #include "mozilla/NullPrincipal.h"
 #include "mozilla/WebBrowserPersistDocumentParent.h"
 #include "ProcessPriorityManager.h"
 #include "nsString.h"
 #include "IHistory.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/ChromeBrowsingContext.h"
 
 #ifdef XP_WIN
 #include "mozilla/plugins/PluginWidgetParent.h"
 #endif
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
 #include "mozilla/a11y/AccessibleWrap.h"
 #include "mozilla/a11y/Compatibility.h"
@@ -983,16 +985,34 @@ bool TabParent::DeallocPIndexedDBPermiss
     PIndexedDBPermissionRequestParent* aActor) {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aActor);
 
   return mozilla::dom::indexedDB::DeallocPIndexedDBPermissionRequestParent(
       aActor);
 }
 
+IPCResult TabParent::RecvPWindowGlobalConstructor(
+    PWindowGlobalParent* aActor, const WindowGlobalInit& aInit) {
+  static_cast<WindowGlobalParent*>(aActor)->Init(aInit);
+  return IPC_OK();
+}
+
+PWindowGlobalParent* TabParent::AllocPWindowGlobalParent(
+    const WindowGlobalInit& aInit) {
+  // Reference freed in DeallocPWindowGlobalParent.
+  return do_AddRef(new WindowGlobalParent(aInit, /* inproc */ false)).take();
+}
+
+bool TabParent::DeallocPWindowGlobalParent(PWindowGlobalParent* aActor) {
+  // Free reference from AllocPWindowGlobalParent.
+  static_cast<WindowGlobalParent*>(aActor)->Release();
+  return true;
+}
+
 void TabParent::SendMouseEvent(const nsAString& aType, float aX, float aY,
                                int32_t aButton, int32_t aClickCount,
                                int32_t aModifiers,
                                bool aIgnoreRootScrollFrame) {
   if (!mIsDestroyed) {
     Unused << PBrowserParent::SendMouseEvent(nsString(aType), aX, aY, aButton,
                                              aClickCount, aModifiers,
                                              aIgnoreRootScrollFrame);
@@ -3366,16 +3386,24 @@ mozilla::ipc::IPCResult TabParent::RecvS
 mozilla::ipc::IPCResult TabParent::RecvGetSystemFont(nsCString* aFontName) {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (widget) {
     widget->GetSystemFont(*aFontName);
   }
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult TabParent::RecvRootBrowsingContext(
+    const BrowsingContextId& aId) {
+  MOZ_ASSERT(!mBrowsingContext, "May only set browsing context once!");
+  mBrowsingContext = ChromeBrowsingContext::Get(aId);
+  MOZ_ASSERT(mBrowsingContext, "Invalid ID!");
+  return IPC_OK();
+}
+
 NS_IMETHODIMP
 FakeChannel::OnAuthAvailable(nsISupports* aContext,
                              nsIAuthInformation* aAuthInfo) {
   nsAuthInformationHolder* holder =
       static_cast<nsAuthInformationHolder*>(aAuthInfo);
 
   if (!net::gNeckoChild->SendOnAuthAvailable(
           mCallbackId, holder->User(), holder->Password(), holder->Domain())) {
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -62,16 +62,17 @@ struct IMENotification;
 
 namespace gfx {
 class SourceSurface;
 class DataSourceSurface;
 }  // namespace gfx
 
 namespace dom {
 
+class ChromeBrowsingContext;
 class ClonedMessageData;
 class nsIContentParent;
 class Element;
 class DataTransfer;
 
 namespace ipc {
 class StructuredCloneData;
 }  // namespace ipc
@@ -120,16 +121,18 @@ class TabParent final : public PBrowserP
   }
 
   already_AddRefed<nsILoadContext> GetLoadContext();
 
   already_AddRefed<nsIWidget> GetTopLevelWidget();
 
   nsIXULBrowserWindow* GetXULBrowserWindow();
 
+  ChromeBrowsingContext* GetBrowsingContext() { return mBrowsingContext; }
+
   void Destroy();
 
   void RemoveWindowListeners();
 
   void AddWindowListeners();
 
   virtual mozilla::ipc::IPCResult RecvMoveFocus(
       const bool& aForward, const bool& aForDocumentNavigation) override;
@@ -300,16 +303,24 @@ class TabParent final : public PBrowserP
       const uint64_t& aParentID, const uint32_t& aMsaaID,
       const IAccessibleHolder& aDocCOMProxy) override;
 
   /**
    * Return the top level doc accessible parent for this tab.
    */
   a11y::DocAccessibleParent* GetTopLevelDocAccessible() const;
 
+  virtual PWindowGlobalParent* AllocPWindowGlobalParent(
+      const WindowGlobalInit& aInit) override;
+
+  virtual bool DeallocPWindowGlobalParent(PWindowGlobalParent* aActor) override;
+
+  virtual mozilla::ipc::IPCResult RecvPWindowGlobalConstructor(
+      PWindowGlobalParent* aActor, const WindowGlobalInit& aInit) override;
+
   void LoadURL(nsIURI* aURI);
 
   void InitRendering();
   void MaybeShowFrame();
 
   // XXX/cjones: it's not clear what we gain by hiding these
   // message-sending functions under a layer of indirection and
   // eating the return values
@@ -571,16 +582,19 @@ class TabParent final : public PBrowserP
 
   virtual mozilla::ipc::IPCResult RecvSetDimensions(
       const uint32_t& aFlags, const int32_t& aX, const int32_t& aY,
       const int32_t& aCx, const int32_t& aCy) override;
 
   virtual mozilla::ipc::IPCResult RecvShowCanvasPermissionPrompt(
       const nsCString& aFirstPartyURI) override;
 
+  virtual mozilla::ipc::IPCResult RecvRootBrowsingContext(
+      const BrowsingContextId& aId) override;
+
   mozilla::ipc::IPCResult RecvSetSystemFont(
       const nsCString& aFontName) override;
   mozilla::ipc::IPCResult RecvGetSystemFont(nsCString* aFontName) override;
 
   virtual mozilla::ipc::IPCResult RecvVisitURI(
       const URIParams& aURI, const OptionalURIParams& aLastVisitedURI,
       const uint32_t& aFlags) override;
 
@@ -648,16 +662,19 @@ class TabParent final : public PBrowserP
 
   nsCOMPtr<nsILoadContext> mLoadContext;
 
   // We keep a strong reference to the frameloader after we've sent the
   // Destroy message and before we've received __delete__. This allows us to
   // dispatch message manager messages during this time.
   RefPtr<nsFrameLoader> mFrameLoader;
 
+  // The root browsing context loaded in this TabParent.
+  RefPtr<ChromeBrowsingContext> mBrowsingContext;
+
   TabId mTabId;
 
   // When loading a new tab or window via window.open, the child is
   // responsible for loading the URL it wants into the new TabChild. When the
   // parent receives the CreateWindow message, though, it sends a LoadURL
   // message, usually for about:blank. It's important for the about:blank load
   // to get processed because the Firefox frontend expects every new window to
   // immediately start loading something (see bug 1123090). However, we want
new file mode 100644
--- /dev/null
+++ b/dom/ipc/WindowGlobalChild.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* 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/dom/WindowGlobalChild.h"
+#include "mozilla/ipc/InProcessChild.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/WindowGlobalActorsBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+typedef nsRefPtrHashtable<nsUint64HashKey, WindowGlobalChild> WGCByIdMap;
+static StaticAutoPtr<WGCByIdMap> gWindowGlobalChildById;
+
+WindowGlobalChild::WindowGlobalChild(nsGlobalWindowInner* aWindow,
+                                     dom::BrowsingContext* aBrowsingContext)
+  : mWindowGlobal(aWindow)
+  , mBrowsingContext(aBrowsingContext)
+  , mInnerWindowId(aWindow->WindowID())
+  , mOuterWindowId(aWindow->GetOuterWindow()->WindowID())
+  , mIPCClosed(true)
+{
+}
+
+already_AddRefed<WindowGlobalChild>
+WindowGlobalChild::Create(nsGlobalWindowInner* aWindow)
+{
+  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();
+  RefPtr<WindowGlobalChild> wgc = new WindowGlobalChild(aWindow, bc);
+
+  WindowGlobalInit init(principal,
+                        BrowsingContextId(wgc->BrowsingContext()->Id()),
+                        wgc->mInnerWindowId,
+                        wgc->mOuterWindowId);
+
+  // Send the link constructor over PInProcessChild or PBrowser.
+  if (XRE_IsParentProcess()) {
+    InProcessChild* ipc = InProcessChild::Singleton();
+    if (!ipc) {
+      return nullptr;
+    }
+
+    // Note: ref is released in DeallocPWindowGlobalChild
+    ipc->SendPWindowGlobalConstructor(do_AddRef(wgc).take(), init);
+  } else {
+    RefPtr<TabChild> tabChild = TabChild::GetFrom(static_cast<mozIDOMWindow*>(aWindow));
+    MOZ_ASSERT(tabChild);
+
+    // Note: ref is released in DeallocPWindowGlobalChild
+    tabChild->SendPWindowGlobalConstructor(do_AddRef(wgc).take(), init);
+  }
+  wgc->mIPCClosed = false;
+
+  // Register this WindowGlobal in the gWindowGlobalParentsById map.
+  if (!gWindowGlobalChildById) {
+    gWindowGlobalChildById = new WGCByIdMap();
+    ClearOnShutdown(&gWindowGlobalChildById);
+  }
+  auto entry = gWindowGlobalChildById->LookupForAdd(wgc->mInnerWindowId);
+  MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowGlobalChild entry for ID!");
+  entry.OrInsert([&] { return wgc; });
+
+  return wgc.forget();
+}
+
+/* static */ already_AddRefed<WindowGlobalChild>
+WindowGlobalChild::GetByInnerWindowId(uint64_t aInnerWindowId)
+{
+  if (!gWindowGlobalChildById) {
+    return nullptr;
+  }
+  return gWindowGlobalChildById->Get(aInnerWindowId);
+}
+
+bool
+WindowGlobalChild::IsCurrentGlobal()
+{
+  return !mIPCClosed && mWindowGlobal->IsCurrentInnerWindow();
+}
+
+already_AddRefed<WindowGlobalParent>
+WindowGlobalChild::GetParentActor()
+{
+  if (mIPCClosed) {
+    return nullptr;
+  }
+  IProtocol* otherSide = InProcessChild::ParentActorFor(this);
+  return do_AddRef(static_cast<WindowGlobalParent*>(otherSide));
+}
+
+void
+WindowGlobalChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mIPCClosed = true;
+  gWindowGlobalChildById->Remove(mInnerWindowId);
+}
+
+WindowGlobalChild::~WindowGlobalChild()
+{
+  MOZ_ASSERT(!gWindowGlobalChildById ||
+             !gWindowGlobalChildById->Contains(mInnerWindowId));
+}
+
+JSObject*
+WindowGlobalChild::WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto)
+{
+  return WindowGlobalChild_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports*
+WindowGlobalChild::GetParentObject()
+{
+  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WindowGlobalChild,
+                                      mWindowGlobal,
+                                      mBrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WindowGlobalChild, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WindowGlobalChild, Release)
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/WindowGlobalChild.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* 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/. */
+
+#ifndef mozilla_dom_WindowGlobalChild_h
+#define mozilla_dom_WindowGlobalChild_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PWindowGlobalChild.h"
+#include "nsWrapperCache.h"
+
+class nsGlobalWindowInner;
+class nsDocShell;
+
+namespace mozilla {
+namespace dom  {
+
+class BrowsingContext;
+class WindowGlobalParent;
+
+/**
+ * Actor for a single nsGlobalWindowInner. This actor is used to communicate
+ * information to the parent process asynchronously.
+ */
+class WindowGlobalChild : public nsWrapperCache
+                        , public PWindowGlobalChild
+{
+public:
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WindowGlobalChild)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WindowGlobalChild)
+
+  static already_AddRefed<WindowGlobalChild>
+  GetByInnerWindowId(uint64_t aInnerWindowId);
+
+  static already_AddRefed<WindowGlobalChild>
+  GetByInnerWindowId(const GlobalObject& aGlobal, uint64_t aInnerWindowId) {
+    return GetByInnerWindowId(aInnerWindowId);
+  }
+
+  dom::BrowsingContext* BrowsingContext() { return mBrowsingContext; }
+  nsGlobalWindowInner* WindowGlobal() { return mWindowGlobal; }
+
+  // Has this actor been shut down
+  bool IsClosed() { return mIPCClosed; }
+
+  // Check if this actor is managed by PInProcess, as-in the document is loaded
+  // in the chrome process.
+  bool IsInProcess() { return XRE_IsParentProcess(); }
+
+  // The Window ID for this WindowGlobal
+  uint64_t InnerWindowId() { return mInnerWindowId; }
+  uint64_t OuterWindowId() { return mOuterWindowId; }
+
+  bool IsCurrentGlobal();
+
+  // Get the other side of this actor if it is an in-process actor. Returns
+  // |nullptr| if the actor has been torn down, or is not in-process.
+  already_AddRefed<WindowGlobalParent> GetParentActor();
+
+  // Create and initialize the WindowGlobalChild object.
+  static already_AddRefed<WindowGlobalChild>
+  Create(nsGlobalWindowInner* aWindow);
+
+  nsISupports* GetParentObject();
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+  WindowGlobalChild(nsGlobalWindowInner* aWindow, dom::BrowsingContext* aBc);
+  ~WindowGlobalChild();
+
+  RefPtr<nsGlobalWindowInner> mWindowGlobal;
+  RefPtr<dom::BrowsingContext> mBrowsingContext;
+  uint64_t mInnerWindowId;
+  uint64_t mOuterWindowId;
+  bool mIPCClosed;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_WindowGlobalChild_h)
new file mode 100644
--- /dev/null
+++ b/dom/ipc/WindowGlobalParent.cpp
@@ -0,0 +1,175 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* 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/dom/WindowGlobalParent.h"
+#include "mozilla/ipc/InProcessParent.h"
+#include "mozilla/dom/ChromeBrowsingContext.h"
+#include "mozilla/dom/WindowGlobalActorsBinding.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+typedef nsRefPtrHashtable<nsUint64HashKey, WindowGlobalParent> WGPByIdMap;
+static StaticAutoPtr<WGPByIdMap> gWindowGlobalParentsById;
+
+WindowGlobalParent::WindowGlobalParent(const WindowGlobalInit& aInit,
+                                       bool aInProcess)
+  : mDocumentPrincipal(aInit.principal())
+  , mInnerWindowId(aInit.innerWindowId())
+  , mOuterWindowId(aInit.outerWindowId())
+  , mInProcess(aInProcess)
+  , mIPCClosed(true)  // Closed until WGP::Init
+{
+  MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess(), "Parent process only");
+  MOZ_RELEASE_ASSERT(mDocumentPrincipal, "Must have a valid principal");
+
+  // NOTE: mBrowsingContext initialized in Init()
+  MOZ_RELEASE_ASSERT(aInit.browsingContextId() != 0,
+                     "Must be made in BrowsingContext");
+}
+
+void
+WindowGlobalParent::Init(const WindowGlobalInit& aInit)
+{
+  MOZ_ASSERT(Manager(), "Should have a manager!");
+  MOZ_ASSERT(!mFrameLoader, "Cannot Init() a WindowGlobalParent twice!");
+
+  MOZ_ASSERT(mIPCClosed, "IPC shouldn't be open yet");
+  mIPCClosed = false;
+
+  // Register this WindowGlobal in the gWindowGlobalParentsById map.
+  if (!gWindowGlobalParentsById) {
+    gWindowGlobalParentsById = new WGPByIdMap();
+    ClearOnShutdown(&gWindowGlobalParentsById);
+  }
+  auto entry = gWindowGlobalParentsById->LookupForAdd(mInnerWindowId);
+  MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowGlobalParent entry for ID!");
+  entry.OrInsert([&] { return this; });
+
+  // Determine which content process the window global is coming from.
+  ContentParentId processId(0);
+  if (!mInProcess) {
+    processId = static_cast<ContentParent*>(Manager()->Manager())->ChildID();
+  }
+
+  mBrowsingContext = ChromeBrowsingContext::Get(aInit.browsingContextId());
+  MOZ_ASSERT(mBrowsingContext);
+
+  // XXX(nika): This won't be the case soon, but for now this is a good
+  // assertion as we can't switch processes. We should relax this eventually.
+  MOZ_ASSERT(mBrowsingContext->IsOwnedByProcess(processId));
+
+  // Attach ourself to the browsing context.
+  mBrowsingContext->RegisterWindowGlobal(this);
+
+  // Determine what toplevel frame element our WindowGlobalParent is being
+  // embedded in.
+  RefPtr<Element> frameElement;
+  if (mInProcess) {
+    // In the in-process case, we can get it from the other side's
+    // WindowGlobalChild.
+    MOZ_ASSERT(Manager()->GetProtocolTypeId() == PInProcessMsgStart);
+    RefPtr<WindowGlobalChild> otherSide = GetChildActor();
+    if (otherSide && otherSide->WindowGlobal()) {
+      // Get the toplevel window from the other side.
+      RefPtr<nsDocShell> docShell = nsDocShell::Cast(otherSide->WindowGlobal()->GetDocShell());
+      if (docShell) {
+        docShell->GetTopFrameElement(getter_AddRefs(frameElement));
+      }
+    }
+  } else {
+    // In the cross-process case, we can get the frame element from our manager.
+    MOZ_ASSERT(Manager()->GetProtocolTypeId() == PBrowserMsgStart);
+    frameElement = static_cast<TabParent*>(Manager())->GetOwnerElement();
+  }
+
+  // Extract the nsFrameLoader from the current frame element. We may not have a
+  // nsFrameLoader if we are a chrome document.
+  nsCOMPtr<nsIFrameLoaderOwner> flOwner = do_QueryInterface(frameElement);
+  if (flOwner) {
+    mFrameLoader = flOwner->GetFrameLoader();
+  }
+}
+
+/* static */ already_AddRefed<WindowGlobalParent>
+WindowGlobalParent::GetByInnerWindowId(uint64_t aInnerWindowId)
+{
+  if (!gWindowGlobalParentsById) {
+    return nullptr;
+  }
+  return gWindowGlobalParentsById->Get(aInnerWindowId);
+}
+
+already_AddRefed<WindowGlobalChild>
+WindowGlobalParent::GetChildActor()
+{
+  if (mIPCClosed) {
+    return nullptr;
+  }
+  IProtocol* otherSide = InProcessParent::ChildActorFor(this);
+  return do_AddRef(static_cast<WindowGlobalChild*>(otherSide));
+}
+
+IPCResult
+WindowGlobalParent::RecvUpdateDocumentURI(nsIURI* aURI)
+{
+  // XXX(nika): Assert that the URI change was one which makes sense (either
+  // about:blank -> a real URI, or a legal push/popstate URI change?)
+  mDocumentURI = aURI;
+  return IPC_OK();
+}
+
+IPCResult
+WindowGlobalParent::RecvBecomeCurrentWindowGlobal()
+{
+  mBrowsingContext->SetCurrentWindowGlobal(this);
+  return IPC_OK();
+}
+
+bool
+WindowGlobalParent::IsCurrentGlobal()
+{
+  return !mIPCClosed && mBrowsingContext->GetCurrentWindowGlobal() == this;
+}
+
+void
+WindowGlobalParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  mIPCClosed = true;
+  gWindowGlobalParentsById->Remove(mInnerWindowId);
+  mBrowsingContext->UnregisterWindowGlobal(this);
+}
+
+WindowGlobalParent::~WindowGlobalParent()
+{
+  MOZ_ASSERT(!gWindowGlobalParentsById ||
+             !gWindowGlobalParentsById->Contains(mInnerWindowId));
+}
+
+JSObject*
+WindowGlobalParent::WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto)
+{
+  return WindowGlobalParent_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsISupports*
+WindowGlobalParent::GetParentObject()
+{
+  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WindowGlobalParent,
+                                      mFrameLoader,
+                                      mBrowsingContext)
+
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WindowGlobalParent, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WindowGlobalParent, Release)
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/ipc/WindowGlobalParent.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* 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/. */
+
+#ifndef mozilla_dom_WindowGlobalParent_h
+#define mozilla_dom_WindowGlobalParent_h
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/PWindowGlobalParent.h"
+#include "nsWrapperCache.h"
+
+class nsIPrincipal;
+class nsIURI;
+class nsFrameLoader;
+
+namespace mozilla {
+namespace dom  {
+
+class ChromeBrowsingContext;
+class WindowGlobalChild;
+
+/**
+ * A handle in the parent process to a specific nsGlobalWindowInner object.
+ */
+class WindowGlobalParent final : public nsWrapperCache
+                               , public PWindowGlobalParent
+{
+public:
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WindowGlobalParent)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WindowGlobalParent)
+
+  static already_AddRefed<WindowGlobalParent>
+  GetByInnerWindowId(uint64_t aInnerWindowId);
+
+  static already_AddRefed<WindowGlobalParent>
+  GetByInnerWindowId(const GlobalObject& aGlobal, uint64_t aInnerWindowId) {
+    return GetByInnerWindowId(aInnerWindowId);
+  }
+
+  // Has this actor been shut down
+  bool IsClosed() { return mIPCClosed; }
+
+  // Check if this actor is managed by PInProcess, as-in the document is loaded
+  // in-process.
+  bool IsInProcess() { return mInProcess; }
+
+  // Get the other side of this actor if it is an in-process actor. Returns
+  // |nullptr| if the actor has been torn down, or is not in-process.
+  already_AddRefed<WindowGlobalChild> GetChildActor();
+
+  // The principal of this WindowGlobal. This value will not change over the
+  // lifetime of the WindowGlobal object, even to reflect changes in
+  // |document.domain|.
+  nsIPrincipal* DocumentPrincipal() { return mDocumentPrincipal; }
+
+  // The BrowsingContext which this WindowGlobal has been loaded into.
+  ChromeBrowsingContext* BrowsingContext() { return mBrowsingContext; }
+
+  // Get the root nsFrameLoader object for the tree of BrowsingContext nodes
+  // which this WindowGlobal is a part of. This will be the nsFrameLoader
+  // holding the TabParent for remote tabs, and the root content frameloader for
+  // non-remote tabs.
+  nsFrameLoader* GetRootFrameLoader() { return mFrameLoader; }
+
+  // The current URI which loaded in the document.
+  nsIURI* GetDocumentURI() { return mDocumentURI; }
+
+  // Window IDs for inner/outer windows.
+  uint64_t OuterWindowId() { return mOuterWindowId; }
+  uint64_t InnerWindowId() { return mInnerWindowId; }
+
+  bool IsCurrentGlobal();
+
+  // Create a WindowGlobalParent from over IPC. This method should not be called
+  // from outside of the IPC constructors.
+  WindowGlobalParent(const WindowGlobalInit& aInit, bool aInProcess);
+
+  // Initialize the mFrameLoader fields for a created WindowGlobalParent. Must
+  // be called after setting the Manager actor.
+  void Init(const WindowGlobalInit& aInit);
+
+  nsISupports* GetParentObject();
+  JSObject* WrapObject(JSContext* aCx,
+                       JS::Handle<JSObject*> aGivenProto) override;
+
+protected:
+  // IPC messages
+  mozilla::ipc::IPCResult RecvUpdateDocumentURI(nsIURI* aURI) override;
+  mozilla::ipc::IPCResult RecvBecomeCurrentWindowGlobal() override;
+
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+  ~WindowGlobalParent();
+
+  // NOTE: This document principal doesn't reflect possible |document.domain|
+  // mutations which may have been made in the actual document.
+  nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
+  nsCOMPtr<nsIURI> mDocumentURI;
+  RefPtr<nsFrameLoader> mFrameLoader;
+  RefPtr<ChromeBrowsingContext> mBrowsingContext;
+  uint64_t mInnerWindowId;
+  uint64_t mOuterWindowId;
+  bool mInProcess;
+  bool mIPCClosed;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // !defined(mozilla_dom_WindowGlobalParent_h)
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -44,16 +44,18 @@ EXPORTS.mozilla.dom += [
     'nsIContentParent.h',
     'PermissionMessageUtils.h',
     'TabChild.h',
     'TabContext.h',
     'TabMessageUtils.h',
     'TabParent.h',
     'URLClassifierChild.h',
     'URLClassifierParent.h',
+    'WindowGlobalChild.h',
+    'WindowGlobalParent.h',
 ]
 
 EXPORTS.mozilla += [
     'PreallocatedProcessManager.h',
     'ProcessHangMonitor.h',
     'ProcessHangMonitorIPC.h',
     'ProcessPriorityManager.h',
 ]
@@ -78,16 +80,18 @@ UNIFIED_SOURCES += [
     'SharedMap.cpp',
     'SharedStringMap.cpp',
     'StructuredCloneData.cpp',
     'TabChild.cpp',
     'TabContext.cpp',
     'TabMessageUtils.cpp',
     'TabParent.cpp',
     'URLClassifierParent.cpp',
+    'WindowGlobalChild.cpp',
+    'WindowGlobalParent.cpp',
 ]
 
 # ContentChild.cpp cannot be compiled in unified mode on  linux due to Time conflict
 SOURCES += [
     'ContentChild.cpp',
     'ProcessHangMonitor.cpp',
 ]
 
@@ -105,16 +109,17 @@ IPDL_SOURCES += [
     'PFilePicker.ipdl',
     'PLoginReputation.ipdl',
     'PPluginWidget.ipdl',
     'PProcessHangMonitor.ipdl',
     'PTabContext.ipdlh',
     'PURLClassifier.ipdl',
     'PURLClassifierInfo.ipdlh',
     'PURLClassifierLocal.ipdl',
+    'PWindowGlobal.ipdl',
     'ServiceWorkerConfiguration.ipdlh',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_TARGET'] == 'Darwin':
--- a/dom/webidl/FrameLoader.webidl
+++ b/dom/webidl/FrameLoader.webidl
@@ -29,16 +29,22 @@ interface FrameLoader {
   /**
    * Get an nsILoadContext for the top-level docshell. For remote
    * frames, a shim is returned that contains private browsing and app
    * information.
    */
   readonly attribute LoadContext loadContext;
 
   /**
+   * Get the root BrowsingContext within the frame.
+   * This may be null immediately after creating a remote frame.
+   */
+  readonly attribute BrowsingContext? browsingContext;
+
+  /**
    * Get the ParentSHistory for the nsFrameLoader. May return null if this
    * frameloader is not for a toplevel frame.
    */
   readonly attribute ParentSHistory? parentSHistory;
 
   /**
    * Find out whether the loader's frame is at too great a depth in
    * the frame tree.  This can be used to decide what operations may
--- a/image/AnimationFrameBuffer.cpp
+++ b/image/AnimationFrameBuffer.cpp
@@ -30,16 +30,17 @@ AnimationFrameRetainedBuffer::AnimationF
   mPending = mBatch * 2;
 }
 
 bool AnimationFrameRetainedBuffer::InsertInternal(RefPtr<imgFrame>&& aFrame) {
   // We should only insert new frames if we actually asked for them.
   MOZ_ASSERT(!mSizeKnown);
   MOZ_ASSERT(mFrames.Length() < mThreshold);
 
+  ++mSize;
   mFrames.AppendElement(std::move(aFrame));
   MOZ_ASSERT(mSize == mFrames.Length());
   return mSize < mThreshold;
 }
 
 bool AnimationFrameRetainedBuffer::ResetInternal() {
   // If we haven't crossed the threshold, then we know by definition we have
   // not discarded any frames. If we previously requested more frames, but
@@ -135,30 +136,42 @@ void AnimationFrameRetainedBuffer::AddSi
                                   });
   }
 }
 
 AnimationFrameDiscardingQueue::AnimationFrameDiscardingQueue(
     AnimationFrameRetainedBuffer&& aQueue)
     : AnimationFrameBuffer(aQueue),
       mInsertIndex(aQueue.mFrames.Length()),
-      mFirstFrame(std::move(aQueue.mFrames[0])) {
+      mFirstFrame(aQueue.mFrames[0]) {
   MOZ_ASSERT(!mSizeKnown);
   MOZ_ASSERT(!mRedecodeError);
   MOZ_ASSERT(mInsertIndex > 0);
-  MOZ_ASSERT(mGetIndex > 0);
   mMayDiscard = true;
 
-  for (size_t i = aQueue.mGetIndex; i < mInsertIndex; ++i) {
+  // We avoided moving aQueue.mFrames[0] for mFirstFrame above because it is
+  // possible the animation was reset back to the beginning, and then we crossed
+  // the threshold without advancing further. That would mean mGetIndex is 0.
+  for (size_t i = mGetIndex; i < mInsertIndex; ++i) {
     MOZ_ASSERT(aQueue.mFrames[i]);
     mDisplay.push_back(std::move(aQueue.mFrames[i]));
   }
 }
 
 bool AnimationFrameDiscardingQueue::InsertInternal(RefPtr<imgFrame>&& aFrame) {
+  if (mInsertIndex == mSize) {
+    if (mSizeKnown) {
+      // We produced more frames on a subsequent decode than on the first pass.
+      mRedecodeError = true;
+      mPending = 0;
+      return true;
+    }
+    ++mSize;
+  }
+
   // Even though we don't use redecoded first frames for display purposes, we
   // will still use them for recycling, so we still need to insert it.
   mDisplay.push_back(std::move(aFrame));
   ++mInsertIndex;
   MOZ_ASSERT(mInsertIndex <= mSize);
   return true;
 }
 
@@ -169,17 +182,16 @@ bool AnimationFrameDiscardingQueue::Rese
   bool restartDecoder = mPending == 0;
   mPending = 2 * mBatch;
   return restartDecoder;
 }
 
 bool AnimationFrameDiscardingQueue::MarkComplete(
     const gfx::IntRect& aFirstFrameRefreshArea) {
   if (NS_WARN_IF(mInsertIndex != mSize)) {
-    MOZ_ASSERT(mSizeKnown);
     mRedecodeError = true;
     mPending = 0;
   }
 
   // We reached the end of the animation, the next frame we get, if we get
   // another, will be the first frame again.
   mInsertIndex = 0;
   mSizeKnown = true;
@@ -291,16 +303,20 @@ void AnimationFrameDiscardingQueue::AddS
 AnimationFrameRecyclingQueue::AnimationFrameRecyclingQueue(
     AnimationFrameRetainedBuffer&& aQueue)
     : AnimationFrameDiscardingQueue(std::move(aQueue)),
       mForceUseFirstFrameRefreshArea(false) {
   // In an ideal world, we would always save the already displayed frames for
   // recycling but none of the frames were marked as recyclable. We will incur
   // the extra allocation cost for a few more frames.
   mRecycling = true;
+
+  // Until we reach the end of the animation, set the first frame refresh area
+  // to match that of the full area of the first frame.
+  mFirstFrameRefreshArea = mFirstFrame->GetRect();
 }
 
 void AnimationFrameRecyclingQueue::AddSizeOfExcludingThis(
     MallocSizeOf aMallocSizeOf, const AddSizeOfCb& aCallback) {
   AnimationFrameDiscardingQueue::AddSizeOfExcludingThis(aMallocSizeOf,
                                                         aCallback);
 
   for (const RecycleEntry& entry : mRecycle) {
@@ -365,30 +381,41 @@ void AnimationFrameRecyclingQueue::Advan
       // is still in use.
       newPending = 1;
     }
     mPending = newPending;
   }
 }
 
 bool AnimationFrameRecyclingQueue::ResetInternal() {
-  mRecycle.clear();
+  // We should save any display frames that we can to save on at least the
+  // allocation. The first frame refresh area is guaranteed to be the aggregate
+  // dirty rect or the entire frame, and so the bare minimum area we can
+  // recycle. We don't need to worry about updating the dirty rect for the
+  // existing mRecycle entries, because that will happen in RecycleFrame when
+  // we try to pull out a frame to redecode the first frame.
+  for (RefPtr<imgFrame>& frame : mDisplay) {
+    if (frame->ShouldRecycle()) {
+      RecycleEntry newEntry(mFirstFrameRefreshArea);
+      newEntry.mFrame = std::move(frame);
+      mRecycle.push_back(std::move(newEntry));
+    }
+  }
+
   return AnimationFrameDiscardingQueue::ResetInternal();
 }
 
 RawAccessFrameRef AnimationFrameRecyclingQueue::RecycleFrame(
     gfx::IntRect& aRecycleRect) {
   if (mInsertIndex == 0) {
     // If we are recreating the first frame, then we actually have already
     // precomputed aggregate of the dirty rects as the first frame refresh
     // area. We know that all of the frames still in the recycling queue
     // need to take into account the same dirty rect because they are also
     // frames which cross the boundary.
-    MOZ_ASSERT(mSizeKnown);
-    MOZ_ASSERT(!mFirstFrameRefreshArea.IsEmpty());
     for (RecycleEntry& entry : mRecycle) {
       MOZ_ASSERT(mFirstFrameRefreshArea.Contains(entry.mDirtyRect));
       entry.mDirtyRect = mFirstFrameRefreshArea;
     }
     // Until we advance to the first frame again, any subsequent recycled
     // frames should also use the first frame refresh area.
     mForceUseFirstFrameRefreshArea = true;
   }
@@ -434,18 +461,17 @@ RawAccessFrameRef AnimationFrameRecyclin
   return recycledFrame;
 }
 
 bool AnimationFrameRecyclingQueue::MarkComplete(
     const gfx::IntRect& aFirstFrameRefreshArea) {
   bool continueDecoding =
       AnimationFrameDiscardingQueue::MarkComplete(aFirstFrameRefreshArea);
 
-  MOZ_ASSERT_IF(!mRedecodeError, mFirstFrameRefreshArea.IsEmpty() ||
-                                     mFirstFrameRefreshArea.IsEqualEdges(
-                                         aFirstFrameRefreshArea));
-
-  mFirstFrameRefreshArea = aFirstFrameRefreshArea;
+  // If we encounter a redecode error, just make the first frame refresh area to
+  // be the full frame, because we don't really know what we can safely recycle.
+  mFirstFrameRefreshArea = mRedecodeError ? mFirstFrame->GetRect()
+                                          : aFirstFrameRefreshArea;
   return continueDecoding;
 }
 
 }  // namespace image
 }  // namespace mozilla
--- a/image/AnimationFrameBuffer.h
+++ b/image/AnimationFrameBuffer.h
@@ -184,20 +184,16 @@ class AnimationFrameBuffer {
    *
    * @returns True if the decoder should decode another frame.
    */
   InsertStatus Insert(RefPtr<imgFrame>&& aFrame) {
     MOZ_ASSERT(mPending > 0);
     MOZ_ASSERT(aFrame);
 
     --mPending;
-    if (!mSizeKnown) {
-      ++mSize;
-    }
-
     bool retain = InsertInternal(std::move(aFrame));
 
     if (mAdvance > 0 && mSize > 1) {
       --mAdvance;
       ++mGetIndex;
       AdvanceInternal();
     }
 
--- a/image/AnimationSurfaceProvider.cpp
+++ b/image/AnimationSurfaceProvider.cpp
@@ -284,16 +284,26 @@ bool AnimationSurfaceProvider::CheckForN
     }
 
     // We should've gotten a different frame than last time.
     MOZ_ASSERT(!mFrames->IsLastInsertedFrame(frame));
 
     // Append the new frame to the list.
     AnimationFrameBuffer::InsertStatus status =
         mFrames->Insert(std::move(frame));
+
+    // If we hit a redecode error, then we actually want to stop. This happens
+    // when we tried to insert more frames than we originally had (e.g. the
+    // original decoder attempt hit an OOM error sooner than we did). Better to
+    // stop the animation than to get out of sync with FrameAnimator.
+    if (mFrames->HasRedecodeError()) {
+      mDecoder = nullptr;
+      return false;
+    }
+
     switch (status) {
       case AnimationFrameBuffer::InsertStatus::DISCARD_CONTINUE:
         continueDecoding = true;
         MOZ_FALLTHROUGH;
       case AnimationFrameBuffer::InsertStatus::DISCARD_YIELD:
         RequestFrameDiscarding();
         break;
       case AnimationFrameBuffer::InsertStatus::CONTINUE:
@@ -348,16 +358,23 @@ bool AnimationSurfaceProvider::CheckForN
 
     if (!frame || mFrames->IsLastInsertedFrame(frame)) {
       return mFrames->MarkComplete(mDecoder->GetFirstFrameRefreshArea());
     }
 
     // Append the new frame to the list.
     AnimationFrameBuffer::InsertStatus status =
         mFrames->Insert(std::move(frame));
+
+    // If we hit a redecode error, then we actually want to stop. This will be
+    // fully handled in FinishDecoding.
+    if (mFrames->HasRedecodeError()) {
+      return false;
+    }
+
     switch (status) {
       case AnimationFrameBuffer::InsertStatus::DISCARD_CONTINUE:
       case AnimationFrameBuffer::InsertStatus::DISCARD_YIELD:
         RequestFrameDiscarding();
         break;
       case AnimationFrameBuffer::InsertStatus::CONTINUE:
       case AnimationFrameBuffer::InsertStatus::YIELD:
         break;
--- a/image/test/gtest/TestAnimationFrameBuffer.cpp
+++ b/image/test/gtest/TestAnimationFrameBuffer.cpp
@@ -141,17 +141,18 @@ VerifyInsertAndAdvance(AnimationFrameBuf
 static void
 VerifyMarkComplete(AnimationFrameBuffer& aQueue,
                    bool aExpectedContinue,
                    const IntRect& aRefreshArea = IntRect(0, 0, 1, 1))
 {
   if (aQueue.IsRecycling() && !aQueue.SizeKnown()) {
     const AnimationFrameRecyclingQueue& queue =
       *static_cast<AnimationFrameRecyclingQueue*>(&aQueue);
-    EXPECT_TRUE(queue.FirstFrameRefreshArea().IsEmpty());
+    EXPECT_EQ(queue.FirstFrame()->GetRect(),
+              queue.FirstFrameRefreshArea());
   }
 
   bool keepDecoding = aQueue.MarkComplete(aRefreshArea);
   EXPECT_EQ(aExpectedContinue, keepDecoding);
 
   if (aQueue.IsRecycling()) {
     const AnimationFrameRecyclingQueue& queue =
       *static_cast<AnimationFrameRecyclingQueue*>(&aQueue);
@@ -189,22 +190,16 @@ VerifyReset(AnimationFrameBuffer& aQueue
   } else {
     const AnimationFrameDiscardingQueue& queue =
       *static_cast<AnimationFrameDiscardingQueue*>(&aQueue);
     EXPECT_EQ(size_t(0), queue.PendingInsert());
     EXPECT_EQ(size_t(0), queue.Display().size());
     EXPECT_EQ(aFirstFrame, queue.FirstFrame());
     EXPECT_EQ(nullptr, aQueue.Get(0, false));
   }
-
-  if (aQueue.IsRecycling()) {
-    const AnimationFrameRecyclingQueue& queue =
-      *static_cast<AnimationFrameRecyclingQueue*>(&aQueue);
-    EXPECT_EQ(size_t(0), queue.Recycle().size());
-  }
 }
 
 class ImageAnimationFrameBuffer : public ::testing::Test
 {
 public:
   ImageAnimationFrameBuffer()
   { }
 
@@ -630,28 +625,180 @@ TEST_F(ImageAnimationFrameBuffer, Discar
   const size_t kStartFrame = 0;
   AnimationFrameRetainedBuffer retained(kThreshold, kBatch, kStartFrame);
   PrepareForDiscardingQueue(retained);
   const imgFrame* firstFrame = retained.Frames()[0].get();
   AnimationFrameDiscardingQueue buffer(std::move(retained));
   TestDiscardingQueueReset(buffer, firstFrame, kThreshold, kBatch, kStartFrame);
 }
 
+TEST_F(ImageAnimationFrameBuffer, ResetBeforeDiscardingThreshold)
+{
+  const size_t kThreshold = 3;
+  const size_t kBatch = 1;
+  const size_t kStartFrame = 0;
+
+  // Get the starting buffer to just before the point where we need to switch
+  // to a discarding buffer, reset the animation so advancing points at the
+  // first frame, and insert the last frame to cross the threshold.
+  AnimationFrameRetainedBuffer retained(kThreshold, kBatch, kStartFrame);
+  VerifyInsert(retained, AnimationFrameBuffer::InsertStatus::CONTINUE);
+  VerifyInsertAndAdvance(retained, 1, AnimationFrameBuffer::InsertStatus::YIELD);
+  bool restartDecoder = retained.Reset();
+  EXPECT_FALSE(restartDecoder);
+  VerifyInsert(retained, AnimationFrameBuffer::InsertStatus::DISCARD_YIELD);
+
+  const imgFrame* firstFrame = retained.Frames()[0].get();
+  EXPECT_TRUE(firstFrame != nullptr);
+  AnimationFrameDiscardingQueue buffer(std::move(retained));
+  const imgFrame* displayFirstFrame = buffer.Get(0, true);
+  const imgFrame* advanceFirstFrame = buffer.Get(0, false);
+  EXPECT_EQ(firstFrame, displayFirstFrame);
+  EXPECT_EQ(firstFrame, advanceFirstFrame);
+}
+
+TEST_F(ImageAnimationFrameBuffer, DiscardingTooFewFrames)
+{
+  const size_t kThreshold = 3;
+  const size_t kBatch = 1;
+  const size_t kStartFrame = 0;
+
+  // First get us to a discarding buffer state.
+  AnimationFrameRetainedBuffer retained(kThreshold, kBatch, kStartFrame);
+  VerifyInsert(retained, AnimationFrameBuffer::InsertStatus::CONTINUE);
+  VerifyInsertAndAdvance(retained, 1, AnimationFrameBuffer::InsertStatus::YIELD);
+  VerifyInsert(retained, AnimationFrameBuffer::InsertStatus::DISCARD_YIELD);
+
+  // Insert one more frame.
+  AnimationFrameDiscardingQueue buffer(std::move(retained));
+  VerifyAdvance(buffer, 2, true);
+  VerifyInsert(buffer, AnimationFrameBuffer::InsertStatus::YIELD);
+
+  // Mark it as complete.
+  bool restartDecoder = buffer.MarkComplete(IntRect(0, 0, 1, 1));
+  EXPECT_FALSE(restartDecoder);
+  EXPECT_FALSE(buffer.HasRedecodeError());
+
+  // Insert one fewer frame than before.
+  VerifyAdvance(buffer, 3, true);
+  VerifyInsertAndAdvance(buffer, 0, AnimationFrameBuffer::InsertStatus::YIELD);
+  VerifyInsertAndAdvance(buffer, 1, AnimationFrameBuffer::InsertStatus::YIELD);
+  VerifyInsertAndAdvance(buffer, 2, AnimationFrameBuffer::InsertStatus::YIELD);
+
+  // When we mark it as complete, it should fail due to too few frames.
+  restartDecoder = buffer.MarkComplete(IntRect(0, 0, 1, 1));
+  EXPECT_TRUE(buffer.HasRedecodeError());
+  EXPECT_EQ(size_t(0), buffer.PendingDecode());
+  EXPECT_EQ(size_t(4), buffer.Size());
+}
+
+TEST_F(ImageAnimationFrameBuffer, DiscardingTooManyFrames)
+{
+  const size_t kThreshold = 3;
+  const size_t kBatch = 1;
+  const size_t kStartFrame = 0;
+
+  // First get us to a discarding buffer state.
+  AnimationFrameRetainedBuffer retained(kThreshold, kBatch, kStartFrame);
+  VerifyInsert(retained, AnimationFrameBuffer::InsertStatus::CONTINUE);
+  VerifyInsertAndAdvance(retained, 1, AnimationFrameBuffer::InsertStatus::YIELD);
+  VerifyInsert(retained, AnimationFrameBuffer::InsertStatus::DISCARD_YIELD);
+
+  // Insert one more frame.
+  AnimationFrameDiscardingQueue buffer(std::move(retained));
+  VerifyAdvance(buffer, 2, true);
+  VerifyInsert(buffer, AnimationFrameBuffer::InsertStatus::YIELD);
+
+  // Mark it as complete.
+  bool restartDecoder = buffer.MarkComplete(IntRect(0, 0, 1, 1));
+  EXPECT_FALSE(restartDecoder);
+  EXPECT_FALSE(buffer.HasRedecodeError());
+
+  // Advance and insert to get us back to the end on the redecode.
+  VerifyAdvance(buffer, 3, true);
+  VerifyInsertAndAdvance(buffer, 0, AnimationFrameBuffer::InsertStatus::YIELD);
+  VerifyInsertAndAdvance(buffer, 1, AnimationFrameBuffer::InsertStatus::YIELD);
+  VerifyInsertAndAdvance(buffer, 2, AnimationFrameBuffer::InsertStatus::YIELD);
+  VerifyInsertAndAdvance(buffer, 3, AnimationFrameBuffer::InsertStatus::YIELD);
+
+  // Attempt to insert a 5th frame, it should fail.
+  RefPtr<imgFrame> frame = CreateEmptyFrame();
+  AnimationFrameBuffer::InsertStatus status = buffer.Insert(std::move(frame));
+  EXPECT_EQ(AnimationFrameBuffer::InsertStatus::YIELD, status);
+  EXPECT_TRUE(buffer.HasRedecodeError());
+  EXPECT_EQ(size_t(0), buffer.PendingDecode());
+  EXPECT_EQ(size_t(4), buffer.Size());
+}
+
 TEST_F(ImageAnimationFrameBuffer, RecyclingReset)
 {
   const size_t kThreshold = 8;
   const size_t kBatch = 3;
   const size_t kStartFrame = 0;
   AnimationFrameRetainedBuffer retained(kThreshold, kBatch, kStartFrame);
   PrepareForDiscardingQueue(retained);
   const imgFrame* firstFrame = retained.Frames()[0].get();
   AnimationFrameRecyclingQueue buffer(std::move(retained));
   TestDiscardingQueueReset(buffer, firstFrame, kThreshold, kBatch, kStartFrame);
 }
 
+TEST_F(ImageAnimationFrameBuffer, RecyclingResetBeforeComplete)
+{
+  const size_t kThreshold = 3;
+  const size_t kBatch = 1;
+  const size_t kStartFrame = 0;
+  const IntSize kImageSize(100, 100);
+  const IntRect kImageRect(IntPoint(0, 0), kImageSize);
+  AnimationFrameRetainedBuffer retained(kThreshold, kBatch, kStartFrame);
+
+  // Get the starting buffer to just before the point where we need to switch
+  // to a discarding buffer, reset the animation so advancing points at the
+  // first frame, and insert the last frame to cross the threshold.
+  RefPtr<imgFrame> frame;
+  frame = CreateEmptyFrame(kImageSize, kImageRect, false);
+  AnimationFrameBuffer::InsertStatus status = retained.Insert(std::move(frame));
+  EXPECT_EQ(AnimationFrameBuffer::InsertStatus::CONTINUE, status);
+
+  frame = CreateEmptyFrame(kImageSize, IntRect(IntPoint(10, 10), IntSize(1, 1)), false);
+  status = retained.Insert(std::move(frame));
+  EXPECT_EQ(AnimationFrameBuffer::InsertStatus::YIELD, status);
+
+  VerifyAdvance(retained, 1, true);
+
+  frame = CreateEmptyFrame(kImageSize, IntRect(IntPoint(20, 10), IntSize(1, 1)), false);
+  status = retained.Insert(std::move(frame));
+  EXPECT_EQ(AnimationFrameBuffer::InsertStatus::DISCARD_YIELD, status);
+
+  AnimationFrameRecyclingQueue buffer(std::move(retained));
+  bool restartDecoding = buffer.Reset();
+  EXPECT_TRUE(restartDecoding);
+
+  // None of the buffers were recyclable.
+  EXPECT_TRUE(buffer.Recycle().empty());
+
+  // Reinsert the first two frames as recyclable and reset again.
+  frame = CreateEmptyFrame(kImageSize, kImageRect, true);
+  status = buffer.Insert(std::move(frame));
+  EXPECT_EQ(AnimationFrameBuffer::InsertStatus::CONTINUE, status);
+
+  frame = CreateEmptyFrame(kImageSize, IntRect(IntPoint(10, 10), IntSize(1, 1)), true);
+  status = buffer.Insert(std::move(frame));
+  EXPECT_EQ(AnimationFrameBuffer::InsertStatus::YIELD, status);
+
+  restartDecoding = buffer.Reset();
+  EXPECT_TRUE(restartDecoding);
+
+  // Now both buffers should have been saved and the dirty rect replaced with
+  // the full image rect since we don't know the first frame refresh area yet.
+  EXPECT_EQ(size_t(2), buffer.Recycle().size());
+  for (const auto& entry : buffer.Recycle()) {
+    EXPECT_EQ(kImageRect, entry.mDirtyRect);
+  }
+}
+
 TEST_F(ImageAnimationFrameBuffer, RecyclingRect)
 {
   const size_t kThreshold = 5;
   const size_t kBatch = 2;
   const size_t kStartFrame = 0;
   const IntSize kImageSize(100, 100);
   AnimationFrameRetainedBuffer retained(kThreshold, kBatch, kStartFrame);
 
new file mode 100644
--- /dev/null
+++ b/ipc/glue/InProcessChild.cpp
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/ipc/InProcessChild.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace ipc {
+
+PWindowGlobalChild*
+InProcessChild::AllocPWindowGlobalChild(const WindowGlobalInit& aInit)
+{
+  MOZ_ASSERT_UNREACHABLE("PWindowGlobalChild should not be created manually");
+  return nullptr;
+}
+
+bool
+InProcessChild::DeallocPWindowGlobalChild(PWindowGlobalChild* aActor)
+{
+  // Free IPC-held reference
+  static_cast<WindowGlobalChild*>(aActor)->Release();
+  return true;
+}
+
+} // namespace ipc
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/glue/InProcessChild.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_ipc_InProcessChild_h
+#define mozilla_ipc_InProcessChild_h
+
+#include "mozilla/ipc/PInProcessChild.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace dom {
+class PWindowGlobalParent;
+class PWindowGlobalChild;
+} // namespace dom
+
+namespace ipc {
+
+class InProcessParent;
+
+/**
+ * The `InProcessChild` class represents the child half of a main-thread to
+ * main-thread actor.
+ *
+ * The `PInProcess` actor should be used as an alternate manager to `PContent`
+ * for async actors which want to communicate uniformly between Content->Chrome
+ * and Chrome->Chrome situations.
+ */
+class InProcessChild : public PInProcessChild
+{
+public:
+  friend class InProcessParent;
+
+  NS_INLINE_DECL_REFCOUNTING(InProcessChild)
+
+  // Get the singleton instance of this actor.
+  static InProcessChild* Singleton();
+
+  // Get the parent side of the in-process child actor |aActor|. If |aActor| is
+  // not an in-process actor, or is not connected, this method will return
+  // |nullptr|.
+  static IProtocol* ParentActorFor(IProtocol* aActor);
+
+protected:
+  virtual mozilla::dom::PWindowGlobalChild*
+  AllocPWindowGlobalChild(const WindowGlobalInit& aInit) override;
+
+  virtual bool
+  DeallocPWindowGlobalChild(mozilla::dom::PWindowGlobalChild* aActor) override;
+
+private:
+  // NOTE: PInProcess lifecycle management is declared as staic methods and
+  // state on InProcessParent, and implemented in InProcessImpl.cpp.
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+  virtual void DeallocPInProcessChild() override;
+  ~InProcessChild() = default;
+
+  static StaticRefPtr<InProcessChild> sSingleton;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // defined(mozilla_ipc_InProcessChild_h)
new file mode 100644
--- /dev/null
+++ b/ipc/glue/InProcessImpl.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/ipc/InProcessParent.h"
+#include "mozilla/ipc/InProcessChild.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+
+// This file contains the implementation of core InProcess lifecycle management
+// facilities.
+
+namespace mozilla {
+namespace ipc {
+
+StaticRefPtr<InProcessParent> InProcessParent::sSingleton;
+StaticRefPtr<InProcessChild> InProcessChild::sSingleton;
+bool InProcessParent::sShutdown = false;
+
+
+//////////////////////////////////////////
+// InProcess actor lifecycle management //
+//////////////////////////////////////////
+
+/* static */ InProcessChild*
+InProcessChild::Singleton() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sSingleton) {
+    InProcessParent::Startup();
+  }
+  return sSingleton;
+}
+
+/* static */ InProcessParent*
+InProcessParent::Singleton() {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sSingleton) {
+    InProcessParent::Startup();
+  }
+  return sSingleton;
+}
+
+/* static */ void
+InProcessParent::Startup()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (sShutdown) {
+    NS_WARNING("Could not get in-process actor while shutting down!");
+    return;
+  }
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (!obs) {
+    sShutdown = true;
+    NS_WARNING("Failed to get nsIObserverService for in-process actor");
+    return;
+  }
+
+  RefPtr<InProcessParent> parent = new InProcessParent();
+  RefPtr<InProcessChild> child = new InProcessChild();
+
+  // Observe the shutdown event to close & clean up after ourselves.
+  nsresult rv = obs->AddObserver(parent, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  // Link the two actors
+  if (!child->OpenOnSameThread(parent->GetIPCChannel(), ChildSide)) {
+    MOZ_CRASH("Failed to open InProcessChild!");
+  }
+
+  parent->SetOtherProcessId(base::GetCurrentProcId());
+
+  // Create references held by the IPC layer which will be freed in
+  // DeallocPInProcess{Parent,Child}.
+  parent.get()->AddRef();
+  child.get()->AddRef();
+
+  // Stash global references to fetch the other side of the reference.
+  InProcessParent::sSingleton = parent.forget();
+  InProcessChild::sSingleton = child.forget();
+}
+
+
+/* static */ void
+InProcessParent::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sSingleton || sShutdown) {
+    return;
+  }
+
+  sShutdown = true;
+
+  RefPtr<InProcessParent> parent = sSingleton;
+  InProcessParent::sSingleton = nullptr;
+  InProcessChild::sSingleton = nullptr;
+
+  // Calling `Close` on the actor will cause the `Dealloc` methods to be called,
+  // freeing the remaining references.
+  parent->Close();
+}
+
+NS_IMETHODIMP
+InProcessParent::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
+{
+  MOZ_ASSERT(!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID));
+  InProcessParent::Shutdown();
+  return NS_OK;
+}
+
+void
+InProcessParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  InProcessParent::Shutdown();
+}
+
+void
+InProcessChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+  InProcessParent::Shutdown();
+}
+
+void
+InProcessParent::DeallocPInProcessParent()
+{
+  MOZ_ASSERT(!InProcessParent::sSingleton);
+  Release(); // Release the reference taken in InProcessParent::Startup.
+}
+
+void
+InProcessChild::DeallocPInProcessChild()
+{
+  MOZ_ASSERT(!InProcessChild::sSingleton);
+  Release(); // Release the reference taken in InProcessParent::Startup.
+}
+
+////////////////////////////////
+// In-Process Actor Utilities //
+////////////////////////////////
+
+// Helper method for implementing ParentActorFor and ChildActorFor.
+static IProtocol*
+GetOtherInProcessActor(IProtocol* aActor)
+{
+  MOZ_ASSERT(aActor->GetSide() != UnknownSide, "bad unknown side");
+
+  // Discover the manager of aActor which is PInProcess.
+  IProtocol* current = aActor;
+  while (current) {
+    if (current->GetProtocolTypeId() == PInProcessMsgStart) {
+      break; // Found the correct actor.
+    }
+    current = current->Manager();
+  }
+  if (!current) {
+    return nullptr; // Not a PInProcess actor, return |nullptr|
+  }
+
+  MOZ_ASSERT(current->GetSide() == aActor->GetSide(), "side changed?");
+  MOZ_ASSERT_IF(aActor->GetSide() == ParentSide,
+                current == InProcessParent::Singleton());
+  MOZ_ASSERT_IF(aActor->GetSide() == ChildSide,
+                current == InProcessChild::Singleton());
+
+  // Check whether this is InProcessParent or InProcessChild, and get the other
+  // side's toplevel actor.
+  IProtocol* otherRoot = nullptr;
+  if (aActor->GetSide() == ParentSide) {
+    otherRoot = InProcessChild::Singleton();
+  } else {
+    otherRoot = InProcessParent::Singleton();
+  }
+  if (NS_WARN_IF(!otherRoot)) {
+    return nullptr;
+  }
+
+  // Look up the actor on the other side, and return it.
+  IProtocol* otherActor = otherRoot->Lookup(aActor->Id());
+  if (otherActor) {
+    MOZ_ASSERT(otherActor->GetSide() != UnknownSide, "bad unknown side");
+    MOZ_ASSERT(otherActor->GetSide() != aActor->GetSide(), "Wrong side!");
+    MOZ_ASSERT(otherActor->GetProtocolTypeId() == aActor->GetProtocolTypeId(),
+               "Wrong type of protocol!");
+  }
+
+  return otherActor;
+}
+
+/* static */ IProtocol*
+InProcessParent::ChildActorFor(IProtocol* aActor)
+{
+  MOZ_ASSERT(aActor && aActor->GetSide() == ParentSide);
+  return GetOtherInProcessActor(aActor);
+}
+
+/* static */ IProtocol*
+InProcessChild::ParentActorFor(IProtocol* aActor)
+{
+  MOZ_ASSERT(aActor && aActor->GetSide() == ChildSide);
+  return GetOtherInProcessActor(aActor);
+}
+
+} // namespace ipc
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/ipc/glue/InProcessParent.cpp
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "mozilla/ipc/InProcessParent.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+namespace ipc {
+
+NS_IMPL_ISUPPORTS(InProcessParent, nsIObserver)
+
+IPCResult
+InProcessParent::RecvPWindowGlobalConstructor(PWindowGlobalParent* aActor,
+                                              const WindowGlobalInit& aInit)
+{
+  static_cast<WindowGlobalParent*>(aActor)->Init(aInit);
+  return IPC_OK();
+}
+
+PWindowGlobalParent*
+InProcessParent::AllocPWindowGlobalParent(const WindowGlobalInit& aInit)
+{
+  // Reference freed in DeallocPWindowGlobalParent.
+  return do_AddRef(new WindowGlobalParent(aInit, /* inproc */ true)).take();
+}
+
+bool
+InProcessParent::DeallocPWindowGlobalParent(PWindowGlobalParent* aActor)
+{
+  // Free IPC-held reference.
+  static_cast<WindowGlobalParent*>(aActor)->Release();
+  return true;
+}
+
+} // namespace ipc
+} // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/ipc/glue/InProcessParent.h
@@ -0,0 +1,76 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_ipc_InProcessParent_h
+#define mozilla_ipc_InProcessParent_h
+
+#include "mozilla/ipc/PInProcessParent.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace dom {
+class PWindowGlobalParent;
+class PWindowGlobalChild;
+} // namespace dom
+
+namespace ipc {
+
+class InProcessChild;
+
+/**
+ * The `InProcessParent` class represents the parent half of a main-thread to
+ * main-thread actor.
+ *
+ * The `PInProcess` actor should be used as an alternate manager to `PContent`
+ * for async actors which want to communicate uniformly between Content->Chrome
+ * and Chrome->Chrome situations.
+ */
+class InProcessParent : public nsIObserver
+                      , public PInProcessParent
+{
+public:
+  friend class InProcessChild;
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  // Get the singleton instance of this actor.
+  static InProcessParent* Singleton();
+
+  // Get the child side of the in-process child actor |aActor|. If |aActor| is
+  // not an in-process actor, or is not connected, this method will return
+  // |nullptr|.
+  static IProtocol* ChildActorFor(IProtocol* aActor);
+
+protected:
+  virtual mozilla::dom::PWindowGlobalParent*
+  AllocPWindowGlobalParent(const WindowGlobalInit& aInit) override;
+
+  virtual bool
+  DeallocPWindowGlobalParent(mozilla::dom::PWindowGlobalParent* aActor) override;
+
+  virtual IPCResult
+  RecvPWindowGlobalConstructor(mozilla::dom::PWindowGlobalParent* aActor,
+                               const WindowGlobalInit& aInit) override;
+
+private:
+  // Lifecycle management is implemented in InProcessImpl.cpp
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+  virtual void DeallocPInProcessParent() override;
+  ~InProcessParent() = default;
+
+  static void Startup();
+  static void Shutdown();
+
+  static StaticRefPtr<InProcessParent> sSingleton;
+  static bool sShutdown;
+};
+
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // defined(mozilla_ipc_InProcessParent_h)
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -579,17 +579,18 @@ MessageChannel::MessageChannel(const cha
       mSawInterruptOutMsg(false),
       mIsWaitingForIncoming(false),
       mAbortOnError(false),
       mNotifiedChannelDone(false),
       mFlags(REQUIRE_DEFAULT),
       mPeerPidSet(false),
       mPeerPid(-1),
       mIsPostponingSends(false),
-      mBuildIDsConfirmedMatch(false) {
+      mBuildIDsConfirmedMatch(false),
+      mIsSameThreadChannel(false) {
   MOZ_COUNT_CTOR(ipc::MessageChannel);
 
 #ifdef OS_WIN
   mTopFrame = nullptr;
   mIsSyncWaitingOnNonMainThread = false;
 #endif
 
   mOnChannelConnectedTask = NewNonOwningCancelableRunnableMethod(
@@ -896,16 +897,48 @@ void MessageChannel::CommonThreadOpenIni
   mWorkerThread = GetCurrentVirtualThread();
   mWorkerLoop->AddDestructionObserver(this);
   mListener->SetIsMainThreadProtocol();
 
   mLink = new ThreadLink(this, aTargetChan);
   mSide = aSide;
 }
 
+bool MessageChannel::OpenOnSameThread(MessageChannel* aTargetChan,
+                                      mozilla::ipc::Side aSide) {
+  CommonThreadOpenInit(aTargetChan, aSide);
+
+  Side oppSide = UnknownSide;
+  switch (aSide) {
+    case ChildSide:
+      oppSide = ParentSide;
+      break;
+    case ParentSide:
+      oppSide = ChildSide;
+      break;
+    case UnknownSide:
+      break;
+  }
+  mIsSameThreadChannel = true;
+
+  // XXX(nika): Avoid setting up a monitor for same thread channels? We
+  // shouldn't need it.
+  mMonitor = new RefCountedMonitor();
+
+  mChannelState = ChannelOpening;
+  aTargetChan->CommonThreadOpenInit(this, oppSide);
+
+  aTargetChan->mIsSameThreadChannel = true;
+  aTargetChan->mMonitor = mMonitor;
+
+  mChannelState = ChannelConnected;
+  aTargetChan->mChannelState = ChannelConnected;
+  return true;
+}
+
 bool MessageChannel::Echo(Message* aMsg) {
   UniquePtr<Message> msg(aMsg);
   AssertWorkerThread();
   mMonitor->AssertNotCurrentThreadOwns();
   if (MSG_ROUTING_NONE == msg->routing_id()) {
     ReportMessageRouteError("MessageChannel::Echo");
     return false;
   }
@@ -1372,16 +1405,18 @@ bool MessageChannel::Send(Message* aMsg,
     Telemetry::Accumulate(Telemetry::IPC_MESSAGE_SIZE2, aMsg->size());
   }
 
   UniquePtr<Message> msg(aMsg);
 
   // Sanity checks.
   AssertWorkerThread();
   mMonitor->AssertNotCurrentThreadOwns();
+  MOZ_RELEASE_ASSERT(!mIsSameThreadChannel,
+                     "sync send over same-thread channel will deadlock!");
 
 #ifdef OS_WIN
   SyncStackFrame frame(this, false);
   NeuteredWindowRegion neuteredRgn(mFlags &
                                    REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 #endif
 #ifdef MOZ_TASK_TRACER
   AutoScopedLabel autolabel("sync message %s", aMsg->name());
@@ -1580,16 +1615,18 @@ bool MessageChannel::Send(Message* aMsg,
   }
   return true;
 }
 
 bool MessageChannel::Call(Message* aMsg, Message* aReply) {
   UniquePtr<Message> msg(aMsg);
   AssertWorkerThread();
   mMonitor->AssertNotCurrentThreadOwns();
+  MOZ_RELEASE_ASSERT(!mIsSameThreadChannel,
+                     "intr call send over same-thread channel will deadlock!");
 
 #ifdef OS_WIN
   SyncStackFrame frame(this, true);
 #endif
 #ifdef MOZ_TASK_TRACER
   AutoScopedLabel autolabel("sync message %s", aMsg->name());
 #endif
 
@@ -2295,16 +2332,19 @@ bool MessageChannel::WaitForSyncNotify(b
   // WARNING: We don't release the lock here. We can't because the link thread
   // could signal at this time and we would miss it. Instead we require
   // ArtificialTimeout() to be extremely simple.
   if (mListener->ArtificialTimeout()) {
     return false;
   }
 #endif
 
+  MOZ_RELEASE_ASSERT(!mIsSameThreadChannel,
+                     "Wait on same-thread channel will deadlock!");
+
   TimeDuration timeout = (kNoTimeout == mTimeoutMs)
                              ? TimeDuration::Forever()
                              : TimeDuration::FromMilliseconds(mTimeoutMs);
   CVStatus status = mMonitor->Wait(timeout);
 
   // If the timeout didn't expire, we know we received an event. The
   // converse is not true.
   return WaitResponse(status == CVStatus::Timeout);
@@ -2575,16 +2615,20 @@ class GoodbyeMessage : public IPC::Messa
     fputs("(special `Goodbye' message)", aOutf);
   }
 };
 
 void MessageChannel::SynchronouslyClose() {
   AssertWorkerThread();
   mMonitor->AssertCurrentThreadOwns();
   mLink->SendClose();
+
+  MOZ_RELEASE_ASSERT(!mIsSameThreadChannel || ChannelClosed == mChannelState,
+                     "same-thread channel failed to synchronously close?");
+
   while (ChannelClosed != mChannelState) mMonitor->Wait();
 }
 
 void MessageChannel::CloseWithError() {
   AssertWorkerThread();
 
   MonitorAutoLock lock(*mMonitor);
   if (ChannelConnected != mChannelState) {
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -159,16 +159,25 @@ class MessageChannel : HasResultCodes, M
   // i.e., mChannelState == ChannelConnected.
   //
   // For more details on the process of opening a channel between
   // threads, see the extended comment on this function
   // in MessageChannel.cpp.
   bool Open(MessageChannel* aTargetChan, nsIEventTarget* aEventTarget,
             Side aSide);
 
+  // "Open" a connection to an actor on the current thread.
+  //
+  // Returns true if the transport layer was successfully connected,
+  // i.e., mChannelState == ChannelConnected.
+  //
+  // Same-thread channels may not perform synchronous or blocking message
+  // sends, to avoid deadlocks.
+  bool OpenOnSameThread(MessageChannel* aTargetChan, Side aSide);
+
   // Close the underlying transport channel.
   void Close();
 
   // Force the channel to behave as if a channel error occurred. Valid
   // for process links only, not thread links.
   void CloseWithError();
 
   void CloseWithTimeout();
@@ -525,20 +534,29 @@ class MessageChannel : HasResultCodes, M
  private:
   // Can be run on either thread
   void AssertWorkerThread() const {
     MOZ_ASSERT(mWorkerThread, "Channel hasn't been opened yet");
     MOZ_RELEASE_ASSERT(mWorkerThread == GetCurrentVirtualThread(),
                        "not on worker thread!");
   }
 
-  // The "link" thread is either the I/O thread (ProcessLink) or the
-  // other actor's work thread (ThreadLink).  In either case, it is
-  // NOT our worker thread.
+  // The "link" thread is either the I/O thread (ProcessLink), the other
+  // actor's work thread (ThreadLink), or the worker thread (same-thread
+  // channels).
   void AssertLinkThread() const {
+    if (mIsSameThreadChannel) {
+      // If we're a same-thread channel, we have to be on our worker
+      // thread.
+      AssertWorkerThread();
+      return;
+    }
+
+    // If we aren't a same-thread channel, our "link" thread is _not_ our
+    // worker thread!
     MOZ_ASSERT(mWorkerThread, "Channel hasn't been opened yet");
     MOZ_RELEASE_ASSERT(mWorkerThread != GetCurrentVirtualThread(),
                        "on worker thread but should not be!");
   }
 
  private:
   class MessageTask : public CancelableRunnable,
                       public LinkedListElement<RefPtr<MessageTask>>,
@@ -822,16 +840,20 @@ class MessageChannel : HasResultCodes, M
   int32_t mPeerPid;
 
   // Channels can enter messages are not sent immediately; instead, they are
   // held in a queue until another thread deems it is safe to send them.
   bool mIsPostponingSends;
   std::vector<UniquePtr<Message>> mPostponedSends;
 
   bool mBuildIDsConfirmedMatch;
+
+  // If this is true, both ends of this message channel have event targets
+  // on the same thread.
+  bool mIsSameThreadChannel;
 };
 
 void CancelCPOWs();
 
 }  // namespace ipc
 }  // namespace mozilla
 
 namespace IPC {
new file mode 100644
--- /dev/null
+++ b/ipc/glue/PInProcess.ipdl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* 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 protocol PWindowGlobal;
+
+include DOMTypes;
+
+namespace mozilla {
+namespace ipc {
+
+/**
+ * PInProcess is intended for use as an alternative actor manager to PContent
+ * for async actors which want to be used uniformly in both Content->Chrome and
+ * Chrome->Chrome circumstances.
+ *
+ * `mozilla::ipc::InProcess{Parent, Child}::Singleton()` should be used to get
+ * an instance of this actor.
+ */
+async protocol PInProcess
+{
+  manages PWindowGlobal;
+
+parent:
+  /**
+   * Construct a new WindowGlobal actor for a window global in the given
+   * BrowsingContext and with the given principal.
+   */
+  async PWindowGlobal(WindowGlobalInit init);
+};
+
+} // namespace ipc
+} // namespace mozilla
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -622,33 +622,54 @@ bool IToplevelProtocol::Open(MessageChan
 }
 
 bool IToplevelProtocol::OpenWithAsyncPid(mozilla::ipc::Transport* aTransport,
                                          MessageLoop* aThread,
                                          mozilla::ipc::Side aSide) {
   return GetIPCChannel()->Open(aTransport, aThread, aSide);
 }
 
+bool IToplevelProtocol::OpenOnSameThread(MessageChannel* aChannel, Side aSide) {
+  SetOtherProcessId(base::GetCurrentProcId());
+  return GetIPCChannel()->OpenOnSameThread(aChannel, aSide);
+}
+
 void IToplevelProtocol::Close() { GetIPCChannel()->Close(); }
 
 void IToplevelProtocol::SetReplyTimeoutMs(int32_t aTimeoutMs) {
   GetIPCChannel()->SetReplyTimeoutMs(aTimeoutMs);
 }
 
 bool IToplevelProtocol::IsOnCxxStack() const {
   return GetIPCChannel()->IsOnCxxStack();
 }
 
+int32_t IToplevelProtocol::ToplevelState::NextId() {
+  // Genreate the next ID to use for a shared memory or protocol. Parent and
+  // Child sides of the protocol use different pools, and actors created in the
+  // middleman need to use a distinct pool as well.
+  int32_t tag = 0;
+  if (recordreplay::IsMiddleman()) {
+    tag |= 1 << 0;
+  }
+  if (mProtocol->GetSide() == ParentSide) {
+    tag |= 1 << 1;
+  }
+
+  // Compute the ID to use with the low two bits as our tag, and the remaining
+  // bits as a monotonic.
+  return (++mLastLocalId << 2) | tag;
+}
+
 int32_t IToplevelProtocol::ToplevelState::Register(IProtocol* aRouted) {
   if (aRouted->Id() != kNullActorId && aRouted->Id() != kFreedActorId) {
     // If there's already an ID, just return that.
     return aRouted->Id();
   }
-  int32_t id =
-      mProtocol->GetSide() == ParentSide ? ++mLastRouteId : --mLastRouteId;
+  int32_t id = NextId();
   mActorMap.AddWithID(aRouted, id);
   aRouted->SetId(id);
 
   // Inherit our event target from our manager.
   if (IProtocol* manager = aRouted->Manager()) {
     MutexAutoLock lock(mEventTargetMutex);
     if (nsCOMPtr<nsIEventTarget> target =
             mEventTargetMap.Lookup(manager->Id())) {
@@ -677,32 +698,30 @@ void IToplevelProtocol::ToplevelState::U
   mEventTargetMap.RemoveIfPresent(aId);
 }
 
 IToplevelProtocol::ToplevelState::ToplevelState(const char* aName,
                                                 IToplevelProtocol* aProtocol,
                                                 Side aSide)
     : ProtocolState(),
       mProtocol(aProtocol),
-      mLastRouteId(aSide == ParentSide ? kFreedActorId : kNullActorId),
-      mLastShmemId(aSide == ParentSide ? kFreedActorId : kNullActorId),
+      mLastLocalId(0),
       mEventTargetMutex("ProtocolEventTargetMutex"),
       mChannel(aName, aProtocol) {}
 
 Shmem::SharedMemory* IToplevelProtocol::ToplevelState::CreateSharedMemory(
     size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, bool aUnsafe,
     Shmem::id_t* aId) {
   // XXX the mProtocol uses here should go away!
   RefPtr<Shmem::SharedMemory> segment(
       Shmem::Alloc(Shmem::PrivateIPDLCaller(), aSize, aType, aUnsafe));
   if (!segment) {
     return nullptr;
   }
-  int32_t id =
-      mProtocol->GetSide() == ParentSide ? ++mLastShmemId : --mLastShmemId;
+  int32_t id = NextId();
   Shmem shmem(Shmem::PrivateIPDLCaller(), segment.get(), id);
 
   base::ProcessId pid =
 #ifdef ANDROID
       // We use OtherPidMaybeInvalid() because on Android this method is
       // actually called on an unconnected protocol, but Android's shared memory
       // implementation doesn't actually use the PID.
       mProtocol->OtherPidMaybeInvalid();
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -433,21 +433,22 @@ class IToplevelProtocol : public IProtoc
 
     virtual already_AddRefed<nsIEventTarget> GetMessageEventTarget(
         const Message& aMsg);
 
     const MessageChannel* GetIPCChannel() const override;
     MessageChannel* GetIPCChannel() override;
 
    private:
+    int32_t NextId();
+
     IToplevelProtocol* const mProtocol;
+    int32_t mLastLocalId;
     IDMap<IProtocol*> mActorMap;
-    int32_t mLastRouteId;
     IDMap<Shmem::SharedMemory*> mShmemMap;
-    Shmem::id_t mLastShmemId;
 
     Mutex mEventTargetMutex;
     IDMap<nsCOMPtr<nsIEventTarget>> mEventTargetMap;
 
     MessageChannel mChannel;
   };
 
   using SchedulerGroupSet = nsILabelableRunnable::SchedulerGroupSet;
@@ -477,16 +478,25 @@ class IToplevelProtocol : public IProtoc
 
   bool Open(MessageChannel* aChannel, nsIEventTarget* aEventTarget,
             mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
 
   bool OpenWithAsyncPid(mozilla::ipc::Transport* aTransport,
                         MessageLoop* aThread = nullptr,
                         mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
 
+  // Open a toplevel actor such that both ends of the actor's channel are on
+  // the same thread. This method should be called on the thread to perform
+  // the link.
+  //
+  // WARNING: Attempting to send a sync or intr message on the same thread
+  // will crash.
+  bool OpenOnSameThread(MessageChannel* aChannel,
+                        mozilla::ipc::Side aSide = mozilla::ipc::UnknownSide);
+
   void Close();
 
   void SetReplyTimeoutMs(int32_t aTimeoutMs);
 
   void DeallocShmems() { DowncastState()->DeallocShmems(); }
 
   bool ShmemCreated(const Message& aMsg) {
     return DowncastState()->ShmemCreated(aMsg);
--- a/ipc/glue/moz.build
+++ b/ipc/glue/moz.build
@@ -22,16 +22,18 @@ EXPORTS.mozilla.ipc += [
     'CrossProcessMutex.h',
     'CrossProcessSemaphore.h',
     'EnvironmentMap.h',
     'FileDescriptor.h',
     'FileDescriptorSetChild.h',
     'FileDescriptorSetParent.h',
     'FileDescriptorUtils.h',
     'GeckoChildProcessHost.h',
+    'InProcessChild.h',
+    'InProcessParent.h',
     'InputStreamUtils.h',
     'IOThreadChild.h',
     'IPCStreamAlloc.h',
     'IPCStreamDestination.h',
     'IPCStreamSource.h',
     'IPCStreamUtils.h',
     'IPDLParamTraits.h',
     'MessageChannel.h',
@@ -142,16 +144,19 @@ UNIFIED_SOURCES += [
     'BackgroundImpl.cpp',
     'BackgroundUtils.cpp',
     'BrowserProcessSubThread.cpp',
     'CrashReporterClient.cpp',
     'CrashReporterHost.cpp',
     'CrashReporterMetadataShmem.cpp',
     'FileDescriptor.cpp',
     'FileDescriptorUtils.cpp',
+    'InProcessChild.cpp',
+    'InProcessImpl.cpp',
+    'InProcessParent.cpp',
     'InputStreamUtils.cpp',
     'IPCMessageUtils.cpp',
     'IPCStreamChild.cpp',
     'IPCStreamDestination.cpp',
     'IPCStreamParent.cpp',
     'IPCStreamSource.cpp',
     'IPCStreamUtils.cpp',
     'MessageChannel.cpp',
@@ -203,16 +208,17 @@ LOCAL_INCLUDES += [
 IPDL_SOURCES = [
     'InputStreamParams.ipdlh',
     'IPCStream.ipdlh',
     'PBackground.ipdl',
     'PBackgroundSharedTypes.ipdlh',
     'PBackgroundTest.ipdl',
     'PChildToParentStream.ipdl',
     'PFileDescriptorSet.ipdl',
+    'PInProcess.ipdl',
     'PParentToChildStream.ipdl',
     'ProtocolTypes.ipdlh',
     'URIParams.ipdlh',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/ipc',
     '/toolkit/crashreporter',
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -3905,23 +3905,41 @@ class _GenerateProtocolActorCode(ipdl.as
 
     def genAsyncCtor(self, md):
         actor = md.actorDecl()
         method = MethodDefn(self.makeSendMethodDecl(md))
         method.addstmts(self.ctorPrologue(md) + [Whitespace.NL])
 
         msgvar, stmts = self.makeMessage(md, errfnSendCtor)
         sendok, sendstmts = self.sendAsync(md, msgvar)
+
+        warnif = StmtIf(ExprNot(sendok))
+        warnif.addifstmt(_printWarningMessage('Error sending constructor'))
+
         method.addstmts(
+            # Build our constructor message & verify it.
             stmts
             + self.genVerifyMessage(md.decl.type.verify, md.params,
                                     errfnSendCtor, ExprVar('msg__'))
+
+            # Notify the other side about the newly created actor.
+            #
+            # If the MessageChannel is closing, and we haven't been told yet,
+            # this send may fail. This error is ignored to treat it like a
+            # message being lost due to the other side shutting down before
+            # processing it.
+            #
+            # NOTE: We don't free the actor here, as our caller may be
+            # depending on it being alive after calling SendConstructor.
             + sendstmts
-            + self.failCtorIf(md, ExprNot(sendok))
-            + [StmtReturn(actor.var())])
+
+            # Warn if the message failed to send, and return our newly created
+            # actor.
+            + [warnif,
+               StmtReturn(actor.var())])
 
         lbl = CaseLabel(md.pqReplyId())
         case = StmtBlock()
         case.addstmt(StmtReturn(_Result.Processed))
         # TODO not really sure what to do with async ctor "replies" yet.
         # destroy actor if there was an error?  tricky ...
 
         return method, (lbl, case)
--- a/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js
+++ b/toolkit/content/tests/browser/browser_keyevents_during_autoscrolling.js
@@ -1,11 +1,12 @@
 add_task(async function() {
   const kPrefName_AutoScroll = "general.autoScroll";
   Services.prefs.setBoolPref(kPrefName_AutoScroll, true);
+  registerCleanupFunction(() => Services.prefs.clearUserPref(kPrefName_AutoScroll));
 
   const kNoKeyEvents   = 0;
   const kKeyDownEvent  = 1;
   const kKeyPressEvent = 2;
   const kKeyUpEvent    = 4;
   const kAllKeyEvents  = 7;
 
   var expectedKeyEvents;
@@ -56,60 +57,57 @@ add_task(async function() {
         return;
     }
     dispatchedKeyEvents |= keyFlag;
     is(keyFlag, expectedKeyEvents & keyFlag, aEvent.type + " fired: " + key);
   }
 
   var dataUri = 'data:text/html,<body style="height:10000px;"></body>';
 
-  let loadedPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
-  BrowserTestUtils.loadURI(gBrowser, dataUri);
-  await loadedPromise;
-
-  await SimpleTest.promiseFocus(gBrowser.selectedBrowser);
-
-  window.addEventListener("keydown", onKey);
-  window.addEventListener("keypress", onKey);
-  window.addEventListener("keyup", onKey);
+  await BrowserTestUtils.withNewTab(dataUri, async function(browser) {
+    info("Loaded data URI in new tab");
+    await SimpleTest.promiseFocus(browser);
+    info("Focused selected browser");
 
-  // Test whether the key events are handled correctly under normal condition
-  expectedKeyEvents = kAllKeyEvents;
-  sendChar("A");
+    window.addEventListener("keydown", onKey);
+    window.addEventListener("keypress", onKey);
+    window.addEventListener("keyup", onKey);
+    registerCleanupFunction(() => {
+      window.removeEventListener("keydown", onKey);
+      window.removeEventListener("keypress", onKey);
+      window.removeEventListener("keyup", onKey);
+    });
 
-  // Start autoscrolling by middle button click on the page
-  let shownPromise = BrowserTestUtils.waitForEvent(window, "popupshown", false,
-                       event => event.originalTarget.className == "autoscroller");
-  await BrowserTestUtils.synthesizeMouseAtPoint(10, 10, { button: 1 },
-                                                gBrowser.selectedBrowser);
-  await shownPromise;
+    // Test whether the key events are handled correctly under normal condition
+    expectedKeyEvents = kAllKeyEvents;
+    sendChar("A");
 
-  // Most key events should be eaten by the browser.
-  expectedKeyEvents = kNoKeyEvents;
-  sendChar("A");
-  sendKey("DOWN");
-  sendKey("RETURN");
-  sendKey("RETURN");
-  sendKey("HOME");
-  sendKey("END");
-  sendKey("TAB");
-  sendKey("RETURN");
+    // Start autoscrolling by middle button click on the page
+    info("Creating popup shown promise");
+    let shownPromise = BrowserTestUtils.waitForEvent(window, "popupshown", false,
+                         event => event.originalTarget.className == "autoscroller");
+    await BrowserTestUtils.synthesizeMouseAtPoint(10, 10, { button: 1 },
+                                                  gBrowser.selectedBrowser);
+    info("Waiting for autoscroll popup to show");
+    await shownPromise;
 
-  // Finish autoscrolling by ESC key.  Note that only keydown and keypress
-  // events are eaten because keyup event is fired *after* the autoscrolling
-  // is finished.
-  expectedKeyEvents = kKeyUpEvent;
-  sendKey("ESCAPE");
-
-  // Test whether the key events are handled correctly under normal condition
-  expectedKeyEvents = kAllKeyEvents;
-  sendChar("A");
+    // Most key events should be eaten by the browser.
+    expectedKeyEvents = kNoKeyEvents;
+    sendChar("A");
+    sendKey("DOWN");
+    sendKey("RETURN");
+    sendKey("RETURN");
+    sendKey("HOME");
+    sendKey("END");
+    sendKey("TAB");
+    sendKey("RETURN");
 
-  window.removeEventListener("keydown", onKey);
-  window.removeEventListener("keypress", onKey);
-  window.removeEventListener("keyup", onKey);
+    // Finish autoscrolling by ESC key.  Note that only keydown and keypress
+    // events are eaten because keyup event is fired *after* the autoscrolling
+    // is finished.
+    expectedKeyEvents = kKeyUpEvent;
+    sendKey("ESCAPE");
 
-  // restore the changed prefs
-  if (Services.prefs.prefHasUserValue(kPrefName_AutoScroll))
-    Services.prefs.clearUserPref(kPrefName_AutoScroll);
-
-  finish();
+    // Test whether the key events are handled correctly under normal condition
+    expectedKeyEvents = kAllKeyEvents;
+    sendChar("A");
+  });
 });