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
--- 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)
{
@@ -28,39 +29,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
@@ -87,8 +96,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
@@ -68,17 +71,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,77 +1,127 @@
/* -*- 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);
mThrottledEventQueues[i] = nullptr;
}
// 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);
for (size_t i = 0; i < size_t(TaskCategory::Count); i++) {
TaskCategory category = static_cast<TaskCategory>(i);
if (category == TaskCategory::Worker || category == TaskCategory::Timer) {
// This may return nullptr during xpcom shutdown. This is ok as we
// do not guarantee a ThrottledEventQueue will be present.
mThrottledEventQueues[i] = ThrottledEventQueue::Create(mEventTargets[i]);
}
}
}
-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>
@@ -177,16 +227,17 @@ TabGroup::GetTopLevelWindows()
}
return array;
}
ThrottledEventQueue*
TabGroup::GetThrottledEventQueue(TaskCategory aCategory) const
{
+ MOZ_RELEASE_ASSERT(mThrottledQueuesInitialized || this == GetChromeTabGroup());
MOZ_RELEASE_ASSERT(aCategory == TaskCategory::Worker ||
aCategory == TaskCategory::Timer);
MOZ_RELEASE_ASSERT(!mLastWindowLeft);
return mThrottledEventQueues[size_t(aCategory)];
}
NS_IMPL_ISUPPORTS(TabGroup, nsISupports)
@@ -207,16 +258,21 @@ TabGroup::Dispatch(const char* aName,
}
return NS_DispatchToMainThread(runnable.forget());
}
already_AddRefed<nsIEventTarget>
TabGroup::EventTargetFor(TaskCategory aCategory) const
{
MOZ_RELEASE_ASSERT(!mLastWindowLeft);
+ MOZ_RELEASE_ASSERT(mThrottledQueuesInitialized ||
+ this == sChromeTabGroup ||
+ (aCategory != TaskCategory::Worker &&
+ aCategory != TaskCategory::Timer));
+
size_t index = size_t(aCategory);
if (mThrottledEventQueues[index]) {
return do_AddRef(mThrottledEventQueues[index]);
}
nsCOMPtr<nsIEventTarget> target = mEventTargets[index];
return target.forget();
}
--- 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;
nsCOMPtr<nsIEventTarget> mEventTargets[size_t(TaskCategory::Count)];
RefPtr<ThrottledEventQueue> mThrottledEventQueues[size_t(TaskCategory::Count)];
};
} // namespace dom
} // namespace mozilla
#endif // defined(TabGroup_h)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -14918,17 +14918,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
@@ -23,23 +23,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/gfx/gfxVars.h"
#include "mozilla/psm/PSMContentListener.h"
#include "mozilla/hal_sandbox/PHalChild.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/FileDescriptorSetChild.h"
#include "mozilla/ipc/FileDescriptorUtils.h"
@@ -701,16 +703,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);
nsAutoCString features(aFeatures);
@@ -3227,10 +3242,27 @@ ContentChild::FatalErrorIfNotUsingGPUPro
formattedMessage.AppendASCII(aProtocolName);
formattedMessage.AppendLiteral(R"(]: ")");
formattedMessage.AppendASCII(aErrorMsg);
formattedMessage.AppendLiteral(R"(".)");
NS_WARNING(formattedMessage.get());
}
}
+// 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
@@ -644,16 +644,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
@@ -3068,16 +3068,24 @@ TabChild::ForcePaint(uint64_t aLayerObse
// message on the PContent channel.
return;
}
nsAutoScriptBlocker scriptBlocker;
RecvSetDocShellIsActive(true, false, aLayerObserverEpoch);
}
+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
@@ -656,16 +657,18 @@ public:
bool TakeIsFreshProcess()
{
bool wasFreshProcess = mIsFreshProcess;
mIsFreshProcess = false;
return wasFreshProcess;
}
+ 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
@@ -377,16 +377,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;