/* -*- 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/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 sChromeTabGroup; TabGroup::TabGroup(bool aIsChrome) : mLastWindowLeft(false) { for (size_t i = 0; i < size_t(TaskCategory::Count); i++) { TaskCategory category = static_cast(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; } nsCOMPtr 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; } 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) { 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) { RefPtr tabGroup = aTabGroup; if (!tabGroup) { tabGroup = new TabGroup(); } MOZ_ASSERT(!tabGroup->mWindows.Contains(aWindow)); tabGroup->mWindows.AppendElement(aWindow); return tabGroup.forget(); } void TabGroup::Leave(nsPIDOMWindowOuter* aWindow) { MOZ_ASSERT(mWindows.Contains(aWindow)); mWindows.RemoveElement(aWindow); if (mWindows.IsEmpty()) { mLastWindowLeft = true; // There is a RefPtr cycle TabGroup -> DispatcherEventTarget -> TabGroup. To // avoid leaks, we need to break the chain somewhere. We shouldn't be using // the ThrottledEventQueue for this TabGroup when no windows belong to it, // so it's safe to null out the queue here. for (size_t i = 0; i < size_t(TaskCategory::Count); i++) { mEventTargets[i] = nullptr; } } } nsresult TabGroup::FindItemWithName(const nsAString& aName, nsIDocShellTreeItem* aRequestor, nsIDocShellTreeItem* aOriginalRequestor, nsIDocShellTreeItem** aFoundItem) { 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, this, aOriginalRequestor, aFoundItem); if (*aFoundItem) { break; } } } return NS_OK; } nsTArray TabGroup::GetTopLevelWindows() { nsTArray array; for (nsPIDOMWindowOuter* outerWindow : mWindows) { if (outerWindow->GetDocShell() && !outerWindow->GetScriptableParentOrNull()) { array.AppendElement(outerWindow); } } return array; } ThrottledEventQueue* TabGroup::GetThrottledEventQueue() const { return mThrottledEventQueue; } NS_IMPL_ISUPPORTS(TabGroup, nsISupports) TabGroup::HashEntry::HashEntry(const nsACString* aKey) : nsCStringHashKey(aKey), mDocGroup(nullptr) {} nsresult TabGroup::Dispatch(const char* aName, TaskCategory aCategory, already_AddRefed&& aRunnable) { nsCOMPtr runnable(aRunnable); if (aName) { if (nsCOMPtr named = do_QueryInterface(runnable)) { named->SetName(aName); } } if (NS_IsMainThread()) { return NS_DispatchToCurrentThread(runnable.forget()); } else { return NS_DispatchToMainThread(runnable.forget()); } } already_AddRefed TabGroup::EventTargetFor(TaskCategory aCategory) const { MOZ_RELEASE_ASSERT(!mLastWindowLeft); nsCOMPtr target = mEventTargets[size_t(aCategory)]; return target.forget(); } } }