summaryrefslogtreecommitdiffstats
path: root/dom/ipc/jsactor
diff options
context:
space:
mode:
Diffstat (limited to 'dom/ipc/jsactor')
-rw-r--r--dom/ipc/jsactor/JSActor.cpp503
-rw-r--r--dom/ipc/jsactor/JSActor.h173
-rw-r--r--dom/ipc/jsactor/JSActorManager.cpp250
-rw-r--r--dom/ipc/jsactor/JSActorManager.h100
-rw-r--r--dom/ipc/jsactor/JSActorProtocolUtils.h117
-rw-r--r--dom/ipc/jsactor/JSActorService.cpp322
-rw-r--r--dom/ipc/jsactor/JSActorService.h112
-rw-r--r--dom/ipc/jsactor/JSProcessActorChild.cpp86
-rw-r--r--dom/ipc/jsactor/JSProcessActorChild.h54
-rw-r--r--dom/ipc/jsactor/JSProcessActorParent.cpp90
-rw-r--r--dom/ipc/jsactor/JSProcessActorParent.h63
-rw-r--r--dom/ipc/jsactor/JSProcessActorProtocol.cpp147
-rw-r--r--dom/ipc/jsactor/JSProcessActorProtocol.h79
-rw-r--r--dom/ipc/jsactor/JSWindowActorChild.cpp160
-rw-r--r--dom/ipc/jsactor/JSWindowActorChild.h82
-rw-r--r--dom/ipc/jsactor/JSWindowActorParent.cpp102
-rw-r--r--dom/ipc/jsactor/JSWindowActorParent.h66
-rw-r--r--dom/ipc/jsactor/JSWindowActorProtocol.cpp380
-rw-r--r--dom/ipc/jsactor/JSWindowActorProtocol.h103
-rw-r--r--dom/ipc/jsactor/moz.build42
-rw-r--r--dom/ipc/jsactor/nsQueryActor.h90
21 files changed, 3121 insertions, 0 deletions
diff --git a/dom/ipc/jsactor/JSActor.cpp b/dom/ipc/jsactor/JSActor.cpp
new file mode 100644
index 0000000000..8ac0442a35
--- /dev/null
+++ b/dom/ipc/jsactor/JSActor.cpp
@@ -0,0 +1,503 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/JSActor.h"
+#include "mozilla/dom/JSActorBinding.h"
+
+#include "chrome/common/ipc_channel.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FunctionRef.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/ClonedErrorHolder.h"
+#include "mozilla/dom/ClonedErrorHolderBinding.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
+#include "mozilla/dom/JSActorManager.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+#include "mozilla/dom/PWindowGlobal.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ipc/StructuredCloneData.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "js/Promise.h"
+#include "xpcprivate.h"
+#include "nsFrameMessageManager.h"
+#include "nsICrashReporter.h"
+
+namespace mozilla::dom {
+
+struct JSActorMessageMarker {
+ static constexpr Span<const char> MarkerTypeName() {
+ return MakeStringSpan("JSActorMessage");
+ }
+ static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter,
+ const ProfilerString8View& aActorName,
+ const ProfilerString16View& aMessageName) {
+ aWriter.StringProperty("actor", aActorName);
+ aWriter.StringProperty("name", NS_ConvertUTF16toUTF8(aMessageName));
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable};
+ schema.AddKeyLabelFormatSearchable(
+ "actor", "Actor Name", MS::Format::String, MS::Searchable::Searchable);
+ schema.AddKeyLabelFormatSearchable(
+ "name", "Message Name", MS::Format::String, MS::Searchable::Searchable);
+ schema.SetTooltipLabel("JSActor - {marker.name}");
+ schema.SetTableLabel(
+ "{marker.name} - [{marker.data.actor}] {marker.data.name}");
+ return schema;
+ }
+};
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSActor)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(JSActor)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(JSActor)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(JSActor)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSActor)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWrappedJS)
+ tmp->mPendingQueries.Clear();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSActor)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWrappedJS)
+ for (const auto& query : tmp->mPendingQueries.Values()) {
+ CycleCollectionNoteChild(cb, query.mPromise.get(), "Pending Query Promise");
+ }
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+JSActor::JSActor(nsISupports* aGlobal) {
+ mGlobal = do_QueryInterface(aGlobal);
+ if (!mGlobal) {
+ mGlobal = xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+ }
+}
+
+void JSActor::StartDestroy() { mCanSend = false; }
+
+void JSActor::AfterDestroy() {
+ mCanSend = false;
+
+ // Take our queries out, in case somehow rejecting promises can trigger
+ // additions or removals.
+ const nsTHashMap<nsUint64HashKey, PendingQuery> pendingQueries =
+ std::move(mPendingQueries);
+ for (const auto& entry : pendingQueries.Values()) {
+ nsPrintfCString message(
+ "Actor '%s' destroyed before query '%s' was resolved", mName.get(),
+ NS_LossyConvertUTF16toASCII(entry.mMessageName).get());
+ entry.mPromise->MaybeRejectWithAbortError(message);
+ }
+
+ InvokeCallback(CallbackFunction::DidDestroy);
+ ClearManager();
+}
+
+void JSActor::InvokeCallback(CallbackFunction callback) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ AutoEntryScript aes(GetParentObject(), "JSActor destroy callback");
+ JSContext* cx = aes.cx();
+ MozJSActorCallbacks callbacksHolder;
+ JS::Rooted<JS::Value> val(cx, JS::ObjectOrNullValue(GetWrapper()));
+ if (NS_WARN_IF(!callbacksHolder.Init(cx, val))) {
+ return;
+ }
+
+ // Destroy callback is optional.
+ if (callback == CallbackFunction::DidDestroy) {
+ if (callbacksHolder.mDidDestroy.WasPassed()) {
+ callbacksHolder.mDidDestroy.Value()->Call(this);
+ }
+ } else {
+ if (callbacksHolder.mActorCreated.WasPassed()) {
+ callbacksHolder.mActorCreated.Value()->Call(this);
+ }
+ }
+}
+
+nsresult JSActor::QueryInterfaceActor(const nsIID& aIID, void** aPtr) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+ if (!GetWrapperPreserveColor()) {
+ // If we have no preserved wrapper, we won't implement any interfaces.
+ return NS_NOINTERFACE;
+ }
+
+ if (!mWrappedJS) {
+ AutoEntryScript aes(GetParentObject(), "JSActor query interface");
+ JSContext* cx = aes.cx();
+
+ JS::Rooted<JSObject*> self(cx, GetWrapper());
+ JSAutoRealm ar(cx, self);
+
+ RefPtr<nsXPCWrappedJS> wrappedJS;
+ nsresult rv = nsXPCWrappedJS::GetNewOrUsed(
+ cx, self, NS_GET_IID(nsISupports), getter_AddRefs(wrappedJS));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mWrappedJS = do_QueryInterface(wrappedJS);
+ MOZ_ASSERT(mWrappedJS);
+ }
+
+ return mWrappedJS->QueryInterface(aIID, aPtr);
+}
+
+void JSActor::SetName(const nsACString& aName) {
+ MOZ_ASSERT(mName.IsEmpty(), "Cannot set name twice!");
+ mName = aName;
+}
+
+void JSActor::ThrowStateErrorForGetter(const char* aName,
+ ErrorResult& aRv) const {
+ if (mName.IsEmpty()) {
+ aRv.ThrowInvalidStateError(nsPrintfCString(
+ "Cannot access property '%s' before actor is initialized", aName));
+ } else {
+ aRv.ThrowInvalidStateError(nsPrintfCString(
+ "Cannot access property '%s' after actor '%s' has been destroyed",
+ aName, mName.get()));
+ }
+}
+
+static Maybe<ipc::StructuredCloneData> TryClone(JSContext* aCx,
+ JS::Handle<JS::Value> aValue) {
+ Maybe<ipc::StructuredCloneData> data{std::in_place};
+
+ // Try to directly serialize the passed-in data, and return it to our caller.
+ IgnoredErrorResult rv;
+ data->Write(aCx, aValue, rv);
+ if (rv.Failed()) {
+ // Serialization failed, return `Nothing()` instead.
+ JS_ClearPendingException(aCx);
+ data.reset();
+ }
+ return data;
+}
+
+static Maybe<ipc::StructuredCloneData> CloneJSStack(
+ JSContext* aCx, JS::Handle<JSObject*> aStack) {
+ JS::Rooted<JS::Value> stackVal(aCx, JS::ObjectOrNullValue(aStack));
+ return TryClone(aCx, stackVal);
+}
+
+static Maybe<ipc::StructuredCloneData> CaptureJSStack(JSContext* aCx) {
+ JS::Rooted<JSObject*> stack(aCx, nullptr);
+ if (JS::IsAsyncStackCaptureEnabledForRealm(aCx) &&
+ !JS::CaptureCurrentStack(aCx, &stack)) {
+ JS_ClearPendingException(aCx);
+ }
+
+ return CloneJSStack(aCx, stack);
+}
+
+void JSActor::SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj, ErrorResult& aRv) {
+ profiler_add_marker("SendAsyncMessage", geckoprofiler::category::IPC, {},
+ JSActorMessageMarker{}, mName, aMessageName);
+ Maybe<ipc::StructuredCloneData> data{std::in_place};
+ if (!nsFrameMessageManager::GetParamsForMessage(
+ aCx, aObj, JS::UndefinedHandleValue, *data)) {
+ aRv.ThrowDataCloneError(nsPrintfCString(
+ "Failed to serialize message '%s::%s'",
+ NS_LossyConvertUTF16toASCII(aMessageName).get(), mName.get()));
+ return;
+ }
+
+ JSActorMessageMeta meta;
+ meta.actorName() = mName;
+ meta.messageName() = aMessageName;
+ meta.kind() = JSActorMessageKind::Message;
+
+ SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv);
+}
+
+already_AddRefed<Promise> JSActor::SendQuery(JSContext* aCx,
+ const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj,
+ ErrorResult& aRv) {
+ profiler_add_marker("SendQuery", geckoprofiler::category::IPC, {},
+ JSActorMessageMarker{}, mName, aMessageName);
+ Maybe<ipc::StructuredCloneData> data{std::in_place};
+ if (!nsFrameMessageManager::GetParamsForMessage(
+ aCx, aObj, JS::UndefinedHandleValue, *data)) {
+ aRv.ThrowDataCloneError(nsPrintfCString(
+ "Failed to serialize message '%s::%s'",
+ NS_LossyConvertUTF16toASCII(aMessageName).get(), mName.get()));
+ return nullptr;
+ }
+
+ nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!global)) {
+ aRv.ThrowUnknownError("Unable to get current native global");
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ JSActorMessageMeta meta;
+ meta.actorName() = mName;
+ meta.messageName() = aMessageName;
+ meta.queryId() = mNextQueryId++;
+ meta.kind() = JSActorMessageKind::Query;
+
+ mPendingQueries.InsertOrUpdate(meta.queryId(),
+ PendingQuery{promise, meta.messageName()});
+
+ SendRawMessage(meta, std::move(data), CaptureJSStack(aCx), aRv);
+ return promise.forget();
+}
+
+void JSActor::CallReceiveMessage(JSContext* aCx,
+ const JSActorMessageMeta& aMetadata,
+ JS::Handle<JS::Value> aData,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv) {
+ // The argument which we want to pass to IPC.
+ RootedDictionary<ReceiveMessageArgument> argument(aCx);
+ argument.mTarget = this;
+ argument.mName = aMetadata.messageName();
+ argument.mData = aData;
+ argument.mJson = aData;
+ argument.mSync = false;
+
+ if (GetWrapperPreserveColor()) {
+ // Invoke the actual callback.
+ JS::Rooted<JSObject*> global(aCx, JS::GetNonCCWObjectGlobal(GetWrapper()));
+ RefPtr<MessageListener> messageListener =
+ new MessageListener(GetWrapper(), global, nullptr, nullptr);
+ messageListener->ReceiveMessage(argument, aRetVal, aRv,
+ "JSActor receive message",
+ MessageListener::eRethrowExceptions);
+ } else {
+ aRv.ThrowTypeError<MSG_NOT_CALLABLE>("Property 'receiveMessage'");
+ }
+}
+
+void JSActor::ReceiveMessage(JSContext* aCx,
+ const JSActorMessageMeta& aMetadata,
+ JS::Handle<JS::Value> aData, ErrorResult& aRv) {
+ MOZ_ASSERT(aMetadata.kind() == JSActorMessageKind::Message);
+ profiler_add_marker("ReceiveMessage", geckoprofiler::category::IPC, {},
+ JSActorMessageMarker{}, mName, aMetadata.messageName());
+
+ JS::Rooted<JS::Value> retval(aCx);
+ CallReceiveMessage(aCx, aMetadata, aData, &retval, aRv);
+}
+
+void JSActor::ReceiveQuery(JSContext* aCx, const JSActorMessageMeta& aMetadata,
+ JS::Handle<JS::Value> aData, ErrorResult& aRv) {
+ MOZ_ASSERT(aMetadata.kind() == JSActorMessageKind::Query);
+ profiler_add_marker("ReceiveQuery", geckoprofiler::category::IPC, {},
+ JSActorMessageMarker{}, mName, aMetadata.messageName());
+
+ // This promise will be resolved or rejected once the listener has been
+ // called. Our listener on this promise will then send the reply.
+ RefPtr<Promise> promise = Promise::Create(GetParentObject(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ RefPtr<QueryHandler> handler = new QueryHandler(this, aMetadata, promise);
+ promise->AppendNativeHandler(handler);
+
+ ErrorResult error;
+ JS::Rooted<JS::Value> retval(aCx);
+ CallReceiveMessage(aCx, aMetadata, aData, &retval, error);
+
+ // If we have a promise, resolve or reject it respectively.
+ if (error.Failed()) {
+ if (error.IsUncatchableException()) {
+ promise->MaybeRejectWithTimeoutError(
+ "Message handler threw uncatchable exception");
+ } else {
+ promise->MaybeReject(std::move(error));
+ }
+ } else {
+ promise->MaybeResolve(retval);
+ }
+ error.SuppressException();
+}
+
+void JSActor::ReceiveQueryReply(JSContext* aCx,
+ const JSActorMessageMeta& aMetadata,
+ JS::Handle<JS::Value> aData, ErrorResult& aRv) {
+ if (NS_WARN_IF(aMetadata.actorName() != mName)) {
+ aRv.ThrowUnknownError("Mismatched actor name for query reply");
+ return;
+ }
+
+ Maybe<PendingQuery> query = mPendingQueries.Extract(aMetadata.queryId());
+ if (NS_WARN_IF(!query)) {
+ aRv.ThrowUnknownError("Received reply for non-pending query");
+ return;
+ }
+
+ profiler_add_marker("ReceiveQueryReply", geckoprofiler::category::IPC, {},
+ JSActorMessageMarker{}, mName, aMetadata.messageName());
+
+ Promise* promise = query->mPromise;
+ JSAutoRealm ar(aCx, promise->PromiseObj());
+ JS::RootedValue data(aCx, aData);
+ if (NS_WARN_IF(!JS_WrapValue(aCx, &data))) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ if (aMetadata.kind() == JSActorMessageKind::QueryResolve) {
+ promise->MaybeResolve(data);
+ } else {
+ promise->MaybeReject(data);
+ }
+}
+
+void JSActor::SendRawMessageInProcess(const JSActorMessageMeta& aMeta,
+ Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack,
+ OtherSideCallback&& aGetOtherSide) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "JSActor Async Message",
+ [aMeta, data{std::move(aData)}, stack{std::move(aStack)},
+ getOtherSide{std::move(aGetOtherSide)}]() mutable {
+ if (RefPtr<JSActorManager> otherSide = getOtherSide()) {
+ otherSide->ReceiveRawMessage(aMeta, std::move(data),
+ std::move(stack));
+ }
+ }));
+}
+
+// Native handler for our generated promise which is used to handle Queries and
+// send the reply when their promises have been resolved.
+JSActor::QueryHandler::QueryHandler(JSActor* aActor,
+ const JSActorMessageMeta& aMetadata,
+ Promise* aPromise)
+ : mActor(aActor),
+ mPromise(aPromise),
+ mMessageName(aMetadata.messageName()),
+ mQueryId(aMetadata.queryId()) {}
+
+void JSActor::QueryHandler::RejectedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ if (!mActor) {
+ // Make sure that this rejection is reported. See comment below.
+ if (!JS::CallOriginalPromiseReject(aCx, aValue)) {
+ JS_ClearPendingException(aCx);
+ }
+ return;
+ }
+
+ JS::Rooted<JS::Value> value(aCx, aValue);
+ if (value.isObject()) {
+ JS::Rooted<JSObject*> error(aCx, &value.toObject());
+ if (RefPtr<ClonedErrorHolder> ceh =
+ ClonedErrorHolder::Create(aCx, error, IgnoreErrors())) {
+ JS::RootedObject obj(aCx);
+ // Note: We can't use `ToJSValue` here because ClonedErrorHolder isn't
+ // wrapper cached.
+ if (ceh->WrapObject(aCx, nullptr, &obj)) {
+ value.setObject(*obj);
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ }
+
+ Maybe<ipc::StructuredCloneData> data = TryClone(aCx, value);
+ if (!data) {
+ // Failed to clone the rejection value. Make sure that this
+ // rejection is reported, despite being "handled". This is done by
+ // creating a new promise in the rejected state, and throwing it
+ // away. This will be reported as an unhandled rejected promise.
+ if (!JS::CallOriginalPromiseReject(aCx, aValue)) {
+ JS_ClearPendingException(aCx);
+ }
+ }
+
+ SendReply(aCx, JSActorMessageKind::QueryReject, std::move(data));
+}
+
+void JSActor::QueryHandler::ResolvedCallback(JSContext* aCx,
+ JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ if (!mActor) {
+ return;
+ }
+
+ Maybe<ipc::StructuredCloneData> data{std::in_place};
+ data->InitScope(JS::StructuredCloneScope::DifferentProcess);
+
+ IgnoredErrorResult error;
+ data->Write(aCx, aValue, error);
+ if (NS_WARN_IF(error.Failed())) {
+ JS_ClearPendingException(aCx);
+
+ nsAutoCString msg;
+ msg.Append(mActor->Name());
+ msg.Append(':');
+ msg.Append(NS_LossyConvertUTF16toASCII(mMessageName));
+ msg.AppendLiteral(": message reply cannot be cloned.");
+
+ auto exc = MakeRefPtr<Exception>(msg, NS_ERROR_FAILURE, "DataCloneError"_ns,
+ nullptr, nullptr);
+
+ JS::Rooted<JS::Value> val(aCx);
+ if (ToJSValue(aCx, exc, &val)) {
+ RejectedCallback(aCx, val, aRv);
+ } else {
+ JS_ClearPendingException(aCx);
+ }
+ return;
+ }
+
+ SendReply(aCx, JSActorMessageKind::QueryResolve, std::move(data));
+}
+
+void JSActor::QueryHandler::SendReply(JSContext* aCx, JSActorMessageKind aKind,
+ Maybe<ipc::StructuredCloneData>&& aData) {
+ MOZ_ASSERT(mActor);
+ profiler_add_marker("SendQueryReply", geckoprofiler::category::IPC, {},
+ JSActorMessageMarker{}, mActor->Name(), mMessageName);
+
+ JSActorMessageMeta meta;
+ meta.actorName() = mActor->Name();
+ meta.messageName() = mMessageName;
+ meta.queryId() = mQueryId;
+ meta.kind() = aKind;
+
+ JS::Rooted<JSObject*> promise(aCx, mPromise->PromiseObj());
+ JS::Rooted<JSObject*> stack(aCx, JS::GetPromiseResolutionSite(promise));
+
+ mActor->SendRawMessage(meta, std::move(aData), CloneJSStack(aCx, stack),
+ IgnoreErrors());
+ mActor = nullptr;
+ mPromise = nullptr;
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSActor::QueryHandler)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(JSActor::QueryHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(JSActor::QueryHandler)
+
+NS_IMPL_CYCLE_COLLECTION(JSActor::QueryHandler, mActor, mPromise)
+
+} // namespace mozilla::dom
diff --git a/dom/ipc/jsactor/JSActor.h b/dom/ipc/jsactor/JSActor.h
new file mode 100644
index 0000000000..54af93d1ce
--- /dev/null
+++ b/dom/ipc/jsactor/JSActor.h
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_dom_JSActor_h
+#define mozilla_dom_JSActor_h
+
+#include "js/TypeDecls.h"
+#include "ipc/EnumSerializer.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTHashMap.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+class nsQueryJSActor;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+namespace ipc {
+class StructuredCloneData;
+}
+
+class JSActorManager;
+class JSActorMessageMeta;
+class QueryPromiseHandler;
+
+enum class JSActorMessageKind {
+ Message,
+ Query,
+ QueryResolve,
+ QueryReject,
+ EndGuard_,
+};
+
+// Common base class for JSWindowActor{Parent,Child}.
+class JSActor : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(JSActor)
+
+ explicit JSActor(nsISupports* aGlobal = nullptr);
+
+ const nsCString& Name() const { return mName; }
+ void GetName(nsCString& aName) { aName = Name(); }
+
+ void SendAsyncMessage(JSContext* aCx, const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj, ErrorResult& aRv);
+
+ already_AddRefed<Promise> SendQuery(JSContext* aCx,
+ const nsAString& aMessageName,
+ JS::Handle<JS::Value> aObj,
+ ErrorResult& aRv);
+
+ nsIGlobalObject* GetParentObject() const { return mGlobal; };
+
+ protected:
+ // Send the message described by the structured clone data |aData|, and the
+ // message metadata |aMetadata|. The underlying transport should call the
+ // |ReceiveMessage| method on the other side asynchronously.
+ virtual void SendRawMessage(const JSActorMessageMeta& aMetadata,
+ Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack,
+ ErrorResult& aRv) = 0;
+
+ // Helper method to send an in-process raw message.
+ using OtherSideCallback = std::function<already_AddRefed<JSActorManager>()>;
+ static void SendRawMessageInProcess(const JSActorMessageMeta& aMeta,
+ Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack,
+ OtherSideCallback&& aGetOtherSide);
+
+ virtual ~JSActor() = default;
+
+ void SetName(const nsACString& aName);
+
+ bool CanSend() const { return mCanSend; }
+
+ void ThrowStateErrorForGetter(const char* aName, ErrorResult& aRv) const;
+
+ void StartDestroy();
+ void AfterDestroy();
+
+ enum class CallbackFunction { DidDestroy, ActorCreated };
+ void InvokeCallback(CallbackFunction callback);
+
+ virtual void ClearManager() = 0;
+
+ private:
+ friend class JSActorManager;
+ friend class ::nsQueryJSActor; // for QueryInterfaceActor
+
+ nsresult QueryInterfaceActor(const nsIID& aIID, void** aPtr);
+
+ // Called by JSActorManager when they receive raw message data destined for
+ // this actor.
+ void ReceiveMessage(JSContext* aCx, const JSActorMessageMeta& aMetadata,
+ JS::Handle<JS::Value> aData, ErrorResult& aRv);
+ void ReceiveQuery(JSContext* aCx, const JSActorMessageMeta& aMetadata,
+ JS::Handle<JS::Value> aData, ErrorResult& aRv);
+ void ReceiveQueryReply(JSContext* aCx, const JSActorMessageMeta& aMetadata,
+ JS::Handle<JS::Value> aData, ErrorResult& aRv);
+
+ // Call the actual `ReceiveMessage` method, and get the return value.
+ void CallReceiveMessage(JSContext* aCx, const JSActorMessageMeta& aMetadata,
+ JS::Handle<JS::Value> aData,
+ JS::MutableHandle<JS::Value> aRetVal,
+ ErrorResult& aRv);
+
+ // Helper object used while processing query messages to send the final reply
+ // message.
+ class QueryHandler final : public PromiseNativeHandler {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(QueryHandler)
+
+ QueryHandler(JSActor* aActor, const JSActorMessageMeta& aMetadata,
+ Promise* aPromise);
+
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ private:
+ ~QueryHandler() = default;
+
+ void SendReply(JSContext* aCx, JSActorMessageKind aKind,
+ Maybe<ipc::StructuredCloneData>&& aData);
+
+ RefPtr<JSActor> mActor;
+ RefPtr<Promise> mPromise;
+ nsString mMessageName;
+ uint64_t mQueryId;
+ };
+
+ // A query which hasn't been resolved yet, along with metadata about what
+ // query the promise is for.
+ struct PendingQuery {
+ RefPtr<Promise> mPromise;
+ nsString mMessageName;
+ };
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ nsCOMPtr<nsISupports> mWrappedJS;
+ nsCString mName;
+ nsTHashMap<nsUint64HashKey, PendingQuery> mPendingQueries;
+ uint64_t mNextQueryId = 0;
+ bool mCanSend = true;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::JSActorMessageKind>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::JSActorMessageKind,
+ mozilla::dom::JSActorMessageKind::Message,
+ mozilla::dom::JSActorMessageKind::EndGuard_> {};
+
+} // namespace IPC
+
+#endif // !defined(mozilla_dom_JSActor_h)
diff --git a/dom/ipc/jsactor/JSActorManager.cpp b/dom/ipc/jsactor/JSActorManager.cpp
new file mode 100644
index 0000000000..237c00f3a4
--- /dev/null
+++ b/dom/ipc/jsactor/JSActorManager.cpp
@@ -0,0 +1,250 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/JSActorManager.h"
+
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/JSActorService.h"
+#include "mozilla/dom/PWindowGlobal.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/ScopeExit.h"
+#include "mozJSModuleLoader.h"
+#include "jsapi.h"
+#include "js/CallAndConstruct.h" // JS::Construct
+#include "js/PropertyAndElement.h" // JS_GetProperty
+#include "nsContentUtils.h"
+
+namespace mozilla::dom {
+
+already_AddRefed<JSActor> JSActorManager::GetActor(JSContext* aCx,
+ const nsACString& aName,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ // If our connection has been closed, return an error.
+ mozilla::ipc::IProtocol* nativeActor = AsNativeActor();
+ if (!nativeActor->CanSend()) {
+ aRv.ThrowInvalidStateError(nsPrintfCString(
+ "Cannot get actor '%s'. Native '%s' actor is destroyed.",
+ PromiseFlatCString(aName).get(), nativeActor->GetProtocolName()));
+ return nullptr;
+ }
+
+ // Check if this actor has already been created, and return it if it has.
+ if (RefPtr<JSActor> actor = mJSActors.Get(aName)) {
+ return actor.forget();
+ }
+
+ RefPtr<JSActorService> actorSvc = JSActorService::GetSingleton();
+ if (!actorSvc) {
+ aRv.ThrowInvalidStateError("JSActorService hasn't been initialized");
+ return nullptr;
+ }
+
+ // Check if this actor satisfies the requirements of the protocol
+ // corresponding to `aName`, and get the module which implements it.
+ RefPtr<JSActorProtocol> protocol =
+ MatchingJSActorProtocol(actorSvc, aName, aRv);
+ if (!protocol) {
+ return nullptr;
+ }
+
+ bool isParent = nativeActor->GetSide() == mozilla::ipc::ParentSide;
+ auto& side = isParent ? protocol->Parent() : protocol->Child();
+
+ // We're about to construct the actor, so make sure we're in the JSM realm
+ // while importing etc.
+ JSAutoRealm ar(aCx, xpc::PrivilegedJunkScope());
+
+ // Load the module using mozJSModuleLoader.
+ RefPtr loader = mozJSModuleLoader::Get();
+ MOZ_ASSERT(loader);
+
+ // If a module URI was provided, use it to construct an instance of the actor.
+ JS::Rooted<JSObject*> actorObj(aCx);
+ if (side.mModuleURI || side.mESModuleURI) {
+ JS::Rooted<JSObject*> exports(aCx);
+ if (side.mModuleURI) {
+ JS::Rooted<JSObject*> global(aCx);
+ aRv = loader->Import(aCx, side.mModuleURI.ref(), &global, &exports);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ } else {
+ aRv = loader->ImportESModule(aCx, side.mESModuleURI.ref(), &exports);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+ MOZ_ASSERT(exports, "null exports!");
+
+ // Load the specific property from our module.
+ JS::Rooted<JS::Value> ctor(aCx);
+ nsAutoCString ctorName(aName);
+ ctorName.Append(isParent ? "Parent"_ns : "Child"_ns);
+ if (!JS_GetProperty(aCx, exports, ctorName.get(), &ctor)) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(!ctor.isObject())) {
+ aRv.ThrowNotFoundError(nsPrintfCString(
+ "Could not find actor constructor '%s'", ctorName.get()));
+ return nullptr;
+ }
+
+ // Invoke the constructor loaded from the module.
+ if (!JS::Construct(aCx, ctor, JS::HandleValueArray::empty(), &actorObj)) {
+ aRv.NoteJSContextException(aCx);
+ return nullptr;
+ }
+ }
+
+ // Initialize our newly-constructed actor, and return it.
+ RefPtr<JSActor> actor = InitJSActor(actorObj, aName, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ mJSActors.InsertOrUpdate(aName, RefPtr{actor});
+ return actor.forget();
+}
+
+already_AddRefed<JSActor> JSActorManager::GetExistingActor(
+ const nsACString& aName) {
+ if (!AsNativeActor()->CanSend()) {
+ return nullptr;
+ }
+ return mJSActors.Get(aName);
+}
+
+#define CHILD_DIAGNOSTIC_ASSERT(test, msg) \
+ do { \
+ if (XRE_IsParentProcess()) { \
+ MOZ_ASSERT(test, msg); \
+ } else { \
+ MOZ_DIAGNOSTIC_ASSERT(test, msg); \
+ } \
+ } while (0)
+
+void JSActorManager::ReceiveRawMessage(
+ const JSActorMessageMeta& aMetadata,
+ Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ CrashReporter::AutoAnnotateCrashReport autoActorName(
+ CrashReporter::Annotation::JSActorName, aMetadata.actorName());
+ CrashReporter::AutoAnnotateCrashReport autoMessageName(
+ CrashReporter::Annotation::JSActorMessage,
+ NS_LossyConvertUTF16toASCII(aMetadata.messageName()));
+
+ // We're going to be running JS. Enter the privileged junk realm so we can set
+ // up our JS state correctly.
+ AutoEntryScript aes(xpc::PrivilegedJunkScope(), "JSActor message handler");
+ JSContext* cx = aes.cx();
+
+ // Ensure any errors reported to `error` are set on the scope, so they're
+ // reported.
+ ErrorResult error;
+ auto autoSetException =
+ MakeScopeExit([&] { Unused << error.MaybeSetPendingException(cx); });
+
+ // If an async stack was provided, set up our async stack state.
+ JS::Rooted<JSObject*> stack(cx);
+ Maybe<JS::AutoSetAsyncStackForNewCalls> stackSetter;
+ {
+ JS::Rooted<JS::Value> stackVal(cx);
+ if (aStack) {
+ aStack->Read(cx, &stackVal, error);
+ if (error.Failed()) {
+ error.SuppressException();
+ JS_ClearPendingException(cx);
+ stackVal.setUndefined();
+ }
+ }
+
+ if (stackVal.isObject()) {
+ stack = &stackVal.toObject();
+ if (!js::IsSavedFrame(stack)) {
+ CHILD_DIAGNOSTIC_ASSERT(false, "Stack must be a SavedFrame object");
+ error.ThrowDataError("Actor async stack must be a SavedFrame object");
+ return;
+ }
+ stackSetter.emplace(cx, stack, "JSActor query");
+ }
+ }
+
+ RefPtr<JSActor> actor = GetActor(cx, aMetadata.actorName(), error);
+ if (error.Failed()) {
+ return;
+ }
+
+ JS::Rooted<JS::Value> data(cx);
+ if (aData) {
+ aData->Read(cx, &data, error);
+ if (error.Failed()) {
+ CHILD_DIAGNOSTIC_ASSERT(CycleCollectedJSRuntime::Get()->OOMReported(),
+ "Should not receive non-decodable data");
+ return;
+ }
+ }
+
+ switch (aMetadata.kind()) {
+ case JSActorMessageKind::QueryResolve:
+ case JSActorMessageKind::QueryReject:
+ actor->ReceiveQueryReply(cx, aMetadata, data, error);
+ break;
+
+ case JSActorMessageKind::Message:
+ actor->ReceiveMessage(cx, aMetadata, data, error);
+ break;
+
+ case JSActorMessageKind::Query:
+ actor->ReceiveQuery(cx, aMetadata, data, error);
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE();
+ }
+}
+
+void JSActorManager::JSActorWillDestroy() {
+ for (const auto& entry : mJSActors.Values()) {
+ entry->StartDestroy();
+ }
+}
+
+void JSActorManager::JSActorDidDestroy() {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+ CrashReporter::AutoAnnotateCrashReport autoMessageName(
+ CrashReporter::Annotation::JSActorMessage, "<DidDestroy>"_ns);
+
+ // Swap the table with `mJSActors` so that we don't invalidate it while
+ // iterating.
+ const nsRefPtrHashtable<nsCStringHashKey, JSActor> actors =
+ std::move(mJSActors);
+ for (const auto& entry : actors.Values()) {
+ CrashReporter::AutoAnnotateCrashReport autoActorName(
+ CrashReporter::Annotation::JSActorName, entry->Name());
+ // Do not risk to run script very late in shutdown
+ if (!AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownFinal)) {
+ entry->AfterDestroy();
+ }
+ }
+}
+
+void JSActorManager::JSActorUnregister(const nsACString& aName) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ RefPtr<JSActor> actor;
+ if (mJSActors.Remove(aName, getter_AddRefs(actor))) {
+ actor->AfterDestroy();
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/ipc/jsactor/JSActorManager.h b/dom/ipc/jsactor/JSActorManager.h
new file mode 100644
index 0000000000..86efa9d7af
--- /dev/null
+++ b/dom/ipc/jsactor/JSActorManager.h
@@ -0,0 +1,100 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_JSActorManager_h
+#define mozilla_dom_JSActorManager_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/dom/JSActor.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace ipc {
+class IProtocol;
+}
+
+namespace dom {
+
+class JSActorProtocol;
+class JSActorService;
+
+class JSActorManager : public nsISupports {
+ public:
+ /**
+ * Get or create an actor by its name.
+ *
+ * Will set an error on |aRv| if the actor fails to be constructed.
+ */
+ already_AddRefed<JSActor> GetActor(JSContext* aCx, const nsACString& aName,
+ ErrorResult& aRv);
+
+ /**
+ * Look up an existing actor by its name, returning nullptr if it doesn't
+ * already exist. Will not attempt to create the actor.
+ */
+ already_AddRefed<JSActor> GetExistingActor(const nsACString& aName);
+
+ /**
+ * Handle receiving a raw message from the other side.
+ */
+ void ReceiveRawMessage(const JSActorMessageMeta& aMetadata,
+ Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack);
+
+ protected:
+ /**
+ * The actor is about to be destroyed so prevent it from sending any
+ * more messages.
+ */
+ void JSActorWillDestroy();
+
+ /**
+ * Lifecycle method which will fire the `didDestroy` methods on relevant
+ * actors.
+ */
+ void JSActorDidDestroy();
+
+ /**
+ * Return the protocol with the given name, if it is supported by the current
+ * actor.
+ */
+ virtual already_AddRefed<JSActorProtocol> MatchingJSActorProtocol(
+ JSActorService* aActorSvc, const nsACString& aName, ErrorResult& aRv) = 0;
+
+ /**
+ * Initialize a JSActor instance given the constructed JS object.
+ * `aMaybeActor` may be `nullptr`, which should construct the default empty
+ * actor.
+ */
+ virtual already_AddRefed<JSActor> InitJSActor(
+ JS::Handle<JSObject*> aMaybeActor, const nsACString& aName,
+ ErrorResult& aRv) = 0;
+
+ /**
+ * Return this native actor. This should be the same object which is
+ * implementing `JSActorManager`.
+ */
+ virtual mozilla::ipc::IProtocol* AsNativeActor() = 0;
+
+ private:
+ friend class JSActorService;
+
+ /**
+ * Note that a particular actor name has been unregistered, and fire the
+ * `didDestroy` method on the actor, if it's been initialized.
+ */
+ void JSActorUnregister(const nsACString& aName);
+
+ nsRefPtrHashtable<nsCStringHashKey, JSActor> mJSActors;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_JSActorManager_h
diff --git a/dom/ipc/jsactor/JSActorProtocolUtils.h b/dom/ipc/jsactor/JSActorProtocolUtils.h
new file mode 100644
index 0000000000..b1fb4d461c
--- /dev/null
+++ b/dom/ipc/jsactor/JSActorProtocolUtils.h
@@ -0,0 +1,117 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_JSActorProtocolUtils_h
+#define mozilla_dom_JSActorProtocolUtils_h
+
+#include "mozilla/Assertions.h" // MOZ_ASSERT
+#include "mozilla/ErrorResult.h" // ErrorResult
+
+namespace mozilla {
+
+namespace dom {
+
+class JSActorProtocolUtils {
+ public:
+ template <typename ProtoT, typename ActorInfoT>
+ static void FromIPCShared(ProtoT& aProto, const ActorInfoT& aInfo) {
+ aProto->mRemoteTypes = aInfo.remoteTypes().Clone();
+
+ if (aInfo.isESModule()) {
+ aProto->mChild.mESModuleURI = aInfo.url();
+ } else {
+ aProto->mChild.mModuleURI = aInfo.url();
+ }
+
+ aProto->mChild.mObservers = aInfo.observers().Clone();
+ }
+
+ template <typename ProtoT, typename ActorInfoT>
+ static void ToIPCShared(ActorInfoT& aInfo, const ProtoT& aProto) {
+ aInfo.name() = aProto->mName;
+
+ aInfo.remoteTypes() = aProto->mRemoteTypes.Clone();
+
+ if (aProto->mChild.mModuleURI) {
+ aInfo.url() = aProto->mChild.mModuleURI;
+ aInfo.isESModule() = false;
+ } else {
+ aInfo.url() = aProto->mChild.mESModuleURI;
+ aInfo.isESModule() = true;
+ }
+
+ aInfo.observers() = aProto->mChild.mObservers.Clone();
+ }
+
+ template <typename ProtoT, typename ActorOptionsT>
+ static bool FromWebIDLOptionsShared(ProtoT& aProto,
+ const ActorOptionsT& aOptions,
+ ErrorResult& aRv) {
+ if (aOptions.mRemoteTypes.WasPassed()) {
+ MOZ_ASSERT(aOptions.mRemoteTypes.Value().Length());
+ aProto->mRemoteTypes = aOptions.mRemoteTypes.Value();
+ }
+
+ if (aOptions.mParent.WasPassed()) {
+ const auto& parentOptions = aOptions.mParent.Value();
+
+ if (parentOptions.mModuleURI.WasPassed()) {
+ if (parentOptions.mEsModuleURI.WasPassed()) {
+ aRv.ThrowNotSupportedError(
+ "moduleURI and esModuleURI are mutually exclusive.");
+ return false;
+ }
+
+ aProto->mParent.mModuleURI.emplace(parentOptions.mModuleURI.Value());
+ } else if (parentOptions.mEsModuleURI.WasPassed()) {
+ aProto->mParent.mESModuleURI.emplace(
+ parentOptions.mEsModuleURI.Value());
+ } else {
+ aRv.ThrowNotSupportedError(
+ "Either moduleURI or esModuleURI is required.");
+ return false;
+ }
+ }
+ if (aOptions.mChild.WasPassed()) {
+ const auto& childOptions = aOptions.mChild.Value();
+
+ if (childOptions.mModuleURI.WasPassed()) {
+ if (childOptions.mEsModuleURI.WasPassed()) {
+ aRv.ThrowNotSupportedError(
+ "moduleURI and esModuleURI are exclusive.");
+ return false;
+ }
+
+ aProto->mChild.mModuleURI.emplace(childOptions.mModuleURI.Value());
+ } else if (childOptions.mEsModuleURI.WasPassed()) {
+ aProto->mChild.mESModuleURI.emplace(childOptions.mEsModuleURI.Value());
+ } else {
+ aRv.ThrowNotSupportedError(
+ "Either moduleURI or esModuleURI is required.");
+ return false;
+ }
+ }
+
+ if (!aOptions.mChild.WasPassed() && !aOptions.mParent.WasPassed()) {
+ aRv.ThrowNotSupportedError(
+ "No point registering an actor with neither child nor parent "
+ "specifications.");
+ return false;
+ }
+
+ if (aOptions.mChild.WasPassed() &&
+ aOptions.mChild.Value().mObservers.WasPassed()) {
+ aProto->mChild.mObservers = aOptions.mChild.Value().mObservers.Value();
+ }
+
+ return true;
+ }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_JSActorProtocolUtils_h
diff --git a/dom/ipc/jsactor/JSActorService.cpp b/dom/ipc/jsactor/JSActorService.cpp
new file mode 100644
index 0000000000..3fde76a206
--- /dev/null
+++ b/dom/ipc/jsactor/JSActorService.cpp
@@ -0,0 +1,322 @@
+/* -*- 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/JSActorService.h"
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/EventListenerBinding.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTargetBinding.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/dom/InProcessChild.h"
+#include "mozilla/dom/InProcessParent.h"
+#include "mozilla/dom/JSActorManager.h"
+#include "mozilla/dom/JSProcessActorBinding.h"
+#include "mozilla/dom/JSProcessActorChild.h"
+#include "mozilla/dom/JSProcessActorProtocol.h"
+#include "mozilla/dom/JSWindowActorBinding.h"
+#include "mozilla/dom/JSWindowActorChild.h"
+#include "mozilla/dom/JSWindowActorProtocol.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+#include "mozilla/dom/PContent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/ArrayAlgorithm.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Logging.h"
+#include "nsIObserverService.h"
+
+namespace mozilla::dom {
+namespace {
+StaticRefPtr<JSActorService> gJSActorService;
+}
+
+JSActorService::JSActorService() { MOZ_ASSERT(NS_IsMainThread()); }
+
+JSActorService::~JSActorService() { MOZ_ASSERT(NS_IsMainThread()); }
+
+/* static */
+already_AddRefed<JSActorService> JSActorService::GetSingleton() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!gJSActorService) {
+ gJSActorService = new JSActorService();
+ ClearOnShutdown(&gJSActorService);
+ }
+
+ RefPtr<JSActorService> service = gJSActorService.get();
+ return service.forget();
+}
+
+void JSActorService::RegisterWindowActor(const nsACString& aName,
+ const WindowActorOptions& aOptions,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ const auto proto = mWindowActorDescriptors.WithEntryHandle(
+ aName, [&](auto&& entry) -> RefPtr<JSWindowActorProtocol> {
+ if (entry) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("'%s' actor is already registered.",
+ PromiseFlatCString(aName).get()));
+ return nullptr;
+ }
+
+ // Insert a new entry for the protocol.
+ RefPtr<JSWindowActorProtocol> protocol =
+ JSWindowActorProtocol::FromWebIDLOptions(aName, aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ entry.Insert(protocol);
+
+ return protocol;
+ });
+
+ if (!proto) {
+ MOZ_ASSERT(aRv.Failed());
+ return;
+ }
+
+ // Send information about the newly added entry to every existing content
+ // process.
+ AutoTArray<JSWindowActorInfo, 1> windowInfos{proto->ToIPC()};
+ nsTArray<JSProcessActorInfo> contentInfos{};
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ Unused << cp->SendInitJSActorInfos(contentInfos, windowInfos);
+ }
+
+ // Register event listeners for any existing chrome targets.
+ for (EventTarget* target : mChromeEventTargets) {
+ proto->RegisterListenersFor(target);
+ }
+
+ // Add observers to the protocol.
+ proto->AddObservers();
+}
+
+void JSActorService::UnregisterWindowActor(const nsACString& aName) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+ CrashReporter::AutoAnnotateCrashReport autoActorName(
+ CrashReporter::Annotation::JSActorName, aName);
+ CrashReporter::AutoAnnotateCrashReport autoMessageName(
+ CrashReporter::Annotation::JSActorMessage, "<Unregister>"_ns);
+
+ nsAutoCString name(aName);
+ RefPtr<JSWindowActorProtocol> proto;
+ if (mWindowActorDescriptors.Remove(name, getter_AddRefs(proto))) {
+ // Remove listeners for this actor from each of our chrome targets.
+ for (EventTarget* target : mChromeEventTargets) {
+ proto->UnregisterListenersFor(target);
+ }
+
+ // Remove observers for this actor from observer serivce.
+ proto->RemoveObservers();
+
+ // Tell every content process to also unregister, and accumulate the set of
+ // potential managers, to have the actor disabled.
+ nsTArray<RefPtr<JSActorManager>> managers;
+ if (XRE_IsParentProcess()) {
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ Unused << cp->SendUnregisterJSWindowActor(name);
+ for (const auto& bp : cp->ManagedPBrowserParent()) {
+ for (const auto& wgp : bp->ManagedPWindowGlobalParent()) {
+ managers.AppendElement(static_cast<WindowGlobalParent*>(wgp));
+ }
+ }
+ }
+
+ for (const auto& wgp :
+ InProcessParent::Singleton()->ManagedPWindowGlobalParent()) {
+ managers.AppendElement(static_cast<WindowGlobalParent*>(wgp));
+ }
+ for (const auto& wgc :
+ InProcessChild::Singleton()->ManagedPWindowGlobalChild()) {
+ managers.AppendElement(static_cast<WindowGlobalChild*>(wgc));
+ }
+ } else {
+ for (const auto& bc :
+ ContentChild::GetSingleton()->ManagedPBrowserChild()) {
+ for (const auto& wgc : bc->ManagedPWindowGlobalChild()) {
+ managers.AppendElement(static_cast<WindowGlobalChild*>(wgc));
+ }
+ }
+ }
+
+ for (auto& mgr : managers) {
+ mgr->JSActorUnregister(name);
+ }
+ }
+}
+
+void JSActorService::LoadJSActorInfos(nsTArray<JSProcessActorInfo>& aProcess,
+ nsTArray<JSWindowActorInfo>& aWindow) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsContentProcess());
+
+ for (auto& info : aProcess) {
+ // Create our JSProcessActorProtocol, register it in
+ // mProcessActorDescriptors.
+ auto name = info.name();
+ RefPtr<JSProcessActorProtocol> proto =
+ JSProcessActorProtocol::FromIPC(std::move(info));
+ mProcessActorDescriptors.InsertOrUpdate(std::move(name), RefPtr{proto});
+
+ // Add observers for each actor.
+ proto->AddObservers();
+ }
+
+ for (auto& info : aWindow) {
+ auto name = info.name();
+ RefPtr<JSWindowActorProtocol> proto =
+ JSWindowActorProtocol::FromIPC(std::move(info));
+ mWindowActorDescriptors.InsertOrUpdate(std::move(name), RefPtr{proto});
+
+ // Register listeners for each chrome target.
+ for (EventTarget* target : mChromeEventTargets) {
+ proto->RegisterListenersFor(target);
+ }
+
+ // Add observers for each actor.
+ proto->AddObservers();
+ }
+}
+
+void JSActorService::GetJSWindowActorInfos(
+ nsTArray<JSWindowActorInfo>& aInfos) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ for (const auto& data : mWindowActorDescriptors.Values()) {
+ aInfos.AppendElement(data->ToIPC());
+ }
+}
+
+void JSActorService::RegisterChromeEventTarget(EventTarget* aTarget) {
+ MOZ_ASSERT(!mChromeEventTargets.Contains(aTarget));
+ mChromeEventTargets.AppendElement(aTarget);
+
+ // Register event listeners on the newly added Window Root.
+ for (const auto& data : mWindowActorDescriptors.Values()) {
+ data->RegisterListenersFor(aTarget);
+ }
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ obs->NotifyObservers(aTarget, "chrome-event-target-created", nullptr);
+}
+
+/* static */
+void JSActorService::UnregisterChromeEventTarget(EventTarget* aTarget) {
+ if (gJSActorService) {
+ // NOTE: No need to unregister listeners here, as the target is going away.
+ gJSActorService->mChromeEventTargets.RemoveElement(aTarget);
+ }
+}
+
+void JSActorService::RegisterProcessActor(const nsACString& aName,
+ const ProcessActorOptions& aOptions,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ const auto proto = mProcessActorDescriptors.WithEntryHandle(
+ aName, [&](auto&& entry) -> RefPtr<JSProcessActorProtocol> {
+ if (entry) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("'%s' actor is already registered.",
+ PromiseFlatCString(aName).get()));
+ return nullptr;
+ }
+
+ // Insert a new entry for the protocol.
+ RefPtr<JSProcessActorProtocol> protocol =
+ JSProcessActorProtocol::FromWebIDLOptions(aName, aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ entry.Insert(protocol);
+
+ return protocol;
+ });
+
+ if (!proto) {
+ MOZ_ASSERT(aRv.Failed());
+ return;
+ }
+
+ // Send information about the newly added entry to every existing content
+ // process.
+ AutoTArray<JSProcessActorInfo, 1> contentInfos{proto->ToIPC()};
+ nsTArray<JSWindowActorInfo> windowInfos{};
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ Unused << cp->SendInitJSActorInfos(contentInfos, windowInfos);
+ }
+
+ // Add observers to the protocol.
+ proto->AddObservers();
+}
+
+void JSActorService::UnregisterProcessActor(const nsACString& aName) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+ CrashReporter::AutoAnnotateCrashReport autoActorName(
+ CrashReporter::Annotation::JSActorName, aName);
+ CrashReporter::AutoAnnotateCrashReport autoMessageName(
+ CrashReporter::Annotation::JSActorMessage, "<Unregister>"_ns);
+
+ nsAutoCString name(aName);
+ RefPtr<JSProcessActorProtocol> proto;
+ if (mProcessActorDescriptors.Remove(name, getter_AddRefs(proto))) {
+ // Remove observers for this actor from observer serivce.
+ proto->RemoveObservers();
+
+ // Tell every content process to also unregister, and accumulate the set of
+ // potential managers, to have the actor disabled.
+ nsTArray<RefPtr<JSActorManager>> managers;
+ if (XRE_IsParentProcess()) {
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ Unused << cp->SendUnregisterJSProcessActor(name);
+ managers.AppendElement(cp);
+ }
+ managers.AppendElement(InProcessChild::Singleton());
+ managers.AppendElement(InProcessParent::Singleton());
+ } else {
+ managers.AppendElement(ContentChild::GetSingleton());
+ }
+
+ for (auto& mgr : managers) {
+ mgr->JSActorUnregister(name);
+ }
+ }
+}
+
+void JSActorService::GetJSProcessActorInfos(
+ nsTArray<JSProcessActorInfo>& aInfos) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ for (const auto& data : mProcessActorDescriptors.Values()) {
+ aInfos.AppendElement(data->ToIPC());
+ }
+}
+
+already_AddRefed<JSProcessActorProtocol>
+JSActorService::GetJSProcessActorProtocol(const nsACString& aName) {
+ return mProcessActorDescriptors.Get(aName);
+}
+
+already_AddRefed<JSWindowActorProtocol>
+JSActorService::GetJSWindowActorProtocol(const nsACString& aName) {
+ return mWindowActorDescriptors.Get(aName);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/ipc/jsactor/JSActorService.h b/dom/ipc/jsactor/JSActorService.h
new file mode 100644
index 0000000000..e3296e1934
--- /dev/null
+++ b/dom/ipc/jsactor/JSActorService.h
@@ -0,0 +1,112 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_JSActorService_h
+#define mozilla_dom_JSActorService_h
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "nsIURI.h"
+#include "nsRefPtrHashtable.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/dom/JSActor.h"
+#include "nsIObserver.h"
+#include "nsIDOMEventListener.h"
+#include "mozilla/EventListenerManager.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+struct ProcessActorOptions;
+struct WindowActorOptions;
+class JSProcessActorInfo;
+class JSWindowActorInfo;
+class EventTarget;
+class JSWindowActorProtocol;
+class JSProcessActorProtocol;
+
+class JSActorService final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(JSActorService)
+
+ static already_AddRefed<JSActorService> GetSingleton();
+
+ // Register or unregister a chrome event target.
+ void RegisterChromeEventTarget(EventTarget* aTarget);
+
+ // NOTE: This method is static, as it may be called during shutdown.
+ static void UnregisterChromeEventTarget(EventTarget* aTarget);
+
+ // Register child's Actor for content process.
+ void LoadJSActorInfos(nsTArray<JSProcessActorInfo>& aProcess,
+ nsTArray<JSWindowActorInfo>& aWindow);
+
+ // --- Window Actor
+
+ void RegisterWindowActor(const nsACString& aName,
+ const WindowActorOptions& aOptions,
+ ErrorResult& aRv);
+
+ void UnregisterWindowActor(const nsACString& aName);
+
+ // Get the named of Window Actor and the child's WindowActorOptions
+ // from mDescriptors to JSWindowActorInfos.
+ void GetJSWindowActorInfos(nsTArray<JSWindowActorInfo>& aInfos);
+
+ already_AddRefed<JSWindowActorProtocol> GetJSWindowActorProtocol(
+ const nsACString& aName);
+
+ // -- Content Actor
+
+ void RegisterProcessActor(const nsACString& aName,
+ const ProcessActorOptions& aOptions,
+ ErrorResult& aRv);
+
+ void UnregisterProcessActor(const nsACString& aName);
+
+ // Get the named of Content Actor and the child's ProcessActorOptions
+ // from mDescriptors to JSProcessActorInfos.
+ void GetJSProcessActorInfos(nsTArray<JSProcessActorInfo>& aInfos);
+
+ already_AddRefed<JSProcessActorProtocol> GetJSProcessActorProtocol(
+ const nsACString& aName);
+
+ private:
+ JSActorService();
+ ~JSActorService();
+
+ nsTArray<EventTarget*> mChromeEventTargets;
+
+ // -- Window Actor
+ nsRefPtrHashtable<nsCStringHashKey, JSWindowActorProtocol>
+ mWindowActorDescriptors;
+
+ // -- Process Actor
+ nsRefPtrHashtable<nsCStringHashKey, JSProcessActorProtocol>
+ mProcessActorDescriptors;
+};
+
+/**
+ * Base clsas for both `JSWindowActorProtocol` and `JSProcessActorProtocol`
+ * which can be used by generic code.
+ */
+class JSActorProtocol : public nsISupports {
+ public:
+ struct Sided {
+ Maybe<nsCString> mModuleURI;
+ Maybe<nsCString> mESModuleURI;
+ };
+
+ virtual const Sided& Parent() const = 0;
+ virtual const Sided& Child() const = 0;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_JSActorService_h
diff --git a/dom/ipc/jsactor/JSProcessActorChild.cpp b/dom/ipc/jsactor/JSProcessActorChild.cpp
new file mode 100644
index 0000000000..a9379838cf
--- /dev/null
+++ b/dom/ipc/jsactor/JSProcessActorChild.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/ContentChild.h"
+#include "mozilla/dom/JSProcessActorBinding.h"
+#include "mozilla/dom/JSProcessActorChild.h"
+#include "mozilla/dom/InProcessChild.h"
+#include "mozilla/dom/InProcessParent.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(JSProcessActorChild, JSActor, mManager)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSProcessActorChild)
+NS_INTERFACE_MAP_END_INHERITING(JSActor)
+
+NS_IMPL_ADDREF_INHERITED(JSProcessActorChild, JSActor)
+NS_IMPL_RELEASE_INHERITED(JSProcessActorChild, JSActor)
+
+JSObject* JSProcessActorChild::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return JSProcessActorChild_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void JSProcessActorChild::SendRawMessage(
+ const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) {
+ if (NS_WARN_IF(!CanSend() || !mManager || !mManager->GetCanSend())) {
+ aRv.ThrowInvalidStateError("JSProcessActorChild cannot send at the moment");
+ return;
+ }
+
+ // If the parent side is in the same process, we have a PInProcess manager,
+ // and can dispatch the message directly to the event loop.
+ ContentChild* contentChild = mManager->AsContentChild();
+ if (!contentChild) {
+ SendRawMessageInProcess(aMeta, std::move(aData), std::move(aStack), []() {
+ return do_AddRef(InProcessParent::Singleton());
+ });
+ return;
+ }
+
+ // Cross-process case - send data over ContentChild to other side.
+ Maybe<ClonedMessageData> msgData;
+ if (aData) {
+ msgData.emplace();
+ if (NS_WARN_IF(!aData->BuildClonedMessageData(*msgData))) {
+ aRv.ThrowDataCloneError(
+ nsPrintfCString("JSProcessActorChild serialization error: cannot "
+ "clone, in actor '%s'",
+ PromiseFlatCString(aMeta.actorName()).get()));
+ return;
+ }
+ }
+
+ Maybe<ClonedMessageData> stackData;
+ if (aStack) {
+ stackData.emplace();
+ if (!aStack->BuildClonedMessageData(*stackData)) {
+ stackData.reset();
+ }
+ }
+
+ if (NS_WARN_IF(!contentChild->SendRawMessage(aMeta, msgData, stackData))) {
+ aRv.ThrowOperationError(
+ nsPrintfCString("JSProcessActorChild send error in actor '%s'",
+ PromiseFlatCString(aMeta.actorName()).get()));
+ return;
+ }
+}
+
+void JSProcessActorChild::Init(const nsACString& aName,
+ nsIDOMProcessChild* aManager) {
+ MOZ_ASSERT(!mManager, "Cannot Init() a JSProcessActorChild twice!");
+ SetName(aName);
+ mManager = aManager;
+
+ InvokeCallback(CallbackFunction::ActorCreated);
+}
+
+void JSProcessActorChild::ClearManager() { mManager = nullptr; }
+
+} // namespace mozilla::dom
diff --git a/dom/ipc/jsactor/JSProcessActorChild.h b/dom/ipc/jsactor/JSProcessActorChild.h
new file mode 100644
index 0000000000..a8be65211c
--- /dev/null
+++ b/dom/ipc/jsactor/JSProcessActorChild.h
@@ -0,0 +1,54 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_JSProcessActorChild_h
+#define mozilla_dom_JSProcessActorChild_h
+
+#include "mozilla/dom/JSActor.h"
+#include "nsIDOMProcessChild.h"
+
+namespace mozilla::dom {
+
+// Placeholder implementation.
+class JSProcessActorChild final : public JSActor {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSProcessActorChild, JSActor)
+
+ explicit JSProcessActorChild(nsISupports* aGlobal = nullptr)
+ : JSActor(aGlobal) {}
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<JSProcessActorChild> Constructor(
+ GlobalObject& aGlobal) {
+ return MakeAndAddRef<JSProcessActorChild>(aGlobal.GetAsSupports());
+ }
+
+ nsIDOMProcessChild* Manager() const { return mManager; }
+
+ void Init(const nsACString& aName, nsIDOMProcessChild* aManager);
+ void ClearManager() override;
+
+ protected:
+ // Send the message described by the structured clone data |aData|, and the
+ // message metadata |aMetadata|. The underlying transport should call the
+ // |ReceiveMessage| method on the other side asynchronously.
+ virtual void SendRawMessage(const JSActorMessageMeta& aMetadata,
+ Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack,
+ ErrorResult& aRv) override;
+
+ private:
+ ~JSProcessActorChild() { MOZ_ASSERT(!mManager); }
+
+ nsCOMPtr<nsIDOMProcessChild> mManager;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_JSProcessActorChild_h
diff --git a/dom/ipc/jsactor/JSProcessActorParent.cpp b/dom/ipc/jsactor/JSProcessActorParent.cpp
new file mode 100644
index 0000000000..eec8ad17c7
--- /dev/null
+++ b/dom/ipc/jsactor/JSProcessActorParent.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/JSProcessActorBinding.h"
+#include "mozilla/dom/JSProcessActorParent.h"
+#include "mozilla/dom/InProcessChild.h"
+#include "mozilla/dom/InProcessParent.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(JSProcessActorParent, JSActor, mManager)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSProcessActorParent)
+NS_INTERFACE_MAP_END_INHERITING(JSActor)
+
+NS_IMPL_ADDREF_INHERITED(JSProcessActorParent, JSActor)
+NS_IMPL_RELEASE_INHERITED(JSProcessActorParent, JSActor)
+
+JSObject* JSProcessActorParent::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return JSProcessActorParent_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void JSProcessActorParent::Init(const nsACString& aName,
+ nsIDOMProcessParent* aManager) {
+ MOZ_ASSERT(!mManager, "Cannot Init() a JSProcessActorParent twice!");
+ SetName(aName);
+ mManager = aManager;
+
+ InvokeCallback(CallbackFunction::ActorCreated);
+}
+
+JSProcessActorParent::~JSProcessActorParent() { MOZ_ASSERT(!mManager); }
+
+void JSProcessActorParent::SendRawMessage(
+ const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) {
+ if (NS_WARN_IF(!CanSend() || !mManager || !mManager->GetCanSend())) {
+ aRv.ThrowInvalidStateError(
+ nsPrintfCString("Actor '%s' cannot send message '%s' during shutdown.",
+ PromiseFlatCString(aMeta.actorName()).get(),
+ NS_ConvertUTF16toUTF8(aMeta.messageName()).get()));
+ return;
+ }
+
+ // If the parent side is in the same process, we have a PInProcess manager,
+ // and can dispatch the message directly to the event loop.
+ ContentParent* contentParent = mManager->AsContentParent();
+ if (!contentParent) {
+ SendRawMessageInProcess(aMeta, std::move(aData), std::move(aStack), []() {
+ return do_AddRef(InProcessChild::Singleton());
+ });
+ return;
+ }
+
+ // Cross-process case - send data over ContentParent to other side.
+ Maybe<ClonedMessageData> msgData;
+ if (aData) {
+ msgData.emplace();
+ if (NS_WARN_IF(!aData->BuildClonedMessageData(*msgData))) {
+ aRv.ThrowDataCloneError(
+ nsPrintfCString("Actor '%s' cannot send message '%s': cannot clone.",
+ PromiseFlatCString(aMeta.actorName()).get(),
+ NS_ConvertUTF16toUTF8(aMeta.messageName()).get()));
+ return;
+ }
+ }
+
+ Maybe<ClonedMessageData> stackData;
+ if (aStack) {
+ stackData.emplace();
+ if (!aStack->BuildClonedMessageData(*stackData)) {
+ stackData.reset();
+ }
+ }
+
+ if (NS_WARN_IF(!contentParent->SendRawMessage(aMeta, msgData, stackData))) {
+ aRv.ThrowOperationError(
+ nsPrintfCString("JSProcessActorParent send error in actor '%s'",
+ PromiseFlatCString(aMeta.actorName()).get()));
+ return;
+ }
+}
+
+void JSProcessActorParent::ClearManager() { mManager = nullptr; }
+
+} // namespace mozilla::dom
diff --git a/dom/ipc/jsactor/JSProcessActorParent.h b/dom/ipc/jsactor/JSProcessActorParent.h
new file mode 100644
index 0000000000..7b2a1535f9
--- /dev/null
+++ b/dom/ipc/jsactor/JSProcessActorParent.h
@@ -0,0 +1,63 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_JSProcessActorParent_h
+#define mozilla_dom_JSProcessActorParent_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/JSActor.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIDOMProcessParent.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class JSProcessActorParent final : public JSActor {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSProcessActorParent, JSActor)
+
+ explicit JSProcessActorParent(nsISupports* aGlobal = nullptr)
+ : JSActor(aGlobal) {}
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<JSProcessActorParent> Constructor(
+ GlobalObject& aGlobal) {
+ return MakeAndAddRef<JSProcessActorParent>(aGlobal.GetAsSupports());
+ }
+
+ nsIDOMProcessParent* Manager() const { return mManager; }
+
+ void Init(const nsACString& aName, nsIDOMProcessParent* aManager);
+ void ClearManager() override;
+
+ protected:
+ // Send the message described by the structured clone data |aData|, and the
+ // message metadata |aMetadata|. The underlying transport should call the
+ // |ReceiveMessage| method on the other side asynchronously.
+ virtual void SendRawMessage(const JSActorMessageMeta& aMetadata,
+ Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack,
+ ErrorResult& aRv) override;
+
+ private:
+ ~JSProcessActorParent();
+
+ nsCOMPtr<nsIDOMProcessParent> mManager;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_JSProcessActorParent_h
diff --git a/dom/ipc/jsactor/JSProcessActorProtocol.cpp b/dom/ipc/jsactor/JSProcessActorProtocol.cpp
new file mode 100644
index 0000000000..367db5305b
--- /dev/null
+++ b/dom/ipc/jsactor/JSProcessActorProtocol.cpp
@@ -0,0 +1,147 @@
+/* -*- 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/JSProcessActorProtocol.h"
+#include "mozilla/dom/InProcessChild.h"
+#include "mozilla/dom/JSProcessActorBinding.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/JSActorBinding.h"
+#include "mozilla/dom/PContent.h"
+
+#include "nsContentUtils.h"
+#include "JSActorProtocolUtils.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(JSProcessActorProtocol)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(JSProcessActorProtocol)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSProcessActorProtocol)
+ NS_INTERFACE_MAP_ENTRY(nsIObserver)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(JSProcessActorProtocol)
+
+/* static */ already_AddRefed<JSProcessActorProtocol>
+JSProcessActorProtocol::FromIPC(const JSProcessActorInfo& aInfo) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsContentProcess());
+
+ RefPtr<JSProcessActorProtocol> proto =
+ new JSProcessActorProtocol(aInfo.name());
+ JSActorProtocolUtils::FromIPCShared(proto, aInfo);
+
+ // Content processes aren't the parent process, so this flag is irrelevant and
+ // not propagated.
+ proto->mIncludeParent = false;
+
+ return proto.forget();
+}
+
+JSProcessActorInfo JSProcessActorProtocol::ToIPC() {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+ JSProcessActorInfo info;
+ JSActorProtocolUtils::ToIPCShared(info, this);
+
+ return info;
+}
+
+already_AddRefed<JSProcessActorProtocol>
+JSProcessActorProtocol::FromWebIDLOptions(const nsACString& aName,
+ const ProcessActorOptions& aOptions,
+ ErrorResult& aRv) {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+ RefPtr<JSProcessActorProtocol> proto = new JSProcessActorProtocol(aName);
+ if (!JSActorProtocolUtils::FromWebIDLOptionsShared(proto, aOptions, aRv)) {
+ return nullptr;
+ }
+
+ proto->mIncludeParent = aOptions.mIncludeParent;
+
+ return proto.forget();
+}
+
+NS_IMETHODIMP JSProcessActorProtocol::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
+
+ RefPtr<JSActorManager> manager;
+ if (XRE_IsParentProcess()) {
+ manager = InProcessChild::Singleton();
+ } else {
+ manager = ContentChild::GetSingleton();
+ }
+
+ // Ensure our actor is present.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ RefPtr<JSActor> actor = manager->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 JSProcessActorProtocol::AddObservers() {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ for (auto& topic : mChild.mObservers) {
+ // This makes the observer service hold an owning reference to the
+ // JSProcessActorProtocol. 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 JSProcessActorProtocol::RemoveObservers() {
+ nsCOMPtr<nsIObserverService> os = services::GetObserverService();
+ for (auto& topic : mChild.mObservers) {
+ os->RemoveObserver(this, topic.get());
+ }
+}
+
+bool JSProcessActorProtocol::RemoteTypePrefixMatches(
+ const nsDependentCSubstring& aRemoteType) {
+ for (auto& remoteType : mRemoteTypes) {
+ if (StringBeginsWith(aRemoteType, remoteType)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool JSProcessActorProtocol::Matches(const nsACString& aRemoteType,
+ ErrorResult& aRv) {
+ if (!mIncludeParent && aRemoteType.IsEmpty()) {
+ aRv.ThrowNotSupportedError(nsPrintfCString(
+ "Process protocol '%s' doesn't match the parent process", mName.get()));
+ return false;
+ }
+
+ if (!mRemoteTypes.IsEmpty() &&
+ !RemoteTypePrefixMatches(RemoteTypePrefix(aRemoteType))) {
+ aRv.ThrowNotSupportedError(nsPrintfCString(
+ "Process protocol '%s' doesn't support remote type '%s'", mName.get(),
+ PromiseFlatCString(aRemoteType).get()));
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/ipc/jsactor/JSProcessActorProtocol.h b/dom/ipc/jsactor/JSProcessActorProtocol.h
new file mode 100644
index 0000000000..eab8876c35
--- /dev/null
+++ b/dom/ipc/jsactor/JSProcessActorProtocol.h
@@ -0,0 +1,79 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_JSProcessActorProtocol_h
+#define mozilla_dom_JSProcessActorProtocol_h
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/JSActorService.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIObserver.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+struct ProcessActorOptions;
+class JSProcessActorInfo;
+class EventTarget;
+class JSActorProtocolUtils;
+
+/**
+ * Object corresponding to a single process actor protocol
+ *
+ * This object also can act as a carrier for methods and other state related to
+ * a single protocol managed by the JSActorService.
+ */
+class JSProcessActorProtocol final : public JSActorProtocol,
+ public nsIObserver {
+ public:
+ NS_DECL_NSIOBSERVER
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(JSProcessActorProtocol, nsIObserver)
+
+ static already_AddRefed<JSProcessActorProtocol> FromIPC(
+ const JSProcessActorInfo& aInfo);
+ JSProcessActorInfo ToIPC();
+
+ static already_AddRefed<JSProcessActorProtocol> FromWebIDLOptions(
+ const nsACString& aName, const ProcessActorOptions& aOptions,
+ ErrorResult& aRv);
+
+ struct ParentSide : public Sided {};
+
+ struct ChildSide : public Sided {
+ nsTArray<nsCString> mObservers;
+ };
+
+ const ParentSide& Parent() const override { return mParent; }
+ const ChildSide& Child() const override { return mChild; }
+
+ void AddObservers();
+ void RemoveObservers();
+ bool Matches(const nsACString& aRemoteType, ErrorResult& aRv);
+
+ private:
+ explicit JSProcessActorProtocol(const nsACString& aName) : mName(aName) {}
+ bool RemoteTypePrefixMatches(const nsDependentCSubstring& aRemoteType);
+ ~JSProcessActorProtocol() = default;
+
+ nsCString mName;
+ nsTArray<nsCString> mRemoteTypes;
+ bool mIncludeParent = false;
+
+ friend class JSActorProtocolUtils;
+
+ ParentSide mParent;
+ ChildSide mChild;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_JSProcessActorProtocol_h
diff --git a/dom/ipc/jsactor/JSWindowActorChild.cpp b/dom/ipc/jsactor/JSWindowActorChild.cpp
new file mode 100644
index 0000000000..4d028a0789
--- /dev/null
+++ b/dom/ipc/jsactor/JSWindowActorChild.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/JSWindowActorBinding.h"
+#include "mozilla/dom/JSWindowActorChild.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/WindowProxyHolder.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "nsGlobalWindowInner.h"
+
+namespace mozilla::dom {
+
+JSWindowActorChild::~JSWindowActorChild() { MOZ_ASSERT(!mManager); }
+
+JSObject* JSWindowActorChild::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return JSWindowActorChild_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+WindowGlobalChild* JSWindowActorChild::GetManager() const { return mManager; }
+
+WindowContext* JSWindowActorChild::GetWindowContext() const {
+ return mManager ? mManager->WindowContext() : nullptr;
+}
+
+void JSWindowActorChild::Init(const nsACString& aName,
+ WindowGlobalChild* aManager) {
+ MOZ_ASSERT(!mManager, "Cannot Init() a JSWindowActorChild twice!");
+ SetName(aName);
+ mManager = aManager;
+
+ InvokeCallback(CallbackFunction::ActorCreated);
+}
+
+#ifdef DEBUG
+# define DEBUG_WARN_MESSAGE_UNSENT(aMeta, aWarning) \
+ NS_DebugBreak( \
+ NS_DEBUG_WARNING, \
+ nsPrintfCString( \
+ "JSWindowActorChild::SendRawMessage (%s, %s) not sent: %s", \
+ (aMeta).actorName().get(), \
+ NS_LossyConvertUTF16toASCII((aMeta).messageName()).get(), \
+ (aWarning)) \
+ .get(), \
+ nullptr, __FILE__, __LINE__)
+#else
+# define DEBUG_WARN_MESSAGE_UNSENT(aMeta, aWarning)
+#endif
+
+void JSWindowActorChild::SendRawMessage(
+ const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) {
+ if (!CanSend() || !mManager || !mManager->CanSend()) {
+ DEBUG_WARN_MESSAGE_UNSENT(
+ aMeta, "!CanSend() || !mManager || !mManager->CanSend()");
+ aRv.ThrowInvalidStateError("JSWindowActorChild cannot send at the moment");
+ return;
+ }
+
+ if (mManager->IsInProcess()) {
+ SendRawMessageInProcess(
+ aMeta, std::move(aData), std::move(aStack),
+ [manager{mManager}]() { return manager->GetParentActor(); });
+ return;
+ }
+
+ // Cross-process case - send data over WindowGlobalChild to other side.
+ Maybe<ClonedMessageData> msgData;
+ if (aData) {
+ msgData.emplace();
+ if (!aData->BuildClonedMessageData(*msgData)) {
+ DEBUG_WARN_MESSAGE_UNSENT(aMeta,
+ "!aData->BuildClonedMessageData(*msgData)");
+ aRv.ThrowDataCloneError(
+ nsPrintfCString("JSWindowActorChild serialization error: cannot "
+ "clone, in actor '%s'",
+ PromiseFlatCString(aMeta.actorName()).get()));
+ return;
+ }
+ }
+
+ Maybe<ClonedMessageData> stackData;
+ if (aStack) {
+ stackData.emplace();
+ if (!aStack->BuildClonedMessageData(*stackData)) {
+ stackData.reset();
+ }
+ }
+
+ if (!mManager->SendRawMessage(aMeta, msgData, stackData)) {
+ DEBUG_WARN_MESSAGE_UNSENT(
+ aMeta, "!mManager->SendRawMessage(aMeta, msgData, stackData)");
+ aRv.ThrowOperationError(
+ nsPrintfCString("JSWindowActorChild send error in actor '%s'",
+ PromiseFlatCString(aMeta.actorName()).get()));
+ return;
+ }
+}
+
+Document* JSWindowActorChild::GetDocument(ErrorResult& aRv) {
+ if (!mManager) {
+ ThrowStateErrorForGetter("document", aRv);
+ return nullptr;
+ }
+
+ nsGlobalWindowInner* window = mManager->GetWindowGlobal();
+ return window ? window->GetDocument() : nullptr;
+}
+
+BrowsingContext* JSWindowActorChild::GetBrowsingContext(ErrorResult& aRv) {
+ if (!mManager) {
+ ThrowStateErrorForGetter("browsingContext", aRv);
+ return nullptr;
+ }
+
+ return mManager->BrowsingContext();
+}
+
+nsIDocShell* JSWindowActorChild::GetDocShell(ErrorResult& aRv) {
+ if (!mManager) {
+ ThrowStateErrorForGetter("docShell", aRv);
+ return nullptr;
+ }
+
+ return mManager->BrowsingContext()->GetDocShell();
+}
+
+Nullable<WindowProxyHolder> JSWindowActorChild::GetContentWindow(
+ ErrorResult& aRv) {
+ if (!mManager) {
+ ThrowStateErrorForGetter("contentWindow", aRv);
+ return nullptr;
+ }
+
+ if (nsGlobalWindowInner* window = mManager->GetWindowGlobal()) {
+ if (window->IsCurrentInnerWindow()) {
+ return WindowProxyHolder(window->GetBrowsingContext());
+ }
+ }
+
+ return nullptr;
+}
+
+void JSWindowActorChild::ClearManager() { mManager = nullptr; }
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(JSWindowActorChild, JSActor, mManager)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorChild)
+NS_INTERFACE_MAP_END_INHERITING(JSActor)
+
+NS_IMPL_ADDREF_INHERITED(JSWindowActorChild, JSActor)
+NS_IMPL_RELEASE_INHERITED(JSWindowActorChild, JSActor)
+
+} // namespace mozilla::dom
diff --git a/dom/ipc/jsactor/JSWindowActorChild.h b/dom/ipc/jsactor/JSWindowActorChild.h
new file mode 100644
index 0000000000..8e43d6a6c7
--- /dev/null
+++ b/dom/ipc/jsactor/JSWindowActorChild.h
@@ -0,0 +1,82 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_JSWindowActorChild_h
+#define mozilla_dom_JSWindowActorChild_h
+
+#include "js/RootingAPI.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/JSActor.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+#include "nsStringFwd.h"
+
+class nsIDocShell;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+template <typename>
+struct Nullable;
+
+class BrowsingContext;
+class Document;
+class WindowProxyHolder;
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla::dom {
+
+class JSWindowActorChild final : public JSActor {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSWindowActorChild, JSActor)
+
+ explicit JSWindowActorChild(nsISupports* aGlobal = nullptr)
+ : JSActor(aGlobal) {}
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<JSWindowActorChild> Constructor(
+ GlobalObject& aGlobal) {
+ return MakeAndAddRef<JSWindowActorChild>(aGlobal.GetAsSupports());
+ }
+
+ WindowGlobalChild* GetManager() const;
+ WindowContext* GetWindowContext() const;
+ void Init(const nsACString& aName, WindowGlobalChild* aManager);
+ void ClearManager() override;
+ Document* GetDocument(ErrorResult& aRv);
+ BrowsingContext* GetBrowsingContext(ErrorResult& aRv);
+ nsIDocShell* GetDocShell(ErrorResult& aRv);
+ Nullable<WindowProxyHolder> GetContentWindow(ErrorResult& aRv);
+
+ protected:
+ void SendRawMessage(const JSActorMessageMeta& aMeta,
+ Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack,
+ ErrorResult& aRv) override;
+
+ private:
+ ~JSWindowActorChild();
+
+ RefPtr<WindowGlobalChild> mManager;
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_JSWindowActorChild_h
diff --git a/dom/ipc/jsactor/JSWindowActorParent.cpp b/dom/ipc/jsactor/JSWindowActorParent.cpp
new file mode 100644
index 0000000000..0bb82a9b8e
--- /dev/null
+++ b/dom/ipc/jsactor/JSWindowActorParent.cpp
@@ -0,0 +1,102 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/JSWindowActorBinding.h"
+#include "mozilla/dom/JSWindowActorParent.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/dom/MessageManagerBinding.h"
+
+namespace mozilla::dom {
+
+JSWindowActorParent::~JSWindowActorParent() { MOZ_ASSERT(!mManager); }
+
+JSObject* JSWindowActorParent::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return JSWindowActorParent_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+WindowGlobalParent* JSWindowActorParent::GetManager() const { return mManager; }
+
+WindowContext* JSWindowActorParent::GetWindowContext() const {
+ return mManager;
+}
+
+void JSWindowActorParent::Init(const nsACString& aName,
+ WindowGlobalParent* aManager) {
+ MOZ_ASSERT(!mManager, "Cannot Init() a JSWindowActorParent twice!");
+ SetName(aName);
+ mManager = aManager;
+
+ InvokeCallback(CallbackFunction::ActorCreated);
+}
+
+void JSWindowActorParent::SendRawMessage(
+ const JSActorMessageMeta& aMeta, Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack, ErrorResult& aRv) {
+ if (NS_WARN_IF(!CanSend() || !mManager || !mManager->CanSend())) {
+ aRv.ThrowInvalidStateError("JSWindowActorParent cannot send at the moment");
+ return;
+ }
+
+ if (mManager->IsInProcess()) {
+ SendRawMessageInProcess(
+ aMeta, std::move(aData), std::move(aStack),
+ [manager{mManager}]() { return manager->GetChildActor(); });
+ return;
+ }
+
+ Maybe<ClonedMessageData> msgData;
+ if (aData) {
+ msgData.emplace();
+ if (NS_WARN_IF(!aData->BuildClonedMessageData(*msgData))) {
+ aRv.ThrowDataCloneError(
+ nsPrintfCString("JSWindowActorParent serialization error: cannot "
+ "clone, in actor '%s'",
+ PromiseFlatCString(aMeta.actorName()).get()));
+ return;
+ }
+ }
+
+ Maybe<ClonedMessageData> stackData;
+ if (aStack) {
+ stackData.emplace();
+ if (!aStack->BuildClonedMessageData(*stackData)) {
+ stackData.reset();
+ }
+ }
+
+ if (NS_WARN_IF(!mManager->SendRawMessage(aMeta, msgData, stackData))) {
+ aRv.ThrowOperationError(
+ nsPrintfCString("JSWindowActorParent send error in actor '%s'",
+ PromiseFlatCString(aMeta.actorName()).get()));
+ return;
+ }
+}
+
+CanonicalBrowsingContext* JSWindowActorParent::GetBrowsingContext(
+ ErrorResult& aRv) {
+ if (!mManager) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return nullptr;
+ }
+
+ return mManager->BrowsingContext();
+}
+
+void JSWindowActorParent::ClearManager() { mManager = nullptr; }
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(JSWindowActorParent, JSActor, mManager)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActorParent)
+NS_INTERFACE_MAP_END_INHERITING(JSActor)
+
+NS_IMPL_ADDREF_INHERITED(JSWindowActorParent, JSActor)
+NS_IMPL_RELEASE_INHERITED(JSWindowActorParent, JSActor)
+
+} // namespace mozilla::dom
diff --git a/dom/ipc/jsactor/JSWindowActorParent.h b/dom/ipc/jsactor/JSWindowActorParent.h
new file mode 100644
index 0000000000..05fb88f8ac
--- /dev/null
+++ b/dom/ipc/jsactor/JSWindowActorParent.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_dom_JSWindowActorParent_h
+#define mozilla_dom_JSWindowActorParent_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/JSActor.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+class WindowGlobalParent;
+
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla::dom {
+
+class JSWindowActorParent final : public JSActor {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSWindowActorParent, JSActor)
+
+ explicit JSWindowActorParent(nsISupports* aGlobal = nullptr)
+ : JSActor(aGlobal) {}
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<JSWindowActorParent> Constructor(
+ GlobalObject& aGlobal) {
+ return MakeAndAddRef<JSWindowActorParent>(aGlobal.GetAsSupports());
+ }
+
+ WindowGlobalParent* GetManager() const;
+ WindowContext* GetWindowContext() const;
+ void Init(const nsACString& aName, WindowGlobalParent* aManager);
+ void ClearManager() override;
+ CanonicalBrowsingContext* GetBrowsingContext(ErrorResult& aRv);
+
+ protected:
+ void SendRawMessage(const JSActorMessageMeta& aMeta,
+ Maybe<ipc::StructuredCloneData>&& aData,
+ Maybe<ipc::StructuredCloneData>&& aStack,
+ ErrorResult& aRv) override;
+
+ private:
+ ~JSWindowActorParent();
+
+ RefPtr<WindowGlobalParent> mManager;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_JSWindowActorParent_h
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
diff --git a/dom/ipc/jsactor/JSWindowActorProtocol.h b/dom/ipc/jsactor/JSWindowActorProtocol.h
new file mode 100644
index 0000000000..b0128d9307
--- /dev/null
+++ b/dom/ipc/jsactor/JSWindowActorProtocol.h
@@ -0,0 +1,103 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_JSWindowActorProtocol_h
+#define mozilla_dom_JSWindowActorProtocol_h
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/JSActorService.h"
+#include "mozilla/extensions/MatchPattern.h"
+#include "nsIURI.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIObserver.h"
+#include "nsIDOMEventListener.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+
+struct WindowActorOptions;
+class JSWindowActorInfo;
+class EventTarget;
+class JSActorProtocolUtils;
+
+/**
+ * Object corresponding to a single window actor protocol. This object acts as
+ * an Event listener for the actor which is called for events which would
+ * trigger actor creation.
+ *
+ * This object also can act as a carrier for methods and other state related to
+ * a single protocol managed by the JSActorService.
+ */
+class JSWindowActorProtocol final : public JSActorProtocol,
+ public nsIObserver,
+ public nsIDOMEventListener {
+ public:
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(JSWindowActorProtocol, nsIObserver)
+
+ static already_AddRefed<JSWindowActorProtocol> FromIPC(
+ const JSWindowActorInfo& aInfo);
+ JSWindowActorInfo ToIPC();
+
+ static already_AddRefed<JSWindowActorProtocol> FromWebIDLOptions(
+ const nsACString& aName, const WindowActorOptions& aOptions,
+ ErrorResult& aRv);
+
+ struct ParentSide : public Sided {};
+
+ struct EventDecl {
+ nsString mName;
+ EventListenerFlags mFlags;
+ Optional<bool> mPassive;
+ bool mCreateActor = true;
+ };
+
+ struct ChildSide : public Sided {
+ nsTArray<EventDecl> mEvents;
+ nsTArray<nsCString> mObservers;
+ };
+
+ const ParentSide& Parent() const override { return mParent; }
+ const ChildSide& Child() const override { return mChild; }
+
+ void RegisterListenersFor(EventTarget* aTarget);
+ void UnregisterListenersFor(EventTarget* aTarget);
+ void AddObservers();
+ void RemoveObservers();
+ bool Matches(BrowsingContext* aBrowsingContext, nsIURI* aURI,
+ const nsACString& aRemoteType, ErrorResult& aRv);
+
+ private:
+ explicit JSWindowActorProtocol(const nsACString& aName) : mName(aName) {}
+ extensions::MatchPatternSetCore* GetURIMatcher();
+ bool RemoteTypePrefixMatches(const nsDependentCSubstring& aRemoteType);
+ bool MessageManagerGroupMatches(BrowsingContext* aBrowsingContext);
+ ~JSWindowActorProtocol() = default;
+
+ nsCString mName;
+ bool mAllFrames = false;
+ bool mIncludeChrome = false;
+ nsTArray<nsString> mMatches;
+ nsTArray<nsCString> mRemoteTypes;
+ nsTArray<nsString> mMessageManagerGroups;
+
+ friend class JSActorProtocolUtils;
+
+ ParentSide mParent;
+ ChildSide mChild;
+
+ RefPtr<extensions::MatchPatternSetCore> mURIMatcher;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_JSWindowActorProtocol_h
diff --git a/dom/ipc/jsactor/moz.build b/dom/ipc/jsactor/moz.build
new file mode 100644
index 0000000000..6bc66df923
--- /dev/null
+++ b/dom/ipc/jsactor/moz.build
@@ -0,0 +1,42 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+ "JSActor.h",
+ "JSActorManager.h",
+ "JSActorService.h",
+ "JSProcessActorChild.h",
+ "JSProcessActorParent.h",
+ "JSProcessActorProtocol.h",
+ "JSWindowActorChild.h",
+ "JSWindowActorParent.h",
+ "JSWindowActorProtocol.h",
+]
+
+EXPORTS += [
+ "nsQueryActor.h",
+]
+
+UNIFIED_SOURCES += [
+ "JSActor.cpp",
+ "JSActorManager.cpp",
+ "JSActorService.cpp",
+ "JSProcessActorChild.cpp",
+ "JSProcessActorParent.cpp",
+ "JSProcessActorProtocol.cpp",
+ "JSWindowActorChild.cpp",
+ "JSWindowActorParent.cpp",
+ "JSWindowActorProtocol.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/js/xpconnect/loader",
+ "/js/xpconnect/src",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/ipc/jsactor/nsQueryActor.h b/dom/ipc/jsactor/nsQueryActor.h
new file mode 100644
index 0000000000..cbbda7e473
--- /dev/null
+++ b/dom/ipc/jsactor/nsQueryActor.h
@@ -0,0 +1,90 @@
+/* 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/. */
+
+#ifndef nsQueryActor_h
+#define nsQueryActor_h
+
+#include <type_traits>
+
+#include "nsCOMPtr.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/JSActor.h"
+#include "mozilla/dom/JSActorManager.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+
+class nsIDOMProcessChild;
+class nsIDOMProcessParent;
+
+// This type is used to get an XPCOM interface implemented by a JSActor from its
+// native manager.
+class MOZ_STACK_CLASS nsQueryJSActor final : public nsCOMPtr_helper {
+ public:
+ nsQueryJSActor(const nsLiteralCString aActorName,
+ mozilla::dom::JSActorManager* aManager)
+ : mActorName(aActorName), mManager(aManager) {}
+
+ nsresult NS_FASTCALL operator()(const nsIID& aIID,
+ void** aResult) const override {
+ if (!mManager) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ mozilla::dom::AutoJSAPI jsapi;
+ jsapi.Init();
+
+ RefPtr<mozilla::dom::JSActor> actor =
+ mManager->GetActor(jsapi.cx(), mActorName, mozilla::IgnoreErrors());
+ if (!actor) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+
+ return actor->QueryInterfaceActor(aIID, aResult);
+ }
+
+ private:
+ const nsLiteralCString mActorName;
+ mozilla::dom::JSActorManager* mManager;
+};
+
+// Request an XPCOM interface from a JSActor managed by `aManager`.
+//
+// These APIs will work with both JSWindowActors and JSProcessActors.
+template <size_t length>
+inline nsQueryJSActor do_QueryActor(const char (&aActorName)[length],
+ mozilla::dom::JSActorManager* aManager) {
+ return nsQueryJSActor(nsLiteralCString(aActorName), aManager);
+}
+
+// Overload for directly querying a JSWindowActorChild from an inner window.
+template <size_t length>
+inline nsQueryJSActor do_QueryActor(const char (&aActorName)[length],
+ nsPIDOMWindowInner* aWindow) {
+ return nsQueryJSActor(nsLiteralCString(aActorName),
+ aWindow ? aWindow->GetWindowGlobalChild() : nullptr);
+}
+
+// Overload for directly querying a JSWindowActorChild from a document.
+template <size_t length>
+inline nsQueryJSActor do_QueryActor(const char (&aActorName)[length],
+ mozilla::dom::Document* aDoc) {
+ return nsQueryJSActor(nsLiteralCString(aActorName),
+ aDoc ? aDoc->GetWindowGlobalChild() : nullptr);
+}
+
+// Overload for directly querying from a nsIDOMProcess{Parent,Child} without
+// confusing overload selection for types inheriting from both
+// nsIDOMProcess{Parent,Child} and JSActorManager.
+template <size_t length, typename T,
+ typename = std::enable_if_t<std::is_same_v<T, nsIDOMProcessParent> ||
+ std::is_same_v<T, nsIDOMProcessChild>>>
+inline nsQueryJSActor do_QueryActor(const char (&aActorName)[length],
+ T* aManager) {
+ return nsQueryJSActor(nsLiteralCString(aActorName),
+ aManager ? aManager->AsJSActorManager() : nullptr);
+}
+
+#endif // defined nsQueryActor_h