Merge mozilla-central to autoland. a=merge CLOSED TREE
authorshindli <shindli@mozilla.com>
Wed, 05 Dec 2018 23:36:55 +0200
changeset 508693 0bcaabfc8a1d39d96314a881d3ea9989c025a8fa
parent 508673 4e65e04a0d2bdfa613ee1623b7bd6bd1003a152b (current diff)
parent 508692 643a4a6bbfe98aacb7f285b8f9e0a001794a9f8e (diff)
child 508694 5732f962288c49e1520f04abb7f2f90b1942b175
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge CLOSED TREE
dom/base/nsDocument.cpp
--- 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");
+  });
 });