Bug 1318506 - Assign a TabGroup to every PBrowser (r=mystor,ehsan)
authorBill McCloskey <billm@mozilla.com>
Fri, 04 Nov 2016 11:13:52 -0700
changeset 453587 32a005fd81f39ec493a735c71d3c3848078bbd44
parent 453586 83c36290906985c3f6a4a211cb7ed45ab902f9c2
child 453588 30b32a2aabb1d18f952492ea41f9465c32ecebe7
push id39711
push userdmitchell@mozilla.com
push dateFri, 23 Dec 2016 21:59:47 +0000
reviewersmystor, ehsan
bugs1318506
milestone53.0a1
Bug 1318506 - Assign a TabGroup to every PBrowser (r=mystor,ehsan) Every new PBrowser, whether it's created by the parent or the child, needs to get a TabGroup assigned to it. That way IPC messages for the PBrowser will be dispatched to that TabGroup. For new PBrowsers created by the child, we just create a new TabGroup or reuse the opener's TabGroup. For PBrowsers created by the parent, the child needs to intercept the PBrowserConstructor message and assign a TabGroup immediately. PBrowsers created by the parent never have an opener so we can always create a new TabGroup. In both cases, the nsGlobalWindow::TabGroupOuter logic needs to be updated to read the TabGroup out of the IPC code. Otherwise the DOM and IPC code will get out of sync about TabGroups. MozReview-Commit-ID: D5iEdgirfvK
dom/base/Dispatcher.cpp
dom/base/Dispatcher.h
dom/base/TabGroup.cpp
dom/base/TabGroup.h
dom/base/nsGlobalWindow.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
ipc/glue/ProtocolUtils.cpp
ipc/glue/ProtocolUtils.h
--- a/dom/base/Dispatcher.cpp
+++ b/dom/base/Dispatcher.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/Dispatcher.h"
 #include "mozilla/Move.h"
 #include "nsINamed.h"
+#include "nsQueryObject.h"
 
 using namespace mozilla;
 
 nsresult
 DispatcherTrait::Dispatch(const char* aName,
                           TaskCategory aCategory,
                           already_AddRefed<nsIRunnable>&& aRunnable)
 {
@@ -32,39 +33,47 @@ already_AddRefed<nsIEventTarget>
 DispatcherTrait::EventTargetFor(TaskCategory aCategory) const
 {
   nsCOMPtr<nsIEventTarget> main = do_GetMainThread();
   return main.forget();
 }
 
 namespace {
 
+#define NS_DISPATCHEREVENTTARGET_IID \
+{ 0xbf4e36c8, 0x7d04, 0x4ef4, \
+  { 0xbb, 0xd8, 0x11, 0x09, 0x0a, 0xdb, 0x4d, 0xf7 } }
+
 class DispatcherEventTarget final : public nsIEventTarget
 {
   RefPtr<dom::Dispatcher> mDispatcher;
   TaskCategory mCategory;
 
 public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_DISPATCHEREVENTTARGET_IID)
+
   DispatcherEventTarget(dom::Dispatcher* aDispatcher, TaskCategory aCategory)
    : mDispatcher(aDispatcher)
    , mCategory(aCategory)
   {}
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIEVENTTARGET
 
   dom::Dispatcher* Dispatcher() const { return mDispatcher; }
 
 private:
   virtual ~DispatcherEventTarget() {}
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(DispatcherEventTarget, NS_DISPATCHEREVENTTARGET_IID)
+
 } // namespace
 
-NS_IMPL_ISUPPORTS(DispatcherEventTarget, nsIEventTarget)
+NS_IMPL_ISUPPORTS(DispatcherEventTarget, DispatcherEventTarget, nsIEventTarget)
 
 NS_IMETHODIMP
 DispatcherEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags)
 {
   return Dispatch(do_AddRef(aRunnable), aFlags);
 }
 
 NS_IMETHODIMP
@@ -91,8 +100,18 @@ DispatcherEventTarget::IsOnCurrentThread
 
 already_AddRefed<nsIEventTarget>
 Dispatcher::CreateEventTargetFor(TaskCategory aCategory)
 {
   RefPtr<DispatcherEventTarget> target =
     new DispatcherEventTarget(this, aCategory);
   return target.forget();
 }
+
+/* static */ Dispatcher*
+Dispatcher::FromEventTarget(nsIEventTarget* aEventTarget)
+{
+  RefPtr<DispatcherEventTarget> target = do_QueryObject(aEventTarget);
+  if (!target) {
+    return nullptr;
+  }
+  return target->Dispatcher();
+}
--- a/dom/base/Dispatcher.h
+++ b/dom/base/Dispatcher.h
@@ -11,16 +11,19 @@
 #include "nsISupports.h"
 
 class nsIEventTarget;
 class nsIRunnable;
 
 namespace mozilla {
 namespace dom {
 
+class TabGroup;
+class DocGroup;
+
 enum class TaskCategory {
   // User input (clicks, keypresses, etc.)
   UI,
 
   // Data from the network
   Network,
 
   // setTimeout, setInterval
@@ -65,17 +68,23 @@ public:
                             TaskCategory aCategory,
                             already_AddRefed<nsIRunnable>&& aRunnable) = 0;
 
   // This method is always safe to call off the main thread. The nsIEventTarget
   // can always be used off the main thread.
   virtual already_AddRefed<nsIEventTarget>
   EventTargetFor(TaskCategory aCategory) const = 0;
 
+  // These methods perform a safe cast. They return null if |this| is not of the
+  // requested type.
+  virtual TabGroup* AsTabGroup() { return nullptr; }
+
 protected:
   virtual already_AddRefed<nsIEventTarget>
   CreateEventTargetFor(TaskCategory aCategory);
+
+  static Dispatcher* FromEventTarget(nsIEventTarget* aEventTarget);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Dispatcher_h
--- a/dom/base/TabGroup.cpp
+++ b/dom/base/TabGroup.cpp
@@ -1,71 +1,121 @@
 /* -*- 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/dom/TabGroup.h"
 
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/DocGroup.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/ThrottledEventQueue.h"
 #include "nsIDocShell.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsIURI.h"
 
 namespace mozilla {
 namespace dom {
 
 static StaticRefPtr<TabGroup> sChromeTabGroup;
 
 TabGroup::TabGroup(bool aIsChrome)
  : mLastWindowLeft(false)
+ , mThrottledQueuesInitialized(false)
 {
   for (size_t i = 0; i < size_t(TaskCategory::Count); i++) {
     TaskCategory category = static_cast<TaskCategory>(i);
     mEventTargets[i] = CreateEventTargetFor(category);
   }
 
   // Do not throttle runnables from chrome windows.  In theory we should
   // not have abuse issues from these windows and many browser chrome
   // tests have races that fail if we do throttle chrome runnables.
   if (aIsChrome) {
     MOZ_ASSERT(!sChromeTabGroup);
     return;
   }
 
+  // This constructor can be called from the IPC I/O thread. In that case, we
+  // won't actually use the TabGroup on the main thread until GetFromWindowActor
+  // is called, so we initialize the throttled queues there.
+  if (NS_IsMainThread()) {
+    EnsureThrottledEventQueues();
+  }
+}
+
+TabGroup::~TabGroup()
+{
+  MOZ_ASSERT(mDocGroups.IsEmpty());
+  MOZ_ASSERT(mWindows.IsEmpty());
+}
+
+void
+TabGroup::EnsureThrottledEventQueues()
+{
+  if (mThrottledQueuesInitialized) {
+    return;
+  }
+
+  mThrottledQueuesInitialized = true;
+
   nsCOMPtr<nsIThread> mainThread;
   NS_GetMainThread(getter_AddRefs(mainThread));
   MOZ_DIAGNOSTIC_ASSERT(mainThread);
 
   // This may return nullptr during xpcom shutdown.  This is ok as we
   // do not guarantee a ThrottledEventQueue will be present.
   mThrottledEventQueue = ThrottledEventQueue::Create(mainThread);
 }
 
-TabGroup::~TabGroup()
-{
-  MOZ_ASSERT(mDocGroups.IsEmpty());
-  MOZ_ASSERT(mWindows.IsEmpty());
-}
-
 TabGroup*
 TabGroup::GetChromeTabGroup()
 {
   if (!sChromeTabGroup) {
     sChromeTabGroup = new TabGroup(true /* chrome tab group */);
     ClearOnShutdown(&sChromeTabGroup);
   }
   return sChromeTabGroup;
 }
 
+/* static */ TabGroup*
+TabGroup::GetFromWindowActor(mozIDOMWindowProxy* aWindow)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+  TabChild* tabChild = TabChild::GetFrom(aWindow);
+  if (!tabChild) {
+    return nullptr;
+  }
+
+  ContentChild* cc = ContentChild::GetSingleton();
+  nsCOMPtr<nsIEventTarget> target = cc->GetActorEventTarget(tabChild);
+  if (!target) {
+    return nullptr;
+  }
+
+  // We have an event target. We assume the IPC code created it via
+  // TabGroup::CreateEventTarget.
+  RefPtr<Dispatcher> dispatcher = Dispatcher::FromEventTarget(target);
+  MOZ_RELEASE_ASSERT(dispatcher);
+  auto tabGroup = dispatcher->AsTabGroup();
+  MOZ_RELEASE_ASSERT(tabGroup);
+
+  // We delay creating the event targets until now since the TabGroup
+  // constructor ran off the main thread.
+  tabGroup->EnsureThrottledEventQueues();
+
+  return tabGroup;
+}
+
 already_AddRefed<DocGroup>
 TabGroup::GetDocGroup(const nsACString& aKey)
 {
   RefPtr<DocGroup> docGroup(mDocGroups.GetEntry(aKey)->mDocGroup);
   return docGroup.forget();
 }
 
 already_AddRefed<DocGroup>
@@ -171,16 +221,18 @@ TabGroup::GetTopLevelWindows()
   }
 
   return array;
 }
 
 ThrottledEventQueue*
 TabGroup::GetThrottledEventQueue() const
 {
+  MOZ_RELEASE_ASSERT(mThrottledQueuesInitialized || this == GetChromeTabGroup());
+  MOZ_RELEASE_ASSERT(!mLastWindowLeft);
   return mThrottledEventQueue;
 }
 
 NS_IMPL_ISUPPORTS(TabGroup, nsISupports)
 
 TabGroup::HashEntry::HashEntry(const nsACString* aKey)
   : nsCStringHashKey(aKey), mDocGroup(nullptr)
 {}
--- a/dom/base/TabGroup.h
+++ b/dom/base/TabGroup.h
@@ -55,16 +55,24 @@ public:
 
   friend class DocGroup;
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   static TabGroup*
   GetChromeTabGroup();
 
+  // Checks if the PBrowserChild associated with aWindow already has a TabGroup
+  // assigned to it in IPDL. Returns this TabGroup if it does. This could happen
+  // if the parent process created the PBrowser and we needed to assign a
+  // TabGroup immediately upon receiving the IPDL message. This method is main
+  // thread only.
+  static TabGroup*
+  GetFromWindowActor(mozIDOMWindowProxy* aWindow);
+
   explicit TabGroup(bool aIsChrome = false);
 
   // Get the docgroup for the corresponding doc group key.
   // Returns null if the given key hasn't been seen yet.
   already_AddRefed<DocGroup>
   GetDocGroup(const nsACString& aKey);
 
   already_AddRefed<DocGroup>
@@ -108,21 +116,26 @@ public:
 
   virtual nsresult Dispatch(const char* aName,
                             TaskCategory aCategory,
                             already_AddRefed<nsIRunnable>&& aRunnable) override;
 
   virtual already_AddRefed<nsIEventTarget>
   EventTargetFor(TaskCategory aCategory) const override;
 
+  TabGroup* AsTabGroup() override { return this; }
+
 private:
+  void EnsureThrottledEventQueues();
+
   ~TabGroup();
   DocGroupMap mDocGroups;
   bool mLastWindowLeft;
   nsTArray<nsPIDOMWindowOuter*> mWindows;
+  bool mThrottledQueuesInitialized;
   RefPtr<ThrottledEventQueue> mThrottledEventQueue;
   nsCOMPtr<nsIEventTarget> mEventTargets[size_t(TaskCategory::Count)];
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // defined(TabGroup_h)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -14156,17 +14156,28 @@ nsGlobalWindow::TabGroupOuter()
 
     mozilla::dom::TabGroup* toJoin = nullptr;
     if (GetDocShell()->ItemType() == nsIDocShellTreeItem::typeChrome) {
       toJoin = TabGroup::GetChromeTabGroup();
     } else if (opener) {
       toJoin = opener->TabGroup();
     } else if (parent) {
       toJoin = parent->TabGroup();
-    }
+    } else {
+      // If the tab was created by the parent process, the IPC code may have
+      // already created a TabGroup for us. Fetch it in that case.
+      toJoin = TabGroup::GetFromWindowActor(AsOuter());
+    }
+#ifdef DEBUG
+    // Make sure that, if we have a tab group from the actor, it matches the one
+    // we're planning to join.
+    mozilla::dom::TabGroup* actorTabGroup = TabGroup::GetFromWindowActor(AsOuter());
+    MOZ_ASSERT_IF(actorTabGroup, actorTabGroup == toJoin);
+#endif
+
     mTabGroup = mozilla::dom::TabGroup::Join(AsOuter(), toJoin);
   }
   MOZ_ASSERT(mTabGroup);
 
 #ifdef DEBUG
   // Ensure that we don't recurse forever
   if (!mIsValidatingTabGroup) {
     mIsValidatingTabGroup = true;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -22,23 +22,25 @@
 #include "mozilla/Unused.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
 #include "mozilla/docshell/OfflineCacheUpdateChild.h"
 #include "mozilla/dom/ContentBridgeChild.h"
 #include "mozilla/dom/ContentBridgeParent.h"
 #include "mozilla/dom/VideoDecoderManagerChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DataTransfer.h"
+#include "mozilla/dom/DocGroup.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/FlyWebPublishedServerIPC.h"
 #include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/dom/PCrashReporterChild.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/PushNotifier.h"
+#include "mozilla/dom/TabGroup.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/dom/nsIContentChild.h"
 #include "mozilla/dom/URLClassifierChild.h"
 #include "mozilla/dom/ipc/BlobChild.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/psm/PSMContentListener.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
@@ -781,16 +783,29 @@ ContentChild::ProvideWindowCommon(TabChi
     return NS_ERROR_ABORT;
   }
 
   if (aTabOpener) {
     MOZ_ASSERT(ipcContext->type() == IPCTabContext::TPopupIPCTabContext);
     ipcContext->get_PopupIPCTabContext().opener() = aTabOpener;
   }
 
+  // We need to assign a TabGroup to the PBrowser actor before we send it to the
+  // parent. Otherwise, the parent could send messages to us before we have a
+  // proper TabGroup for that actor.
+  RefPtr<TabGroup> tabGroup;
+  if (aTabOpener && !aForceNoOpener) {
+    // The new actor will use the same tab group as the opener.
+    tabGroup = aTabOpener->TabGroup();
+  } else {
+    tabGroup = new TabGroup();
+  }
+  nsCOMPtr<nsIEventTarget> target = tabGroup->EventTargetFor(TaskCategory::Other);
+  SetEventTargetForActor(newChild, target);
+
   Unused << SendPBrowserConstructor(
     // We release this ref in DeallocPBrowserChild
     RefPtr<TabChild>(newChild).forget().take(),
     tabId, *ipcContext, aChromeFlags,
     GetID(), IsForBrowser());
 
   nsString name(aName);
   nsTArray<FrameScriptInfo> frameScripts;
@@ -3220,10 +3235,27 @@ ContentChild::AllocPURLClassifierChild(c
 bool
 ContentChild::DeallocPURLClassifierChild(PURLClassifierChild* aActor)
 {
   MOZ_ASSERT(aActor);
   delete aActor;
   return true;
 }
 
+// The IPC code will call this method asking us to assign an event target to new
+// actors created by the ContentParent.
+already_AddRefed<nsIEventTarget>
+ContentChild::GetConstructedEventTarget(const Message& aMsg)
+{
+  // Currently we only set targets for PBrowser.
+  if (aMsg.type() != PContent::Msg_PBrowserConstructor__ID) {
+    return nullptr;
+  }
+
+  // If the request for a new TabChild is coming from the parent process, then
+  // there is no opener. Therefore, we create a fresh TabGroup.
+  RefPtr<TabGroup> tabGroup = new TabGroup();
+  nsCOMPtr<nsIEventTarget> target = tabGroup->EventTargetFor(TaskCategory::Other);
+  return target.forget();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -642,16 +642,19 @@ public:
 private:
   static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
   void StartForceKillTimer();
 
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   virtual void ProcessingError(Result aCode, const char* aReason) override;
 
+  virtual already_AddRefed<nsIEventTarget>
+  GetConstructedEventTarget(const Message& aMsg) override;
+
   InfallibleTArray<nsAutoPtr<AlertObserver> > mAlertObservers;
   RefPtr<ConsoleListener> mConsoleListener;
 
   nsTHashtable<nsPtrHashKey<nsIObserver>> mIdleObservers;
 
   InfallibleTArray<nsString> mAvailableDictionaries;
 
   // Temporary storage for a list of available font families, passed from the
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -3112,16 +3112,24 @@ TabChildSHistoryListener::SHistoryDidUpd
 
   // XXX: It would be nice if we could batch these updates like SessionStore
   // does, and provide a form of `Flush` command which would allow us to trigger
   // an update, and wait for the state to become consistent.
   NS_ENSURE_TRUE(tabChild->SendSHistoryUpdate(count, index, aTruncate), NS_ERROR_FAILURE);
   return NS_OK;
 }
 
+mozilla::dom::TabGroup*
+TabChild::TabGroup()
+{
+  nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(WebNavigation());
+  MOZ_ASSERT(window);
+  return window->TabGroup();
+}
+
 /*******************************************************************************
  * nsISHistoryListener
  ******************************************************************************/
 
 NS_IMETHODIMP
 TabChildSHistoryListener::OnHistoryNewEntry(nsIURI *aNewURI, int32_t aOldIndex)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -63,16 +63,17 @@ struct AutoCacheNativeKeyCommands;
 
 namespace plugins {
 class PluginWidgetChild;
 } // namespace plugins
 
 namespace dom {
 
 class TabChild;
+class TabGroup;
 class ClonedMessageData;
 class TabChildBase;
 
 class TabChildGlobal : public DOMEventTargetHelper,
                        public nsIContentFrameMessageManager,
                        public nsIScriptObjectPrincipal,
                        public nsIGlobalObject,
                        public nsSupportsWeakReference
@@ -660,16 +661,18 @@ public:
   {
     bool wasFreshProcess = mIsFreshProcess;
     mIsFreshProcess = false;
     return wasFreshProcess;
   }
 
   already_AddRefed<nsISHistory> GetRelatedSHistory();
 
+  mozilla::dom::TabGroup* TabGroup();
+
 protected:
   virtual ~TabChild();
 
   virtual PRenderFrameChild* AllocPRenderFrameChild() override;
 
   virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
 
   virtual mozilla::ipc::IPCResult RecvDestroy() override;
--- a/ipc/glue/ProtocolUtils.cpp
+++ b/ipc/glue/ProtocolUtils.cpp
@@ -794,16 +794,26 @@ IToplevelProtocol::GetMessageEventTarget
     }
 
     mEventTargetMap.AddWithID(target, handle.mId);
   }
 
   return target.forget();
 }
 
+already_AddRefed<nsIEventTarget>
+IToplevelProtocol::GetActorEventTarget(IProtocol* aActor)
+{
+  MOZ_RELEASE_ASSERT(aActor->Id() != kNullActorId && aActor->Id() != kFreedActorId);
+
+  MutexAutoLock lock(mEventTargetMutex);
+  nsCOMPtr<nsIEventTarget> target = mEventTargetMap.Lookup(aActor->Id());
+  return target.forget();
+}
+
 void
 IToplevelProtocol::SetEventTargetForActorInternal(IProtocol* aActor,
                                                   nsIEventTarget* aEventTarget)
 {
   // We should only call this function on actors that haven't been used for IPC
   // code yet. Otherwise we'll be posting stuff to the wrong event target before
   // we're called.
   MOZ_RELEASE_ASSERT(aActor->Id() == kNullActorId || aActor->Id() == kFreedActorId);
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -357,16 +357,19 @@ public:
     }
 
     virtual void ProcessRemoteNativeEventsInInterruptCall() {
     }
 
     virtual already_AddRefed<nsIEventTarget>
     GetMessageEventTarget(const Message& aMsg);
 
+    already_AddRefed<nsIEventTarget>
+    GetActorEventTarget(IProtocol* aActor);
+
 protected:
     virtual already_AddRefed<nsIEventTarget>
     GetConstructedEventTarget(const Message& aMsg) { return nullptr; }
 
     virtual void SetEventTargetForActorInternal(IProtocol* aActor, nsIEventTarget* aEventTarget);
 
   private:
     ProtocolId mProtocolId;