/* -*- 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 "nsIGlobalObject.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/StorageAccess.h" #include "mozilla/dom/BlobURLProtocolHandler.h" #include "mozilla/dom/FunctionBinding.h" #include "mozilla/dom/Report.h" #include "mozilla/dom/ReportingObserver.h" #include "mozilla/dom/ServiceWorker.h" #include "mozilla/dom/ServiceWorkerRegistration.h" #include "nsContentUtils.h" #include "nsThreadUtils.h" #include "nsGlobalWindowInner.h" // Max number of Report objects constexpr auto MAX_REPORT_RECORDS = 100; using mozilla::AutoSlowOperation; using mozilla::CycleCollectedJSContext; using mozilla::DOMEventTargetHelper; using mozilla::ErrorResult; using mozilla::IgnoredErrorResult; using mozilla::MallocSizeOf; using mozilla::Maybe; using mozilla::MicroTaskRunnable; using mozilla::dom::BlobURLProtocolHandler; using mozilla::dom::ClientInfo; using mozilla::dom::Report; using mozilla::dom::ReportingObserver; using mozilla::dom::ServiceWorker; using mozilla::dom::ServiceWorkerDescriptor; using mozilla::dom::ServiceWorkerRegistration; using mozilla::dom::ServiceWorkerRegistrationDescriptor; using mozilla::dom::VoidFunction; nsIGlobalObject::nsIGlobalObject() : mIsDying(false), mIsScriptForbidden(false), mIsInnerWindow(false) {} bool nsIGlobalObject::IsScriptForbidden(JSObject* aCallback, bool aIsJSImplementedWebIDL) const { if (mIsScriptForbidden || mIsDying) { return true; } if (NS_IsMainThread()) { if (aIsJSImplementedWebIDL) { return false; } if (!xpc::Scriptability::AllowedIfExists(aCallback)) { return true; } } return false; } nsIGlobalObject::~nsIGlobalObject() { UnlinkObjectsInGlobal(); DisconnectEventTargetObjects(); MOZ_DIAGNOSTIC_ASSERT(mEventTargetObjects.isEmpty()); } nsIPrincipal* nsIGlobalObject::PrincipalOrNull() { if (!NS_IsMainThread()) { return nullptr; } JSObject* global = GetGlobalJSObjectPreserveColor(); if (NS_WARN_IF(!global)) return nullptr; return nsContentUtils::ObjectPrincipal(global); } void nsIGlobalObject::RegisterHostObjectURI(const nsACString& aURI) { MOZ_ASSERT(!mHostObjectURIs.Contains(aURI)); mHostObjectURIs.AppendElement(aURI); } void nsIGlobalObject::UnregisterHostObjectURI(const nsACString& aURI) { mHostObjectURIs.RemoveElement(aURI); } namespace { class UnlinkHostObjectURIsRunnable final : public mozilla::Runnable { public: explicit UnlinkHostObjectURIsRunnable(nsTArray&& aURIs) : mozilla::Runnable("UnlinkHostObjectURIsRunnable"), mURIs(std::move(aURIs)) {} NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); for (uint32_t index = 0; index < mURIs.Length(); ++index) { BlobURLProtocolHandler::RemoveDataEntry(mURIs[index]); } return NS_OK; } private: ~UnlinkHostObjectURIsRunnable() = default; const nsTArray mURIs; }; } // namespace void nsIGlobalObject::UnlinkObjectsInGlobal() { if (!mHostObjectURIs.IsEmpty()) { // BlobURLProtocolHandler is main-thread only. if (NS_IsMainThread()) { for (uint32_t index = 0; index < mHostObjectURIs.Length(); ++index) { BlobURLProtocolHandler::RemoveDataEntry(mHostObjectURIs[index]); } mHostObjectURIs.Clear(); } else { RefPtr runnable = new UnlinkHostObjectURIsRunnable(std::move(mHostObjectURIs)); MOZ_ASSERT(mHostObjectURIs.IsEmpty()); nsresult rv = NS_DispatchToMainThread(runnable); if (NS_FAILED(rv)) { NS_WARNING("Failed to dispatch a runnable to the main-thread."); } } } mReportRecords.Clear(); mReportingObservers.Clear(); mCountQueuingStrategySizeFunction = nullptr; mByteLengthQueuingStrategySizeFunction = nullptr; } void nsIGlobalObject::TraverseObjectsInGlobal( nsCycleCollectionTraversalCallback& cb) { // Currently we only store BlobImpl objects off the the main-thread and they // are not CCed. if (!mHostObjectURIs.IsEmpty() && NS_IsMainThread()) { for (uint32_t index = 0; index < mHostObjectURIs.Length(); ++index) { BlobURLProtocolHandler::Traverse(mHostObjectURIs[index], cb); } } nsIGlobalObject* tmp = this; NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportRecords) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReportingObservers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCountQueuingStrategySizeFunction) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mByteLengthQueuingStrategySizeFunction) } void nsIGlobalObject::AddEventTargetObject(DOMEventTargetHelper* aObject) { MOZ_DIAGNOSTIC_ASSERT(aObject); MOZ_ASSERT(!aObject->isInList()); mEventTargetObjects.insertBack(aObject); } void nsIGlobalObject::RemoveEventTargetObject(DOMEventTargetHelper* aObject) { MOZ_DIAGNOSTIC_ASSERT(aObject); MOZ_ASSERT(aObject->isInList()); MOZ_ASSERT(aObject->GetOwnerGlobal() == this); aObject->remove(); } void nsIGlobalObject::ForEachEventTargetObject( const std::function& aFunc) const { // Protect against the function call triggering a mutation of the list // while we are iterating by copying the DETH references to a temporary // list. AutoTArray, 64> targetList; for (const DOMEventTargetHelper* deth = mEventTargetObjects.getFirst(); deth; deth = deth->getNext()) { targetList.AppendElement(const_cast(deth)); } // Iterate the target list and call the function on each one. bool done = false; for (auto target : targetList) { // Check to see if a previous iteration's callback triggered the removal // of this target as a side-effect. If it did, then just ignore it. if (target->GetOwnerGlobal() != this) { continue; } aFunc(target, &done); if (done) { break; } } } void nsIGlobalObject::DisconnectEventTargetObjects() { ForEachEventTargetObject([&](DOMEventTargetHelper* aTarget, bool* aDoneOut) { aTarget->DisconnectFromOwner(); // Calling DisconnectFromOwner() should result in // RemoveEventTargetObject() being called. MOZ_DIAGNOSTIC_ASSERT(aTarget->GetOwnerGlobal() != this); }); } Maybe nsIGlobalObject::GetClientInfo() const { // By default globals do not expose themselves as a client. Only real // window and worker globals are currently considered clients. return Maybe(); } Maybe nsIGlobalObject::GetAgentClusterId() const { Maybe ci = GetClientInfo(); if (ci.isSome()) { return ci.value().AgentClusterId(); } return mozilla::Nothing(); } Maybe nsIGlobalObject::GetController() const { // By default globals do not have a service worker controller. Only real // window and worker globals can currently be controlled as a client. return Maybe(); } RefPtr nsIGlobalObject::GetOrCreateServiceWorker( const ServiceWorkerDescriptor& aDescriptor) { MOZ_DIAGNOSTIC_ASSERT(false, "this global should not have any service workers"); return nullptr; } RefPtr nsIGlobalObject::GetServiceWorkerRegistration( const mozilla::dom::ServiceWorkerRegistrationDescriptor& aDescriptor) const { MOZ_DIAGNOSTIC_ASSERT(false, "this global should not have any service workers"); return nullptr; } RefPtr nsIGlobalObject::GetOrCreateServiceWorkerRegistration( const ServiceWorkerRegistrationDescriptor& aDescriptor) { MOZ_DIAGNOSTIC_ASSERT( false, "this global should not have any service worker registrations"); return nullptr; } mozilla::StorageAccess nsIGlobalObject::GetStorageAccess() { return mozilla::StorageAccess::eDeny; } nsPIDOMWindowInner* nsIGlobalObject::AsInnerWindow() { if (MOZ_LIKELY(mIsInnerWindow)) { return static_cast( static_cast(this)); } return nullptr; } size_t nsIGlobalObject::ShallowSizeOfExcludingThis(MallocSizeOf aSizeOf) const { size_t rtn = mHostObjectURIs.ShallowSizeOfExcludingThis(aSizeOf); return rtn; } class QueuedMicrotask : public MicroTaskRunnable { public: QueuedMicrotask(nsIGlobalObject* aGlobal, VoidFunction& aCallback) : mGlobal(aGlobal), mCallback(&aCallback) {} MOZ_CAN_RUN_SCRIPT_BOUNDARY void Run(AutoSlowOperation& aAso) final { IgnoredErrorResult rv; MOZ_KnownLive(mCallback)->Call(static_cast(rv)); } bool Suppressed() final { return mGlobal->IsInSyncOperation(); } private: nsCOMPtr mGlobal; RefPtr mCallback; }; void nsIGlobalObject::QueueMicrotask(VoidFunction& aCallback) { CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); if (context) { RefPtr mt = new QueuedMicrotask(this, aCallback); context->DispatchToMicroTask(mt.forget()); } } void nsIGlobalObject::RegisterReportingObserver(ReportingObserver* aObserver, bool aBuffered) { MOZ_ASSERT(aObserver); if (mReportingObservers.Contains(aObserver)) { return; } if (NS_WARN_IF( !mReportingObservers.AppendElement(aObserver, mozilla::fallible))) { return; } if (!aBuffered) { return; } for (Report* report : mReportRecords) { aObserver->MaybeReport(report); } } void nsIGlobalObject::UnregisterReportingObserver( ReportingObserver* aObserver) { MOZ_ASSERT(aObserver); mReportingObservers.RemoveElement(aObserver); } void nsIGlobalObject::BroadcastReport(Report* aReport) { MOZ_ASSERT(aReport); for (ReportingObserver* observer : mReportingObservers) { observer->MaybeReport(aReport); } if (NS_WARN_IF(!mReportRecords.AppendElement(aReport, mozilla::fallible))) { return; } while (mReportRecords.Length() > MAX_REPORT_RECORDS) { mReportRecords.RemoveElementAt(0); } } void nsIGlobalObject::NotifyReportingObservers() { for (auto& observer : mReportingObservers.Clone()) { // MOZ_KnownLive because the clone of 'mReportingObservers' is guaranteed to // keep it alive. // // This can go away once // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. MOZ_KnownLive(observer)->MaybeNotify(); } } void nsIGlobalObject::RemoveReportRecords() { mReportRecords.Clear(); for (auto& observer : mReportingObservers) { observer->ForgetReports(); } } already_AddRefed nsIGlobalObject::GetCountQueuingStrategySizeFunction() { return do_AddRef(mCountQueuingStrategySizeFunction); } void nsIGlobalObject::SetCountQueuingStrategySizeFunction( mozilla::dom::Function* aFunction) { mCountQueuingStrategySizeFunction = aFunction; } already_AddRefed nsIGlobalObject::GetByteLengthQueuingStrategySizeFunction() { return do_AddRef(mByteLengthQueuingStrategySizeFunction); } void nsIGlobalObject::SetByteLengthQueuingStrategySizeFunction( mozilla::dom::Function* aFunction) { mByteLengthQueuingStrategySizeFunction = aFunction; } bool nsIGlobalObject::ShouldResistFingerprinting() const { return nsContentUtils::ShouldResistFingerprinting(); }