Bug 1505838 - Add BrowsingContextGroup. r=peterv
authorAndreas Farre <farre@mozilla.com>
Mon, 17 Dec 2018 10:45:37 +0000
changeset 451277 b068ba3cc5c3019d059e03321f6ee48d83a2cac8
parent 451276 283ebb9fc26f7ea9e296e520ef23a5d36f2c0931
child 451278 e633fd3fbd905d1cbbcd35ed5b888bcfe4e89e7f
push id35232
push userebalazs@mozilla.com
push dateWed, 19 Dec 2018 15:45:00 +0000
treeherdermozilla-central@335cc6f2cbb1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1505838
milestone66.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
Bug 1505838 - Add BrowsingContextGroup. r=peterv Add top-level and auxiliary browsing contexts to a group of BrowsingContexts on creation and store a pointer to that group in all children of the BrowsingContexts in the group. With this it is possible to compute the transitive closure of related browsing contexts. Since we'll not be using linked lists of BrowsingContexts for neither groups nor children we can move children to be an array of BrowsingContexts and adjust to use a the more convenient HashMap for roots. Differential Revision: https://phabricator.services.mozilla.com/D13227
docshell/base/BrowsingContext.cpp
docshell/base/BrowsingContext.h
docshell/base/nsDocShell.cpp
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -7,44 +7,45 @@
 #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/HashTable.h"
 #include "mozilla/Logging.h"
 #include "mozilla/StaticPtr.h"
 
-#include "nsDataHashtable.h"
 #include "nsDocShell.h"
-#include "nsRefPtrHashtable.h"
 #include "nsContentUtils.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 static LazyLogModule gBrowsingContextLog("BrowsingContext");
 
 static StaticAutoPtr<BrowsingContext::Children> sRootBrowsingContexts;
 
-static StaticAutoPtr<nsDataHashtable<nsUint64HashKey, BrowsingContext*>>
-    sBrowsingContexts;
+template <template <typename> class PtrType>
+using BrowsingContextMap =
+    HashMap<uint64_t, PtrType<BrowsingContext>, DefaultHasher<uint64_t>,
+            InfallibleAllocPolicy>;
+
+static StaticAutoPtr<BrowsingContextMap<WeakPtr>> sBrowsingContexts;
 
 // TODO(farre): This duplicates some of the work performed by the
 // bfcache. This should be unified. [Bug 1471601]
-static StaticAutoPtr<nsRefPtrHashtable<nsUint64HashKey, BrowsingContext>>
-    sCachedBrowsingContexts;
+static StaticAutoPtr<BrowsingContextMap<RefPtr>> sCachedBrowsingContexts;
 
 static void Register(BrowsingContext* aBrowsingContext) {
-  auto entry = sBrowsingContexts->LookupForAdd(aBrowsingContext->Id());
-  MOZ_RELEASE_ASSERT(!entry, "Duplicate BrowsingContext ID");
-  entry.OrInsert([&] { return aBrowsingContext; });
+  MOZ_ALWAYS_TRUE(
+      sBrowsingContexts->putNew(aBrowsingContext->Id(), aBrowsingContext));
 }
 
 static void Sync(BrowsingContext* aBrowsingContext) {
   if (!XRE_IsContentProcess()) {
     return;
   }
 
   auto cc = ContentChild::GetSingleton();
@@ -61,36 +62,37 @@ static void Sync(BrowsingContext* aBrows
 
 /* static */ void BrowsingContext::Init() {
   if (!sRootBrowsingContexts) {
     sRootBrowsingContexts = new BrowsingContext::Children();
     ClearOnShutdown(&sRootBrowsingContexts);
   }
 
   if (!sBrowsingContexts) {
-    sBrowsingContexts =
-        new nsDataHashtable<nsUint64HashKey, BrowsingContext*>();
+    sBrowsingContexts = new BrowsingContextMap<WeakPtr>();
     ClearOnShutdown(&sBrowsingContexts);
   }
 
   if (!sCachedBrowsingContexts) {
-    sCachedBrowsingContexts =
-        new nsRefPtrHashtable<nsUint64HashKey, BrowsingContext>();
+    sCachedBrowsingContexts = new BrowsingContextMap<RefPtr>();
     ClearOnShutdown(&sCachedBrowsingContexts);
   }
 }
 
 /* static */ LogModule* BrowsingContext::GetLog() {
   return gBrowsingContextLog;
 }
 
 /* static */ already_AddRefed<BrowsingContext> BrowsingContext::Get(
     uint64_t aId) {
-  RefPtr<BrowsingContext> abc = sBrowsingContexts->Get(aId);
-  return abc.forget();
+  if (BrowsingContextMap<WeakPtr>::Ptr abc = sBrowsingContexts->lookup(aId)) {
+    return do_AddRef(abc->value().get());
+  }
+
+  return nullptr;
 }
 
 /* static */ already_AddRefed<BrowsingContext> BrowsingContext::Create(
     BrowsingContext* aParent, BrowsingContext* aOpener, const nsAString& aName,
     Type aType) {
   MOZ_DIAGNOSTIC_ASSERT(!aParent || aParent->mType == aType);
 
   uint64_t id = nsContentUtils::GenerateBrowsingContextId();
@@ -144,114 +146,123 @@ static void Sync(BrowsingContext* aBrows
 BrowsingContext::BrowsingContext(BrowsingContext* aParent,
                                  BrowsingContext* aOpener,
                                  const nsAString& aName,
                                  uint64_t aBrowsingContextId, Type aType)
     : mType(aType),
       mBrowsingContextId(aBrowsingContextId),
       mParent(aParent),
       mOpener(aOpener),
-      mName(aName) {}
+      mName(aName) {
+  if (mParent) {
+    mBrowsingContextGroup = mParent->mBrowsingContextGroup;
+  } else if (mOpener) {
+    mBrowsingContextGroup = mOpener->mBrowsingContextGroup;
+  } else {
+    mBrowsingContextGroup = new BrowsingContextGroup();
+  }
+
+  if (!mParent) {
+    // If we don't have a parent we're either a top level or auxiliary
+    // BrowsingContext.
+    mBrowsingContextGroup->AppendElement(this);
+  }
+}
 
 void BrowsingContext::SetDocShell(nsIDocShell* aDocShell) {
   // XXX(nika): We should communicate that we are now an active BrowsingContext
   // process to the parent & do other validation here.
   MOZ_RELEASE_ASSERT(nsDocShell::Cast(aDocShell)->GetBrowsingContext() == this);
   mDocShell = aDocShell;
 }
 
 void BrowsingContext::Attach() {
-  if (isInList()) {
-    MOZ_LOG(GetLog(), LogLevel::Debug,
-            ("%s: Connecting already existing 0x%08" PRIx64 " to 0x%08" PRIx64,
-             XRE_IsParentProcess() ? "Parent" : "Child", Id(),
-             mParent ? mParent->Id() : 0));
-    MOZ_DIAGNOSTIC_ASSERT(sBrowsingContexts->Contains(Id()));
-    MOZ_DIAGNOSTIC_ASSERT(!IsCached());
-    return;
-  }
-
-  bool wasCached = sCachedBrowsingContexts->Remove(Id());
-
   MOZ_LOG(GetLog(), LogLevel::Debug,
           ("%s: %s 0x%08" PRIx64 " to 0x%08" PRIx64,
            XRE_IsParentProcess() ? "Parent" : "Child",
-           wasCached ? "Re-connecting" : "Connecting", Id(),
-           mParent ? mParent->Id() : 0));
+           sCachedBrowsingContexts->has(Id()) ? "Re-connecting" : "Connecting",
+           Id(), mParent ? mParent->Id() : 0));
+
+  sCachedBrowsingContexts->remove(Id());
 
   auto* children = mParent ? &mParent->mChildren : sRootBrowsingContexts.get();
-  children->insertBack(this);
+  MOZ_DIAGNOSTIC_ASSERT(!children->Contains(this));
+
+  children->AppendElement(this);
 
   Sync(this);
 }
 
 void BrowsingContext::Detach() {
-  RefPtr<BrowsingContext> kungFuDeathGrip(this);
-
-  if (sCachedBrowsingContexts) {
-    sCachedBrowsingContexts->Remove(Id());
-  }
-
-  if (!isInList()) {
-    MOZ_LOG(GetLog(), LogLevel::Debug,
-            ("%s: Detaching already detached 0x%08" PRIx64,
-             XRE_IsParentProcess() ? "Parent" : "Child", Id()));
-    return;
-  }
-
   MOZ_LOG(GetLog(), LogLevel::Debug,
           ("%s: Detaching 0x%08" PRIx64 " from 0x%08" PRIx64,
            XRE_IsParentProcess() ? "Parent" : "Child", Id(),
            mParent ? mParent->Id() : 0));
 
-  remove();
+  RefPtr<BrowsingContext> kungFuDeathGrip(this);
+
+  BrowsingContextMap<RefPtr>::Ptr p;
+  if (sCachedBrowsingContexts && (p = sCachedBrowsingContexts->lookup(Id()))) {
+    MOZ_DIAGNOSTIC_ASSERT(!mParent || !mParent->mChildren.Contains(this));
+    MOZ_DIAGNOSTIC_ASSERT(!sRootBrowsingContexts->Contains(this));
+    sCachedBrowsingContexts->remove(p);
+  } else {
+    auto* children =
+        mParent ? &mParent->mChildren : sRootBrowsingContexts.get();
+    // TODO(farre): This assert looks extremely fishy, I know, but
+    // what we're actually saying is this: if we're detaching, but our
+    // parent doesn't have any children, it is because we're being
+    // detached by the cycle collector destroying docshells out of
+    // order.
+    MOZ_DIAGNOSTIC_ASSERT(children->IsEmpty() || children->Contains(this));
+
+    children->RemoveElement(this);
+  }
 
   if (!XRE_IsContentProcess()) {
     return;
   }
 
   auto cc = ContentChild::GetSingleton();
   MOZ_DIAGNOSTIC_ASSERT(cc);
   cc->SendDetachBrowsingContext(BrowsingContextId(Id()),
                                 false /* aMoveToBFCache */);
 }
 
 void BrowsingContext::CacheChildren() {
-  if (mChildren.isEmpty()) {
+  if (mChildren.IsEmpty()) {
     return;
   }
 
   MOZ_LOG(GetLog(), LogLevel::Debug,
           ("%s: Caching children of 0x%08" PRIx64 "",
            XRE_IsParentProcess() ? "Parent" : "Child", Id()));
 
-  while (!mChildren.isEmpty()) {
-    RefPtr<BrowsingContext> child = mChildren.popFirst();
-    sCachedBrowsingContexts->Put(child->Id(), child);
+  MOZ_ALWAYS_TRUE(sCachedBrowsingContexts->reserve(mChildren.Length()));
+
+  for (BrowsingContext* child : mChildren) {
+    MOZ_ALWAYS_TRUE(sCachedBrowsingContexts->putNew(child->Id(), child));
   }
+  mChildren.Clear();
 
   if (!XRE_IsContentProcess()) {
     return;
   }
 
   auto cc = ContentChild::GetSingleton();
   MOZ_DIAGNOSTIC_ASSERT(cc);
   cc->SendDetachBrowsingContext(BrowsingContextId(Id()),
                                 true /* aMoveToBFCache */);
 }
 
-bool BrowsingContext::IsCached() {
-  return sCachedBrowsingContexts->Contains(Id());
-}
+bool BrowsingContext::IsCached() { return sCachedBrowsingContexts->has(Id()); }
 
 void BrowsingContext::GetChildren(
     nsTArray<RefPtr<BrowsingContext>>& aChildren) {
-  for (BrowsingContext* context : mChildren) {
-    aChildren.AppendElement(context);
-  }
+  MOZ_ALWAYS_TRUE(aChildren.AppendElements(mChildren));
 }
 
 void BrowsingContext::SetOpener(BrowsingContext* aOpener) {
   if (mOpener == aOpener) {
     return;
   }
 
   mOpener = aOpener;
@@ -263,63 +274,52 @@ void BrowsingContext::SetOpener(Browsing
   auto cc = ContentChild::GetSingleton();
   MOZ_DIAGNOSTIC_ASSERT(cc);
   cc->SendSetOpenerBrowsingContext(
       BrowsingContextId(Id()), BrowsingContextId(aOpener ? aOpener->Id() : 0));
 }
 
 /* static */ void BrowsingContext::GetRootBrowsingContexts(
     nsTArray<RefPtr<BrowsingContext>>& aBrowsingContexts) {
-  for (BrowsingContext* context : *sRootBrowsingContexts) {
-    aBrowsingContexts.AppendElement(context);
-  }
+  MOZ_ALWAYS_TRUE(aBrowsingContexts.AppendElements(*sRootBrowsingContexts));
 }
 
 BrowsingContext::~BrowsingContext() {
-  MOZ_DIAGNOSTIC_ASSERT(!isInList());
+  MOZ_DIAGNOSTIC_ASSERT(!mParent || !mParent->mChildren.Contains(this));
+  MOZ_DIAGNOSTIC_ASSERT(!sRootBrowsingContexts ||
+                        !sRootBrowsingContexts->Contains(this));
+  MOZ_DIAGNOSTIC_ASSERT(!sCachedBrowsingContexts ||
+                        !sCachedBrowsingContexts->has(Id()));
 
   if (sBrowsingContexts) {
-    sBrowsingContexts->Remove(mBrowsingContextId);
+    sBrowsingContexts->remove(Id());
   }
 }
 
 nsISupports* BrowsingContext::GetParentObject() const {
   return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
 }
 
 JSObject* BrowsingContext::WrapObject(JSContext* aCx,
                                       JS::Handle<JSObject*> aGivenProto) {
   return BrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
 }
 
-static void ImplCycleCollectionUnlink(BrowsingContext::Children& aField) {
-  aField.clear();
-}
-
-static void ImplCycleCollectionTraverse(
-    nsCycleCollectionTraversalCallback& aCallback,
-    BrowsingContext::Children& aField, const char* aName, uint32_t aFlags = 0) {
-  for (BrowsingContext* aContext : aField) {
-    aCallback.NoteNativeChild(aContext,
-                              NS_CYCLE_COLLECTION_PARTICIPANT(BrowsingContext));
-  }
-}
-
 NS_IMPL_CYCLE_COLLECTION_CLASS(BrowsingContext)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(BrowsingContext)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell, mChildren)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell, mChildren, mParent)
   if (XRE_IsParentProcess()) {
     ChromeBrowsingContext::Cast(tmp)->Unlink();
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(BrowsingContext)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell, mChildren)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell, mChildren, mParent)
   if (XRE_IsParentProcess()) {
     ChromeBrowsingContext::Cast(tmp)->Traverse(cb);
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(BrowsingContext)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(BrowsingContext, AddRef)
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -3,41 +3,49 @@
 /* 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_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"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 
 class nsIDocShell;
 
 namespace mozilla {
 
 class LogModule;
 
 namespace dom {
 
+class BrowsingContext;
 class ContentParent;
 
+// List of top-level or auxiliary BrowsingContexts
+class BrowsingContextGroup : public nsTArray<WeakPtr<BrowsingContext>> {
+ public:
+  NS_INLINE_DECL_REFCOUNTING(BrowsingContextGroup)
+ private:
+  ~BrowsingContextGroup() {}
+};
+
 // BrowsingContext, in this context, is the cross process replicated
 // environment in which information about documents is stored. In
 // particular the tree structure of nested browsing contexts is
 // represented by the tree of BrowsingContexts.
 //
-// The tree of BrowsingContexts in created in step with its
+// The tree of BrowsingContexts is created in step with its
 // corresponding nsDocShell, and when nsDocShells are connected
 // through a parent/child relationship, so are BrowsingContexts. The
 // major difference is that BrowsingContexts are replicated (synced)
 // to the parent process, making it possible to traverse the
 // BrowsingContext tree for a tab, in both the parent and the child
 // process.
 //
 // Trees of BrowsingContexts should only ever contain nodes of the
@@ -114,32 +122,33 @@ class BrowsingContext : public nsWrapper
   nsISupports* GetParentObject() const;
   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>>;
+  using Children = nsTArray<RefPtr<BrowsingContext>>;
 
  protected:
   virtual ~BrowsingContext();
   BrowsingContext(BrowsingContext* aParent, BrowsingContext* aOpener,
                   const nsAString& aName, uint64_t aBrowsingContextId,
                   Type aType);
 
  private:
   // Type of BrowsingContent
   const Type mType;
 
   // Unique id identifying BrowsingContext
   const uint64_t mBrowsingContextId;
 
-  WeakPtr<BrowsingContext> mParent;
+  RefPtr<BrowsingContextGroup> mBrowsingContextGroup;
+  RefPtr<BrowsingContext> mParent;
   Children mChildren;
   WeakPtr<BrowsingContext> mOpener;
   nsCOMPtr<nsIDocShell> mDocShell;
   nsString mName;
 };
 
 }  // namespace dom
 }  // namespace mozilla
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7435,24 +7435,16 @@ nsDocShell::BeginRestore(nsIContentViewe
     if (channel) {
       mEODForCurrentDocument = false;
       mIsRestoringDocument = true;
       mLoadGroup->AddRequest(channel, nullptr);
       mIsRestoringDocument = false;
     }
   }
 
-  // When re-attaching browsing context, a mozbrowser docshell will be
-  // considered as top level, but its browsing context will have a
-  // parent.
-  MOZ_DIAGNOSTIC_ASSERT(
-      (aTop && !mBrowsingContext->GetParent()) ||
-      ((!aTop || GetIsMozBrowser()) && mBrowsingContext->GetParent()));
-  mBrowsingContext->Attach();
-
   if (!aTop) {
     // This point corresponds to us having gotten OnStartRequest or
     // STATE_START, so do the same thing that CreateContentViewer does at
     // this point to ensure that unload/pagehide events for this document
     // will fire when it's unloaded again.
     mFiredUnloadEvent = false;
 
     // For non-top frames, there is no notion of making sure that the
@@ -7959,16 +7951,23 @@ nsresult nsDocShell::RestoreFromHistory(
     uint32_t defaultLoadFlags;
     childShell->GetDefaultLoadFlags(&defaultLoadFlags);
 
     // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning that
     // the child inherits our state. Among other things, this means that the
     // child inherits our mIsActive mPrivateBrowsingId, which is what we want.
     AddChild(childItem);
 
+    // TODO(farre): This results in sending an IPC message to
+    // re-attach the 'BrowsingContext' to the 'ContentParent'. We
+    // would much rather do this in bulk. See Bug 1410260.
+    RefPtr<BrowsingContext> childContext =
+        nsDocShell::Cast(childShell)->GetBrowsingContext();
+    childContext->Attach();
+
     childShell->SetAllowPlugins(allowPlugins);
     childShell->SetAllowJavascript(allowJavascript);
     childShell->SetAllowMetaRedirects(allowRedirects);
     childShell->SetAllowSubframes(allowSubframes);
     childShell->SetAllowImages(allowImages);
     childShell->SetAllowMedia(allowMedia);
     childShell->SetAllowDNSPrefetch(allowDNSPrefetch);
     childShell->SetAllowContentRetargeting(allowContentRetargeting);