diff options
Diffstat (limited to 'dom/ipc/jsactor/JSWindowActorProtocol.cpp')
-rw-r--r-- | dom/ipc/jsactor/JSWindowActorProtocol.cpp | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/dom/ipc/jsactor/JSWindowActorProtocol.cpp b/dom/ipc/jsactor/JSWindowActorProtocol.cpp new file mode 100644 index 0000000000..a459d94204 --- /dev/null +++ b/dom/ipc/jsactor/JSWindowActorProtocol.cpp @@ -0,0 +1,380 @@ +/* -*- 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/ContentParent.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/JSActorBinding.h" +#include "mozilla/dom/JSActorService.h" +#include "mozilla/dom/JSWindowActorBinding.h" +#include "mozilla/dom/JSWindowActorChild.h" +#include "mozilla/dom/JSWindowActorProtocol.h" +#include "mozilla/dom/PContent.h" +#include "mozilla/dom/WindowGlobalChild.h" + +#include "mozilla/extensions/MatchPattern.h" +#include "nsContentUtils.h" +#include "JSActorProtocolUtils.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTING_ADDREF(JSWindowActorProtocol) +NS_IMPL_CYCLE_COLLECTING_RELEASE(JSWindowActorProtocol) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorProtocol) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION(JSWindowActorProtocol) + +/* static */ already_AddRefed<JSWindowActorProtocol> +JSWindowActorProtocol::FromIPC(const JSWindowActorInfo& aInfo) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess()); + + RefPtr<JSWindowActorProtocol> proto = new JSWindowActorProtocol(aInfo.name()); + JSActorProtocolUtils::FromIPCShared(proto, aInfo); + + // Content processes cannot load chrome browsing contexts, so this flag is + // irrelevant and not propagated. + proto->mIncludeChrome = false; + proto->mAllFrames = aInfo.allFrames(); + proto->mMatches = aInfo.matches().Clone(); + proto->mMessageManagerGroups = aInfo.messageManagerGroups().Clone(); + + proto->mChild.mEvents.SetCapacity(aInfo.events().Length()); + for (auto& ipc : aInfo.events()) { + auto event = proto->mChild.mEvents.AppendElement(); + event->mName.Assign(ipc.name()); + event->mFlags.mCapture = ipc.capture(); + event->mFlags.mInSystemGroup = ipc.systemGroup(); + event->mFlags.mAllowUntrustedEvents = ipc.allowUntrusted(); + if (ipc.passive()) { + event->mPassive.Construct(ipc.passive().value()); + } + event->mCreateActor = ipc.createActor(); + } + + return proto.forget(); +} + +JSWindowActorInfo JSWindowActorProtocol::ToIPC() { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + JSWindowActorInfo info; + JSActorProtocolUtils::ToIPCShared(info, this); + + info.allFrames() = mAllFrames; + info.matches() = mMatches.Clone(); + info.messageManagerGroups() = mMessageManagerGroups.Clone(); + + info.events().SetCapacity(mChild.mEvents.Length()); + for (auto& event : mChild.mEvents) { + auto ipc = info.events().AppendElement(); + ipc->name().Assign(event.mName); + ipc->capture() = event.mFlags.mCapture; + ipc->systemGroup() = event.mFlags.mInSystemGroup; + ipc->allowUntrusted() = event.mFlags.mAllowUntrustedEvents; + if (event.mPassive.WasPassed()) { + ipc->passive() = Some(event.mPassive.Value()); + } + ipc->createActor() = event.mCreateActor; + } + + return info; +} + +already_AddRefed<JSWindowActorProtocol> +JSWindowActorProtocol::FromWebIDLOptions(const nsACString& aName, + const WindowActorOptions& aOptions, + ErrorResult& aRv) { + MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); + + RefPtr<JSWindowActorProtocol> proto = new JSWindowActorProtocol(aName); + if (!JSActorProtocolUtils::FromWebIDLOptionsShared(proto, aOptions, aRv)) { + return nullptr; + } + + proto->mAllFrames = aOptions.mAllFrames; + proto->mIncludeChrome = aOptions.mIncludeChrome; + + if (aOptions.mMatches.WasPassed()) { + MOZ_ASSERT(aOptions.mMatches.Value().Length()); + proto->mMatches = aOptions.mMatches.Value(); + } + + if (aOptions.mMessageManagerGroups.WasPassed()) { + proto->mMessageManagerGroups = aOptions.mMessageManagerGroups.Value(); + } + + // For each event declared in the source dictionary, initialize the + // corresponding event declaration entry in the JSWindowActorProtocol. + if (aOptions.mChild.WasPassed() && + aOptions.mChild.Value().mEvents.WasPassed()) { + auto& entries = aOptions.mChild.Value().mEvents.Value().Entries(); + proto->mChild.mEvents.SetCapacity(entries.Length()); + + for (auto& entry : entries) { + // We don't support the mOnce field, as it doesn't work well in this + // environment. For now, throw an error in that case. + if (entry.mValue.mOnce) { + aRv.ThrowNotSupportedError("mOnce is not supported"); + return nullptr; + } + + // Add the EventDecl to our list of events. + EventDecl* evt = proto->mChild.mEvents.AppendElement(); + evt->mName = entry.mKey; + evt->mFlags.mCapture = entry.mValue.mCapture; + evt->mFlags.mInSystemGroup = entry.mValue.mMozSystemGroup; + evt->mFlags.mAllowUntrustedEvents = + entry.mValue.mWantUntrusted.WasPassed() + ? entry.mValue.mWantUntrusted.Value() + : false; + if (entry.mValue.mPassive.WasPassed()) { + evt->mPassive.Construct(entry.mValue.mPassive.Value()); + } + evt->mCreateActor = entry.mValue.mCreateActor; + } + } + + return proto.forget(); +} + +/** + * This listener only listens for events for the child side of the protocol. + * This will work in both content and parent processes. + */ +NS_IMETHODIMP JSWindowActorProtocol::HandleEvent(Event* aEvent) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + // Determine which inner window we're associated with, and get its + // WindowGlobalChild actor. + EventTarget* target = aEvent->GetOriginalTarget(); + if (NS_WARN_IF(!target)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsPIDOMWindowInner> inner = + do_QueryInterface(target->GetOwnerGlobal()); + if (NS_WARN_IF(!inner)) { + return NS_ERROR_FAILURE; + } + + RefPtr<WindowGlobalChild> wgc = inner->GetWindowGlobalChild(); + if (NS_WARN_IF(!wgc)) { + return NS_ERROR_FAILURE; + } + + if (aEvent->ShouldIgnoreChromeEventTargetListener()) { + return NS_OK; + } + + // Ensure our actor is present. + RefPtr<JSActor> actor = wgc->GetExistingActor(mName); + if (!actor) { + // Check if we're supposed to create the actor when this event is fired. + bool createActor = true; + nsAutoString typeStr; + aEvent->GetType(typeStr); + for (auto& event : mChild.mEvents) { + if (event.mName == typeStr) { + createActor = event.mCreateActor; + break; + } + } + + // If we're supposed to create the actor, call GetActor to cause it to be + // created. + if (createActor) { + AutoJSAPI jsapi; + jsapi.Init(); + actor = wgc->GetActor(jsapi.cx(), mName, IgnoreErrors()); + } + } + if (!actor || NS_WARN_IF(!actor->GetWrapperPreserveColor())) { + return NS_OK; + } + + // Build our event listener & call it. + JS::Rooted<JSObject*> global(RootingCx(), + JS::GetNonCCWObjectGlobal(actor->GetWrapper())); + RefPtr<EventListener> eventListener = + new EventListener(actor->GetWrapper(), global, nullptr, nullptr); + eventListener->HandleEvent(*aEvent, "JSWindowActorProtocol::HandleEvent"); + return NS_OK; +} + +NS_IMETHODIMP JSWindowActorProtocol::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); + + nsCOMPtr<nsPIDOMWindowInner> inner = do_QueryInterface(aSubject); + RefPtr<WindowGlobalChild> wgc; + + if (!inner) { + nsCOMPtr<nsPIDOMWindowOuter> outer = do_QueryInterface(aSubject); + if (NS_WARN_IF(!outer)) { + nsContentUtils::LogSimpleConsoleError( + NS_ConvertUTF8toUTF16(nsPrintfCString( + "JSWindowActor %s: expected window subject for topic '%s'.", + mName.get(), aTopic)), + "JSActor"_ns, + /* aFromPrivateWindow */ false, + /* aFromChromeContext */ true); + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(!outer->GetCurrentInnerWindow())) { + return NS_ERROR_FAILURE; + } + wgc = outer->GetCurrentInnerWindow()->GetWindowGlobalChild(); + } else { + wgc = inner->GetWindowGlobalChild(); + } + + if (NS_WARN_IF(!wgc)) { + return NS_ERROR_FAILURE; + } + + // Ensure our actor is present. + AutoJSAPI jsapi; + jsapi.Init(); + RefPtr<JSActor> actor = wgc->GetActor(jsapi.cx(), mName, IgnoreErrors()); + if (!actor || NS_WARN_IF(!actor->GetWrapperPreserveColor())) { + return NS_OK; + } + + // Build a observer callback. + JS::Rooted<JSObject*> global(jsapi.cx(), + JS::GetNonCCWObjectGlobal(actor->GetWrapper())); + RefPtr<MozObserverCallback> observerCallback = + new MozObserverCallback(actor->GetWrapper(), global, nullptr, nullptr); + observerCallback->Observe(aSubject, nsDependentCString(aTopic), + aData ? nsDependentString(aData) : VoidString()); + return NS_OK; +} + +void JSWindowActorProtocol::RegisterListenersFor(EventTarget* aTarget) { + EventListenerManager* elm = aTarget->GetOrCreateListenerManager(); + + for (auto& event : mChild.mEvents) { + elm->AddEventListenerByType(EventListenerHolder(this), event.mName, + event.mFlags, event.mPassive); + } +} + +void JSWindowActorProtocol::UnregisterListenersFor(EventTarget* aTarget) { + EventListenerManager* elm = aTarget->GetOrCreateListenerManager(); + + for (auto& event : mChild.mEvents) { + elm->RemoveEventListenerByType(EventListenerHolder(this), event.mName, + event.mFlags); + } +} + +void JSWindowActorProtocol::AddObservers() { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + for (auto& topic : mChild.mObservers) { + // This makes the observer service hold an owning reference to the + // JSWindowActorProtocol. The JSWindowActorProtocol objects will be living + // for the full lifetime of the content process, thus the extra strong + // referencec doesn't have a negative impact. + os->AddObserver(this, topic.get(), false); + } +} + +void JSWindowActorProtocol::RemoveObservers() { + nsCOMPtr<nsIObserverService> os = services::GetObserverService(); + for (auto& topic : mChild.mObservers) { + os->RemoveObserver(this, topic.get()); + } +} + +extensions::MatchPatternSetCore* JSWindowActorProtocol::GetURIMatcher() { + // If we've already created the pattern set, return it. + if (mURIMatcher || mMatches.IsEmpty()) { + return mURIMatcher; + } + + nsTArray<RefPtr<extensions::MatchPatternCore>> patterns(mMatches.Length()); + for (const nsString& pattern : mMatches) { + patterns.AppendElement(new extensions::MatchPatternCore( + pattern, false, false, IgnoreErrors())); + } + mURIMatcher = new extensions::MatchPatternSetCore(std::move(patterns)); + return mURIMatcher; +} + +bool JSWindowActorProtocol::RemoteTypePrefixMatches( + const nsDependentCSubstring& aRemoteType) { + for (auto& remoteType : mRemoteTypes) { + if (StringBeginsWith(aRemoteType, remoteType)) { + return true; + } + } + return false; +} + +bool JSWindowActorProtocol::MessageManagerGroupMatches( + BrowsingContext* aBrowsingContext) { + BrowsingContext* top = aBrowsingContext->Top(); + for (auto& group : mMessageManagerGroups) { + if (group == top->GetMessageManagerGroup()) { + return true; + } + } + return false; +} + +bool JSWindowActorProtocol::Matches(BrowsingContext* aBrowsingContext, + nsIURI* aURI, const nsACString& aRemoteType, + ErrorResult& aRv) { + MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!"); + MOZ_ASSERT(aURI, "Must have URI!"); + + if (!mAllFrames && aBrowsingContext->GetParent()) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Window protocol '%s' doesn't match subframes", mName.get())); + return false; + } + + if (!mIncludeChrome && !aBrowsingContext->IsContent()) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Window protocol '%s' doesn't match chrome browsing contexts", + mName.get())); + return false; + } + + if (!mRemoteTypes.IsEmpty() && + !RemoteTypePrefixMatches(RemoteTypePrefix(aRemoteType))) { + aRv.ThrowNotSupportedError( + nsPrintfCString("Window protocol '%s' doesn't match remote type '%s'", + mName.get(), PromiseFlatCString(aRemoteType).get())); + return false; + } + + if (!mMessageManagerGroups.IsEmpty() && + !MessageManagerGroupMatches(aBrowsingContext)) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Window protocol '%s' doesn't match message manager group", + mName.get())); + return false; + } + + if (extensions::MatchPatternSetCore* uriMatcher = GetURIMatcher()) { + if (!uriMatcher->Matches(aURI)) { + aRv.ThrowNotSupportedError(nsPrintfCString( + "Window protocol '%s' doesn't match uri %s", mName.get(), + nsContentUtils::TruncatedURLForDisplay(aURI).get())); + return false; + } + } + + return true; +} + +} // namespace mozilla::dom |