/* -*- 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/AbstractThread.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 sChromeTabGroup; TabGroup::TabGroup(bool aIsChrome) : mLastWindowLeft(false) , mThrottledQueuesInitialized(false) , mIsChrome(aIsChrome) { CreateEventTargets(/* aNeedValidation = */ !aIsChrome); // 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()); MOZ_RELEASE_ASSERT(mLastWindowLeft || mIsChrome); } void TabGroup::EnsureThrottledEventQueues() { if (mThrottledQueuesInitialized) { return; } mThrottledQueuesInitialized = true; for (size_t i = 0; i < size_t(TaskCategory::Count); i++) { TaskCategory category = static_cast(i); if (category == TaskCategory::Worker || category == TaskCategory::Timer) { nsCOMPtr target = ThrottledEventQueue::Create(mEventTargets[i]); if (target) { // This may return nullptr during xpcom shutdown. This is ok as we // do not guarantee a ThrottledEventQueue will be present. mEventTargets[i] = target; } } } } /* static */ TabGroup* TabGroup::GetChromeTabGroup() { if (!sChromeTabGroup) { sChromeTabGroup = new TabGroup(true /* chrome tab group */); ClearOnShutdown(&sChromeTabGroup); } return sChromeTabGroup; } /* static */ TabGroup* TabGroup::GetFromWindow(mozIDOMWindowProxy* aWindow) { if (TabChild* tabChild = TabChild::GetFrom(aWindow)) { return tabChild->TabGroup(); } return nullptr; } /* static */ TabGroup* TabGroup::GetFromActor(TabChild* aTabChild) { MOZ_RELEASE_ASSERT(NS_IsMainThread()); nsCOMPtr target = aTabChild->Manager()->GetEventTargetFor(aTabChild); if (!target) { return nullptr; } // We have an event target. We assume the IPC code created it via // TabGroup::CreateEventTarget. RefPtr group = SchedulerGroup::FromEventTarget(target); MOZ_RELEASE_ASSERT(group); auto tabGroup = group->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 TabGroup::GetDocGroup(const nsACString& aKey) { RefPtr docGroup(mDocGroups.GetEntry(aKey)->mDocGroup); return docGroup.forget(); } already_AddRefed TabGroup::AddDocument(const nsACString& aKey, nsIDocument* aDocument) { MOZ_ASSERT(NS_IsMainThread()); HashEntry* entry = mDocGroups.PutEntry(aKey); RefPtr docGroup; if (entry->mDocGroup) { docGroup = entry->mDocGroup; } else { docGroup = new DocGroup(this, aKey); entry->mDocGroup = docGroup; } // Make sure that the hashtable was updated and now contains the correct value MOZ_ASSERT(RefPtr(GetDocGroup(aKey)) == docGroup); docGroup->mDocuments.AppendElement(aDocument); return docGroup.forget(); } /* static */ already_AddRefed TabGroup::Join(nsPIDOMWindowOuter* aWindow, TabGroup* aTabGroup) { MOZ_ASSERT(NS_IsMainThread()); RefPtr tabGroup = aTabGroup; if (!tabGroup) { tabGroup = new TabGroup(); } MOZ_RELEASE_ASSERT(!tabGroup->mLastWindowLeft); MOZ_ASSERT(!tabGroup->mWindows.Contains(aWindow)); tabGroup->mWindows.AppendElement(aWindow); return tabGroup.forget(); } void TabGroup::Leave(nsPIDOMWindowOuter* aWindow) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mWindows.Contains(aWindow)); mWindows.RemoveElement(aWindow); // The Chrome TabGroup doesn't have cyclical references through mEventTargets // to itself, meaning that we don't have to worry about nulling mEventTargets // out after the last window leaves. if (!mIsChrome && mWindows.IsEmpty()) { mLastWindowLeft = true; Shutdown(false); } } nsresult TabGroup::FindItemWithName(const nsAString& aName, nsIDocShellTreeItem* aRequestor, nsIDocShellTreeItem* aOriginalRequestor, nsIDocShellTreeItem** aFoundItem) { MOZ_ASSERT(NS_IsMainThread()); NS_ENSURE_ARG_POINTER(aFoundItem); *aFoundItem = nullptr; MOZ_ASSERT(!aName.LowerCaseEqualsLiteral("_blank") && !aName.LowerCaseEqualsLiteral("_top") && !aName.LowerCaseEqualsLiteral("_parent") && !aName.LowerCaseEqualsLiteral("_self")); for (nsPIDOMWindowOuter* outerWindow : mWindows) { // Ignore non-toplevel windows if (outerWindow->GetScriptableParentOrNull()) { continue; } nsCOMPtr docshell = outerWindow->GetDocShell(); if (!docshell) { continue; } nsCOMPtr root; docshell->GetSameTypeRootTreeItem(getter_AddRefs(root)); MOZ_RELEASE_ASSERT(docshell == root); if (root && aRequestor != root) { root->FindItemWithName(aName, aRequestor, aOriginalRequestor, /* aSkipTabGroup = */ true, aFoundItem); if (*aFoundItem) { break; } } } return NS_OK; } nsTArray TabGroup::GetTopLevelWindows() const { MOZ_ASSERT(NS_IsMainThread()); nsTArray array; for (nsPIDOMWindowOuter* outerWindow : mWindows) { if (outerWindow->GetDocShell() && !outerWindow->GetScriptableParentOrNull()) { array.AppendElement(outerWindow); } } return array; } TabGroup::HashEntry::HashEntry(const nsACString* aKey) : nsCStringHashKey(aKey), mDocGroup(nullptr) {} nsIEventTarget* TabGroup::EventTargetFor(TaskCategory aCategory) const { if (aCategory == TaskCategory::Worker || aCategory == TaskCategory::Timer) { MOZ_RELEASE_ASSERT(mThrottledQueuesInitialized || mIsChrome); } return SchedulerGroup::EventTargetFor(aCategory); } AbstractThread* TabGroup::AbstractMainThreadForImpl(TaskCategory aCategory) { // The mEventTargets of the chrome TabGroup are all set to do_GetMainThread(). // We could just return AbstractThread::MainThread() without a wrapper. // Once we've disconnected everything, we still allow people to dispatch. // We'll just go directly to the main thread. if (this == sChromeTabGroup || NS_WARN_IF(mLastWindowLeft)) { return AbstractThread::MainThread(); } return SchedulerGroup::AbstractMainThreadForImpl(aCategory); } bool TabGroup::IsBackground() const { MOZ_RELEASE_ASSERT(NS_IsMainThread()); for (nsPIDOMWindowOuter* outerWindow : GetTopLevelWindows()) { if (!outerWindow->IsBackground()) { return false; } } return true; } } // namespace dom } // namespace mozilla