diff options
Diffstat (limited to 'dom/base/DocGroup.cpp')
-rw-r--r-- | dom/base/DocGroup.cpp | 417 |
1 files changed, 417 insertions, 0 deletions
diff --git a/dom/base/DocGroup.cpp b/dom/base/DocGroup.cpp new file mode 100644 index 0000000000..815213a6dc --- /dev/null +++ b/dom/base/DocGroup.cpp @@ -0,0 +1,417 @@ +/* -*- 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/DocGroup.h" + +#include "mozilla/AbstractThread.h" +#include "mozilla/PerformanceUtils.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ThrottledEventQueue.h" +#include "mozilla/dom/BrowsingContext.h" +#include "mozilla/dom/CustomElementRegistry.h" +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/dom/WindowContext.h" +#include "nsDOMMutationObserver.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsProxyRelease.h" +#include "nsThread.h" +#if defined(XP_WIN) +# include <processthreadsapi.h> // for GetCurrentProcessId() +#else +# include <unistd.h> // for getpid() +#endif // defined(XP_WIN) + +namespace { + +#define NS_LABELLINGEVENTTARGET_IID \ + { \ + 0x6087fa50, 0xe387, 0x45c8, { \ + 0xab, 0x72, 0xd2, 0x1f, 0x69, 0xee, 0xd3, 0x15 \ + } \ + } + +// LabellingEventTarget labels all dispatches with the DocGroup that +// created it. +class LabellingEventTarget final : public nsISerialEventTarget, + public nsIDirectTaskDispatcher { + // This creates a cycle with DocGroup. Therefore, when DocGroup + // looses its last Document, the DocGroup of the + // LabellingEventTarget needs to be cleared. + RefPtr<mozilla::dom::DocGroup> mDocGroup; + + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_LABELLINGEVENTTARGET_IID) + + explicit LabellingEventTarget(mozilla::dom::DocGroup* aDocGroup) + : mDocGroup(aDocGroup), + mMainThread( + static_cast<nsThread*>(mozilla::GetMainThreadSerialEventTarget())) { + } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSIDIRECTTASKDISPATCHER + + private: + ~LabellingEventTarget() = default; + const RefPtr<nsThread> mMainThread; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(LabellingEventTarget, NS_LABELLINGEVENTTARGET_IID) + +} // namespace + +NS_IMETHODIMP +LabellingEventTarget::DispatchFromScript(nsIRunnable* aRunnable, + uint32_t aFlags) { + return Dispatch(do_AddRef(aRunnable), aFlags); +} + +NS_IMETHODIMP +LabellingEventTarget::Dispatch(already_AddRefed<nsIRunnable> aRunnable, + uint32_t aFlags) { + if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) { + return NS_ERROR_UNEXPECTED; + } + + return mozilla::SchedulerGroup::DispatchWithDocGroup( + mozilla::TaskCategory::Other, std::move(aRunnable), mDocGroup); +} + +NS_IMETHODIMP +LabellingEventTarget::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LabellingEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) { + *aIsOnCurrentThread = NS_IsMainThread(); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +LabellingEventTarget::IsOnCurrentThreadInfallible() { + return NS_IsMainThread(); +} + +//----------------------------------------------------------------------------- +// nsIDirectTaskDispatcher +//----------------------------------------------------------------------------- +// We are always running on the main thread, forward to the nsThread's +// MainThread +NS_IMETHODIMP +LabellingEventTarget::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) { + return mMainThread->DispatchDirectTask(std::move(aEvent)); +} + +NS_IMETHODIMP LabellingEventTarget::DrainDirectTasks() { + return mMainThread->DrainDirectTasks(); +} + +NS_IMETHODIMP LabellingEventTarget::HaveDirectTasks(bool* aValue) { + return mMainThread->HaveDirectTasks(aValue); +} + +NS_IMPL_ISUPPORTS(LabellingEventTarget, nsIEventTarget, nsISerialEventTarget, + nsIDirectTaskDispatcher) + +namespace mozilla::dom { + +AutoTArray<RefPtr<DocGroup>, 2>* DocGroup::sPendingDocGroups = nullptr; + +/* static */ +already_AddRefed<DocGroup> DocGroup::Create( + BrowsingContextGroup* aBrowsingContextGroup, const nsACString& aKey) { + RefPtr<DocGroup> docGroup = new DocGroup(aBrowsingContextGroup, aKey); + docGroup->mEventTarget = new LabellingEventTarget(docGroup); + return docGroup.forget(); +} + +/* static */ +nsresult DocGroup::GetKey(nsIPrincipal* aPrincipal, bool aCrossOriginIsolated, + nsACString& aKey) { + // Use GetBaseDomain() to handle things like file URIs, IP address URIs, + // etc. correctly. + nsresult rv = aCrossOriginIsolated ? aPrincipal->GetOrigin(aKey) + : aPrincipal->GetSiteOrigin(aKey); + if (NS_FAILED(rv)) { + aKey.Truncate(); + } + + return rv; +} + +void DocGroup::SetExecutionManager(JSExecutionManager* aManager) { + mExecutionManager = aManager; +} + +mozilla::dom::CustomElementReactionsStack* +DocGroup::CustomElementReactionsStack() { + MOZ_ASSERT(NS_IsMainThread()); + if (!mReactionsStack) { + mReactionsStack = new mozilla::dom::CustomElementReactionsStack(); + } + + return mReactionsStack; +} + +void DocGroup::AddDocument(Document* aDocument) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mDocuments.Contains(aDocument)); + MOZ_ASSERT(mBrowsingContextGroup); + mDocuments.AppendElement(aDocument); +} + +void DocGroup::RemoveDocument(Document* aDocument) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mDocuments.Contains(aDocument)); + mDocuments.RemoveElement(aDocument); + + if (mDocuments.IsEmpty()) { + mBrowsingContextGroup = nullptr; + // This clears the cycle DocGroup has with LabellingEventTarget. + mEventTarget = nullptr; + } +} + +DocGroup::DocGroup(BrowsingContextGroup* aBrowsingContextGroup, + const nsACString& aKey) + : mKey(aKey), + mBrowsingContextGroup(aBrowsingContextGroup), + mAgentClusterId(nsContentUtils::GenerateUUID()) { + // This method does not add itself to + // mBrowsingContextGroup->mDocGroups as the caller does it for us. + MOZ_ASSERT(NS_IsMainThread()); + if (StaticPrefs::dom_arena_allocator_enabled_AtStartup()) { + mArena = new mozilla::dom::DOMArena(); + } + + mPerformanceCounter = new mozilla::PerformanceCounter("DocGroup:"_ns + aKey); +} + +DocGroup::~DocGroup() { + MOZ_RELEASE_ASSERT(mDocuments.IsEmpty()); + MOZ_RELEASE_ASSERT(!mBrowsingContextGroup); + + if (!NS_IsMainThread()) { + NS_ReleaseOnMainThread("DocGroup::mReactionsStack", + mReactionsStack.forget()); + + NS_ReleaseOnMainThread("DocGroup::mArena", mArena.forget()); + } + + if (mIframePostMessageQueue) { + FlushIframePostMessageQueue(); + } +} + +RefPtr<PerformanceInfoPromise> DocGroup::ReportPerformanceInfo() { + AssertIsOnMainThread(); + MOZ_ASSERT(mPerformanceCounter); +#if defined(XP_WIN) + uint32_t pid = GetCurrentProcessId(); +#else + uint32_t pid = getpid(); +#endif + uint64_t windowID = 0; + uint16_t count = 0; + uint64_t duration = 0; + nsCString host; + bool isTopLevel = false; + RefPtr<BrowsingContext> top; + RefPtr<AbstractThread> mainThread = + AbstractMainThreadFor(TaskCategory::Performance); + + for (const auto& document : *this) { + if (host.IsEmpty()) { + nsCOMPtr<nsIURI> docURI = document->GetDocumentURI(); + if (!docURI) { + continue; + } + + docURI->GetHost(host); + if (host.IsEmpty()) { + host = docURI->GetSpecOrDefault(); + } + } + + BrowsingContext* context = document->GetBrowsingContext(); + if (!context) { + continue; + } + + top = context->Top(); + + if (!top || !top->GetCurrentWindowContext()) { + continue; + } + + isTopLevel = context->IsTop(); + windowID = top->GetCurrentWindowContext()->OuterWindowId(); + break; + }; + + MOZ_ASSERT(!host.IsEmpty()); + duration = mPerformanceCounter->GetExecutionDuration(); + FallibleTArray<CategoryDispatch> items; + + // now that we have the host and window ids, let's look at the perf counters + for (uint32_t index = 0; index < (uint32_t)TaskCategory::Count; index++) { + TaskCategory category = static_cast<TaskCategory>(index); + count = mPerformanceCounter->GetDispatchCount(DispatchCategory(category)); + CategoryDispatch item = CategoryDispatch(index, count); + if (!items.AppendElement(item, fallible)) { + NS_ERROR("Could not complete the operation"); + break; + } + } + + if (!isTopLevel && top && top->IsInProcess()) { + return PerformanceInfoPromise::CreateAndResolve( + PerformanceInfo(host, pid, windowID, duration, + mPerformanceCounter->GetID(), false, isTopLevel, + PerformanceMemoryInfo(), // Empty memory info + items), + __func__); + } + + MOZ_ASSERT(mainThread); + RefPtr<DocGroup> self = this; + return (isTopLevel ? CollectMemoryInfo(top, mainThread) + : CollectMemoryInfo(self, mainThread)) + ->Then( + mainThread, __func__, + [self, host, pid, windowID, duration, isTopLevel, + items = std::move(items)](const PerformanceMemoryInfo& aMemoryInfo) { + PerformanceInfo info = + PerformanceInfo(host, pid, windowID, duration, + self->mPerformanceCounter->GetID(), false, + isTopLevel, aMemoryInfo, items); + + return PerformanceInfoPromise::CreateAndResolve(std::move(info), + __func__); + }, + [self](const nsresult rv) { + return PerformanceInfoPromise::CreateAndReject(rv, __func__); + }); +} + +nsresult DocGroup::Dispatch(TaskCategory aCategory, + already_AddRefed<nsIRunnable>&& aRunnable) { + if (mPerformanceCounter) { + mPerformanceCounter->IncrementDispatchCounter(DispatchCategory(aCategory)); + } + return SchedulerGroup::DispatchWithDocGroup(aCategory, std::move(aRunnable), + this); +} + +nsISerialEventTarget* DocGroup::EventTargetFor(TaskCategory aCategory) const { + MOZ_ASSERT(!mDocuments.IsEmpty()); + // Here we have the same event target for every TaskCategory. The + // reason for that is that currently TaskCategory isn't used, and + // it's unsure if it ever will be (See Bug 1624819). + if (mEventTarget) { + return mEventTarget; + } + + return GetMainThreadSerialEventTarget(); +} + +AbstractThread* DocGroup::AbstractMainThreadFor(TaskCategory aCategory) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mDocuments.IsEmpty()); + + // Here we have the same thread for every TaskCategory. The reason + // for that is that currently TaskCategory isn't used, and it's + // unsure if it ever will be (See Bug 1624819). + return AbstractThread::MainThread(); +} + +void DocGroup::SignalSlotChange(HTMLSlotElement& aSlot) { + MOZ_ASSERT(!mSignalSlotList.Contains(&aSlot)); + mSignalSlotList.AppendElement(&aSlot); + + if (!sPendingDocGroups) { + // Queue a mutation observer compound microtask. + nsDOMMutationObserver::QueueMutationObserverMicroTask(); + sPendingDocGroups = new AutoTArray<RefPtr<DocGroup>, 2>; + } + + sPendingDocGroups->AppendElement(this); +} + +bool DocGroup::TryToLoadIframesInBackground() { + return StaticPrefs::dom_separate_event_queue_for_post_message_enabled() && + StaticPrefs::dom_cross_origin_iframes_loaded_in_background(); +} + +nsresult DocGroup::QueueIframePostMessages( + already_AddRefed<nsIRunnable>&& aRunnable, uint64_t aWindowId) { + if (DocGroup::TryToLoadIframesInBackground()) { + if (!mIframePostMessageQueue) { + nsCOMPtr<nsISerialEventTarget> target = GetMainThreadSerialEventTarget(); + mIframePostMessageQueue = ThrottledEventQueue::Create( + target, "Background Loading Iframe PostMessage Queue", + nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS); + nsresult rv = mIframePostMessageQueue->SetIsPaused(true); + MOZ_ALWAYS_SUCCEEDS(rv); + } + + // Ensure the queue is disabled. Unlike the postMessageEvent queue + // in BrowsingContextGroup, this postMessage queue should always + // be paused, because if we leave it open, the postMessage may get + // dispatched to an unloaded iframe + MOZ_ASSERT(mIframePostMessageQueue); + MOZ_ASSERT(mIframePostMessageQueue->IsPaused()); + + mIframesUsedPostMessageQueue.PutEntry(aWindowId); + + mIframePostMessageQueue->Dispatch(std::move(aRunnable), NS_DISPATCH_NORMAL); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +void DocGroup::TryFlushIframePostMessages(uint64_t aWindowId) { + if (DocGroup::TryToLoadIframesInBackground()) { + mIframesUsedPostMessageQueue.RemoveEntry(aWindowId); + if (mIframePostMessageQueue && mIframesUsedPostMessageQueue.IsEmpty()) { + MOZ_ASSERT(mIframePostMessageQueue->IsPaused()); + nsresult rv = mIframePostMessageQueue->SetIsPaused(true); + MOZ_ALWAYS_SUCCEEDS(rv); + FlushIframePostMessageQueue(); + } + } +} + +void DocGroup::FlushIframePostMessageQueue() { + nsCOMPtr<nsIRunnable> event; + while ((event = mIframePostMessageQueue->GetEvent())) { + Dispatch(TaskCategory::Other, event.forget()); + } +} + +nsTArray<RefPtr<HTMLSlotElement>> DocGroup::MoveSignalSlotList() { + for (const RefPtr<HTMLSlotElement>& slot : mSignalSlotList) { + slot->RemovedFromSignalSlotList(); + } + return std::move(mSignalSlotList); +} + +bool DocGroup::IsActive() const { + for (Document* doc : mDocuments) { + if (doc->IsCurrentActiveDocument()) { + return true; + } + } + + return false; +} + +} // namespace mozilla::dom |