/* -*- 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/WindowContext.h" #include "mozilla/dom/WindowGlobalActorsBinding.h" #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/dom/WindowGlobalParent.h" #include "mozilla/dom/SyncedContextInlines.h" #include "mozilla/dom/BrowsingContext.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/UserActivationIPCUtils.h" #include "mozilla/PermissionDelegateIPCUtils.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/StaticPtr.h" #include "mozilla/ClearOnShutdown.h" #include "nsGlobalWindowInner.h" #include "nsIScriptError.h" #include "nsIXULRuntime.h" #include "nsRefPtrHashtable.h" #include "nsContentUtils.h" namespace mozilla { namespace dom { // Explicit specialization of the `Transaction` type. Required by the `extern // template class` declaration in the header. template class syncedcontext::Transaction; static LazyLogModule gWindowContextLog("WindowContext"); static LazyLogModule gWindowContextSyncLog("WindowContextSync"); extern mozilla::LazyLogModule gUserInteractionPRLog; #define USER_ACTIVATION_LOG(msg, ...) \ MOZ_LOG(gUserInteractionPRLog, LogLevel::Debug, (msg, ##__VA_ARGS__)) using WindowContextByIdMap = nsTHashMap; static StaticAutoPtr gWindowContexts; /* static */ LogModule* WindowContext::GetLog() { return gWindowContextLog; } /* static */ LogModule* WindowContext::GetSyncLog() { return gWindowContextSyncLog; } /* static */ already_AddRefed WindowContext::GetById( uint64_t aInnerWindowId) { if (!gWindowContexts) { return nullptr; } return do_AddRef(gWindowContexts->Get(aInnerWindowId)); } BrowsingContextGroup* WindowContext::Group() const { return mBrowsingContext->Group(); } WindowGlobalParent* WindowContext::Canonical() { MOZ_RELEASE_ASSERT(XRE_IsParentProcess()); return static_cast(this); } bool WindowContext::IsCurrent() const { return mBrowsingContext->mCurrentWindowContext == this; } bool WindowContext::IsInBFCache() { if (mozilla::SessionHistoryInParent()) { return mBrowsingContext->IsInBFCache(); } return TopWindowContext()->GetWindowStateSaved(); } nsGlobalWindowInner* WindowContext::GetInnerWindow() const { return mWindowGlobalChild ? mWindowGlobalChild->GetWindowGlobal() : nullptr; } Document* WindowContext::GetDocument() const { nsGlobalWindowInner* innerWindow = GetInnerWindow(); return innerWindow ? innerWindow->GetDocument() : nullptr; } Document* WindowContext::GetExtantDoc() const { nsGlobalWindowInner* innerWindow = GetInnerWindow(); return innerWindow ? innerWindow->GetExtantDoc() : nullptr; } WindowGlobalChild* WindowContext::GetWindowGlobalChild() const { return mWindowGlobalChild; } WindowContext* WindowContext::GetParentWindowContext() { return mBrowsingContext->GetParentWindowContext(); } WindowContext* WindowContext::TopWindowContext() { WindowContext* current = this; while (current->GetParentWindowContext()) { current = current->GetParentWindowContext(); } return current; } bool WindowContext::IsTop() const { return mBrowsingContext->IsTop(); } bool WindowContext::SameOriginWithTop() const { return mBrowsingContext->SameOriginWithTop(); } nsIGlobalObject* WindowContext::GetParentObject() const { return xpc::NativeGlobal(xpc::PrivilegedJunkScope()); } void WindowContext::AppendChildBrowsingContext( BrowsingContext* aBrowsingContext) { MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(), "Mismatched groups?"); MOZ_DIAGNOSTIC_ASSERT(!mChildren.Contains(aBrowsingContext)); mChildren.AppendElement(aBrowsingContext); if (!nsContentUtils::ShouldHideObjectOrEmbedImageDocument() || !aBrowsingContext->IsEmbedderTypeObjectOrEmbed()) { mNonSyntheticChildren.AppendElement(aBrowsingContext); } // If we're the current WindowContext in our BrowsingContext, make sure to // clear any cached `children` value. if (IsCurrent()) { BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext); } } void WindowContext::RemoveChildBrowsingContext( BrowsingContext* aBrowsingContext) { MOZ_DIAGNOSTIC_ASSERT(Group() == aBrowsingContext->Group(), "Mismatched groups?"); mChildren.RemoveElement(aBrowsingContext); mNonSyntheticChildren.RemoveElement(aBrowsingContext); // If we're the current WindowContext in our BrowsingContext, make sure to // clear any cached `children` value. if (IsCurrent()) { BrowsingContext_Binding::ClearCachedChildrenValue(mBrowsingContext); } } void WindowContext::UpdateChildSynthetic(BrowsingContext* aBrowsingContext, bool aIsSynthetic) { MOZ_ASSERT(nsContentUtils::ShouldHideObjectOrEmbedImageDocument()); if (aIsSynthetic) { mNonSyntheticChildren.RemoveElement(aBrowsingContext); } else { // The same BrowsingContext will be reused for error pages, so it can be in // the list already. if (!mNonSyntheticChildren.Contains(aBrowsingContext)) { mNonSyntheticChildren.AppendElement(aBrowsingContext); } } } void WindowContext::SendCommitTransaction(ContentParent* aParent, const BaseTransaction& aTxn, uint64_t aEpoch) { Unused << aParent->SendCommitWindowContextTransaction(this, aTxn, aEpoch); } void WindowContext::SendCommitTransaction(ContentChild* aChild, const BaseTransaction& aTxn, uint64_t aEpoch) { aChild->SendCommitWindowContextTransaction(this, aTxn, aEpoch); } bool WindowContext::CheckOnlyOwningProcessCanSet(ContentParent* aSource) { if (IsInProcess()) { return true; } if (XRE_IsParentProcess() && aSource) { return Canonical()->GetContentParent() == aSource; } return false; } bool WindowContext::CanSet(FieldIndex, const bool& aIsSecure, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const bool& aAllowMixedContent, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const bool& aHasBeforeUnload, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const Maybe& aValue, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const bool& aValue, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const bool& IsThirdPartyWindow, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const bool& aIsThirdPartyTrackingResourceWindow, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const bool& aIsSecureContext, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const bool& aIsOriginalFrameSource, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const bool& aValue, ContentParent* aSource) { return IsTop(); } bool WindowContext::CanSet(FieldIndex, const uint32_t& aValue, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const uint32_t& aValue, ContentParent* aSource) { return IsTop() && CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const Maybe& aValue, ContentParent* aSource) { return IsTop(); } bool WindowContext::CanSet(FieldIndex, const uint32_t&, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet( FieldIndex, const PermissionDelegateHandler::DelegatedPermissionList& aValue, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet( FieldIndex, const PermissionDelegateHandler::DelegatedPermissionList& aValue, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const bool& aValue, ContentParent* aSource) { return CheckOnlyOwningProcessCanSet(aSource); } bool WindowContext::CanSet(FieldIndex, const bool& aValue, ContentParent* aSource) { return IsTop(); } bool WindowContext::CanSet(FieldIndex, bool aValue, ContentParent* aSource) { return (XRE_IsParentProcess() && !aSource) || CheckOnlyOwningProcessCanSet(aSource); } void WindowContext::DidSet(FieldIndex, bool aOldValue) { RecomputeCanExecuteScripts(); } bool WindowContext::CanSet(FieldIndex, bool, ContentParent*) { return XRE_IsParentProcess() && IsTop(); } void WindowContext::RecomputeCanExecuteScripts(bool aApplyChanges) { const bool old = mCanExecuteScripts; if (!AllowJavascript()) { // Scripting has been explicitly disabled on our WindowContext. mCanExecuteScripts = false; } else { // Otherwise, inherit. mCanExecuteScripts = mBrowsingContext->CanExecuteScripts(); } if (aApplyChanges && old != mCanExecuteScripts) { // Inform our active DOM window. if (nsGlobalWindowInner* window = GetInnerWindow()) { // Only update scriptability if the window is current. Windows will have // scriptability disabled when entering the bfcache and updated when // coming out. if (window->IsCurrentInnerWindow()) { auto& scriptability = xpc::Scriptability::Get(window->GetGlobalJSObject()); scriptability.SetWindowAllowsScript(mCanExecuteScripts); } } for (const RefPtr& child : Children()) { child->RecomputeCanExecuteScripts(); } } } void WindowContext::DidSet(FieldIndex, bool aOldValue) { MOZ_ASSERT( TopWindowContext() == this, "SHEntryHasUserInteraction can only be set on the top window context"); // This field is set when the child notifies us of new user interaction, so we // also set the currently active shentry in the parent as having interaction. if (XRE_IsParentProcess() && mBrowsingContext) { SessionHistoryEntry* activeEntry = mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); if (activeEntry && GetSHEntryHasUserInteraction()) { activeEntry->SetHasUserInteraction(true); } } } void WindowContext::DidSet(FieldIndex) { MOZ_ASSERT_IF(!IsInProcess(), mUserGestureStart.IsNull()); USER_ACTIVATION_LOG("Set user gesture activation %" PRIu8 " for %s browsing context 0x%08" PRIx64, static_cast(GetUserActivationState()), XRE_IsParentProcess() ? "Parent" : "Child", Id()); if (IsInProcess()) { USER_ACTIVATION_LOG( "Set user gesture start time for %s browsing context 0x%08" PRIx64, XRE_IsParentProcess() ? "Parent" : "Child", Id()); if (GetUserActivationState() == UserActivation::State::FullActivated) { mUserGestureStart = TimeStamp::Now(); } else if (GetUserActivationState() == UserActivation::State::None) { mUserGestureStart = TimeStamp(); } } } void WindowContext::DidSet(FieldIndex, bool aOldValue) { if (!aOldValue && GetHasReportedShadowDOMUsage() && IsInProcess()) { MOZ_ASSERT(TopWindowContext() == this); if (mBrowsingContext) { Document* topLevelDoc = mBrowsingContext->GetDocument(); if (topLevelDoc) { nsAutoString uri; Unused << topLevelDoc->GetDocumentURI(uri); if (!uri.IsEmpty()) { nsAutoString msg = u"Shadow DOM used in ["_ns + uri + u"] or in some of its subdocuments."_ns; nsContentUtils::ReportToConsoleNonLocalized( msg, nsIScriptError::infoFlag, "DOM"_ns, topLevelDoc); } } } } } bool WindowContext::CanSet(FieldIndex, bool aValue, ContentParent* aSource) { return !mozilla::SessionHistoryInParent() && IsTop() && CheckOnlyOwningProcessCanSet(aSource); } void WindowContext::CreateFromIPC(IPCInitializer&& aInit) { MOZ_RELEASE_ASSERT(XRE_IsContentProcess(), "Should be a WindowGlobalParent in the parent"); RefPtr bc = BrowsingContext::Get(aInit.mBrowsingContextId); MOZ_RELEASE_ASSERT(bc); if (bc->IsDiscarded()) { // If we have already closed our browsing context, the // WindowGlobalChild actor is bound to be destroyed soon and it's // safe to ignore creating the WindowContext. return; } RefPtr context = new WindowContext( bc, aInit.mInnerWindowId, aInit.mOuterWindowId, std::move(aInit.mFields)); context->Init(); } void WindowContext::Init() { MOZ_LOG(GetLog(), LogLevel::Debug, ("Registering 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId, mBrowsingContext->Id())); // Register the WindowContext in the `WindowContextByIdMap`. if (!gWindowContexts) { gWindowContexts = new WindowContextByIdMap(); ClearOnShutdown(&gWindowContexts); } auto& entry = gWindowContexts->LookupOrInsert(mInnerWindowId); MOZ_RELEASE_ASSERT(!entry, "Duplicate WindowContext for ID!"); entry = this; // Register this to the browsing context. mBrowsingContext->RegisterWindowContext(this); Group()->Register(this); } void WindowContext::Discard() { MOZ_LOG(GetLog(), LogLevel::Debug, ("Discarding 0x%" PRIx64 " (bc=0x%" PRIx64 ")", mInnerWindowId, mBrowsingContext->Id())); if (mIsDiscarded) { return; } mIsDiscarded = true; if (gWindowContexts) { gWindowContexts->Remove(InnerWindowId()); } mBrowsingContext->UnregisterWindowContext(this); Group()->Unregister(this); } void WindowContext::AddSecurityState(uint32_t aStateFlags) { MOZ_ASSERT(TopWindowContext() == this); MOZ_ASSERT((aStateFlags & (nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT | nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADED | nsIWebProgressListener::STATE_HTTPS_ONLY_MODE_UPGRADE_FAILED)) == aStateFlags, "Invalid flags specified!"); if (XRE_IsParentProcess()) { Canonical()->AddSecurityState(aStateFlags); } else { ContentChild* child = ContentChild::GetSingleton(); child->SendAddSecurityState(this, aStateFlags); } } void WindowContext::NotifyUserGestureActivation() { Unused << SetUserActivationState(UserActivation::State::FullActivated); } void WindowContext::NotifyResetUserGestureActivation() { Unused << SetUserActivationState(UserActivation::State::None); } bool WindowContext::HasBeenUserGestureActivated() { return GetUserActivationState() != UserActivation::State::None; } const TimeStamp& WindowContext::GetUserGestureStart() const { MOZ_ASSERT(IsInProcess()); return mUserGestureStart; } bool WindowContext::HasValidTransientUserGestureActivation() { MOZ_ASSERT(IsInProcess()); if (GetUserActivationState() != UserActivation::State::FullActivated) { // mUserGestureStart should be null if the document hasn't ever been // activated by user gesture MOZ_ASSERT_IF(GetUserActivationState() == UserActivation::State::None, mUserGestureStart.IsNull()); return false; } MOZ_ASSERT(!mUserGestureStart.IsNull(), "mUserGestureStart shouldn't be null if the document has ever " "been activated by user gesture"); TimeDuration timeout = TimeDuration::FromMilliseconds( StaticPrefs::dom_user_activation_transient_timeout()); return timeout <= TimeDuration() || (TimeStamp::Now() - mUserGestureStart) <= timeout; } bool WindowContext::ConsumeTransientUserGestureActivation() { MOZ_ASSERT(IsInProcess()); MOZ_ASSERT(IsCurrent()); if (!HasValidTransientUserGestureActivation()) { return false; } BrowsingContext* top = mBrowsingContext->Top(); top->PreOrderWalk([&](BrowsingContext* aBrowsingContext) { WindowContext* windowContext = aBrowsingContext->GetCurrentWindowContext(); if (windowContext && windowContext->GetUserActivationState() == UserActivation::State::FullActivated) { Unused << windowContext->SetUserActivationState( UserActivation::State::HasBeenActivated); } }); return true; } bool WindowContext::CanShowPopup() { uint32_t permit = GetPopupPermission(); if (permit == nsIPermissionManager::ALLOW_ACTION) { return true; } if (permit == nsIPermissionManager::DENY_ACTION) { return false; } return !StaticPrefs::dom_disable_open_during_load(); } WindowContext::IPCInitializer WindowContext::GetIPCInitializer() { IPCInitializer init; init.mInnerWindowId = mInnerWindowId; init.mOuterWindowId = mOuterWindowId; init.mBrowsingContextId = mBrowsingContext->Id(); init.mFields = mFields.RawValues(); return init; } WindowContext::WindowContext(BrowsingContext* aBrowsingContext, uint64_t aInnerWindowId, uint64_t aOuterWindowId, FieldValues&& aInit) : mFields(std::move(aInit)), mInnerWindowId(aInnerWindowId), mOuterWindowId(aOuterWindowId), mBrowsingContext(aBrowsingContext) { MOZ_ASSERT(mBrowsingContext); MOZ_ASSERT(mInnerWindowId); MOZ_ASSERT(mOuterWindowId); RecomputeCanExecuteScripts(/* aApplyChanges */ false); } WindowContext::~WindowContext() { if (gWindowContexts) { gWindowContexts->Remove(InnerWindowId()); } } JSObject* WindowContext::WrapObject(JSContext* cx, JS::Handle aGivenProto) { return WindowContext_Binding::Wrap(cx, this, aGivenProto); } NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowContext) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(WindowContext) NS_IMPL_CYCLE_COLLECTING_RELEASE(WindowContext) NS_IMPL_CYCLE_COLLECTION_CLASS(WindowContext) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WindowContext) if (gWindowContexts) { gWindowContexts->Remove(tmp->InnerWindowId()); } NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext) NS_IMPL_CYCLE_COLLECTION_UNLINK(mChildren) NS_IMPL_CYCLE_COLLECTION_UNLINK(mNonSyntheticChildren) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WindowContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildren) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNonSyntheticChildren) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(WindowContext) } // namespace dom namespace ipc { void IPDLParamTraits>::Write( IPC::MessageWriter* aWriter, IProtocol* aActor, const dom::MaybeDiscarded& aParam) { uint64_t id = aParam.ContextId(); WriteIPDLParam(aWriter, aActor, id); } bool IPDLParamTraits>::Read( IPC::MessageReader* aReader, IProtocol* aActor, dom::MaybeDiscarded* aResult) { uint64_t id = 0; if (!ReadIPDLParam(aReader, aActor, &id)) { return false; } if (id == 0) { *aResult = nullptr; } else if (RefPtr wc = dom::WindowContext::GetById(id)) { *aResult = std::move(wc); } else { aResult->SetDiscarded(id); } return true; } void IPDLParamTraits::Write( IPC::MessageWriter* aWriter, IProtocol* aActor, const dom::WindowContext::IPCInitializer& aInit) { // Write actor ID parameters. WriteIPDLParam(aWriter, aActor, aInit.mInnerWindowId); WriteIPDLParam(aWriter, aActor, aInit.mOuterWindowId); WriteIPDLParam(aWriter, aActor, aInit.mBrowsingContextId); WriteIPDLParam(aWriter, aActor, aInit.mFields); } bool IPDLParamTraits::Read( IPC::MessageReader* aReader, IProtocol* aActor, dom::WindowContext::IPCInitializer* aInit) { // Read actor ID parameters. return ReadIPDLParam(aReader, aActor, &aInit->mInnerWindowId) && ReadIPDLParam(aReader, aActor, &aInit->mOuterWindowId) && ReadIPDLParam(aReader, aActor, &aInit->mBrowsingContextId) && ReadIPDLParam(aReader, aActor, &aInit->mFields); } template struct IPDLParamTraits; } // namespace ipc } // namespace mozilla