diff options
Diffstat (limited to '')
-rw-r--r-- | dom/base/nsIGlobalObject.cpp | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/dom/base/nsIGlobalObject.cpp b/dom/base/nsIGlobalObject.cpp new file mode 100644 index 0000000000..2c7ebb2562 --- /dev/null +++ b/dom/base/nsIGlobalObject.cpp @@ -0,0 +1,420 @@ +/* -*- 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/BasePrincipal.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/GlobalTeardownObserver.h" +#include "mozilla/Result.h" +#include "mozilla/StorageAccess.h" +#include "mozilla/dom/BindingDeclarations.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 "mozilla/ipc/PBackgroundSharedTypes.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::GlobalTeardownObserver; +using mozilla::IgnoredErrorResult; +using mozilla::MallocSizeOf; +using mozilla::Maybe; +using mozilla::MicroTaskRunnable; +using mozilla::dom::BlobURLProtocolHandler; +using mozilla::dom::CallerType; +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(); + DisconnectGlobalTeardownObservers(); + MOZ_DIAGNOSTIC_ASSERT(mGlobalTeardownObservers.isEmpty()); +} + +nsIPrincipal* nsIGlobalObject::PrincipalOrNull() const { + 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<nsCString>&& 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<nsCString> 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<UnlinkHostObjectURIsRunnable> 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::AddGlobalTeardownObserver( + GlobalTeardownObserver* aObject) { + MOZ_DIAGNOSTIC_ASSERT(aObject); + MOZ_ASSERT(!aObject->isInList()); + mGlobalTeardownObservers.insertBack(aObject); +} + +void nsIGlobalObject::RemoveGlobalTeardownObserver( + GlobalTeardownObserver* aObject) { + MOZ_DIAGNOSTIC_ASSERT(aObject); + MOZ_ASSERT(aObject->isInList()); + MOZ_ASSERT(aObject->GetOwnerGlobal() == this); + aObject->remove(); +} + +void nsIGlobalObject::ForEachGlobalTeardownObserver( + const std::function<void(GlobalTeardownObserver*, bool* aDoneOut)>& 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<RefPtr<GlobalTeardownObserver>, 64> targetList; + for (const GlobalTeardownObserver* gto = mGlobalTeardownObservers.getFirst(); + gto; gto = gto->getNext()) { + targetList.AppendElement(const_cast<GlobalTeardownObserver*>(gto)); + } + + // 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::DisconnectGlobalTeardownObservers() { + ForEachGlobalTeardownObserver( + [&](GlobalTeardownObserver* aTarget, bool* aDoneOut) { + aTarget->DisconnectFromOwner(); + + // Calling DisconnectFromOwner() should result in + // RemoveGlobalTeardownObserver() being called. + MOZ_DIAGNOSTIC_ASSERT(aTarget->GetOwnerGlobal() != this); + }); +} + +Maybe<ClientInfo> 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<ClientInfo>(); +} + +Maybe<nsID> nsIGlobalObject::GetAgentClusterId() const { + Maybe<ClientInfo> ci = GetClientInfo(); + if (ci.isSome()) { + return ci.value().AgentClusterId(); + } + return mozilla::Nothing(); +} + +Maybe<ServiceWorkerDescriptor> 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<ServiceWorkerDescriptor>(); +} + +RefPtr<ServiceWorker> nsIGlobalObject::GetOrCreateServiceWorker( + const ServiceWorkerDescriptor& aDescriptor) { + MOZ_DIAGNOSTIC_ASSERT(false, + "this global should not have any service workers"); + return nullptr; +} + +RefPtr<ServiceWorkerRegistration> nsIGlobalObject::GetServiceWorkerRegistration( + const mozilla::dom::ServiceWorkerRegistrationDescriptor& aDescriptor) + const { + MOZ_DIAGNOSTIC_ASSERT(false, + "this global should not have any service workers"); + return nullptr; +} + +RefPtr<ServiceWorkerRegistration> +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::GetAsInnerWindow() { + if (MOZ_LIKELY(mIsInnerWindow)) { + return static_cast<nsPIDOMWindowInner*>( + static_cast<nsGlobalWindowInner*>(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<ErrorResult&>(rv)); + } + + bool Suppressed() final { return mGlobal->IsInSyncOperation(); } + + private: + nsCOMPtr<nsIGlobalObject> mGlobal; + RefPtr<VoidFunction> mCallback; +}; + +void nsIGlobalObject::QueueMicrotask(VoidFunction& aCallback) { + CycleCollectedJSContext* context = CycleCollectedJSContext::Get(); + if (context) { + RefPtr<MicroTaskRunnable> 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<mozilla::dom::Function> +nsIGlobalObject::GetCountQueuingStrategySizeFunction() { + return do_AddRef(mCountQueuingStrategySizeFunction); +} + +void nsIGlobalObject::SetCountQueuingStrategySizeFunction( + mozilla::dom::Function* aFunction) { + mCountQueuingStrategySizeFunction = aFunction; +} + +already_AddRefed<mozilla::dom::Function> +nsIGlobalObject::GetByteLengthQueuingStrategySizeFunction() { + return do_AddRef(mByteLengthQueuingStrategySizeFunction); +} + +void nsIGlobalObject::SetByteLengthQueuingStrategySizeFunction( + mozilla::dom::Function* aFunction) { + mByteLengthQueuingStrategySizeFunction = aFunction; +} + +mozilla::Result<mozilla::ipc::PrincipalInfo, nsresult> +nsIGlobalObject::GetStorageKey() { + return mozilla::Err(NS_ERROR_NOT_AVAILABLE); +} + +mozilla::Result<bool, nsresult> nsIGlobalObject::HasEqualStorageKey( + const mozilla::ipc::PrincipalInfo& aStorageKey) { + auto result = GetStorageKey(); + if (result.isErr()) { + return result.propagateErr(); + } + + const auto& storageKey = result.inspect(); + + return mozilla::ipc::StorageKeysEqual(storageKey, aStorageKey); +} + +mozilla::RTPCallerType nsIGlobalObject::GetRTPCallerType() const { + if (PrincipalOrNull() && PrincipalOrNull()->IsSystemPrincipal()) { + return RTPCallerType::SystemPrincipal; + } + + if (ShouldResistFingerprinting(RFPTarget::ReduceTimerPrecision)) { + return RTPCallerType::ResistFingerprinting; + } + + if (CrossOriginIsolated()) { + return RTPCallerType::CrossOriginIsolated; + } + + return RTPCallerType::Normal; +} + +bool nsIGlobalObject::ShouldResistFingerprinting(CallerType aCallerType, + RFPTarget aTarget) const { + return aCallerType != CallerType::System && + ShouldResistFingerprinting(aTarget); +} |