mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 17:55:50 +00:00
e1913dc244
There are a couple of places where JSWindowActor currently sends uninitialized StructuredCloneData objects in places where it wants the message data to be undefined. The problem with this is that it causes an error when trying to read the data, which leads to odd behavior like `sendQuery` promises never being settled if the message handler throws, or `sendAsyncMessage` and `sendQuery` failing to decode the message if the caller doesn't pass a second argument. The errors reported for these problems also have no context, which means it's very hard for a developer to figure out the source of the problem. And, to make matters worse, the errors look the same as structured clone encoding errors, so there isn't even the clue that the error is happening on decode. This patch updates the offending code to always explicitly initialize the structured clone data with `undefined` when that's what it wants, and adds assertions to make it more obvious where the decode errors are actually happening. Differential Revision: https://phabricator.services.mozilla.com/D35052 --HG-- extra : rebase_source : dd4720d63cd0722a554524e65d16e31b702adbf3 extra : source : 158a4000c44b9b17a7935340db79431d544fb556
336 lines
11 KiB
C++
336 lines
11 KiB
C++
/* -*- 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/JSWindowActor.h"
|
|
#include "mozilla/dom/JSWindowActorBinding.h"
|
|
#include "mozilla/dom/MessageManagerBinding.h"
|
|
#include "mozilla/dom/PWindowGlobal.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "js/Promise.h"
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActor)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(JSWindowActor)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(JSWindowActor)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(JSWindowActor)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSWindowActor)
|
|
tmp->RejectPendingQueries(); // Clear out & reject mPendingQueries
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSWindowActor)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingQueries)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(JSWindowActor)
|
|
|
|
JSWindowActor::JSWindowActor() : mNextQueryId(0) {}
|
|
|
|
void JSWindowActor::StartDestroy() {
|
|
DestroyCallback(DestroyCallbackFunction::WillDestroy);
|
|
}
|
|
|
|
void JSWindowActor::AfterDestroy() {
|
|
DestroyCallback(DestroyCallbackFunction::DidDestroy);
|
|
}
|
|
|
|
void JSWindowActor::DestroyCallback(DestroyCallbackFunction callback) {
|
|
AutoEntryScript aes(GetParentObject(), "JSWindowActor destroy callback");
|
|
JSContext* cx = aes.cx();
|
|
MozActorDestroyCallbacks callbacksHolder;
|
|
NS_ENSURE_TRUE_VOID(GetWrapper());
|
|
JS::Rooted<JS::Value> val(cx, JS::ObjectValue(*GetWrapper()));
|
|
if (NS_WARN_IF(!callbacksHolder.Init(cx, val))) {
|
|
return;
|
|
}
|
|
|
|
// Destroy callback is optional.
|
|
if (callback == DestroyCallbackFunction::WillDestroy) {
|
|
if (callbacksHolder.mWillDestroy.WasPassed()) {
|
|
callbacksHolder.mWillDestroy.Value()->Call(this);
|
|
}
|
|
} else {
|
|
if (callbacksHolder.mDidDestroy.WasPassed()) {
|
|
callbacksHolder.mDidDestroy.Value()->Call(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
void JSWindowActor::RejectPendingQueries() {
|
|
// Take our queries out, in case somehow rejecting promises can trigger
|
|
// additions or removals.
|
|
nsRefPtrHashtable<nsUint64HashKey, Promise> pendingQueries;
|
|
mPendingQueries.SwapElements(pendingQueries);
|
|
for (auto iter = pendingQueries.Iter(); !iter.Done(); iter.Next()) {
|
|
iter.Data()->MaybeReject(NS_ERROR_NOT_AVAILABLE);
|
|
}
|
|
}
|
|
|
|
void JSWindowActor::SetName(const nsAString& aName) {
|
|
MOZ_ASSERT(mName.IsEmpty(), "Cannot set name twice!");
|
|
mName = aName;
|
|
}
|
|
|
|
void JSWindowActor::SendAsyncMessage(JSContext* aCx,
|
|
const nsAString& aMessageName,
|
|
JS::Handle<JS::Value> aObj,
|
|
JS::Handle<JS::Value> aTransfers,
|
|
ErrorResult& aRv) {
|
|
ipc::StructuredCloneData data;
|
|
if (!nsFrameMessageManager::GetParamsForMessage(aCx, aObj, aTransfers,
|
|
data)) {
|
|
aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
|
|
return;
|
|
}
|
|
|
|
JSWindowActorMessageMeta meta;
|
|
meta.actorName() = mName;
|
|
meta.messageName() = aMessageName;
|
|
meta.kind() = JSWindowActorMessageKind::Message;
|
|
|
|
SendRawMessage(meta, std::move(data), aRv);
|
|
}
|
|
|
|
already_AddRefed<Promise> JSWindowActor::SendQuery(
|
|
JSContext* aCx, const nsAString& aMessageName, JS::Handle<JS::Value> aObj,
|
|
JS::Handle<JS::Value> aTransfers, ErrorResult& aRv) {
|
|
ipc::StructuredCloneData data;
|
|
if (!nsFrameMessageManager::GetParamsForMessage(aCx, aObj, aTransfers,
|
|
data)) {
|
|
aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
|
|
return nullptr;
|
|
}
|
|
|
|
nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
|
|
if (NS_WARN_IF(!global)) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<Promise> promise = Promise::Create(global, aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
JSWindowActorMessageMeta meta;
|
|
meta.actorName() = mName;
|
|
meta.messageName() = aMessageName;
|
|
meta.queryId() = mNextQueryId++;
|
|
meta.kind() = JSWindowActorMessageKind::Query;
|
|
|
|
mPendingQueries.Put(meta.queryId(), promise);
|
|
|
|
SendRawMessage(meta, std::move(data), aRv);
|
|
return promise.forget();
|
|
}
|
|
|
|
void JSWindowActor::ReceiveRawMessage(const JSWindowActorMessageMeta& aMetadata,
|
|
ipc::StructuredCloneData&& aData) {
|
|
AutoEntryScript aes(GetParentObject(), "JSWindowActor message handler");
|
|
JSContext* cx = aes.cx();
|
|
|
|
// Read the message into a JS object from IPC.
|
|
ErrorResult error;
|
|
JS::Rooted<JS::Value> data(cx);
|
|
aData.Read(cx, &data, error);
|
|
if (NS_WARN_IF(error.Failed())) {
|
|
if (XRE_IsParentProcess()) {
|
|
MOZ_ASSERT(false, "Should not receive non-decodable data");
|
|
} else {
|
|
MOZ_DIAGNOSTIC_ASSERT(false, "Should not receive non-decodable data");
|
|
}
|
|
MOZ_ALWAYS_TRUE(error.MaybeSetPendingException(cx));
|
|
return;
|
|
}
|
|
|
|
switch (aMetadata.kind()) {
|
|
case JSWindowActorMessageKind::QueryResolve:
|
|
case JSWindowActorMessageKind::QueryReject:
|
|
ReceiveQueryReply(cx, aMetadata, data, error);
|
|
break;
|
|
|
|
case JSWindowActorMessageKind::Message:
|
|
case JSWindowActorMessageKind::Query:
|
|
ReceiveMessageOrQuery(cx, aMetadata, data, error);
|
|
break;
|
|
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE();
|
|
}
|
|
|
|
if (NS_WARN_IF(error.Failed())) {
|
|
MOZ_ALWAYS_TRUE(error.MaybeSetPendingException(cx));
|
|
}
|
|
}
|
|
|
|
void JSWindowActor::ReceiveMessageOrQuery(
|
|
JSContext* aCx, const JSWindowActorMessageMeta& aMetadata,
|
|
JS::Handle<JS::Value> aData, ErrorResult& aRv) {
|
|
// The argument which we want to pass to IPC.
|
|
RootedDictionary<ReceiveMessageArgument> argument(aCx);
|
|
argument.mObjects = JS_NewPlainObject(aCx);
|
|
argument.mTarget = this;
|
|
argument.mName = aMetadata.messageName();
|
|
argument.mData = aData;
|
|
argument.mJson = aData;
|
|
argument.mSync = false;
|
|
|
|
JS::Rooted<JSObject*> self(aCx, GetWrapper());
|
|
JS::Rooted<JSObject*> global(aCx, JS::GetNonCCWObjectGlobal(self));
|
|
|
|
// We only need to create a promise if we're dealing with a query here. It
|
|
// will be resolved or rejected once the listener has been called. Our
|
|
// listener on this promise will then send the reply.
|
|
RefPtr<Promise> promise;
|
|
if (aMetadata.kind() == JSWindowActorMessageKind::Query) {
|
|
promise = Promise::Create(xpc::NativeGlobal(global), aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<QueryHandler> handler = new QueryHandler(this, aMetadata);
|
|
promise->AppendNativeHandler(handler);
|
|
}
|
|
|
|
// Invoke the actual callback.
|
|
JS::Rooted<JS::Value> retval(aCx);
|
|
RefPtr<MessageListener> messageListener =
|
|
new MessageListener(self, global, nullptr, nullptr);
|
|
messageListener->ReceiveMessage(argument, &retval, aRv,
|
|
"JSWindowActor receive message");
|
|
|
|
// If we have a promise, resolve or reject it respectively.
|
|
if (promise) {
|
|
if (aRv.Failed()) {
|
|
promise->MaybeReject(aRv);
|
|
} else {
|
|
promise->MaybeResolve(aCx, retval);
|
|
}
|
|
}
|
|
}
|
|
|
|
void JSWindowActor::ReceiveQueryReply(JSContext* aCx,
|
|
const JSWindowActorMessageMeta& aMetadata,
|
|
JS::Handle<JS::Value> aData,
|
|
ErrorResult& aRv) {
|
|
if (NS_WARN_IF(aMetadata.actorName() != mName)) {
|
|
aRv.Throw(NS_ERROR_UNEXPECTED);
|
|
return;
|
|
}
|
|
|
|
RefPtr<Promise> promise;
|
|
if (NS_WARN_IF(!mPendingQueries.Remove(aMetadata.queryId(),
|
|
getter_AddRefs(promise)))) {
|
|
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
|
return;
|
|
}
|
|
|
|
JSAutoRealm ar(aCx, promise->PromiseObj());
|
|
JS::RootedValue data(aCx, aData);
|
|
if (NS_WARN_IF(!JS_WrapValue(aCx, &data))) {
|
|
aRv.Throw(NS_ERROR_FAILURE);
|
|
return;
|
|
}
|
|
|
|
if (aMetadata.kind() == JSWindowActorMessageKind::QueryResolve) {
|
|
promise->MaybeResolve(aCx, data);
|
|
} else {
|
|
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
|
|
}
|
|
}
|
|
|
|
// Native handler for our generated promise which is used to handle Queries and
|
|
// send the reply when their promises have been resolved.
|
|
JSWindowActor::QueryHandler::QueryHandler(
|
|
JSWindowActor* aActor, const JSWindowActorMessageMeta& aMetadata)
|
|
: mActor(aActor),
|
|
mMessageName(aMetadata.messageName()),
|
|
mQueryId(aMetadata.queryId()) {}
|
|
|
|
void JSWindowActor::QueryHandler::RejectedCallback(
|
|
JSContext* aCx, JS::Handle<JS::Value> aValue) {
|
|
if (!mActor) {
|
|
return;
|
|
}
|
|
|
|
// 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.
|
|
Unused << JS::CallOriginalPromiseReject(aCx, aValue);
|
|
|
|
// The exception probably isn't cloneable, so just send down undefined.
|
|
ipc::StructuredCloneData data;
|
|
data.Write(aCx, JS::UndefinedHandleValue, IgnoredErrorResult());
|
|
SendReply(aCx, JSWindowActorMessageKind::QueryReject, std::move(data));
|
|
}
|
|
|
|
void JSWindowActor::QueryHandler::ResolvedCallback(
|
|
JSContext* aCx, JS::Handle<JS::Value> aValue) {
|
|
if (!mActor) {
|
|
return;
|
|
}
|
|
|
|
ipc::StructuredCloneData data;
|
|
data.InitScope(JS::StructuredCloneScope::DifferentProcess);
|
|
|
|
IgnoredErrorResult error;
|
|
data.Write(aCx, aValue, error);
|
|
if (NS_WARN_IF(error.Failed())) {
|
|
// We failed to serialize the message over IPC. Report this error to the
|
|
// console, and send a reject reply.
|
|
nsAutoString msg;
|
|
msg.Append(mActor->Name());
|
|
msg.Append(':');
|
|
msg.Append(mMessageName);
|
|
msg.Append(NS_LITERAL_STRING(": message reply cannot be cloned."));
|
|
nsContentUtils::LogSimpleConsoleError(msg, "chrome", false, true);
|
|
|
|
JS_ClearPendingException(aCx);
|
|
|
|
ipc::StructuredCloneData data;
|
|
data.Write(aCx, JS::UndefinedHandleValue, IgnoredErrorResult());
|
|
SendReply(aCx, JSWindowActorMessageKind::QueryReject, std::move(data));
|
|
return;
|
|
}
|
|
|
|
SendReply(aCx, JSWindowActorMessageKind::QueryResolve, std::move(data));
|
|
}
|
|
|
|
void JSWindowActor::QueryHandler::SendReply(JSContext* aCx,
|
|
JSWindowActorMessageKind aKind,
|
|
ipc::StructuredCloneData&& aData) {
|
|
MOZ_ASSERT(mActor);
|
|
|
|
JSWindowActorMessageMeta meta;
|
|
meta.actorName() = mActor->Name();
|
|
meta.messageName() = mMessageName;
|
|
meta.queryId() = mQueryId;
|
|
meta.kind() = aKind;
|
|
|
|
mActor->SendRawMessage(meta, std::move(aData), IgnoreErrors());
|
|
mActor = nullptr;
|
|
}
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActor::QueryHandler)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(JSWindowActor::QueryHandler)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(JSWindowActor::QueryHandler)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION(JSWindowActor::QueryHandler, mActor)
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|