summaryrefslogtreecommitdiffstats
path: root/js/ipc
diff options
context:
space:
mode:
Diffstat (limited to 'js/ipc')
-rw-r--r--js/ipc/CPOWTimer.cpp43
-rw-r--r--js/ipc/CPOWTimer.h44
-rw-r--r--js/ipc/CpowHolder.h25
-rw-r--r--js/ipc/CrossProcessObjectWrappers.h101
-rw-r--r--js/ipc/JavaScriptBase.h230
-rw-r--r--js/ipc/JavaScriptChild.cpp100
-rw-r--r--js/ipc/JavaScriptChild.h50
-rw-r--r--js/ipc/JavaScriptLogging.h231
-rw-r--r--js/ipc/JavaScriptParent.cpp212
-rw-r--r--js/ipc/JavaScriptParent.h43
-rw-r--r--js/ipc/JavaScriptShared.cpp760
-rw-r--r--js/ipc/JavaScriptShared.h236
-rw-r--r--js/ipc/JavaScriptTypes.ipdlh161
-rw-r--r--js/ipc/PJavaScript.ipdl60
-rw-r--r--js/ipc/WrapperAnswer.cpp798
-rw-r--r--js/ipc/WrapperAnswer.h80
-rw-r--r--js/ipc/WrapperOwner.cpp1236
-rw-r--r--js/ipc/WrapperOwner.h167
-rw-r--r--js/ipc/moz.build41
19 files changed, 4618 insertions, 0 deletions
diff --git a/js/ipc/CPOWTimer.cpp b/js/ipc/CPOWTimer.cpp
new file mode 100644
index 000000000..4f2add7f5
--- /dev/null
+++ b/js/ipc/CPOWTimer.cpp
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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 "jsfriendapi.h"
+#include "nsContentUtils.h"
+#include "CPOWTimer.h"
+
+#include "jsapi.h"
+
+CPOWTimer::CPOWTimer(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+ : cx_(nullptr)
+ , startInterval_(0)
+{
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ if (!js::GetStopwatchIsMonitoringCPOW(cx))
+ return;
+ cx_ = cx;
+ startInterval_ = JS_Now();
+}
+CPOWTimer::~CPOWTimer()
+{
+ if (!cx_) {
+ // Monitoring was off when we started the timer.
+ return;
+ }
+
+ if (!js::GetStopwatchIsMonitoringCPOW(cx_)) {
+ // Monitoring has been deactivated while we were in the timer.
+ return;
+ }
+
+ const int64_t endInterval = JS_Now();
+ if (endInterval <= startInterval_) {
+ // Do not assume monotonicity.
+ return;
+ }
+
+ js::AddCPOWPerformanceDelta(cx_, endInterval - startInterval_);
+}
diff --git a/js/ipc/CPOWTimer.h b/js/ipc/CPOWTimer.h
new file mode 100644
index 000000000..91dc66fd7
--- /dev/null
+++ b/js/ipc/CPOWTimer.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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 CPOWTIMER_H
+#define CPOWTIMER_H
+
+#include "prinrval.h"
+#include "jsapi.h"
+
+/**
+ * A stopwatch measuring the duration of a CPOW call.
+ *
+ * As the process is consuming neither user time nor system time
+ * during a CPOW call, we measure such durations using wallclock time.
+ *
+ * This stopwatch is active iff JSRuntime::stopwatch.isActive is set.
+ * Upon destruction, update JSRuntime::stopwatch.data.totalCPOWTime.
+ */
+class MOZ_RAII CPOWTimer final {
+ public:
+ explicit inline CPOWTimer(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+ ~CPOWTimer();
+
+ private:
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+ /**
+ * The context in which this timer was created, or `nullptr` if
+ * CPOW monitoring was off when the timer was created.
+ */
+ JSContext* cx_;
+
+ /**
+ * The instant at which the stopwatch was started. Undefined
+ * if CPOW monitoring was off when the timer was created.
+ */
+ int64_t startInterval_;
+};
+
+#endif
diff --git a/js/ipc/CpowHolder.h b/js/ipc/CpowHolder.h
new file mode 100644
index 000000000..2be878f39
--- /dev/null
+++ b/js/ipc/CpowHolder.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et 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_jsipc_CpowHolder_h__
+#define mozilla_jsipc_CpowHolder_h__
+
+#include "js/TypeDecls.h"
+
+namespace mozilla {
+namespace jsipc {
+
+class CpowHolder
+{
+ public:
+ virtual bool ToObject(JSContext* cx, JS::MutableHandle<JSObject*> objp) = 0;
+};
+
+} // namespace jsipc
+} // namespace mozilla
+
+#endif // mozilla_jsipc_CpowHolder_h__
diff --git a/js/ipc/CrossProcessObjectWrappers.h b/js/ipc/CrossProcessObjectWrappers.h
new file mode 100644
index 000000000..cb0db53e9
--- /dev/null
+++ b/js/ipc/CrossProcessObjectWrappers.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et 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_jsipc_CrossProcessObjectWrappers_h__
+#define mozilla_jsipc_CrossProcessObjectWrappers_h__
+
+#include "js/TypeDecls.h"
+#include "mozilla/jsipc/CpowHolder.h"
+#include "mozilla/jsipc/JavaScriptTypes.h"
+#include "nsID.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#ifdef XP_WIN
+#undef GetClassName
+#undef GetClassInfo
+#endif
+
+namespace mozilla {
+
+namespace dom {
+class CPOWManagerGetter;
+} // namespace dom
+
+namespace jsipc {
+
+class PJavaScriptParent;
+class PJavaScriptChild;
+
+class CPOWManager
+{
+ public:
+ virtual bool Unwrap(JSContext* cx,
+ const InfallibleTArray<CpowEntry>& aCpows,
+ JS::MutableHandleObject objp) = 0;
+
+ virtual bool Wrap(JSContext* cx,
+ JS::HandleObject aObj,
+ InfallibleTArray<CpowEntry>* outCpows) = 0;
+};
+
+class CrossProcessCpowHolder : public CpowHolder
+{
+ public:
+ CrossProcessCpowHolder(dom::CPOWManagerGetter* managerGetter,
+ const InfallibleTArray<CpowEntry>& cpows);
+
+ ~CrossProcessCpowHolder();
+
+ bool ToObject(JSContext* cx, JS::MutableHandleObject objp);
+
+ private:
+ CPOWManager* js_;
+ const InfallibleTArray<CpowEntry>& cpows_;
+ bool unwrapped_;
+};
+
+CPOWManager*
+CPOWManagerFor(PJavaScriptParent* aParent);
+
+CPOWManager*
+CPOWManagerFor(PJavaScriptChild* aChild);
+
+bool
+IsCPOW(JSObject* obj);
+
+bool
+IsWrappedCPOW(JSObject* obj);
+
+nsresult
+InstanceOf(JSObject* obj, const nsID* id, bool* bp);
+
+bool
+DOMInstanceOf(JSContext* cx, JSObject* obj, int prototypeID, int depth, bool* bp);
+
+void
+GetWrappedCPOWTag(JSObject* obj, nsACString& out);
+
+PJavaScriptParent*
+NewJavaScriptParent();
+
+void
+ReleaseJavaScriptParent(PJavaScriptParent* parent);
+
+PJavaScriptChild*
+NewJavaScriptChild();
+
+void
+ReleaseJavaScriptChild(PJavaScriptChild* child);
+
+void
+AfterProcessTask();
+
+} // namespace jsipc
+} // namespace mozilla
+
+#endif // mozilla_jsipc_CrossProcessObjectWrappers_h__
diff --git a/js/ipc/JavaScriptBase.h b/js/ipc/JavaScriptBase.h
new file mode 100644
index 000000000..9970bb031
--- /dev/null
+++ b/js/ipc/JavaScriptBase.h
@@ -0,0 +1,230 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et 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_jsipc_JavaScriptBase_h__
+#define mozilla_jsipc_JavaScriptBase_h__
+
+#include "WrapperAnswer.h"
+#include "WrapperOwner.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/jsipc/PJavaScript.h"
+
+namespace mozilla {
+namespace jsipc {
+
+template<class Base>
+class JavaScriptBase : public WrapperOwner, public WrapperAnswer, public Base
+{
+ typedef WrapperAnswer Answer;
+
+ public:
+ virtual ~JavaScriptBase() {}
+
+ virtual void ActorDestroy(WrapperOwner::ActorDestroyReason why) {
+ WrapperOwner::ActorDestroy(why);
+ }
+
+ /*** IPC handlers ***/
+
+ bool RecvPreventExtensions(const uint64_t& objId, ReturnStatus* rs) {
+ return Answer::RecvPreventExtensions(ObjectId::deserialize(objId), rs);
+ }
+ bool RecvGetPropertyDescriptor(const uint64_t& objId, const JSIDVariant& id,
+ ReturnStatus* rs,
+ PPropertyDescriptor* out) {
+ return Answer::RecvGetPropertyDescriptor(ObjectId::deserialize(objId), id, rs, out);
+ }
+ bool RecvGetOwnPropertyDescriptor(const uint64_t& objId,
+ const JSIDVariant& id,
+ ReturnStatus* rs,
+ PPropertyDescriptor* out) {
+ return Answer::RecvGetOwnPropertyDescriptor(ObjectId::deserialize(objId), id, rs, out);
+ }
+ bool RecvDefineProperty(const uint64_t& objId, const JSIDVariant& id,
+ const PPropertyDescriptor& flags, ReturnStatus* rs) {
+ return Answer::RecvDefineProperty(ObjectId::deserialize(objId), id, flags, rs);
+ }
+ bool RecvDelete(const uint64_t& objId, const JSIDVariant& id,
+ ReturnStatus* rs) {
+ return Answer::RecvDelete(ObjectId::deserialize(objId), id, rs);
+ }
+
+ bool RecvHas(const uint64_t& objId, const JSIDVariant& id,
+ ReturnStatus* rs, bool* bp) {
+ return Answer::RecvHas(ObjectId::deserialize(objId), id, rs, bp);
+ }
+ bool RecvHasOwn(const uint64_t& objId, const JSIDVariant& id,
+ ReturnStatus* rs, bool* bp) {
+ return Answer::RecvHasOwn(ObjectId::deserialize(objId), id, rs, bp);
+ }
+ bool RecvGet(const uint64_t& objId, const JSVariant& receiverVar, const JSIDVariant& id,
+ ReturnStatus* rs, JSVariant* result) {
+ return Answer::RecvGet(ObjectId::deserialize(objId), receiverVar, id, rs, result);
+ }
+ bool RecvSet(const uint64_t& objId, const JSIDVariant& id, const JSVariant& value,
+ const JSVariant& receiverVar, ReturnStatus* rs) {
+ return Answer::RecvSet(ObjectId::deserialize(objId), id, value, receiverVar, rs);
+ }
+
+ bool RecvIsExtensible(const uint64_t& objId, ReturnStatus* rs,
+ bool* result) {
+ return Answer::RecvIsExtensible(ObjectId::deserialize(objId), rs, result);
+ }
+ bool RecvCallOrConstruct(const uint64_t& objId, InfallibleTArray<JSParam>&& argv,
+ const bool& construct, ReturnStatus* rs, JSVariant* result,
+ nsTArray<JSParam>* outparams) {
+ return Answer::RecvCallOrConstruct(ObjectId::deserialize(objId), Move(argv), construct, rs, result, outparams);
+ }
+ bool RecvHasInstance(const uint64_t& objId, const JSVariant& v, ReturnStatus* rs, bool* bp) {
+ return Answer::RecvHasInstance(ObjectId::deserialize(objId), v, rs, bp);
+ }
+ bool RecvGetBuiltinClass(const uint64_t& objId, ReturnStatus* rs, uint32_t* classValue) {
+ return Answer::RecvGetBuiltinClass(ObjectId::deserialize(objId), rs, classValue);
+ }
+ bool RecvIsArray(const uint64_t& objId, ReturnStatus* rs, uint32_t* answer) {
+ return Answer::RecvIsArray(ObjectId::deserialize(objId), rs, answer);
+ }
+ bool RecvClassName(const uint64_t& objId, nsCString* result) {
+ return Answer::RecvClassName(ObjectId::deserialize(objId), result);
+ }
+ bool RecvGetPrototype(const uint64_t& objId, ReturnStatus* rs, ObjectOrNullVariant* result) {
+ return Answer::RecvGetPrototype(ObjectId::deserialize(objId), rs, result);
+ }
+ bool RecvGetPrototypeIfOrdinary(const uint64_t& objId, ReturnStatus* rs, bool* isOrdinary,
+ ObjectOrNullVariant* result)
+ {
+ return Answer::RecvGetPrototypeIfOrdinary(ObjectId::deserialize(objId), rs, isOrdinary, result);
+ }
+ bool RecvRegExpToShared(const uint64_t& objId, ReturnStatus* rs, nsString* source, uint32_t* flags) {
+ return Answer::RecvRegExpToShared(ObjectId::deserialize(objId), rs, source, flags);
+ }
+
+ bool RecvGetPropertyKeys(const uint64_t& objId, const uint32_t& flags,
+ ReturnStatus* rs, nsTArray<JSIDVariant>* ids) {
+ return Answer::RecvGetPropertyKeys(ObjectId::deserialize(objId), flags, rs, ids);
+ }
+ bool RecvInstanceOf(const uint64_t& objId, const JSIID& iid,
+ ReturnStatus* rs, bool* instanceof) {
+ return Answer::RecvInstanceOf(ObjectId::deserialize(objId), iid, rs, instanceof);
+ }
+ bool RecvDOMInstanceOf(const uint64_t& objId, const int& prototypeID, const int& depth,
+ ReturnStatus* rs, bool* instanceof) {
+ return Answer::RecvDOMInstanceOf(ObjectId::deserialize(objId), prototypeID, depth, rs, instanceof);
+ }
+
+ bool RecvDropObject(const uint64_t& objId) {
+ return Answer::RecvDropObject(ObjectId::deserialize(objId));
+ }
+
+ /*** Dummy call handlers ***/
+
+ bool SendDropObject(const ObjectId& objId) {
+ return Base::SendDropObject(objId.serialize());
+ }
+ bool SendPreventExtensions(const ObjectId& objId, ReturnStatus* rs) {
+ return Base::SendPreventExtensions(objId.serialize(), rs);
+ }
+ bool SendGetPropertyDescriptor(const ObjectId& objId, const JSIDVariant& id,
+ ReturnStatus* rs,
+ PPropertyDescriptor* out) {
+ return Base::SendGetPropertyDescriptor(objId.serialize(), id, rs, out);
+ }
+ bool SendGetOwnPropertyDescriptor(const ObjectId& objId,
+ const JSIDVariant& id,
+ ReturnStatus* rs,
+ PPropertyDescriptor* out) {
+ return Base::SendGetOwnPropertyDescriptor(objId.serialize(), id, rs, out);
+ }
+ bool SendDefineProperty(const ObjectId& objId, const JSIDVariant& id,
+ const PPropertyDescriptor& flags,
+ ReturnStatus* rs) {
+ return Base::SendDefineProperty(objId.serialize(), id, flags, rs);
+ }
+ bool SendDelete(const ObjectId& objId, const JSIDVariant& id, ReturnStatus* rs) {
+ return Base::SendDelete(objId.serialize(), id, rs);
+ }
+
+ bool SendHas(const ObjectId& objId, const JSIDVariant& id,
+ ReturnStatus* rs, bool* bp) {
+ return Base::SendHas(objId.serialize(), id, rs, bp);
+ }
+ bool SendHasOwn(const ObjectId& objId, const JSIDVariant& id,
+ ReturnStatus* rs, bool* bp) {
+ return Base::SendHasOwn(objId.serialize(), id, rs, bp);
+ }
+ bool SendGet(const ObjectId& objId, const JSVariant& receiverVar, const JSIDVariant& id,
+ ReturnStatus* rs, JSVariant* result) {
+ return Base::SendGet(objId.serialize(), receiverVar, id, rs, result);
+ }
+ bool SendSet(const ObjectId& objId, const JSIDVariant& id, const JSVariant& value,
+ const JSVariant& receiverVar, ReturnStatus* rs) {
+ return Base::SendSet(objId.serialize(), id, value, receiverVar, rs);
+ }
+
+ bool SendIsExtensible(const ObjectId& objId, ReturnStatus* rs,
+ bool* result) {
+ return Base::SendIsExtensible(objId.serialize(), rs, result);
+ }
+ bool SendCallOrConstruct(const ObjectId& objId, const nsTArray<JSParam>& argv,
+ const bool& construct, ReturnStatus* rs, JSVariant* result,
+ nsTArray<JSParam>* outparams) {
+ return Base::SendCallOrConstruct(objId.serialize(), argv, construct, rs, result, outparams);
+ }
+ bool SendHasInstance(const ObjectId& objId, const JSVariant& v, ReturnStatus* rs, bool* bp) {
+ return Base::SendHasInstance(objId.serialize(), v, rs, bp);
+ }
+ bool SendGetBuiltinClass(const ObjectId& objId, ReturnStatus* rs, uint32_t* classValue) {
+ return Base::SendGetBuiltinClass(objId.serialize(), rs, classValue);
+ }
+ bool SendIsArray(const ObjectId& objId, ReturnStatus* rs, uint32_t* answer)
+ {
+ return Base::SendIsArray(objId.serialize(), rs, answer);
+ }
+ bool SendClassName(const ObjectId& objId, nsCString* result) {
+ return Base::SendClassName(objId.serialize(), result);
+ }
+ bool SendGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result) {
+ return Base::SendGetPrototype(objId.serialize(), rs, result);
+ }
+ bool SendGetPrototypeIfOrdinary(const ObjectId& objId, ReturnStatus* rs, bool* isOrdinary,
+ ObjectOrNullVariant* result)
+ {
+ return Base::SendGetPrototypeIfOrdinary(objId.serialize(), rs, isOrdinary, result);
+ }
+
+ bool SendRegExpToShared(const ObjectId& objId, ReturnStatus* rs,
+ nsString* source, uint32_t* flags) {
+ return Base::SendRegExpToShared(objId.serialize(), rs, source, flags);
+ }
+
+ bool SendGetPropertyKeys(const ObjectId& objId, const uint32_t& flags,
+ ReturnStatus* rs, nsTArray<JSIDVariant>* ids) {
+ return Base::SendGetPropertyKeys(objId.serialize(), flags, rs, ids);
+ }
+ bool SendInstanceOf(const ObjectId& objId, const JSIID& iid,
+ ReturnStatus* rs, bool* instanceof) {
+ return Base::SendInstanceOf(objId.serialize(), iid, rs, instanceof);
+ }
+ bool SendDOMInstanceOf(const ObjectId& objId, const int& prototypeID, const int& depth,
+ ReturnStatus* rs, bool* instanceof) {
+ return Base::SendDOMInstanceOf(objId.serialize(), prototypeID, depth, rs, instanceof);
+ }
+
+ /* The following code is needed to suppress a bogus MSVC warning (C4250). */
+
+ virtual bool toObjectVariant(JSContext* cx, JSObject* obj, ObjectVariant* objVarp) {
+ return WrapperOwner::toObjectVariant(cx, obj, objVarp);
+ }
+ virtual JSObject* fromObjectVariant(JSContext* cx, const ObjectVariant& objVar) {
+ return WrapperOwner::fromObjectVariant(cx, objVar);
+ }
+};
+
+} // namespace jsipc
+} // namespace mozilla
+
+#endif
diff --git a/js/ipc/JavaScriptChild.cpp b/js/ipc/JavaScriptChild.cpp
new file mode 100644
index 000000000..1ba5a4ca9
--- /dev/null
+++ b/js/ipc/JavaScriptChild.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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 "JavaScriptChild.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/ipc/MessageChannel.h"
+#include "nsContentUtils.h"
+#include "xpcprivate.h"
+#include "jsfriendapi.h"
+#include "AccessCheck.h"
+
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::jsipc;
+
+using mozilla::AutoSafeJSContext;
+
+static void
+UpdateChildWeakPointersBeforeSweepingZoneGroup(JSContext* cx, void* data)
+{
+ static_cast<JavaScriptChild*>(data)->updateWeakPointers();
+}
+
+static void
+TraceChild(JSTracer* trc, void* data)
+{
+ static_cast<JavaScriptChild*>(data)->trace(trc);
+}
+
+JavaScriptChild::~JavaScriptChild()
+{
+ JSContext* cx = dom::danger::GetJSContext();
+ JS_RemoveWeakPointerZoneGroupCallback(cx, UpdateChildWeakPointersBeforeSweepingZoneGroup);
+ JS_RemoveExtraGCRootsTracer(cx, TraceChild, this);
+}
+
+bool
+JavaScriptChild::init()
+{
+ if (!WrapperOwner::init())
+ return false;
+ if (!WrapperAnswer::init())
+ return false;
+
+ JSContext* cx = dom::danger::GetJSContext();
+ JS_AddWeakPointerZoneGroupCallback(cx, UpdateChildWeakPointersBeforeSweepingZoneGroup, this);
+ JS_AddExtraGCRootsTracer(cx, TraceChild, this);
+ return true;
+}
+
+void
+JavaScriptChild::trace(JSTracer* trc)
+{
+ objects_.trace(trc, strongReferenceObjIdMinimum_);
+}
+
+void
+JavaScriptChild::updateWeakPointers()
+{
+ objects_.sweep();
+ unwaivedObjectIds_.sweep();
+ waivedObjectIds_.sweep();
+}
+
+JSObject*
+JavaScriptChild::scopeForTargetObjects()
+{
+ // CPOWs from the parent need to point into the child's privileged junk
+ // scope so that they can benefit from XrayWrappers in the child.
+ return xpc::PrivilegedJunkScope();
+}
+
+bool
+JavaScriptChild::RecvDropTemporaryStrongReferences(const uint64_t& upToObjId)
+{
+ strongReferenceObjIdMinimum_ = upToObjId + 1;
+ return true;
+}
+
+PJavaScriptChild*
+mozilla::jsipc::NewJavaScriptChild()
+{
+ JavaScriptChild* child = new JavaScriptChild();
+ if (!child->init()) {
+ delete child;
+ return nullptr;
+ }
+ return child;
+}
+
+void
+mozilla::jsipc::ReleaseJavaScriptChild(PJavaScriptChild* child)
+{
+ static_cast<JavaScriptChild*>(child)->decref();
+}
diff --git a/js/ipc/JavaScriptChild.h b/js/ipc/JavaScriptChild.h
new file mode 100644
index 000000000..c545c5f82
--- /dev/null
+++ b/js/ipc/JavaScriptChild.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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_jsipc_JavaScriptChild_h_
+#define mozilla_jsipc_JavaScriptChild_h_
+
+#include "JavaScriptBase.h"
+#include "mozilla/jsipc/PJavaScriptChild.h"
+
+namespace mozilla {
+namespace jsipc {
+
+class JavaScriptChild : public JavaScriptBase<PJavaScriptChild>
+{
+ public:
+ JavaScriptChild() : strongReferenceObjIdMinimum_(0) {}
+ virtual ~JavaScriptChild();
+
+ bool init();
+ void trace(JSTracer* trc);
+ void updateWeakPointers();
+
+ void drop(JSObject* obj);
+
+ bool allowMessage(JSContext* cx) override { return true; }
+
+ protected:
+ virtual bool isParent() override { return false; }
+ virtual JSObject* scopeForTargetObjects() override;
+
+ bool RecvDropTemporaryStrongReferences(const uint64_t& upToObjId) override;
+
+ private:
+ bool fail(JSContext* cx, ReturnStatus* rs);
+ bool ok(ReturnStatus* rs);
+
+ // JavaScriptChild will keep strong references to JS objects that are
+ // referenced by the parent only if their ID is >=
+ // strongReferenceObjIdMinimum_.
+ uint64_t strongReferenceObjIdMinimum_;
+};
+
+} // namespace jsipc
+} // namespace mozilla
+
+#endif
diff --git a/js/ipc/JavaScriptLogging.h b/js/ipc/JavaScriptLogging.h
new file mode 100644
index 000000000..187579a4b
--- /dev/null
+++ b/js/ipc/JavaScriptLogging.h
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et 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_jsipc_JavaScriptLogging__
+#define mozilla_jsipc_JavaScriptLogging__
+
+#include "nsString.h"
+#include "nsPrintfCString.h"
+#include "jsfriendapi.h"
+#include "jswrapper.h"
+
+namespace mozilla {
+namespace jsipc {
+
+#define LOG(...) \
+ PR_BEGIN_MACRO \
+ if (LoggingEnabled()) { \
+ Logging log(this, cx); \
+ log.print(__VA_ARGS__); \
+ } \
+ PR_END_MACRO
+
+#define LOG_STACK() \
+ PR_BEGIN_MACRO \
+ if (StackLoggingEnabled()) { \
+ js::DumpBacktrace(cx); \
+ } \
+ PR_END_MACRO
+
+struct ReceiverObj
+{
+ ObjectId id;
+ explicit ReceiverObj(ObjectId id) : id(id) {}
+};
+
+struct InVariant
+{
+ JSVariant variant;
+ explicit InVariant(const JSVariant& variant) : variant(variant) {}
+};
+
+struct OutVariant
+{
+ JSVariant variant;
+ explicit OutVariant(const JSVariant& variant) : variant(variant) {}
+};
+
+struct Identifier
+{
+ JSIDVariant variant;
+ explicit Identifier(const JSIDVariant& variant) : variant(variant) {}
+};
+
+class Logging
+{
+ public:
+ Logging(JavaScriptShared* shared, JSContext* cx) : shared(shared), cx(cx) {}
+
+ void print(const nsCString& str) {
+ const char* side = shared->isParent() ? "from child" : "from parent";
+ printf("CPOW %s: %s\n", side, str.get());
+ }
+
+ void print(const char* str) {
+ print(nsCString(str));
+ }
+ template<typename T1>
+ void print(const char* fmt, const T1& a1) {
+ nsAutoCString tmp1;
+ format(a1, tmp1);
+ print(nsPrintfCString(fmt, tmp1.get()));
+ }
+ template<typename T1, typename T2>
+ void print(const char* fmt, const T1& a1, const T2& a2) {
+ nsAutoCString tmp1;
+ nsAutoCString tmp2;
+ format(a1, tmp1);
+ format(a2, tmp2);
+ print(nsPrintfCString(fmt, tmp1.get(), tmp2.get()));
+ }
+ template<typename T1, typename T2, typename T3>
+ void print(const char* fmt, const T1& a1, const T2& a2, const T3& a3) {
+ nsAutoCString tmp1;
+ nsAutoCString tmp2;
+ nsAutoCString tmp3;
+ format(a1, tmp1);
+ format(a2, tmp2);
+ format(a3, tmp3);
+ print(nsPrintfCString(fmt, tmp1.get(), tmp2.get(), tmp3.get()));
+ }
+
+ void format(const nsString& str, nsCString& out) {
+ out = NS_ConvertUTF16toUTF8(str);
+ }
+
+ void formatObject(bool incoming, bool local, ObjectId id, nsCString& out) {
+ const char* side;
+ const char* objDesc;
+ void* ptr;
+
+ if (local == incoming) {
+ JS::RootedObject obj(cx);
+ obj = shared->objects_.find(id);
+ if (obj) {
+ JSAutoCompartment ac(cx, obj);
+ objDesc = js::ObjectClassName(cx, obj);
+ } else {
+ objDesc = "<dead object>";
+ }
+
+ side = shared->isParent() ? "parent" : "child";
+ ptr = js::UncheckedUnwrap(obj, true);
+ } else {
+ objDesc = "<cpow>";
+ side = shared->isParent() ? "child" : "parent";
+ ptr = nullptr;
+ }
+
+ out = nsPrintfCString("<%s %s:%d:%p>", side, objDesc, id.serialNumber(), ptr);
+ }
+
+ void format(const ReceiverObj& obj, nsCString& out) {
+ formatObject(true, true, obj.id, out);
+ }
+
+ void format(const nsTArray<JSParam>& values, nsCString& out) {
+ nsAutoCString tmp;
+ out.Truncate();
+ for (size_t i = 0; i < values.Length(); i++) {
+ if (i)
+ out.AppendLiteral(", ");
+ if (values[i].type() == JSParam::Tvoid_t) {
+ out.AppendLiteral("<void>");
+ } else {
+ format(InVariant(values[i].get_JSVariant()), tmp);
+ out += tmp;
+ }
+ }
+ }
+
+ void format(const InVariant& value, nsCString& out) {
+ format(true, value.variant, out);
+ }
+
+ void format(const OutVariant& value, nsCString& out) {
+ format(false, value.variant, out);
+ }
+
+ void format(bool incoming, const JSVariant& value, nsCString& out) {
+ switch (value.type()) {
+ case JSVariant::TUndefinedVariant: {
+ out = "undefined";
+ break;
+ }
+ case JSVariant::TNullVariant: {
+ out = "null";
+ break;
+ }
+ case JSVariant::TnsString: {
+ nsAutoCString tmp;
+ format(value.get_nsString(), tmp);
+ out = nsPrintfCString("\"%s\"", tmp.get());
+ break;
+ }
+ case JSVariant::TObjectVariant: {
+ const ObjectVariant& ovar = value.get_ObjectVariant();
+ if (ovar.type() == ObjectVariant::TLocalObject)
+ formatObject(incoming, true, ObjectId::deserialize(ovar.get_LocalObject().serializedId()), out);
+ else
+ formatObject(incoming, false, ObjectId::deserialize(ovar.get_RemoteObject().serializedId()), out);
+ break;
+ }
+ case JSVariant::TSymbolVariant: {
+ out = "<Symbol>";
+ break;
+ }
+ case JSVariant::Tdouble: {
+ out = nsPrintfCString("%.0f", value.get_double());
+ break;
+ }
+ case JSVariant::Tbool: {
+ out = value.get_bool() ? "true" : "false";
+ break;
+ }
+ case JSVariant::TJSIID: {
+ out = "<JSIID>";
+ break;
+ }
+ default: {
+ out = "<JSIID>";
+ break;
+ }
+ }
+ }
+
+ void format(const Identifier& id, nsCString& out) {
+ switch (id.variant.type()) {
+ case JSIDVariant::TSymbolVariant: {
+ out = "<Symbol>";
+ break;
+ }
+ case JSIDVariant::TnsString: {
+ nsAutoCString tmp;
+ format(id.variant.get_nsString(), tmp);
+ out = nsPrintfCString("\"%s\"", tmp.get());
+ break;
+ }
+ case JSIDVariant::Tint32_t: {
+ out = nsPrintfCString("%d", id.variant.get_int32_t());
+ break;
+ }
+ default: {
+ out = "Unknown";
+ break;
+ }
+ }
+ }
+
+ private:
+ JavaScriptShared* shared;
+ JSContext* cx;
+};
+
+} // namespace jsipc
+} // namespace mozilla
+
+#endif
diff --git a/js/ipc/JavaScriptParent.cpp b/js/ipc/JavaScriptParent.cpp
new file mode 100644
index 000000000..7fe92d662
--- /dev/null
+++ b/js/ipc/JavaScriptParent.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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 "JavaScriptParent.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsJSUtils.h"
+#include "jsfriendapi.h"
+#include "jswrapper.h"
+#include "js/Proxy.h"
+#include "js/HeapAPI.h"
+#include "xpcprivate.h"
+#include "mozilla/Casting.h"
+#include "mozilla/Telemetry.h"
+#include "nsAutoPtr.h"
+
+using namespace js;
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::jsipc;
+using namespace mozilla::dom;
+
+static void
+TraceParent(JSTracer* trc, void* data)
+{
+ static_cast<JavaScriptParent*>(data)->trace(trc);
+}
+
+JavaScriptParent::~JavaScriptParent()
+{
+ JS_RemoveExtraGCRootsTracer(danger::GetJSContext(), TraceParent, this);
+}
+
+bool
+JavaScriptParent::init()
+{
+ if (!WrapperOwner::init())
+ return false;
+
+ JS_AddExtraGCRootsTracer(danger::GetJSContext(), TraceParent, this);
+ return true;
+}
+
+static bool
+ForbidUnsafeBrowserCPOWs()
+{
+ static bool result;
+ static bool cached = false;
+ if (!cached) {
+ cached = true;
+ Preferences::AddBoolVarCache(&result, "dom.ipc.cpows.forbid-unsafe-from-browser", false);
+ }
+ return result;
+}
+
+// Should we allow CPOWs in aAddonId, even though it's marked as multiprocess
+// compatible? This is controlled by two prefs:
+// If dom.ipc.cpows.forbid-cpows-in-compat-addons is false, then we allow the CPOW.
+// If dom.ipc.cpows.forbid-cpows-in-compat-addons is true:
+// We check if aAddonId is listed in dom.ipc.cpows.allow-cpows-in-compat-addons
+// (which should be a comma-separated string). If it's present there, we allow
+// the CPOW. Otherwise we forbid the CPOW.
+static bool
+ForbidCPOWsInCompatibleAddon(const nsACString& aAddonId)
+{
+ bool forbid = Preferences::GetBool("dom.ipc.cpows.forbid-cpows-in-compat-addons", false);
+ if (!forbid) {
+ return false;
+ }
+
+ nsCString allow;
+ allow.Assign(',');
+ allow.Append(Preferences::GetCString("dom.ipc.cpows.allow-cpows-in-compat-addons"));
+ allow.Append(',');
+
+ nsCString searchString(",");
+ searchString.Append(aAddonId);
+ searchString.Append(',');
+ return allow.Find(searchString) == kNotFound;
+}
+
+bool
+JavaScriptParent::allowMessage(JSContext* cx)
+{
+ // If we're running browser code, then we allow all safe CPOWs and forbid
+ // unsafe CPOWs based on a pref (which defaults to forbidden). We also allow
+ // CPOWs unconditionally in selected globals (based on
+ // Cu.permitCPOWsInScope).
+ //
+ // If we're running add-on code, then we check if the add-on is multiprocess
+ // compatible (which eventually translates to a given setting of allowCPOWs
+ // on the scopw). If it's not compatible, then we allow the CPOW but
+ // warn. If it is marked as compatible, then we check the
+ // ForbidCPOWsInCompatibleAddon; see the comment there.
+
+ MessageChannel* channel = GetIPCChannel();
+ bool isSafe = channel->IsInTransaction();
+
+ bool warn = !isSafe;
+ nsIGlobalObject* global = dom::GetIncumbentGlobal();
+ JSObject* jsGlobal = global ? global->GetGlobalJSObject() : nullptr;
+ if (jsGlobal) {
+ JSAutoCompartment ac(cx, jsGlobal);
+ JSAddonId* addonId = JS::AddonIdOfObject(jsGlobal);
+
+ if (!xpc::CompartmentPrivate::Get(jsGlobal)->allowCPOWs) {
+ if (!addonId && ForbidUnsafeBrowserCPOWs() && !isSafe) {
+ Telemetry::Accumulate(Telemetry::BROWSER_SHIM_USAGE_BLOCKED, 1);
+ JS_ReportErrorASCII(cx, "unsafe CPOW usage forbidden");
+ return false;
+ }
+
+ if (addonId) {
+ JSFlatString* flat = JS_ASSERT_STRING_IS_FLAT(JS::StringOfAddonId(addonId));
+ nsString addonIdString;
+ AssignJSFlatString(addonIdString, flat);
+ NS_ConvertUTF16toUTF8 addonIdCString(addonIdString);
+ Telemetry::Accumulate(Telemetry::ADDON_FORBIDDEN_CPOW_USAGE, addonIdCString);
+
+ if (ForbidCPOWsInCompatibleAddon(addonIdCString)) {
+ JS_ReportErrorASCII(cx, "CPOW usage forbidden in this add-on");
+ return false;
+ }
+
+ warn = true;
+ }
+ }
+ }
+
+ if (!warn)
+ return true;
+
+ static bool disableUnsafeCPOWWarnings = PR_GetEnv("DISABLE_UNSAFE_CPOW_WARNINGS");
+ if (!disableUnsafeCPOWWarnings) {
+ nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (console && cx) {
+ nsAutoString filename;
+ uint32_t lineno = 0, column = 0;
+ nsJSUtils::GetCallingLocation(cx, filename, &lineno, &column);
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->Init(NS_LITERAL_STRING("unsafe/forbidden CPOW usage"), filename,
+ EmptyString(), lineno, column,
+ nsIScriptError::warningFlag, "chrome javascript");
+ console->LogMessage(error);
+ } else {
+ NS_WARNING("Unsafe synchronous IPC message");
+ }
+ }
+
+ return true;
+}
+
+void
+JavaScriptParent::trace(JSTracer* trc)
+{
+ objects_.trace(trc);
+ unwaivedObjectIds_.trace(trc);
+ waivedObjectIds_.trace(trc);
+}
+
+JSObject*
+JavaScriptParent::scopeForTargetObjects()
+{
+ // CPWOWs from the child need to point into the parent's unprivileged junk
+ // scope so that a compromised child cannot compromise the parent. In
+ // practice, this means that a child process can only (a) hold parent
+ // objects alive and (b) invoke them if they are callable.
+ return xpc::UnprivilegedJunkScope();
+}
+
+void
+JavaScriptParent::afterProcessTask()
+{
+ if (savedNextCPOWNumber_ == nextCPOWNumber_)
+ return;
+
+ savedNextCPOWNumber_ = nextCPOWNumber_;
+
+ MOZ_ASSERT(nextCPOWNumber_ > 0);
+ if (active())
+ Unused << SendDropTemporaryStrongReferences(nextCPOWNumber_ - 1);
+}
+
+PJavaScriptParent*
+mozilla::jsipc::NewJavaScriptParent()
+{
+ JavaScriptParent* parent = new JavaScriptParent();
+ if (!parent->init()) {
+ delete parent;
+ return nullptr;
+ }
+ return parent;
+}
+
+void
+mozilla::jsipc::ReleaseJavaScriptParent(PJavaScriptParent* parent)
+{
+ static_cast<JavaScriptParent*>(parent)->decref();
+}
+
+void
+mozilla::jsipc::AfterProcessTask()
+{
+ for (auto* cp : ContentParent::AllProcesses(ContentParent::eLive)) {
+ if (PJavaScriptParent* p = LoneManagedOrNullAsserts(cp->ManagedPJavaScriptParent()))
+ static_cast<JavaScriptParent*>(p)->afterProcessTask();
+ }
+}
diff --git a/js/ipc/JavaScriptParent.h b/js/ipc/JavaScriptParent.h
new file mode 100644
index 000000000..19d4b22b5
--- /dev/null
+++ b/js/ipc/JavaScriptParent.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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_jsipc_JavaScriptParent__
+#define mozilla_jsipc_JavaScriptParent__
+
+#include "JavaScriptBase.h"
+#include "mozilla/jsipc/PJavaScriptParent.h"
+
+namespace mozilla {
+namespace jsipc {
+
+class JavaScriptParent : public JavaScriptBase<PJavaScriptParent>
+{
+ public:
+ JavaScriptParent() : savedNextCPOWNumber_(1) {}
+ virtual ~JavaScriptParent();
+
+ bool init();
+ void trace(JSTracer* trc);
+
+ void drop(JSObject* obj);
+
+ bool allowMessage(JSContext* cx) override;
+ void afterProcessTask();
+
+ protected:
+ virtual bool isParent() override { return true; }
+ virtual JSObject* scopeForTargetObjects() override;
+
+ private:
+ uint64_t savedNextCPOWNumber_;
+};
+
+} // namespace jsipc
+} // namespace mozilla
+
+#endif // mozilla_jsipc_JavaScriptWrapper_h__
+
diff --git a/js/ipc/JavaScriptShared.cpp b/js/ipc/JavaScriptShared.cpp
new file mode 100644
index 000000000..9786243f2
--- /dev/null
+++ b/js/ipc/JavaScriptShared.cpp
@@ -0,0 +1,760 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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 "JavaScriptShared.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/CPOWManagerGetter.h"
+#include "mozilla/dom/TabChild.h"
+#include "jsfriendapi.h"
+#include "xpcprivate.h"
+#include "WrapperFactory.h"
+#include "mozilla/Preferences.h"
+
+using namespace js;
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::jsipc;
+
+IdToObjectMap::IdToObjectMap()
+ : table_(SystemAllocPolicy())
+{
+}
+
+bool
+IdToObjectMap::init()
+{
+ if (table_.initialized())
+ return true;
+ return table_.init(32);
+}
+
+void
+IdToObjectMap::trace(JSTracer* trc, uint64_t minimimId)
+{
+ for (Table::Range r(table_.all()); !r.empty(); r.popFront()) {
+ if (r.front().key().serialNumber() >= minimimId)
+ JS::TraceEdge(trc, &r.front().value(), "ipc-object");
+ }
+}
+
+void
+IdToObjectMap::sweep()
+{
+ for (Table::Enum e(table_); !e.empty(); e.popFront()) {
+ JS::Heap<JSObject*>* objp = &e.front().value();
+ JS_UpdateWeakPointerAfterGC(objp);
+ if (!*objp)
+ e.removeFront();
+ }
+}
+
+JSObject*
+IdToObjectMap::find(ObjectId id)
+{
+ Table::Ptr p = table_.lookup(id);
+ if (!p)
+ return nullptr;
+ return p->value();
+}
+
+bool
+IdToObjectMap::add(ObjectId id, JSObject* obj)
+{
+ return table_.put(id, obj);
+}
+
+void
+IdToObjectMap::remove(ObjectId id)
+{
+ table_.remove(id);
+}
+
+void
+IdToObjectMap::clear()
+{
+ table_.clear();
+}
+
+bool
+IdToObjectMap::empty() const
+{
+ return table_.empty();
+}
+
+#ifdef DEBUG
+bool
+IdToObjectMap::has(const ObjectId& id, const JSObject* obj) const
+{
+ auto p = table_.lookup(id);
+ if (!p)
+ return false;
+ return p->value().unbarrieredGet() == obj;
+}
+#endif
+
+bool
+ObjectToIdMap::init()
+{
+ return table_.initialized() || table_.init(32);
+}
+
+void
+ObjectToIdMap::trace(JSTracer* trc)
+{
+ table_.trace(trc);
+}
+
+void
+ObjectToIdMap::sweep()
+{
+ table_.sweep();
+}
+
+ObjectId
+ObjectToIdMap::find(JSObject* obj)
+{
+ Table::Ptr p = table_.lookup(obj);
+ if (!p)
+ return ObjectId::nullId();
+ return p->value();
+}
+
+bool
+ObjectToIdMap::add(JSContext* cx, JSObject* obj, ObjectId id)
+{
+ return table_.put(obj, id);
+}
+
+void
+ObjectToIdMap::remove(JSObject* obj)
+{
+ table_.remove(obj);
+}
+
+void
+ObjectToIdMap::clear()
+{
+ table_.clear();
+}
+
+bool JavaScriptShared::sLoggingInitialized;
+bool JavaScriptShared::sLoggingEnabled;
+bool JavaScriptShared::sStackLoggingEnabled;
+
+JavaScriptShared::JavaScriptShared()
+ : refcount_(1),
+ nextSerialNumber_(1),
+ nextCPOWNumber_(1)
+{
+ if (!sLoggingInitialized) {
+ sLoggingInitialized = true;
+
+ if (PR_GetEnv("MOZ_CPOW_LOG")) {
+ sLoggingEnabled = true;
+ sStackLoggingEnabled = strstr(PR_GetEnv("MOZ_CPOW_LOG"), "stacks");
+ } else {
+ Preferences::AddBoolVarCache(&sLoggingEnabled,
+ "dom.ipc.cpows.log.enabled", false);
+ Preferences::AddBoolVarCache(&sStackLoggingEnabled,
+ "dom.ipc.cpows.log.stack", false);
+ }
+ }
+}
+
+JavaScriptShared::~JavaScriptShared()
+{
+ MOZ_RELEASE_ASSERT(cpows_.empty());
+}
+
+bool
+JavaScriptShared::init()
+{
+ if (!objects_.init())
+ return false;
+ if (!cpows_.init())
+ return false;
+ if (!unwaivedObjectIds_.init())
+ return false;
+ if (!waivedObjectIds_.init())
+ return false;
+
+ return true;
+}
+
+void
+JavaScriptShared::decref()
+{
+ refcount_--;
+ if (!refcount_)
+ delete this;
+}
+
+void
+JavaScriptShared::incref()
+{
+ refcount_++;
+}
+
+bool
+JavaScriptShared::convertIdToGeckoString(JSContext* cx, JS::HandleId id, nsString* to)
+{
+ RootedValue idval(cx);
+ if (!JS_IdToValue(cx, id, &idval))
+ return false;
+
+ RootedString str(cx, ToString(cx, idval));
+ if (!str)
+ return false;
+
+ return AssignJSString(cx, *to, str);
+}
+
+bool
+JavaScriptShared::convertGeckoStringToId(JSContext* cx, const nsString& from, JS::MutableHandleId to)
+{
+ RootedString str(cx, JS_NewUCStringCopyN(cx, from.BeginReading(), from.Length()));
+ if (!str)
+ return false;
+
+ return JS_StringToId(cx, str, to);
+}
+
+bool
+JavaScriptShared::toVariant(JSContext* cx, JS::HandleValue from, JSVariant* to)
+{
+ switch (JS_TypeOfValue(cx, from)) {
+ case JSTYPE_VOID:
+ *to = UndefinedVariant();
+ return true;
+
+ case JSTYPE_OBJECT:
+ case JSTYPE_FUNCTION:
+ {
+ RootedObject obj(cx, from.toObjectOrNull());
+ if (!obj) {
+ MOZ_ASSERT(from.isNull());
+ *to = NullVariant();
+ return true;
+ }
+
+ if (xpc_JSObjectIsID(cx, obj)) {
+ JSIID iid;
+ const nsID* id = xpc_JSObjectToID(cx, obj);
+ ConvertID(*id, &iid);
+ *to = iid;
+ return true;
+ }
+
+ ObjectVariant objVar;
+ if (!toObjectVariant(cx, obj, &objVar))
+ return false;
+ *to = objVar;
+ return true;
+ }
+
+ case JSTYPE_SYMBOL:
+ {
+ RootedSymbol sym(cx, from.toSymbol());
+
+ SymbolVariant symVar;
+ if (!toSymbolVariant(cx, sym, &symVar))
+ return false;
+ *to = symVar;
+ return true;
+ }
+
+ case JSTYPE_STRING:
+ {
+ nsAutoJSString autoStr;
+ if (!autoStr.init(cx, from))
+ return false;
+ *to = autoStr;
+ return true;
+ }
+
+ case JSTYPE_NUMBER:
+ if (from.isInt32())
+ *to = double(from.toInt32());
+ else
+ *to = from.toDouble();
+ return true;
+
+ case JSTYPE_BOOLEAN:
+ *to = from.toBoolean();
+ return true;
+
+ default:
+ MOZ_ASSERT(false);
+ return false;
+ }
+}
+
+bool
+JavaScriptShared::fromVariant(JSContext* cx, const JSVariant& from, MutableHandleValue to)
+{
+ switch (from.type()) {
+ case JSVariant::TUndefinedVariant:
+ to.set(UndefinedValue());
+ return true;
+
+ case JSVariant::TNullVariant:
+ to.set(NullValue());
+ return true;
+
+ case JSVariant::TObjectVariant:
+ {
+ JSObject* obj = fromObjectVariant(cx, from.get_ObjectVariant());
+ if (!obj)
+ return false;
+ to.set(ObjectValue(*obj));
+ return true;
+ }
+
+ case JSVariant::TSymbolVariant:
+ {
+ Symbol* sym = fromSymbolVariant(cx, from.get_SymbolVariant());
+ if (!sym)
+ return false;
+ to.setSymbol(sym);
+ return true;
+ }
+
+ case JSVariant::Tdouble:
+ to.set(JS_NumberValue(from.get_double()));
+ return true;
+
+ case JSVariant::Tbool:
+ to.setBoolean(from.get_bool());
+ return true;
+
+ case JSVariant::TnsString:
+ {
+ const nsString& old = from.get_nsString();
+ JSString* str = JS_NewUCStringCopyN(cx, old.BeginReading(), old.Length());
+ if (!str)
+ return false;
+ to.set(StringValue(str));
+ return true;
+ }
+
+ case JSVariant::TJSIID:
+ {
+ nsID iid;
+ const JSIID& id = from.get_JSIID();
+ ConvertID(id, &iid);
+
+ JSCompartment* compartment = GetContextCompartment(cx);
+ RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, compartment));
+ JSObject* obj = xpc_NewIDObject(cx, global, iid);
+ if (!obj)
+ return false;
+ to.set(ObjectValue(*obj));
+ return true;
+ }
+
+ default:
+ MOZ_CRASH("NYI");
+ return false;
+ }
+}
+
+bool
+JavaScriptShared::toJSIDVariant(JSContext* cx, HandleId from, JSIDVariant* to)
+{
+ if (JSID_IS_STRING(from)) {
+ nsAutoJSString autoStr;
+ if (!autoStr.init(cx, JSID_TO_STRING(from)))
+ return false;
+ *to = autoStr;
+ return true;
+ }
+ if (JSID_IS_INT(from)) {
+ *to = JSID_TO_INT(from);
+ return true;
+ }
+ if (JSID_IS_SYMBOL(from)) {
+ SymbolVariant symVar;
+ if (!toSymbolVariant(cx, JSID_TO_SYMBOL(from), &symVar))
+ return false;
+ *to = symVar;
+ return true;
+ }
+ MOZ_ASSERT(false);
+ return false;
+}
+
+bool
+JavaScriptShared::fromJSIDVariant(JSContext* cx, const JSIDVariant& from, MutableHandleId to)
+{
+ switch (from.type()) {
+ case JSIDVariant::TSymbolVariant: {
+ Symbol* sym = fromSymbolVariant(cx, from.get_SymbolVariant());
+ if (!sym)
+ return false;
+ to.set(SYMBOL_TO_JSID(sym));
+ return true;
+ }
+
+ case JSIDVariant::TnsString:
+ return convertGeckoStringToId(cx, from.get_nsString(), to);
+
+ case JSIDVariant::Tint32_t:
+ to.set(INT_TO_JSID(from.get_int32_t()));
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+bool
+JavaScriptShared::toSymbolVariant(JSContext* cx, JS::Symbol* symArg, SymbolVariant* symVarp)
+{
+ RootedSymbol sym(cx, symArg);
+ MOZ_ASSERT(sym);
+
+ SymbolCode code = GetSymbolCode(sym);
+ if (static_cast<uint32_t>(code) < WellKnownSymbolLimit) {
+ *symVarp = WellKnownSymbol(static_cast<uint32_t>(code));
+ return true;
+ }
+ if (code == SymbolCode::InSymbolRegistry) {
+ nsAutoJSString autoStr;
+ if (!autoStr.init(cx, GetSymbolDescription(sym)))
+ return false;
+ *symVarp = RegisteredSymbol(autoStr);
+ return true;
+ }
+
+ JS_ReportErrorASCII(cx, "unique symbol can't be used with CPOW");
+ return false;
+}
+
+JS::Symbol*
+JavaScriptShared::fromSymbolVariant(JSContext* cx, const SymbolVariant& symVar)
+{
+ switch (symVar.type()) {
+ case SymbolVariant::TWellKnownSymbol: {
+ uint32_t which = symVar.get_WellKnownSymbol().which();
+ if (which < WellKnownSymbolLimit)
+ return GetWellKnownSymbol(cx, static_cast<SymbolCode>(which));
+ MOZ_ASSERT(false, "bad child data");
+ return nullptr;
+ }
+
+ case SymbolVariant::TRegisteredSymbol: {
+ nsString key = symVar.get_RegisteredSymbol().key();
+ RootedString str(cx, JS_NewUCStringCopyN(cx, key.get(), key.Length()));
+ if (!str)
+ return nullptr;
+ return GetSymbolFor(cx, str);
+ }
+
+ default:
+ return nullptr;
+ }
+}
+
+/* static */ void
+JavaScriptShared::ConvertID(const nsID& from, JSIID* to)
+{
+ to->m0() = from.m0;
+ to->m1() = from.m1;
+ to->m2() = from.m2;
+ to->m3_0() = from.m3[0];
+ to->m3_1() = from.m3[1];
+ to->m3_2() = from.m3[2];
+ to->m3_3() = from.m3[3];
+ to->m3_4() = from.m3[4];
+ to->m3_5() = from.m3[5];
+ to->m3_6() = from.m3[6];
+ to->m3_7() = from.m3[7];
+}
+
+/* static */ void
+JavaScriptShared::ConvertID(const JSIID& from, nsID* to)
+{
+ to->m0 = from.m0();
+ to->m1 = from.m1();
+ to->m2 = from.m2();
+ to->m3[0] = from.m3_0();
+ to->m3[1] = from.m3_1();
+ to->m3[2] = from.m3_2();
+ to->m3[3] = from.m3_3();
+ to->m3[4] = from.m3_4();
+ to->m3[5] = from.m3_5();
+ to->m3[6] = from.m3_6();
+ to->m3[7] = from.m3_7();
+}
+
+JSObject*
+JavaScriptShared::findObjectById(JSContext* cx, const ObjectId& objId)
+{
+ RootedObject obj(cx, objects_.find(objId));
+ if (!obj) {
+ JS_ReportErrorASCII(cx, "operation not possible on dead CPOW");
+ return nullptr;
+ }
+
+ // Each process has a dedicated compartment for CPOW targets. All CPOWs
+ // from the other process point to objects in this scope. From there, they
+ // can access objects in other compartments using cross-compartment
+ // wrappers.
+ JSAutoCompartment ac(cx, scopeForTargetObjects());
+ if (objId.hasXrayWaiver()) {
+ {
+ JSAutoCompartment ac2(cx, obj);
+ obj = js::ToWindowProxyIfWindow(obj);
+ MOZ_ASSERT(obj);
+ }
+ if (!xpc::WrapperFactory::WaiveXrayAndWrap(cx, &obj))
+ return nullptr;
+ } else {
+ if (!JS_WrapObject(cx, &obj))
+ return nullptr;
+ }
+ return obj;
+}
+
+static const uint64_t UnknownPropertyOp = 1;
+
+bool
+JavaScriptShared::fromDescriptor(JSContext* cx, Handle<PropertyDescriptor> desc,
+ PPropertyDescriptor* out)
+{
+ out->attrs() = desc.attributes();
+ if (!toVariant(cx, desc.value(), &out->value()))
+ return false;
+
+ if (!toObjectOrNullVariant(cx, desc.object(), &out->obj()))
+ return false;
+
+ if (!desc.getter()) {
+ out->getter() = 0;
+ } else if (desc.hasGetterObject()) {
+ JSObject* getter = desc.getterObject();
+ ObjectVariant objVar;
+ if (!toObjectVariant(cx, getter, &objVar))
+ return false;
+ out->getter() = objVar;
+ } else {
+ MOZ_ASSERT(desc.getter() != JS_PropertyStub);
+ out->getter() = UnknownPropertyOp;
+ }
+
+ if (!desc.setter()) {
+ out->setter() = 0;
+ } else if (desc.hasSetterObject()) {
+ JSObject* setter = desc.setterObject();
+ ObjectVariant objVar;
+ if (!toObjectVariant(cx, setter, &objVar))
+ return false;
+ out->setter() = objVar;
+ } else {
+ MOZ_ASSERT(desc.setter() != JS_StrictPropertyStub);
+ out->setter() = UnknownPropertyOp;
+ }
+
+ return true;
+}
+
+bool
+UnknownPropertyStub(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp)
+{
+ JS_ReportErrorASCII(cx, "getter could not be wrapped via CPOWs");
+ return false;
+}
+
+bool
+UnknownStrictPropertyStub(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp,
+ ObjectOpResult& result)
+{
+ JS_ReportErrorASCII(cx, "setter could not be wrapped via CPOWs");
+ return false;
+}
+
+bool
+JavaScriptShared::toDescriptor(JSContext* cx, const PPropertyDescriptor& in,
+ MutableHandle<PropertyDescriptor> out)
+{
+ out.setAttributes(in.attrs());
+ if (!fromVariant(cx, in.value(), out.value()))
+ return false;
+ out.object().set(fromObjectOrNullVariant(cx, in.obj()));
+
+ if (in.getter().type() == GetterSetter::Tuint64_t && !in.getter().get_uint64_t()) {
+ out.setGetter(nullptr);
+ } else if (in.attrs() & JSPROP_GETTER) {
+ Rooted<JSObject*> getter(cx);
+ getter = fromObjectVariant(cx, in.getter().get_ObjectVariant());
+ if (!getter)
+ return false;
+ out.setGetter(JS_DATA_TO_FUNC_PTR(JSGetterOp, getter.get()));
+ } else {
+ out.setGetter(UnknownPropertyStub);
+ }
+
+ if (in.setter().type() == GetterSetter::Tuint64_t && !in.setter().get_uint64_t()) {
+ out.setSetter(nullptr);
+ } else if (in.attrs() & JSPROP_SETTER) {
+ Rooted<JSObject*> setter(cx);
+ setter = fromObjectVariant(cx, in.setter().get_ObjectVariant());
+ if (!setter)
+ return false;
+ out.setSetter(JS_DATA_TO_FUNC_PTR(JSSetterOp, setter.get()));
+ } else {
+ out.setSetter(UnknownStrictPropertyStub);
+ }
+
+ return true;
+}
+
+bool
+JavaScriptShared::toObjectOrNullVariant(JSContext* cx, JSObject* obj, ObjectOrNullVariant* objVarp)
+{
+ if (!obj) {
+ *objVarp = NullVariant();
+ return true;
+ }
+
+ ObjectVariant objVar;
+ if (!toObjectVariant(cx, obj, &objVar))
+ return false;
+
+ *objVarp = objVar;
+ return true;
+}
+
+JSObject*
+JavaScriptShared::fromObjectOrNullVariant(JSContext* cx, const ObjectOrNullVariant& objVar)
+{
+ if (objVar.type() == ObjectOrNullVariant::TNullVariant)
+ return nullptr;
+
+ return fromObjectVariant(cx, objVar.get_ObjectVariant());
+}
+
+CrossProcessCpowHolder::CrossProcessCpowHolder(dom::CPOWManagerGetter* managerGetter,
+ const InfallibleTArray<CpowEntry>& cpows)
+ : js_(nullptr),
+ cpows_(cpows),
+ unwrapped_(false)
+{
+ // Only instantiate the CPOW manager if we might need it later.
+ if (cpows.Length())
+ js_ = managerGetter->GetCPOWManager();
+}
+
+CrossProcessCpowHolder::~CrossProcessCpowHolder()
+{
+ if (cpows_.Length() && !unwrapped_) {
+ // This should only happen if a message manager message
+ // containing CPOWs gets ignored for some reason. We need to
+ // unwrap every incoming CPOW in this process to ensure that
+ // the corresponding part of the CPOW in the other process
+ // will eventually be collected. The scope for this object
+ // doesn't really matter, because it immediately becomes
+ // garbage.
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(xpc::PrivilegedJunkScope()))
+ return;
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JSObject*> cpows(cx);
+ js_->Unwrap(cx, cpows_, &cpows);
+ }
+}
+
+bool
+CrossProcessCpowHolder::ToObject(JSContext* cx, JS::MutableHandleObject objp)
+{
+ unwrapped_ = true;
+
+ if (!cpows_.Length())
+ return true;
+
+ return js_->Unwrap(cx, cpows_, objp);
+}
+
+bool
+JavaScriptShared::Unwrap(JSContext* cx, const InfallibleTArray<CpowEntry>& aCpows,
+ JS::MutableHandleObject objp)
+{
+ objp.set(nullptr);
+
+ if (!aCpows.Length())
+ return true;
+
+ RootedObject obj(cx, JS_NewPlainObject(cx));
+ if (!obj)
+ return false;
+
+ RootedValue v(cx);
+ RootedString str(cx);
+ for (size_t i = 0; i < aCpows.Length(); i++) {
+ const nsString& name = aCpows[i].name();
+
+ if (!fromVariant(cx, aCpows[i].value(), &v))
+ return false;
+
+ if (!JS_DefineUCProperty(cx,
+ obj,
+ name.BeginReading(),
+ name.Length(),
+ v,
+ JSPROP_ENUMERATE))
+ {
+ return false;
+ }
+ }
+
+ objp.set(obj);
+ return true;
+}
+
+bool
+JavaScriptShared::Wrap(JSContext* cx, HandleObject aObj, InfallibleTArray<CpowEntry>* outCpows)
+{
+ if (!aObj)
+ return true;
+
+ Rooted<IdVector> ids(cx, IdVector(cx));
+ if (!JS_Enumerate(cx, aObj, &ids))
+ return false;
+
+ RootedId id(cx);
+ RootedValue v(cx);
+ for (size_t i = 0; i < ids.length(); i++) {
+ id = ids[i];
+
+ nsString str;
+ if (!convertIdToGeckoString(cx, id, &str))
+ return false;
+
+ if (!JS_GetPropertyById(cx, aObj, id, &v))
+ return false;
+
+ JSVariant var;
+ if (!toVariant(cx, v, &var))
+ return false;
+
+ outCpows->AppendElement(CpowEntry(str, var));
+ }
+
+ return true;
+}
+
+CPOWManager*
+mozilla::jsipc::CPOWManagerFor(PJavaScriptParent* aParent)
+{
+ return static_cast<JavaScriptParent*>(aParent);
+}
+
+CPOWManager*
+mozilla::jsipc::CPOWManagerFor(PJavaScriptChild* aChild)
+{
+ return static_cast<JavaScriptChild*>(aChild);
+}
diff --git a/js/ipc/JavaScriptShared.h b/js/ipc/JavaScriptShared.h
new file mode 100644
index 000000000..4de153826
--- /dev/null
+++ b/js/ipc/JavaScriptShared.h
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et 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_jsipc_JavaScriptShared_h__
+#define mozilla_jsipc_JavaScriptShared_h__
+
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
+#include "mozilla/jsipc/PJavaScript.h"
+#include "js/GCHashTable.h"
+#include "nsJSUtils.h"
+
+namespace mozilla {
+namespace jsipc {
+
+class ObjectId {
+ public:
+ // Use 47 bits at most, to be safe, since jsval privates are encoded as
+ // doubles. See bug 1065811 comment 12 for an explanation.
+ static const size_t SERIAL_NUMBER_BITS = 47;
+ static const size_t FLAG_BITS = 1;
+ static const uint64_t SERIAL_NUMBER_MAX = (uint64_t(1) << SERIAL_NUMBER_BITS) - 1;
+
+ explicit ObjectId(uint64_t serialNumber, bool hasXrayWaiver)
+ : serialNumber_(serialNumber), hasXrayWaiver_(hasXrayWaiver)
+ {
+ if (MOZ_UNLIKELY(serialNumber == 0 || serialNumber > SERIAL_NUMBER_MAX))
+ MOZ_CRASH("Bad CPOW Id");
+ }
+
+ bool operator==(const ObjectId& other) const {
+ bool equal = serialNumber() == other.serialNumber();
+ MOZ_ASSERT_IF(equal, hasXrayWaiver() == other.hasXrayWaiver());
+ return equal;
+ }
+
+ bool isNull() { return !serialNumber_; }
+
+ uint64_t serialNumber() const { return serialNumber_; }
+ bool hasXrayWaiver() const { return hasXrayWaiver_; }
+ uint64_t serialize() const {
+ MOZ_ASSERT(serialNumber(), "Don't send a null ObjectId over IPC");
+ return uint64_t((serialNumber() << FLAG_BITS) | ((hasXrayWaiver() ? 1 : 0) << 0));
+ }
+
+ static ObjectId nullId() { return ObjectId(); }
+ static ObjectId deserialize(uint64_t data) {
+ return ObjectId(data >> FLAG_BITS, data & 1);
+ }
+
+ // For use with StructGCPolicy.
+ void trace(JSTracer*) const {}
+ bool needsSweep() const { return false; }
+
+ private:
+ ObjectId() : serialNumber_(0), hasXrayWaiver_(false) {}
+
+ uint64_t serialNumber_ : SERIAL_NUMBER_BITS;
+ bool hasXrayWaiver_ : 1;
+};
+
+class JavaScriptShared;
+
+// DefaultHasher<T> requires that T coerce to an integral type. We could make
+// ObjectId do that, but doing so would weaken our type invariants, so we just
+// reimplement it manually.
+struct ObjectIdHasher
+{
+ typedef ObjectId Lookup;
+ static js::HashNumber hash(const Lookup& l) {
+ return l.serialize();
+ }
+ static bool match(const ObjectId& k, const ObjectId& l) {
+ return k == l;
+ }
+ static void rekey(ObjectId& k, const ObjectId& newKey) {
+ k = newKey;
+ }
+};
+
+// Map ids -> JSObjects
+class IdToObjectMap
+{
+ typedef js::HashMap<ObjectId, JS::Heap<JSObject*>, ObjectIdHasher, js::SystemAllocPolicy> Table;
+
+ public:
+ IdToObjectMap();
+
+ bool init();
+ void trace(JSTracer* trc, uint64_t minimumId = 0);
+ void sweep();
+
+ bool add(ObjectId id, JSObject* obj);
+ JSObject* find(ObjectId id);
+ void remove(ObjectId id);
+
+ void clear();
+ bool empty() const;
+
+#ifdef DEBUG
+ bool has(const ObjectId& id, const JSObject* obj) const;
+#endif
+
+ private:
+ Table table_;
+};
+
+// Map JSObjects -> ids
+class ObjectToIdMap
+{
+ using Hasher = js::MovableCellHasher<JS::Heap<JSObject*>>;
+ using Table = JS::GCHashMap<JS::Heap<JSObject*>, ObjectId, Hasher, js::SystemAllocPolicy>;
+
+ public:
+ bool init();
+ void trace(JSTracer* trc);
+ void sweep();
+
+ bool add(JSContext* cx, JSObject* obj, ObjectId id);
+ ObjectId find(JSObject* obj);
+ void remove(JSObject* obj);
+ void clear();
+
+ private:
+ Table table_;
+};
+
+class Logging;
+
+class JavaScriptShared : public CPOWManager
+{
+ public:
+ JavaScriptShared();
+ virtual ~JavaScriptShared();
+
+ bool init();
+
+ void decref();
+ void incref();
+
+ bool Unwrap(JSContext* cx, const InfallibleTArray<CpowEntry>& aCpows, JS::MutableHandleObject objp);
+ bool Wrap(JSContext* cx, JS::HandleObject aObj, InfallibleTArray<CpowEntry>* outCpows);
+
+ protected:
+ bool toVariant(JSContext* cx, JS::HandleValue from, JSVariant* to);
+ bool fromVariant(JSContext* cx, const JSVariant& from, JS::MutableHandleValue to);
+
+ bool toJSIDVariant(JSContext* cx, JS::HandleId from, JSIDVariant* to);
+ bool fromJSIDVariant(JSContext* cx, const JSIDVariant& from, JS::MutableHandleId to);
+
+ bool toSymbolVariant(JSContext* cx, JS::Symbol* sym, SymbolVariant* symVarp);
+ JS::Symbol* fromSymbolVariant(JSContext* cx, const SymbolVariant& symVar);
+
+ bool fromDescriptor(JSContext* cx, JS::Handle<JS::PropertyDescriptor> desc,
+ PPropertyDescriptor* out);
+ bool toDescriptor(JSContext* cx, const PPropertyDescriptor& in,
+ JS::MutableHandle<JS::PropertyDescriptor> out);
+
+ bool toObjectOrNullVariant(JSContext* cx, JSObject* obj, ObjectOrNullVariant* objVarp);
+ JSObject* fromObjectOrNullVariant(JSContext* cx, const ObjectOrNullVariant& objVar);
+
+ bool convertIdToGeckoString(JSContext* cx, JS::HandleId id, nsString* to);
+ bool convertGeckoStringToId(JSContext* cx, const nsString& from, JS::MutableHandleId id);
+
+ virtual bool toObjectVariant(JSContext* cx, JSObject* obj, ObjectVariant* objVarp) = 0;
+ virtual JSObject* fromObjectVariant(JSContext* cx, const ObjectVariant& objVar) = 0;
+
+ static void ConvertID(const nsID& from, JSIID* to);
+ static void ConvertID(const JSIID& from, nsID* to);
+
+ JSObject* findCPOWById(const ObjectId& objId) {
+ return cpows_.find(objId);
+ }
+ JSObject* findObjectById(JSContext* cx, const ObjectId& objId);
+
+#ifdef DEBUG
+ bool hasCPOW(const ObjectId& objId, const JSObject* obj) {
+ return cpows_.has(objId, obj);
+ }
+#endif
+
+ static bool LoggingEnabled() { return sLoggingEnabled; }
+ static bool StackLoggingEnabled() { return sStackLoggingEnabled; }
+
+ friend class Logging;
+
+ virtual bool isParent() = 0;
+
+ virtual JSObject* scopeForTargetObjects() = 0;
+
+ protected:
+ uintptr_t refcount_;
+
+ IdToObjectMap objects_;
+ IdToObjectMap cpows_;
+
+ uint64_t nextSerialNumber_;
+
+ // nextCPOWNumber_ should be the value of nextSerialNumber_ in the other
+ // process. The next new CPOW we get should have this serial number.
+ uint64_t nextCPOWNumber_;
+
+ // CPOW references can be weak, and any object we store in a map may be
+ // GCed (at which point the CPOW will report itself "dead" to the owner).
+ // This means that we don't want to store any js::Wrappers in the CPOW map,
+ // because CPOW will die if the wrapper is GCed, even if the underlying
+ // object is still alive.
+ //
+ // This presents a tricky situation for Xray waivers, since they're normally
+ // represented as a special same-compartment wrapper. We have to strip them
+ // off before putting them in the id-to-object and object-to-id maps, so we
+ // need a way of distinguishing them at lookup-time.
+ //
+ // For the id-to-object map, we encode waiver-or-not information into the id
+ // itself, which lets us do the right thing when accessing the object.
+ //
+ // For the object-to-id map, we just keep two maps, one for each type.
+ ObjectToIdMap unwaivedObjectIds_;
+ ObjectToIdMap waivedObjectIds_;
+ ObjectToIdMap& objectIdMap(bool waiver) {
+ return waiver ? waivedObjectIds_ : unwaivedObjectIds_;
+ }
+
+ static bool sLoggingInitialized;
+ static bool sLoggingEnabled;
+ static bool sStackLoggingEnabled;
+};
+
+} // namespace jsipc
+} // namespace mozilla
+
+#endif
diff --git a/js/ipc/JavaScriptTypes.ipdlh b/js/ipc/JavaScriptTypes.ipdlh
new file mode 100644
index 000000000..5129fc304
--- /dev/null
+++ b/js/ipc/JavaScriptTypes.ipdlh
@@ -0,0 +1,161 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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 DOMTypes;
+
+using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace jsipc {
+
+struct JSIID
+{
+ uint32_t m0;
+ uint16_t m1;
+ uint16_t m2;
+ uint8_t m3_0;
+ uint8_t m3_1;
+ uint8_t m3_2;
+ uint8_t m3_3;
+ uint8_t m3_4;
+ uint8_t m3_5;
+ uint8_t m3_6;
+ uint8_t m3_7;
+};
+
+struct LocalObject
+{
+ uint64_t serializedId;
+};
+
+struct RemoteObject
+{
+ uint64_t serializedId;
+ bool isCallable;
+ bool isConstructor;
+ bool isDOMObject;
+ nsCString objectTag;
+};
+
+union ObjectVariant
+{
+ LocalObject;
+ RemoteObject;
+};
+
+struct WellKnownSymbol
+{
+ uint32_t which;
+};
+
+struct RegisteredSymbol
+{
+ nsString key;
+};
+
+union SymbolVariant
+{
+ WellKnownSymbol;
+ RegisteredSymbol;
+};
+
+struct UndefinedVariant {};
+struct NullVariant {};
+
+union ObjectOrNullVariant
+{
+ ObjectVariant;
+ NullVariant;
+};
+
+union JSVariant
+{
+ UndefinedVariant;
+ NullVariant;
+ ObjectVariant;
+ SymbolVariant;
+ nsString; /* StringValue(x) */
+ double; /* NumberValue(x) */
+ bool; /* BooleanValue(x) */
+ JSIID; /* XPC nsIID */
+};
+
+union JSIDVariant
+{
+ SymbolVariant;
+ nsString;
+ int32_t;
+};
+
+struct ReturnSuccess
+{
+};
+
+struct ReturnStopIteration
+{
+};
+
+struct ReturnDeadCPOW
+{
+};
+
+struct ReturnException
+{
+ JSVariant exn;
+};
+
+struct ReturnObjectOpResult
+{
+ uint32_t code;
+};
+
+union ReturnStatus
+{
+ ReturnSuccess;
+ ReturnStopIteration;
+ ReturnDeadCPOW;
+ ReturnException;
+ ReturnObjectOpResult;
+};
+
+union JSParam
+{
+ void_t; /* value is strictly an xpc out param */
+ JSVariant; /* actual value to pass through */
+};
+
+union GetterSetter
+{
+ uint64_t;
+ ObjectVariant;
+};
+
+struct PPropertyDescriptor
+{
+ ObjectOrNullVariant obj;
+ uint32_t attrs;
+ JSVariant value;
+
+ // How to interpret these values depends on whether JSPROP_GETTER/SETTER
+ // are set. If set, the corresponding value is a CPOW or 0 for NULL.
+ // Otherwise, the following table is used:
+ //
+ // 0 - NULL
+ // 1 - Default getter or setter.
+ // 2 - Unknown
+ GetterSetter getter;
+ GetterSetter setter;
+};
+
+struct CpowEntry
+{
+ nsString name;
+ JSVariant value;
+};
+
+}
+}
diff --git a/js/ipc/PJavaScript.ipdl b/js/ipc/PJavaScript.ipdl
new file mode 100644
index 000000000..41110968f
--- /dev/null
+++ b/js/ipc/PJavaScript.ipdl
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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 protocol PContent;
+include protocol PContentBridge;
+include DOMTypes;
+include JavaScriptTypes;
+
+using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace jsipc {
+
+nested(upto inside_sync) sync protocol PJavaScript
+{
+ manager PContent or PContentBridge;
+
+both:
+ // Sent when a CPOW has been finalized and table entries can be freed up.
+ async DropObject(uint64_t objId);
+
+ // These roughly map to the ProxyHandler hooks that CPOWs need.
+ nested(inside_sync) sync PreventExtensions(uint64_t objId) returns (ReturnStatus rs);
+ nested(inside_sync) sync GetPropertyDescriptor(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs, PPropertyDescriptor result);
+ nested(inside_sync) sync GetOwnPropertyDescriptor(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs, PPropertyDescriptor result);
+ nested(inside_sync) sync DefineProperty(uint64_t objId, JSIDVariant id, PPropertyDescriptor descriptor) returns (ReturnStatus rs);
+ nested(inside_sync) sync Delete(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs);
+
+ nested(inside_sync) sync Has(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs, bool has);
+ nested(inside_sync) sync HasOwn(uint64_t objId, JSIDVariant id) returns (ReturnStatus rs, bool has);
+ nested(inside_sync) sync Get(uint64_t objId, JSVariant receiver, JSIDVariant id) returns (ReturnStatus rs, JSVariant result);
+ nested(inside_sync) sync Set(uint64_t objId, JSIDVariant id, JSVariant value, JSVariant receiver) returns (ReturnStatus rs);
+
+ nested(inside_sync) sync IsExtensible(uint64_t objId) returns (ReturnStatus rs, bool result);
+ nested(inside_sync) sync CallOrConstruct(uint64_t objId, JSParam[] argv, bool construct) returns (ReturnStatus rs, JSVariant result, JSParam[] outparams);
+ nested(inside_sync) sync HasInstance(uint64_t objId, JSVariant v) returns (ReturnStatus rs, bool has);
+ nested(inside_sync) sync GetBuiltinClass(uint64_t objId) returns (ReturnStatus rs, uint32_t classValue);
+ nested(inside_sync) sync IsArray(uint64_t objId) returns (ReturnStatus rs, uint32_t ans);
+ nested(inside_sync) sync ClassName(uint64_t objId) returns (nsCString name);
+ nested(inside_sync) sync GetPrototype(uint64_t objId) returns (ReturnStatus rs, ObjectOrNullVariant result);
+ nested(inside_sync) sync GetPrototypeIfOrdinary(uint64_t objId) returns (ReturnStatus rs, bool isOrdinary, ObjectOrNullVariant result);
+ nested(inside_sync) sync RegExpToShared(uint64_t objId) returns (ReturnStatus rs, nsString source, uint32_t flags);
+
+ nested(inside_sync) sync GetPropertyKeys(uint64_t objId, uint32_t flags) returns (ReturnStatus rs, JSIDVariant[] ids);
+ nested(inside_sync) sync InstanceOf(uint64_t objId, JSIID iid) returns (ReturnStatus rs, bool instanceof);
+ nested(inside_sync) sync DOMInstanceOf(uint64_t objId, int prototypeID, int depth) returns (ReturnStatus rs, bool instanceof);
+
+parent:
+ async __delete__();
+
+child:
+ async DropTemporaryStrongReferences(uint64_t upToObjId);
+};
+
+}
+}
diff --git a/js/ipc/WrapperAnswer.cpp b/js/ipc/WrapperAnswer.cpp
new file mode 100644
index 000000000..fc342bbb6
--- /dev/null
+++ b/js/ipc/WrapperAnswer.cpp
@@ -0,0 +1,798 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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 "WrapperAnswer.h"
+#include "JavaScriptLogging.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "xpcprivate.h"
+#include "js/Class.h"
+#include "jsfriendapi.h"
+
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::jsipc;
+
+// Note - Using AutoJSAPI (rather than AutoEntryScript) for a trap means
+// that we don't expect it to run script. For most of these traps that will only
+// happen if the target is a scripted proxy, which is probably something that we
+// don't want to support over CPOWs. When enough code is fixed up, the long-term
+// plan is to have the JS engine throw if it encounters script when it isn't
+// expecting it.
+using mozilla::dom::AutoJSAPI;
+using mozilla::dom::AutoEntryScript;
+
+static void
+MaybeForceDebugGC()
+{
+ static bool sEnvVarInitialized = false;
+ static bool sDebugGCs = false;
+
+ if (!sEnvVarInitialized)
+ sDebugGCs = !!PR_GetEnv("MOZ_DEBUG_DEAD_CPOWS");
+
+ if (sDebugGCs) {
+ JSContext* cx = nsXPConnect::GetContextInstance()->Context();
+ PrepareForFullGC(cx);
+ GCForReason(cx, GC_NORMAL, gcreason::COMPONENT_UTILS);
+ }
+}
+
+bool
+WrapperAnswer::fail(AutoJSAPI& jsapi, ReturnStatus* rs)
+{
+ // By default, we set |undefined| unless we can get a more meaningful
+ // exception.
+ *rs = ReturnStatus(ReturnException(JSVariant(UndefinedVariant())));
+
+ // Note we always return true from this function, since this propagates
+ // to the IPC code, and we don't want a JS failure to cause the death
+ // of the child process.
+
+ JSContext* cx = jsapi.cx();
+ RootedValue exn(cx);
+ if (!jsapi.HasException())
+ return true;
+
+ if (!jsapi.StealException(&exn))
+ return true;
+
+ if (JS_IsStopIteration(exn)) {
+ *rs = ReturnStatus(ReturnStopIteration());
+ return true;
+ }
+
+ // If this fails, we still don't want to exit. Just return an invalid
+ // exception.
+ (void) toVariant(cx, exn, &rs->get_ReturnException().exn());
+ return true;
+}
+
+bool
+WrapperAnswer::ok(ReturnStatus* rs)
+{
+ *rs = ReturnStatus(ReturnSuccess());
+ return true;
+}
+
+bool
+WrapperAnswer::ok(ReturnStatus* rs, const JS::ObjectOpResult& result)
+{
+ *rs = result
+ ? ReturnStatus(ReturnSuccess())
+ : ReturnStatus(ReturnObjectOpResult(result.failureCode()));
+ return true;
+}
+
+bool
+WrapperAnswer::deadCPOW(AutoJSAPI& jsapi, ReturnStatus* rs)
+{
+ JSContext* cx = jsapi.cx();
+ JS_ClearPendingException(cx);
+ *rs = ReturnStatus(ReturnDeadCPOW());
+ return true;
+}
+
+bool
+WrapperAnswer::RecvPreventExtensions(const ObjectId& objId, ReturnStatus* rs)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ ObjectOpResult success;
+ if (!JS_PreventExtensions(cx, obj, success))
+ return fail(jsapi, rs);
+
+ LOG("%s.preventExtensions()", ReceiverObj(objId));
+ return ok(rs, success);
+}
+
+static void
+EmptyDesc(PPropertyDescriptor* desc)
+{
+ desc->obj() = LocalObject(0);
+ desc->attrs() = 0;
+ desc->value() = UndefinedVariant();
+ desc->getter() = 0;
+ desc->setter() = 0;
+}
+
+bool
+WrapperAnswer::RecvGetPropertyDescriptor(const ObjectId& objId, const JSIDVariant& idVar,
+ ReturnStatus* rs, PPropertyDescriptor* out)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+ EmptyDesc(out);
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.getPropertyDescriptor(%s)", ReceiverObj(objId), Identifier(idVar));
+
+ RootedId id(cx);
+ if (!fromJSIDVariant(cx, idVar, &id))
+ return fail(jsapi, rs);
+
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!JS_GetPropertyDescriptorById(cx, obj, id, &desc))
+ return fail(jsapi, rs);
+
+ if (!fromDescriptor(cx, desc, out))
+ return fail(jsapi, rs);
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvGetOwnPropertyDescriptor(const ObjectId& objId, const JSIDVariant& idVar,
+ ReturnStatus* rs, PPropertyDescriptor* out)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+ EmptyDesc(out);
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.getOwnPropertyDescriptor(%s)", ReceiverObj(objId), Identifier(idVar));
+
+ RootedId id(cx);
+ if (!fromJSIDVariant(cx, idVar, &id))
+ return fail(jsapi, rs);
+
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, &desc))
+ return fail(jsapi, rs);
+
+ if (!fromDescriptor(cx, desc, out))
+ return fail(jsapi, rs);
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvDefineProperty(const ObjectId& objId, const JSIDVariant& idVar,
+ const PPropertyDescriptor& descriptor, ReturnStatus* rs)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("define %s[%s]", ReceiverObj(objId), Identifier(idVar));
+
+ RootedId id(cx);
+ if (!fromJSIDVariant(cx, idVar, &id))
+ return fail(jsapi, rs);
+
+ Rooted<PropertyDescriptor> desc(cx);
+ if (!toDescriptor(cx, descriptor, &desc))
+ return fail(jsapi, rs);
+
+ ObjectOpResult success;
+ if (!JS_DefinePropertyById(cx, obj, id, desc, success))
+ return fail(jsapi, rs);
+ return ok(rs, success);
+}
+
+bool
+WrapperAnswer::RecvDelete(const ObjectId& objId, const JSIDVariant& idVar, ReturnStatus* rs)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("delete %s[%s]", ReceiverObj(objId), Identifier(idVar));
+
+ RootedId id(cx);
+ if (!fromJSIDVariant(cx, idVar, &id))
+ return fail(jsapi, rs);
+
+ ObjectOpResult success;
+ if (!JS_DeletePropertyById(cx, obj, id, success))
+ return fail(jsapi, rs);
+ return ok(rs, success);
+}
+
+bool
+WrapperAnswer::RecvHas(const ObjectId& objId, const JSIDVariant& idVar, ReturnStatus* rs,
+ bool* foundp)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+ *foundp = false;
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.has(%s)", ReceiverObj(objId), Identifier(idVar));
+
+ RootedId id(cx);
+ if (!fromJSIDVariant(cx, idVar, &id))
+ return fail(jsapi, rs);
+
+ if (!JS_HasPropertyById(cx, obj, id, foundp))
+ return fail(jsapi, rs);
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvHasOwn(const ObjectId& objId, const JSIDVariant& idVar, ReturnStatus* rs,
+ bool* foundp)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+ *foundp = false;
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.hasOwn(%s)", ReceiverObj(objId), Identifier(idVar));
+
+ RootedId id(cx);
+ if (!fromJSIDVariant(cx, idVar, &id))
+ return fail(jsapi, rs);
+
+ if (!JS_HasOwnPropertyById(cx, obj, id, foundp))
+ return fail(jsapi, rs);
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvGet(const ObjectId& objId, const JSVariant& receiverVar,
+ const JSIDVariant& idVar, ReturnStatus* rs, JSVariant* result)
+{
+ MaybeForceDebugGC();
+
+ // We may run scripted getters.
+ AutoEntryScript aes(scopeForTargetObjects(),
+ "Cross-Process Object Wrapper 'get'");
+ JSContext* cx = aes.cx();
+
+ // The outparam will be written to the buffer, so it must be set even if
+ // the parent won't read it.
+ *result = UndefinedVariant();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(aes, rs);
+
+ RootedValue receiver(cx);
+ if (!fromVariant(cx, receiverVar, &receiver))
+ return fail(aes, rs);
+
+ RootedId id(cx);
+ if (!fromJSIDVariant(cx, idVar, &id))
+ return fail(aes, rs);
+
+ JS::RootedValue val(cx);
+ if (!JS_ForwardGetPropertyTo(cx, obj, id, receiver, &val))
+ return fail(aes, rs);
+
+ if (!toVariant(cx, val, result))
+ return fail(aes, rs);
+
+ LOG("get %s.%s = %s", ReceiverObj(objId), Identifier(idVar), OutVariant(*result));
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvSet(const ObjectId& objId, const JSIDVariant& idVar, const JSVariant& value,
+ const JSVariant& receiverVar, ReturnStatus* rs)
+{
+ MaybeForceDebugGC();
+
+ // We may run scripted setters.
+ AutoEntryScript aes(scopeForTargetObjects(),
+ "Cross-Process Object Wrapper 'set'");
+ JSContext* cx = aes.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(aes, rs);
+
+ LOG("set %s[%s] = %s", ReceiverObj(objId), Identifier(idVar), InVariant(value));
+
+ RootedId id(cx);
+ if (!fromJSIDVariant(cx, idVar, &id))
+ return fail(aes, rs);
+
+ RootedValue val(cx);
+ if (!fromVariant(cx, value, &val))
+ return fail(aes, rs);
+
+ RootedValue receiver(cx);
+ if (!fromVariant(cx, receiverVar, &receiver))
+ return fail(aes, rs);
+
+ ObjectOpResult result;
+ if (!JS_ForwardSetPropertyTo(cx, obj, id, val, receiver, result))
+ return fail(aes, rs);
+
+ return ok(rs, result);
+}
+
+bool
+WrapperAnswer::RecvIsExtensible(const ObjectId& objId, ReturnStatus* rs, bool* result)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+ *result = false;
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.isExtensible()", ReceiverObj(objId));
+
+ bool extensible;
+ if (!JS_IsExtensible(cx, obj, &extensible))
+ return fail(jsapi, rs);
+
+ *result = !!extensible;
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvCallOrConstruct(const ObjectId& objId,
+ InfallibleTArray<JSParam>&& argv,
+ const bool& construct,
+ ReturnStatus* rs,
+ JSVariant* result,
+ nsTArray<JSParam>* outparams)
+{
+ MaybeForceDebugGC();
+
+ AutoEntryScript aes(scopeForTargetObjects(),
+ "Cross-Process Object Wrapper call/construct");
+ JSContext* cx = aes.cx();
+
+ // The outparam will be written to the buffer, so it must be set even if
+ // the parent won't read it.
+ *result = UndefinedVariant();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(aes, rs);
+
+ MOZ_ASSERT(argv.Length() >= 2);
+
+ RootedValue objv(cx);
+ if (!fromVariant(cx, argv[0], &objv))
+ return fail(aes, rs);
+
+ *result = JSVariant(UndefinedVariant());
+
+ AutoValueVector vals(cx);
+ AutoValueVector outobjects(cx);
+ for (size_t i = 0; i < argv.Length(); i++) {
+ if (argv[i].type() == JSParam::Tvoid_t) {
+ // This is an outparam.
+ RootedObject obj(cx, xpc::NewOutObject(cx));
+ if (!obj)
+ return fail(aes, rs);
+ if (!outobjects.append(ObjectValue(*obj)))
+ return fail(aes, rs);
+ if (!vals.append(ObjectValue(*obj)))
+ return fail(aes, rs);
+ } else {
+ RootedValue v(cx);
+ if (!fromVariant(cx, argv[i].get_JSVariant(), &v))
+ return fail(aes, rs);
+ if (!vals.append(v))
+ return fail(aes, rs);
+ }
+ }
+
+ RootedValue rval(cx);
+ {
+ HandleValueArray args = HandleValueArray::subarray(vals, 2, vals.length() - 2);
+ if (construct) {
+ RootedObject obj(cx);
+ if (!JS::Construct(cx, vals[0], args, &obj))
+ return fail(aes, rs);
+ rval.setObject(*obj);
+ } else {
+ if(!JS::Call(cx, vals[1], vals[0], args, &rval))
+ return fail(aes, rs);
+ }
+ }
+
+ if (!toVariant(cx, rval, result))
+ return fail(aes, rs);
+
+ // Prefill everything with a dummy jsval.
+ for (size_t i = 0; i < outobjects.length(); i++)
+ outparams->AppendElement(JSParam(void_t()));
+
+ // Go through each argument that was an outparam, retrieve the "value"
+ // field, and add it to a temporary list. We need to do this separately
+ // because the outparams vector is not rooted.
+ vals.clear();
+ for (size_t i = 0; i < outobjects.length(); i++) {
+ RootedObject obj(cx, &outobjects[i].toObject());
+
+ RootedValue v(cx);
+ bool found;
+ if (JS_HasProperty(cx, obj, "value", &found)) {
+ if (!JS_GetProperty(cx, obj, "value", &v))
+ return fail(aes, rs);
+ } else {
+ v = UndefinedValue();
+ }
+ if (!vals.append(v))
+ return fail(aes, rs);
+ }
+
+ // Copy the outparams. If any outparam is already set to a void_t, we
+ // treat this as the outparam never having been set.
+ for (size_t i = 0; i < vals.length(); i++) {
+ JSVariant variant;
+ if (!toVariant(cx, vals[i], &variant))
+ return fail(aes, rs);
+ outparams->ReplaceElementAt(i, JSParam(variant));
+ }
+
+ LOG("%s.call(%s) = %s", ReceiverObj(objId), argv, OutVariant(*result));
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvHasInstance(const ObjectId& objId, const JSVariant& vVar, ReturnStatus* rs, bool* bp)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.hasInstance(%s)", ReceiverObj(objId), InVariant(vVar));
+
+ RootedValue val(cx);
+ if (!fromVariant(cx, vVar, &val))
+ return fail(jsapi, rs);
+
+ if (!JS_HasInstance(cx, obj, val, bp))
+ return fail(jsapi, rs);
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvGetBuiltinClass(const ObjectId& objId, ReturnStatus* rs,
+ uint32_t* classValue)
+{
+ MaybeForceDebugGC();
+
+ *classValue = uint32_t(js::ESClass::Other);
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.getBuiltinClass()", ReceiverObj(objId));
+
+ js::ESClass cls;
+ if (!js::GetBuiltinClass(cx, obj, &cls))
+ return fail(jsapi, rs);
+
+ *classValue = uint32_t(cls);
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvIsArray(const ObjectId& objId, ReturnStatus* rs,
+ uint32_t* ans)
+{
+ MaybeForceDebugGC();
+
+ *ans = uint32_t(IsArrayAnswer::NotArray);
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.isArray()", ReceiverObj(objId));
+
+ IsArrayAnswer answer;
+ if (!JS::IsArray(cx, obj, &answer))
+ return fail(jsapi, rs);
+
+ *ans = uint32_t(answer);
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvClassName(const ObjectId& objId, nsCString* name)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj) {
+ // This is very unfortunate, but we have no choice.
+ *name = "<dead CPOW>";
+ return true;
+ }
+
+ LOG("%s.className()", ReceiverObj(objId));
+
+ *name = js::ObjectClassName(cx, obj);
+ return true;
+}
+
+bool
+WrapperAnswer::RecvGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result)
+{
+ MaybeForceDebugGC();
+
+ *result = NullVariant();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ JS::RootedObject proto(cx);
+ if (!JS_GetPrototype(cx, obj, &proto))
+ return fail(jsapi, rs);
+
+ if (!toObjectOrNullVariant(cx, proto, result))
+ return fail(jsapi, rs);
+
+ LOG("getPrototype(%s)", ReceiverObj(objId));
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvGetPrototypeIfOrdinary(const ObjectId& objId, ReturnStatus* rs, bool* isOrdinary,
+ ObjectOrNullVariant* result)
+{
+ MaybeForceDebugGC();
+
+ *result = NullVariant();
+ *isOrdinary = false;
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ JS::RootedObject proto(cx);
+ if (!JS_GetPrototypeIfOrdinary(cx, obj, isOrdinary, &proto))
+ return fail(jsapi, rs);
+
+ if (!toObjectOrNullVariant(cx, proto, result))
+ return fail(jsapi, rs);
+
+ LOG("getPrototypeIfOrdinary(%s)", ReceiverObj(objId));
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvRegExpToShared(const ObjectId& objId, ReturnStatus* rs,
+ nsString* source, uint32_t* flags)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ RootedString sourceJSStr(cx, JS_GetRegExpSource(cx, obj));
+ if (!sourceJSStr)
+ return fail(jsapi, rs);
+ nsAutoJSString sourceStr;
+ if (!sourceStr.init(cx, sourceJSStr))
+ return fail(jsapi, rs);
+ source->Assign(sourceStr);
+
+ *flags = JS_GetRegExpFlags(cx, obj);
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvGetPropertyKeys(const ObjectId& objId, const uint32_t& flags,
+ ReturnStatus* rs, nsTArray<JSIDVariant>* ids)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.getPropertyKeys()", ReceiverObj(objId));
+
+ AutoIdVector props(cx);
+ if (!js::GetPropertyKeys(cx, obj, flags, &props))
+ return fail(jsapi, rs);
+
+ for (size_t i = 0; i < props.length(); i++) {
+ JSIDVariant id;
+ if (!toJSIDVariant(cx, props[i], &id))
+ return fail(jsapi, rs);
+
+ ids->AppendElement(id);
+ }
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvInstanceOf(const ObjectId& objId, const JSIID& iid, ReturnStatus* rs,
+ bool* instanceof)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+
+ *instanceof = false;
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.instanceOf()", ReceiverObj(objId));
+
+ nsID nsiid;
+ ConvertID(iid, &nsiid);
+
+ nsresult rv = xpc::HasInstance(cx, obj, &nsiid, instanceof);
+ if (rv != NS_OK)
+ return fail(jsapi, rs);
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvDOMInstanceOf(const ObjectId& objId, const int& prototypeID,
+ const int& depth, ReturnStatus* rs, bool* instanceof)
+{
+ MaybeForceDebugGC();
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(scopeForTargetObjects())))
+ return false;
+ JSContext* cx = jsapi.cx();
+ *instanceof = false;
+
+ RootedObject obj(cx, findObjectById(cx, objId));
+ if (!obj)
+ return deadCPOW(jsapi, rs);
+
+ LOG("%s.domInstanceOf()", ReceiverObj(objId));
+
+ bool tmp;
+ if (!mozilla::dom::InterfaceHasInstance(cx, prototypeID, depth, obj, &tmp))
+ return fail(jsapi, rs);
+ *instanceof = tmp;
+
+ return ok(rs);
+}
+
+bool
+WrapperAnswer::RecvDropObject(const ObjectId& objId)
+{
+ JSObject* obj = objects_.find(objId);
+ if (obj) {
+ objectIdMap(objId.hasXrayWaiver()).remove(obj);
+ objects_.remove(objId);
+ }
+ return true;
+}
diff --git a/js/ipc/WrapperAnswer.h b/js/ipc/WrapperAnswer.h
new file mode 100644
index 000000000..4df7b08ae
--- /dev/null
+++ b/js/ipc/WrapperAnswer.h
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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_jsipc_WrapperAnswer_h_
+#define mozilla_jsipc_WrapperAnswer_h_
+
+#include "JavaScriptShared.h"
+
+namespace mozilla {
+
+namespace dom {
+class AutoJSAPI;
+} // namespace dom
+
+namespace jsipc {
+
+class WrapperAnswer : public virtual JavaScriptShared
+{
+ public:
+ bool RecvPreventExtensions(const ObjectId& objId, ReturnStatus* rs);
+ bool RecvGetPropertyDescriptor(const ObjectId& objId, const JSIDVariant& id,
+ ReturnStatus* rs,
+ PPropertyDescriptor* out);
+ bool RecvGetOwnPropertyDescriptor(const ObjectId& objId,
+ const JSIDVariant& id,
+ ReturnStatus* rs,
+ PPropertyDescriptor* out);
+ bool RecvDefineProperty(const ObjectId& objId, const JSIDVariant& id,
+ const PPropertyDescriptor& flags, ReturnStatus* rs);
+ bool RecvDelete(const ObjectId& objId, const JSIDVariant& id, ReturnStatus* rs);
+
+ bool RecvHas(const ObjectId& objId, const JSIDVariant& id,
+ ReturnStatus* rs, bool* foundp);
+ bool RecvHasOwn(const ObjectId& objId, const JSIDVariant& id,
+ ReturnStatus* rs, bool* foundp);
+ bool RecvGet(const ObjectId& objId, const JSVariant& receiverVar,
+ const JSIDVariant& id,
+ ReturnStatus* rs, JSVariant* result);
+ bool RecvSet(const ObjectId& objId, const JSIDVariant& id, const JSVariant& value,
+ const JSVariant& receiverVar, ReturnStatus* rs);
+
+ bool RecvIsExtensible(const ObjectId& objId, ReturnStatus* rs,
+ bool* result);
+ bool RecvCallOrConstruct(const ObjectId& objId, InfallibleTArray<JSParam>&& argv,
+ const bool& construct, ReturnStatus* rs, JSVariant* result,
+ nsTArray<JSParam>* outparams);
+ bool RecvHasInstance(const ObjectId& objId, const JSVariant& v, ReturnStatus* rs, bool* bp);
+ bool RecvGetBuiltinClass(const ObjectId& objId, ReturnStatus* rs,
+ uint32_t* classValue);
+ bool RecvIsArray(const ObjectId& objId, ReturnStatus* rs, uint32_t* ans);
+ bool RecvClassName(const ObjectId& objId, nsCString* result);
+ bool RecvGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result);
+ bool RecvGetPrototypeIfOrdinary(const ObjectId& objId, ReturnStatus* rs, bool* isOrdinary,
+ ObjectOrNullVariant* result);
+ bool RecvRegExpToShared(const ObjectId& objId, ReturnStatus* rs, nsString* source, uint32_t* flags);
+
+ bool RecvGetPropertyKeys(const ObjectId& objId, const uint32_t& flags,
+ ReturnStatus* rs, nsTArray<JSIDVariant>* ids);
+ bool RecvInstanceOf(const ObjectId& objId, const JSIID& iid,
+ ReturnStatus* rs, bool* instanceof);
+ bool RecvDOMInstanceOf(const ObjectId& objId, const int& prototypeID, const int& depth,
+ ReturnStatus* rs, bool* instanceof);
+
+ bool RecvDropObject(const ObjectId& objId);
+
+ private:
+ bool fail(dom::AutoJSAPI& jsapi, ReturnStatus* rs);
+ bool ok(ReturnStatus* rs);
+ bool ok(ReturnStatus* rs, const JS::ObjectOpResult& result);
+ bool deadCPOW(dom::AutoJSAPI& jsapi, ReturnStatus* rs);
+};
+
+} // namespace jsipc
+} // namespace mozilla
+
+#endif
diff --git a/js/ipc/WrapperOwner.cpp b/js/ipc/WrapperOwner.cpp
new file mode 100644
index 000000000..427ec6ab3
--- /dev/null
+++ b/js/ipc/WrapperOwner.cpp
@@ -0,0 +1,1236 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=4 sw=4 et 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 "WrapperOwner.h"
+#include "JavaScriptLogging.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "jsfriendapi.h"
+#include "js/CharacterEncoding.h"
+#include "xpcprivate.h"
+#include "CPOWTimer.h"
+#include "WrapperFactory.h"
+
+#include "nsIDocShellTreeItem.h"
+#include "nsIDOMDocument.h"
+
+using namespace js;
+using namespace JS;
+using namespace mozilla;
+using namespace mozilla::jsipc;
+
+struct AuxCPOWData
+{
+ ObjectId id;
+ bool isCallable;
+ bool isConstructor;
+ bool isDOMObject;
+
+ // The object tag is just some auxilliary information that clients can use
+ // however they see fit.
+ nsCString objectTag;
+
+ // The class name for WrapperOwner::className, below.
+ nsCString className;
+
+ AuxCPOWData(ObjectId id,
+ bool isCallable,
+ bool isConstructor,
+ bool isDOMObject,
+ const nsACString& objectTag)
+ : id(id),
+ isCallable(isCallable),
+ isConstructor(isConstructor),
+ isDOMObject(isDOMObject),
+ objectTag(objectTag)
+ {}
+};
+
+WrapperOwner::WrapperOwner()
+ : inactive_(false)
+{
+}
+
+static inline AuxCPOWData*
+AuxCPOWDataOf(JSObject* obj)
+{
+ MOZ_ASSERT(IsCPOW(obj));
+ return static_cast<AuxCPOWData*>(GetProxyExtra(obj, 1).toPrivate());
+}
+
+static inline WrapperOwner*
+OwnerOf(JSObject* obj)
+{
+ MOZ_ASSERT(IsCPOW(obj));
+ return reinterpret_cast<WrapperOwner*>(GetProxyExtra(obj, 0).toPrivate());
+}
+
+ObjectId
+WrapperOwner::idOfUnchecked(JSObject* obj)
+{
+ MOZ_ASSERT(IsCPOW(obj));
+
+ AuxCPOWData* aux = AuxCPOWDataOf(obj);
+ MOZ_ASSERT(!aux->id.isNull());
+ return aux->id;
+}
+
+ObjectId
+WrapperOwner::idOf(JSObject* obj)
+{
+ ObjectId objId = idOfUnchecked(obj);
+ MOZ_ASSERT(hasCPOW(objId, obj));
+ return objId;
+}
+
+class CPOWProxyHandler : public BaseProxyHandler
+{
+ public:
+ constexpr CPOWProxyHandler()
+ : BaseProxyHandler(&family) {}
+
+ virtual bool finalizeInBackground(const Value& priv) const override {
+ return false;
+ }
+
+ virtual bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const override;
+ virtual bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const override;
+ virtual bool ownPropertyKeys(JSContext* cx, HandleObject proxy,
+ AutoIdVector& props) const override;
+ virtual bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
+ ObjectOpResult& result) const override;
+ virtual bool enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const override;
+ virtual bool preventExtensions(JSContext* cx, HandleObject proxy,
+ ObjectOpResult& result) const override;
+ virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override;
+ virtual bool has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override;
+ virtual bool get(JSContext* cx, HandleObject proxy, HandleValue receiver,
+ HandleId id, MutableHandleValue vp) const override;
+ virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
+ JS::HandleValue receiver, JS::ObjectOpResult& result) const override;
+ virtual bool call(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
+ virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
+
+ virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const override;
+ virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const override;
+ virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
+ AutoIdVector& props) const override;
+ virtual bool hasInstance(JSContext* cx, HandleObject proxy,
+ MutableHandleValue v, bool* bp) const override;
+ virtual bool getBuiltinClass(JSContext* cx, HandleObject obj, js::ESClass* cls) const override;
+ virtual bool isArray(JSContext* cx, HandleObject obj,
+ IsArrayAnswer* answer) const override;
+ virtual const char* className(JSContext* cx, HandleObject proxy) const override;
+ virtual bool regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const override;
+ virtual void finalize(JSFreeOp* fop, JSObject* proxy) const override;
+ virtual void objectMoved(JSObject* proxy, const JSObject* old) const override;
+ virtual bool isCallable(JSObject* obj) const override;
+ virtual bool isConstructor(JSObject* obj) const override;
+ virtual bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const override;
+ virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject protop) const override;
+
+ static const char family;
+ static const CPOWProxyHandler singleton;
+};
+
+const char CPOWProxyHandler::family = 0;
+const CPOWProxyHandler CPOWProxyHandler::singleton;
+
+#define FORWARD(call, args) \
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::JS); \
+ WrapperOwner* owner = OwnerOf(proxy); \
+ if (!owner->active()) { \
+ JS_ReportErrorASCII(cx, "cannot use a CPOW whose process is gone"); \
+ return false; \
+ } \
+ if (!owner->allowMessage(cx)) { \
+ return false; \
+ } \
+ { \
+ CPOWTimer timer(cx); \
+ return owner->call args; \
+ }
+
+bool
+CPOWProxyHandler::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ FORWARD(getPropertyDescriptor, (cx, proxy, id, desc));
+}
+
+bool
+WrapperOwner::getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc)
+{
+ ObjectId objId = idOf(proxy);
+
+ JSIDVariant idVar;
+ if (!toJSIDVariant(cx, id, &idVar))
+ return false;
+
+ ReturnStatus status;
+ PPropertyDescriptor result;
+ if (!SendGetPropertyDescriptor(objId, idVar, &status, &result))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ if (!ok(cx, status))
+ return false;
+
+ return toDescriptor(cx, result, desc);
+}
+
+bool
+CPOWProxyHandler::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc) const
+{
+ FORWARD(getOwnPropertyDescriptor, (cx, proxy, id, desc));
+}
+
+bool
+WrapperOwner::getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
+ MutableHandle<PropertyDescriptor> desc)
+{
+ ObjectId objId = idOf(proxy);
+
+ JSIDVariant idVar;
+ if (!toJSIDVariant(cx, id, &idVar))
+ return false;
+
+ ReturnStatus status;
+ PPropertyDescriptor result;
+ if (!SendGetOwnPropertyDescriptor(objId, idVar, &status, &result))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ if (!ok(cx, status))
+ return false;
+
+ return toDescriptor(cx, result, desc);
+}
+
+bool
+CPOWProxyHandler::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result) const
+{
+ FORWARD(defineProperty, (cx, proxy, id, desc, result));
+}
+
+bool
+WrapperOwner::defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result)
+{
+ ObjectId objId = idOf(proxy);
+
+ JSIDVariant idVar;
+ if (!toJSIDVariant(cx, id, &idVar))
+ return false;
+
+ PPropertyDescriptor descriptor;
+ if (!fromDescriptor(cx, desc, &descriptor))
+ return false;
+
+ ReturnStatus status;
+ if (!SendDefineProperty(objId, idVar, descriptor, &status))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ return ok(cx, status, result);
+}
+
+bool
+CPOWProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject proxy,
+ AutoIdVector& props) const
+{
+ FORWARD(ownPropertyKeys, (cx, proxy, props));
+}
+
+bool
+WrapperOwner::ownPropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props)
+{
+ return getPropertyKeys(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
+}
+
+bool
+CPOWProxyHandler::delete_(JSContext* cx, HandleObject proxy, HandleId id,
+ ObjectOpResult& result) const
+{
+ FORWARD(delete_, (cx, proxy, id, result));
+}
+
+bool
+WrapperOwner::delete_(JSContext* cx, HandleObject proxy, HandleId id, ObjectOpResult& result)
+{
+ ObjectId objId = idOf(proxy);
+
+ JSIDVariant idVar;
+ if (!toJSIDVariant(cx, id, &idVar))
+ return false;
+
+ ReturnStatus status;
+ if (!SendDelete(objId, idVar, &status))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ return ok(cx, status, result);
+}
+
+bool
+CPOWProxyHandler::enumerate(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const
+{
+ // Using a CPOW for the Iterator would slow down for .. in performance, instead
+ // call the base hook, that will use our implementation of getOwnEnumerablePropertyKeys
+ // and follow the proto chain.
+ return BaseProxyHandler::enumerate(cx, proxy, objp);
+}
+
+bool
+CPOWProxyHandler::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
+{
+ FORWARD(has, (cx, proxy, id, bp));
+}
+
+bool
+WrapperOwner::has(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
+{
+ ObjectId objId = idOf(proxy);
+
+ JSIDVariant idVar;
+ if (!toJSIDVariant(cx, id, &idVar))
+ return false;
+
+ ReturnStatus status;
+ if (!SendHas(objId, idVar, &status, bp))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ return ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) const
+{
+ FORWARD(hasOwn, (cx, proxy, id, bp));
+}
+
+bool
+WrapperOwner::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp)
+{
+ ObjectId objId = idOf(proxy);
+
+ JSIDVariant idVar;
+ if (!toJSIDVariant(cx, id, &idVar))
+ return false;
+
+ ReturnStatus status;
+ if (!SendHasOwn(objId, idVar, &status, bp))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ return !!ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue receiver,
+ HandleId id, MutableHandleValue vp) const
+{
+ FORWARD(get, (cx, proxy, receiver, id, vp));
+}
+
+static bool
+CPOWDOMQI(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ if (!args.thisv().isObject() || !IsCPOW(&args.thisv().toObject())) {
+ JS_ReportErrorASCII(cx, "bad this object passed to special QI");
+ return false;
+ }
+
+ RootedObject proxy(cx, &args.thisv().toObject());
+ FORWARD(DOMQI, (cx, proxy, args));
+}
+
+static bool
+CPOWToString(JSContext* cx, unsigned argc, Value* vp)
+{
+ CallArgs args = CallArgsFromVp(argc, vp);
+ RootedObject callee(cx, &args.callee());
+ RootedValue cpowValue(cx);
+ if (!JS_GetProperty(cx, callee, "__cpow__", &cpowValue))
+ return false;
+
+ if (!cpowValue.isObject() || !IsCPOW(&cpowValue.toObject())) {
+ JS_ReportErrorASCII(cx, "CPOWToString called on an incompatible object");
+ return false;
+ }
+
+ RootedObject proxy(cx, &cpowValue.toObject());
+ FORWARD(toString, (cx, proxy, args));
+}
+
+bool
+WrapperOwner::toString(JSContext* cx, HandleObject cpow, JS::CallArgs& args)
+{
+ // Ask the other side to call its toString method. Update the callee so that
+ // it points to the CPOW and not to the synthesized CPOWToString function.
+ args.setCallee(ObjectValue(*cpow));
+ if (!callOrConstruct(cx, cpow, args, false))
+ return false;
+
+ if (!args.rval().isString())
+ return true;
+
+ RootedString cpowResult(cx, args.rval().toString());
+ nsAutoJSString toStringResult;
+ if (!toStringResult.init(cx, cpowResult))
+ return false;
+
+ // We don't want to wrap toString() results for things like the location
+ // object, where toString() is supposed to return a URL and nothing else.
+ nsAutoString result;
+ if (toStringResult[0] == '[') {
+ result.AppendLiteral("[object CPOW ");
+ result += toStringResult;
+ result.AppendLiteral("]");
+ } else {
+ result += toStringResult;
+ }
+
+ JSString* str = JS_NewUCStringCopyN(cx, result.get(), result.Length());
+ if (!str)
+ return false;
+
+ args.rval().setString(str);
+ return true;
+}
+
+bool
+WrapperOwner::DOMQI(JSContext* cx, JS::HandleObject proxy, JS::CallArgs& args)
+{
+ // Someone's calling us, handle nsISupports specially to avoid unnecessary
+ // CPOW traffic.
+ HandleValue id = args[0];
+ if (id.isObject()) {
+ RootedObject idobj(cx, &id.toObject());
+ nsCOMPtr<nsIJSID> jsid;
+
+ nsresult rv = UnwrapArg<nsIJSID>(idobj, getter_AddRefs(jsid));
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_ASSERT(jsid, "bad wrapJS");
+ const nsID* idptr = jsid->GetID();
+ if (idptr->Equals(NS_GET_IID(nsISupports))) {
+ args.rval().set(args.thisv());
+ return true;
+ }
+
+ // Webidl-implemented DOM objects never have nsIClassInfo.
+ if (idptr->Equals(NS_GET_IID(nsIClassInfo)))
+ return Throw(cx, NS_ERROR_NO_INTERFACE);
+ }
+ }
+
+ // It wasn't nsISupports, call into the other process to do the QI for us
+ // (since we don't know what other interfaces our object supports). Note
+ // that we have to use JS_GetPropertyDescriptor here to avoid infinite
+ // recursion back into CPOWDOMQI via WrapperOwner::get().
+ // We could stash the actual QI function on our own function object to avoid
+ // if we're called multiple times, but since we're transient, there's no
+ // point right now.
+ JS::Rooted<PropertyDescriptor> propDesc(cx);
+ if (!JS_GetPropertyDescriptor(cx, proxy, "QueryInterface", &propDesc))
+ return false;
+
+ if (!propDesc.value().isObject()) {
+ MOZ_ASSERT_UNREACHABLE("We didn't get QueryInterface off a node");
+ return Throw(cx, NS_ERROR_UNEXPECTED);
+ }
+ return JS_CallFunctionValue(cx, proxy, propDesc.value(), args, args.rval());
+}
+
+bool
+WrapperOwner::get(JSContext* cx, HandleObject proxy, HandleValue receiver,
+ HandleId id, MutableHandleValue vp)
+{
+ ObjectId objId = idOf(proxy);
+
+ JSVariant receiverVar;
+ if (!toVariant(cx, receiver, &receiverVar))
+ return false;
+
+ JSIDVariant idVar;
+ if (!toJSIDVariant(cx, id, &idVar))
+ return false;
+
+ AuxCPOWData* data = AuxCPOWDataOf(proxy);
+ if (data->isDOMObject &&
+ idVar.type() == JSIDVariant::TnsString &&
+ idVar.get_nsString().EqualsLiteral("QueryInterface"))
+ {
+ // Handle QueryInterface on DOM Objects specially since we can assume
+ // certain things about their implementation.
+ RootedFunction qi(cx, JS_NewFunction(cx, CPOWDOMQI, 1, 0,
+ "QueryInterface"));
+ if (!qi)
+ return false;
+
+ vp.set(ObjectValue(*JS_GetFunctionObject(qi)));
+ return true;
+ }
+
+ JSVariant val;
+ ReturnStatus status;
+ if (!SendGet(objId, receiverVar, idVar, &status, &val))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ if (!ok(cx, status))
+ return false;
+
+ if (!fromVariant(cx, val, vp))
+ return false;
+
+ if (idVar.type() == JSIDVariant::TnsString &&
+ idVar.get_nsString().EqualsLiteral("toString")) {
+ RootedFunction toString(cx, JS_NewFunction(cx, CPOWToString, 0, 0,
+ "toString"));
+ if (!toString)
+ return false;
+
+ RootedObject toStringObj(cx, JS_GetFunctionObject(toString));
+
+ if (!JS_DefineProperty(cx, toStringObj, "__cpow__", vp, JSPROP_PERMANENT | JSPROP_READONLY))
+ return false;
+
+ vp.set(ObjectValue(*toStringObj));
+ }
+
+ return true;
+}
+
+bool
+CPOWProxyHandler::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
+ JS::HandleValue receiver, JS::ObjectOpResult& result) const
+{
+ FORWARD(set, (cx, proxy, id, v, receiver, result));
+}
+
+bool
+WrapperOwner::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
+ JS::HandleValue receiver, JS::ObjectOpResult& result)
+{
+ ObjectId objId = idOf(proxy);
+
+ JSIDVariant idVar;
+ if (!toJSIDVariant(cx, id, &idVar))
+ return false;
+
+ JSVariant val;
+ if (!toVariant(cx, v, &val))
+ return false;
+
+ JSVariant receiverVar;
+ if (!toVariant(cx, receiver, &receiverVar))
+ return false;
+
+ ReturnStatus status;
+ if (!SendSet(objId, idVar, val, receiverVar, &status))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ return ok(cx, status, result);
+}
+
+bool
+CPOWProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
+ AutoIdVector& props) const
+{
+ FORWARD(getOwnEnumerablePropertyKeys, (cx, proxy, props));
+}
+
+bool
+WrapperOwner::getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy, AutoIdVector& props)
+{
+ return getPropertyKeys(cx, proxy, JSITER_OWNONLY, props);
+}
+
+bool
+CPOWProxyHandler::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result) const
+{
+ FORWARD(preventExtensions, (cx, proxy, result));
+}
+
+bool
+WrapperOwner::preventExtensions(JSContext* cx, HandleObject proxy, ObjectOpResult& result)
+{
+ ObjectId objId = idOf(proxy);
+
+ ReturnStatus status;
+ if (!SendPreventExtensions(objId, &status))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ return ok(cx, status, result);
+}
+
+bool
+CPOWProxyHandler::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const
+{
+ FORWARD(isExtensible, (cx, proxy, extensible));
+}
+
+bool
+WrapperOwner::isExtensible(JSContext* cx, HandleObject proxy, bool* extensible)
+{
+ ObjectId objId = idOf(proxy);
+
+ ReturnStatus status;
+ if (!SendIsExtensible(objId, &status, extensible))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ return ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::call(JSContext* cx, HandleObject proxy, const CallArgs& args) const
+{
+ FORWARD(callOrConstruct, (cx, proxy, args, false));
+}
+
+bool
+CPOWProxyHandler::construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const
+{
+ FORWARD(callOrConstruct, (cx, proxy, args, true));
+}
+
+bool
+WrapperOwner::callOrConstruct(JSContext* cx, HandleObject proxy, const CallArgs& args,
+ bool construct)
+{
+ ObjectId objId = idOf(proxy);
+
+ InfallibleTArray<JSParam> vals;
+ AutoValueVector outobjects(cx);
+
+ RootedValue v(cx);
+ for (size_t i = 0; i < args.length() + 2; i++) {
+ // The |this| value for constructors is a magic value that we won't be
+ // able to convert, so skip it.
+ if (i == 1 && construct)
+ v = UndefinedValue();
+ else
+ v = args.base()[i];
+ if (v.isObject()) {
+ RootedObject obj(cx, &v.toObject());
+ if (xpc::IsOutObject(cx, obj)) {
+ // Make sure it is not an in-out object.
+ bool found;
+ if (!JS_HasProperty(cx, obj, "value", &found))
+ return false;
+ if (found) {
+ JS_ReportErrorASCII(cx, "in-out objects cannot be sent via CPOWs yet");
+ return false;
+ }
+
+ vals.AppendElement(JSParam(void_t()));
+ if (!outobjects.append(ObjectValue(*obj)))
+ return false;
+ continue;
+ }
+ }
+ JSVariant val;
+ if (!toVariant(cx, v, &val))
+ return false;
+ vals.AppendElement(JSParam(val));
+ }
+
+ JSVariant result;
+ ReturnStatus status;
+ InfallibleTArray<JSParam> outparams;
+ if (!SendCallOrConstruct(objId, vals, construct, &status, &result, &outparams))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ if (!ok(cx, status))
+ return false;
+
+ if (outparams.Length() != outobjects.length())
+ return ipcfail(cx);
+
+ RootedObject obj(cx);
+ for (size_t i = 0; i < outparams.Length(); i++) {
+ // Don't bother doing anything for outparams that weren't set.
+ if (outparams[i].type() == JSParam::Tvoid_t)
+ continue;
+
+ // Take the value the child process returned, and set it on the XPC
+ // object.
+ if (!fromVariant(cx, outparams[i], &v))
+ return false;
+
+ obj = &outobjects[i].toObject();
+ if (!JS_SetProperty(cx, obj, "value", v))
+ return false;
+ }
+
+ if (!fromVariant(cx, result, args.rval()))
+ return false;
+
+ return true;
+}
+
+bool
+CPOWProxyHandler::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp) const
+{
+ FORWARD(hasInstance, (cx, proxy, v, bp));
+}
+
+bool
+WrapperOwner::hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v, bool* bp)
+{
+ ObjectId objId = idOf(proxy);
+
+ JSVariant vVar;
+ if (!toVariant(cx, v, &vVar))
+ return false;
+
+ ReturnStatus status;
+ JSVariant result;
+ if (!SendHasInstance(objId, vVar, &status, bp))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ return ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls) const
+{
+ FORWARD(getBuiltinClass, (cx, proxy, cls));
+}
+
+bool
+WrapperOwner::getBuiltinClass(JSContext* cx, HandleObject proxy, ESClass* cls)
+{
+ ObjectId objId = idOf(proxy);
+
+ uint32_t classValue = uint32_t(ESClass::Other);
+ ReturnStatus status;
+ if (!SendGetBuiltinClass(objId, &status, &classValue))
+ return ipcfail(cx);
+ *cls = ESClass(classValue);
+
+ LOG_STACK();
+
+ return ok(cx, status);
+}
+
+bool
+CPOWProxyHandler::isArray(JSContext* cx, HandleObject proxy,
+ IsArrayAnswer* answer) const
+{
+ FORWARD(isArray, (cx, proxy, answer));
+}
+
+bool
+WrapperOwner::isArray(JSContext* cx, HandleObject proxy, IsArrayAnswer* answer)
+{
+ ObjectId objId = idOf(proxy);
+
+ uint32_t ans;
+ ReturnStatus status;
+ if (!SendIsArray(objId, &status, &ans))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ *answer = IsArrayAnswer(ans);
+ MOZ_ASSERT(*answer == IsArrayAnswer::Array ||
+ *answer == IsArrayAnswer::NotArray ||
+ *answer == IsArrayAnswer::RevokedProxy);
+
+ return ok(cx, status);
+}
+
+const char*
+CPOWProxyHandler::className(JSContext* cx, HandleObject proxy) const
+{
+ WrapperOwner* parent = OwnerOf(proxy);
+ if (!parent->active())
+ return "<dead CPOW>";
+ return parent->className(cx, proxy);
+}
+
+const char*
+WrapperOwner::className(JSContext* cx, HandleObject proxy)
+{
+ AuxCPOWData* data = AuxCPOWDataOf(proxy);
+ if (data->className.IsEmpty()) {
+ ObjectId objId = idOf(proxy);
+
+ if (!SendClassName(objId, &data->className))
+ return "<error>";
+
+ LOG_STACK();
+ }
+
+ return data->className.get();
+}
+
+bool
+CPOWProxyHandler::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject objp) const
+{
+ FORWARD(getPrototype, (cx, proxy, objp));
+}
+
+bool
+WrapperOwner::getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject objp)
+{
+ ObjectId objId = idOf(proxy);
+
+ ObjectOrNullVariant val;
+ ReturnStatus status;
+ if (!SendGetPrototype(objId, &status, &val))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ if (!ok(cx, status))
+ return false;
+
+ objp.set(fromObjectOrNullVariant(cx, val));
+
+ return true;
+}
+
+bool
+CPOWProxyHandler::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject objp) const
+{
+ FORWARD(getPrototypeIfOrdinary, (cx, proxy, isOrdinary, objp));
+}
+
+bool
+WrapperOwner::getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
+ MutableHandleObject objp)
+{
+ ObjectId objId = idOf(proxy);
+
+ ObjectOrNullVariant val;
+ ReturnStatus status;
+ if (!SendGetPrototypeIfOrdinary(objId, &status, isOrdinary, &val))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ if (!ok(cx, status))
+ return false;
+
+ objp.set(fromObjectOrNullVariant(cx, val));
+
+ return true;
+}
+
+bool
+CPOWProxyHandler::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g) const
+{
+ FORWARD(regexp_toShared, (cx, proxy, g));
+}
+
+bool
+WrapperOwner::regexp_toShared(JSContext* cx, HandleObject proxy, RegExpGuard* g)
+{
+ ObjectId objId = idOf(proxy);
+
+ ReturnStatus status;
+ nsString source;
+ unsigned flags = 0;
+ if (!SendRegExpToShared(objId, &status, &source, &flags))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ if (!ok(cx, status))
+ return false;
+
+ RootedObject regexp(cx);
+ regexp = JS_NewUCRegExpObject(cx, source.get(), source.Length(), flags);
+ if (!regexp)
+ return false;
+
+ return js::RegExpToSharedNonInline(cx, regexp, g);
+}
+
+void
+CPOWProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const
+{
+ AuxCPOWData* aux = AuxCPOWDataOf(proxy);
+
+ OwnerOf(proxy)->drop(proxy);
+
+ if (aux)
+ delete aux;
+}
+
+void
+CPOWProxyHandler::objectMoved(JSObject* proxy, const JSObject* old) const
+{
+ OwnerOf(proxy)->updatePointer(proxy, old);
+}
+
+bool
+CPOWProxyHandler::isCallable(JSObject* proxy) const
+{
+ AuxCPOWData* aux = AuxCPOWDataOf(proxy);
+ return aux->isCallable;
+}
+
+bool
+CPOWProxyHandler::isConstructor(JSObject* proxy) const
+{
+ AuxCPOWData* aux = AuxCPOWDataOf(proxy);
+ return aux->isConstructor;
+}
+
+void
+WrapperOwner::drop(JSObject* obj)
+{
+ ObjectId objId = idOf(obj);
+
+ cpows_.remove(objId);
+ if (active())
+ Unused << SendDropObject(objId);
+ decref();
+}
+
+void
+WrapperOwner::updatePointer(JSObject* obj, const JSObject* old)
+{
+ ObjectId objId = idOfUnchecked(obj);
+ MOZ_ASSERT(hasCPOW(objId, old));
+ cpows_.add(objId, obj);
+}
+
+bool
+WrapperOwner::init()
+{
+ if (!JavaScriptShared::init())
+ return false;
+
+ return true;
+}
+
+bool
+WrapperOwner::getPropertyKeys(JSContext* cx, HandleObject proxy, uint32_t flags, AutoIdVector& props)
+{
+ ObjectId objId = idOf(proxy);
+
+ ReturnStatus status;
+ InfallibleTArray<JSIDVariant> ids;
+ if (!SendGetPropertyKeys(objId, flags, &status, &ids))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ if (!ok(cx, status))
+ return false;
+
+ for (size_t i = 0; i < ids.Length(); i++) {
+ RootedId id(cx);
+ if (!fromJSIDVariant(cx, ids[i], &id))
+ return false;
+ if (!props.append(id))
+ return false;
+ }
+
+ return true;
+}
+
+namespace mozilla {
+namespace jsipc {
+
+bool
+IsCPOW(JSObject* obj)
+{
+ return IsProxy(obj) && GetProxyHandler(obj) == &CPOWProxyHandler::singleton;
+}
+
+bool
+IsWrappedCPOW(JSObject* obj)
+{
+ JSObject* unwrapped = js::UncheckedUnwrap(obj, true);
+ if (!unwrapped)
+ return false;
+ return IsCPOW(unwrapped);
+}
+
+void
+GetWrappedCPOWTag(JSObject* obj, nsACString& out)
+{
+ JSObject* unwrapped = js::UncheckedUnwrap(obj, true);
+ MOZ_ASSERT(IsCPOW(unwrapped));
+
+ AuxCPOWData* aux = AuxCPOWDataOf(unwrapped);
+ if (aux)
+ out = aux->objectTag;
+}
+
+nsresult
+InstanceOf(JSObject* proxy, const nsID* id, bool* bp)
+{
+ WrapperOwner* parent = OwnerOf(proxy);
+ if (!parent->active())
+ return NS_ERROR_UNEXPECTED;
+ return parent->instanceOf(proxy, id, bp);
+}
+
+bool
+DOMInstanceOf(JSContext* cx, JSObject* proxyArg, int prototypeID, int depth, bool* bp)
+{
+ RootedObject proxy(cx, proxyArg);
+ FORWARD(domInstanceOf, (cx, proxy, prototypeID, depth, bp));
+}
+
+} /* namespace jsipc */
+} /* namespace mozilla */
+
+nsresult
+WrapperOwner::instanceOf(JSObject* obj, const nsID* id, bool* bp)
+{
+ ObjectId objId = idOf(obj);
+
+ JSIID iid;
+ ConvertID(*id, &iid);
+
+ ReturnStatus status;
+ if (!SendInstanceOf(objId, iid, &status, bp))
+ return NS_ERROR_UNEXPECTED;
+
+ if (status.type() != ReturnStatus::TReturnSuccess)
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+bool
+WrapperOwner::domInstanceOf(JSContext* cx, JSObject* obj, int prototypeID, int depth, bool* bp)
+{
+ ObjectId objId = idOf(obj);
+
+ ReturnStatus status;
+ if (!SendDOMInstanceOf(objId, prototypeID, depth, &status, bp))
+ return ipcfail(cx);
+
+ LOG_STACK();
+
+ return ok(cx, status);
+}
+
+void
+WrapperOwner::ActorDestroy(ActorDestroyReason why)
+{
+ inactive_ = true;
+
+ objects_.clear();
+ unwaivedObjectIds_.clear();
+ waivedObjectIds_.clear();
+}
+
+bool
+WrapperOwner::ipcfail(JSContext* cx)
+{
+ JS_ReportErrorASCII(cx, "cross-process JS call failed");
+ return false;
+}
+
+bool
+WrapperOwner::ok(JSContext* cx, const ReturnStatus& status)
+{
+ if (status.type() == ReturnStatus::TReturnSuccess)
+ return true;
+
+ if (status.type() == ReturnStatus::TReturnStopIteration)
+ return JS_ThrowStopIteration(cx);
+
+ if (status.type() == ReturnStatus::TReturnDeadCPOW) {
+ JS_ReportErrorASCII(cx, "operation not possible on dead CPOW");
+ return false;
+ }
+
+ RootedValue exn(cx);
+ if (!fromVariant(cx, status.get_ReturnException().exn(), &exn))
+ return false;
+
+ JS_SetPendingException(cx, exn);
+ return false;
+}
+
+bool
+WrapperOwner::ok(JSContext* cx, const ReturnStatus& status, ObjectOpResult& result)
+{
+ if (status.type() == ReturnStatus::TReturnObjectOpResult)
+ return result.fail(status.get_ReturnObjectOpResult().code());
+ if (!ok(cx, status))
+ return false;
+ return result.succeed();
+}
+
+// CPOWs can have a tag string attached to them, originating in the local
+// process from this function. It's sent with the CPOW to the remote process,
+// where it can be fetched with Components.utils.getCrossProcessWrapperTag.
+static nsCString
+GetRemoteObjectTag(JS::Handle<JSObject*> obj)
+{
+ if (nsCOMPtr<nsISupports> supports = xpc::UnwrapReflectorToISupports(obj)) {
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(do_QueryInterface(supports));
+ if (treeItem)
+ return NS_LITERAL_CSTRING("ContentDocShellTreeItem");
+
+ nsCOMPtr<nsIDOMDocument> doc(do_QueryInterface(supports));
+ if (doc)
+ return NS_LITERAL_CSTRING("ContentDocument");
+ }
+
+ return NS_LITERAL_CSTRING("generic");
+}
+
+static RemoteObject
+MakeRemoteObject(JSContext* cx, ObjectId id, HandleObject obj)
+{
+ return RemoteObject(id.serialize(),
+ JS::IsCallable(obj),
+ JS::IsConstructor(obj),
+ dom::IsDOMObject(obj),
+ GetRemoteObjectTag(obj));
+}
+
+bool
+WrapperOwner::toObjectVariant(JSContext* cx, JSObject* objArg, ObjectVariant* objVarp)
+{
+ RootedObject obj(cx, objArg);
+ MOZ_ASSERT(obj);
+
+ // We always save objects unwrapped in the CPOW table. If we stored
+ // wrappers, then the wrapper might be GCed while the target remained alive.
+ // Whenever operating on an object that comes from the table, we wrap it
+ // in findObjectById.
+ unsigned wrapperFlags = 0;
+ obj = js::UncheckedUnwrap(obj, true, &wrapperFlags);
+ if (obj && IsCPOW(obj) && OwnerOf(obj) == this) {
+ *objVarp = LocalObject(idOf(obj).serialize());
+ return true;
+ }
+ bool waiveXray = wrapperFlags & xpc::WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG;
+
+ ObjectId id = objectIdMap(waiveXray).find(obj);
+ if (!id.isNull()) {
+ MOZ_ASSERT(id.hasXrayWaiver() == waiveXray);
+ *objVarp = MakeRemoteObject(cx, id, obj);
+ return true;
+ }
+
+ // Need to call PreserveWrapper on |obj| in case it's a reflector.
+ // FIXME: What if it's an XPCWrappedNative?
+ if (mozilla::dom::IsDOMObject(obj))
+ mozilla::dom::TryPreserveWrapper(obj);
+
+ id = ObjectId(nextSerialNumber_++, waiveXray);
+ if (!objects_.add(id, obj))
+ return false;
+ if (!objectIdMap(waiveXray).add(cx, obj, id))
+ return false;
+
+ *objVarp = MakeRemoteObject(cx, id, obj);
+ return true;
+}
+
+JSObject*
+WrapperOwner::fromObjectVariant(JSContext* cx, const ObjectVariant& objVar)
+{
+ if (objVar.type() == ObjectVariant::TRemoteObject) {
+ return fromRemoteObjectVariant(cx, objVar.get_RemoteObject());
+ } else {
+ return fromLocalObjectVariant(cx, objVar.get_LocalObject());
+ }
+}
+
+JSObject*
+WrapperOwner::fromRemoteObjectVariant(JSContext* cx, const RemoteObject& objVar)
+{
+ ObjectId objId = ObjectId::deserialize(objVar.serializedId());
+ RootedObject obj(cx, findCPOWById(objId));
+ if (!obj) {
+
+ // All CPOWs live in the privileged junk scope.
+ RootedObject junkScope(cx, xpc::PrivilegedJunkScope());
+ JSAutoCompartment ac(cx, junkScope);
+ RootedValue v(cx, UndefinedValue());
+ // We need to setLazyProto for the getPrototype/getPrototypeIfOrdinary
+ // hooks.
+ ProxyOptions options;
+ options.setLazyProto(true);
+ obj = NewProxyObject(cx,
+ &CPOWProxyHandler::singleton,
+ v,
+ nullptr,
+ options);
+ if (!obj)
+ return nullptr;
+
+ if (!cpows_.add(objId, obj))
+ return nullptr;
+
+ nextCPOWNumber_ = objId.serialNumber() + 1;
+
+ // Incref once we know the decref will be called.
+ incref();
+
+ AuxCPOWData* aux = new AuxCPOWData(objId,
+ objVar.isCallable(),
+ objVar.isConstructor(),
+ objVar.isDOMObject(),
+ objVar.objectTag());
+
+ SetProxyExtra(obj, 0, PrivateValue(this));
+ SetProxyExtra(obj, 1, PrivateValue(aux));
+ }
+
+ if (!JS_WrapObject(cx, &obj))
+ return nullptr;
+ return obj;
+}
+
+JSObject*
+WrapperOwner::fromLocalObjectVariant(JSContext* cx, const LocalObject& objVar)
+{
+ ObjectId id = ObjectId::deserialize(objVar.serializedId());
+ Rooted<JSObject*> obj(cx, findObjectById(cx, id));
+ if (!obj)
+ return nullptr;
+ if (!JS_WrapObject(cx, &obj))
+ return nullptr;
+ return obj;
+}
diff --git a/js/ipc/WrapperOwner.h b/js/ipc/WrapperOwner.h
new file mode 100644
index 000000000..79088b5be
--- /dev/null
+++ b/js/ipc/WrapperOwner.h
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et 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_jsipc_WrapperOwner_h__
+#define mozilla_jsipc_WrapperOwner_h__
+
+#include "JavaScriptShared.h"
+#include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/jsipc/CrossProcessObjectWrappers.h"
+#include "js/Class.h"
+#include "js/Proxy.h"
+
+namespace mozilla {
+namespace jsipc {
+
+class WrapperOwner : public virtual JavaScriptShared
+{
+ public:
+ typedef mozilla::ipc::IProtocol::ActorDestroyReason
+ ActorDestroyReason;
+
+ WrapperOwner();
+ bool init();
+
+ // Standard internal methods.
+ // (The traps should be in the same order like js/Proxy.h)
+ bool getOwnPropertyDescriptor(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc);
+ bool defineProperty(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result);
+ bool ownPropertyKeys(JSContext* cx, JS::HandleObject proxy, JS::AutoIdVector& props);
+ bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
+ JS::ObjectOpResult& result);
+ bool preventExtensions(JSContext* cx, JS::HandleObject proxy, JS::ObjectOpResult& result);
+ bool isExtensible(JSContext* cx, JS::HandleObject proxy, bool* extensible);
+ bool has(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, bool* bp);
+ bool get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue receiver,
+ JS::HandleId id, JS::MutableHandleValue vp);
+ bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue v,
+ JS::HandleValue receiver, JS::ObjectOpResult& result);
+ bool callOrConstruct(JSContext* cx, JS::HandleObject proxy, const JS::CallArgs& args,
+ bool construct);
+
+ // SpiderMonkey extensions.
+ bool getPropertyDescriptor(JSContext* cx, JS::HandleObject proxy, JS::HandleId id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc);
+ bool hasOwn(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, bool* bp);
+ bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::HandleObject proxy,
+ JS::AutoIdVector& props);
+ bool hasInstance(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleValue v, bool* bp);
+ bool getBuiltinClass(JSContext* cx, JS::HandleObject proxy, js::ESClass* cls);
+ bool isArray(JSContext* cx, JS::HandleObject proxy, JS::IsArrayAnswer* answer);
+ const char* className(JSContext* cx, JS::HandleObject proxy);
+ bool getPrototype(JSContext* cx, JS::HandleObject proxy, JS::MutableHandleObject protop);
+ bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject proxy, bool* isOrdinary,
+ JS::MutableHandleObject protop);
+
+ bool regexp_toShared(JSContext* cx, JS::HandleObject proxy, js::RegExpGuard* g);
+
+ nsresult instanceOf(JSObject* obj, const nsID* id, bool* bp);
+
+ bool toString(JSContext* cx, JS::HandleObject callee, JS::CallArgs& args);
+ bool DOMQI(JSContext* cx, JS::HandleObject callee, JS::CallArgs& args);
+
+ /*
+ * Check that |obj| is a DOM wrapper whose prototype chain contains
+ * |prototypeID| at depth |depth|.
+ */
+ bool domInstanceOf(JSContext* cx, JSObject* obj, int prototypeID, int depth, bool* bp);
+
+ bool active() { return !inactive_; }
+
+ virtual bool allowMessage(JSContext* cx) = 0;
+
+ void drop(JSObject* obj);
+ void updatePointer(JSObject* obj, const JSObject* old);
+
+ virtual void ActorDestroy(ActorDestroyReason why);
+
+ virtual bool toObjectVariant(JSContext* cx, JSObject* obj, ObjectVariant* objVarp);
+ virtual JSObject* fromObjectVariant(JSContext* cx, const ObjectVariant& objVar);
+ JSObject* fromRemoteObjectVariant(JSContext* cx, const RemoteObject& objVar);
+ JSObject* fromLocalObjectVariant(JSContext* cx, const LocalObject& objVar);
+
+ protected:
+ ObjectId idOf(JSObject* obj);
+
+ private:
+ ObjectId idOfUnchecked(JSObject* obj);
+
+ bool getPropertyKeys(JSContext* cx, JS::HandleObject proxy, uint32_t flags,
+ JS::AutoIdVector& props);
+
+ // Catastrophic IPC failure.
+ bool ipcfail(JSContext* cx);
+
+ // Check whether a return status is okay, and if not, propagate its error.
+ //
+ // If 'status' might be a ReturnObjectOpResult, which is only possible for
+ // a subset of the operations below, 'result' must be passed.
+ bool ok(JSContext* cx, const ReturnStatus& status, JS::ObjectOpResult& result);
+ bool ok(JSContext* cx, const ReturnStatus& status);
+
+ bool inactive_;
+
+ /*** Dummy call handlers ***/
+ public:
+ virtual bool SendDropObject(const ObjectId& objId) = 0;
+ virtual bool SendPreventExtensions(const ObjectId& objId, ReturnStatus* rs) = 0;
+ virtual bool SendGetPropertyDescriptor(const ObjectId& objId, const JSIDVariant& id,
+ ReturnStatus* rs,
+ PPropertyDescriptor* out) = 0;
+ virtual bool SendGetOwnPropertyDescriptor(const ObjectId& objId,
+ const JSIDVariant& id,
+ ReturnStatus* rs,
+ PPropertyDescriptor* out) = 0;
+ virtual bool SendDefineProperty(const ObjectId& objId, const JSIDVariant& id,
+ const PPropertyDescriptor& flags,
+ ReturnStatus* rs) = 0;
+ virtual bool SendDelete(const ObjectId& objId, const JSIDVariant& id,
+ ReturnStatus* rs) = 0;
+
+ virtual bool SendHas(const ObjectId& objId, const JSIDVariant& id,
+ ReturnStatus* rs, bool* bp) = 0;
+ virtual bool SendHasOwn(const ObjectId& objId, const JSIDVariant& id,
+ ReturnStatus* rs, bool* bp) = 0;
+ virtual bool SendGet(const ObjectId& objId, const JSVariant& receiverVar,
+ const JSIDVariant& id,
+ ReturnStatus* rs, JSVariant* result) = 0;
+ virtual bool SendSet(const ObjectId& objId, const JSIDVariant& id, const JSVariant& value,
+ const JSVariant& receiverVar, ReturnStatus* rs) = 0;
+
+ virtual bool SendIsExtensible(const ObjectId& objId, ReturnStatus* rs,
+ bool* result) = 0;
+ virtual bool SendCallOrConstruct(const ObjectId& objId, const nsTArray<JSParam>& argv,
+ const bool& construct, ReturnStatus* rs, JSVariant* result,
+ nsTArray<JSParam>* outparams) = 0;
+ virtual bool SendHasInstance(const ObjectId& objId, const JSVariant& v,
+ ReturnStatus* rs, bool* bp) = 0;
+ virtual bool SendGetBuiltinClass(const ObjectId& objId, ReturnStatus* rs,
+ uint32_t* classValue) = 0;
+ virtual bool SendIsArray(const ObjectId& objId, ReturnStatus* rs,
+ uint32_t* answer) = 0;
+ virtual bool SendClassName(const ObjectId& objId, nsCString* result) = 0;
+ virtual bool SendGetPrototype(const ObjectId& objId, ReturnStatus* rs, ObjectOrNullVariant* result) = 0;
+ virtual bool SendGetPrototypeIfOrdinary(const ObjectId& objId, ReturnStatus* rs, bool* isOrdinary,
+ ObjectOrNullVariant* result) = 0;
+ virtual bool SendRegExpToShared(const ObjectId& objId, ReturnStatus* rs, nsString* source,
+ uint32_t* flags) = 0;
+
+ virtual bool SendGetPropertyKeys(const ObjectId& objId, const uint32_t& flags,
+ ReturnStatus* rs, nsTArray<JSIDVariant>* ids) = 0;
+ virtual bool SendInstanceOf(const ObjectId& objId, const JSIID& iid,
+ ReturnStatus* rs, bool* instanceof) = 0;
+ virtual bool SendDOMInstanceOf(const ObjectId& objId, const int& prototypeID, const int& depth,
+ ReturnStatus* rs, bool* instanceof) = 0;
+};
+
+} // namespace jsipc
+} // namespace mozilla
+
+#endif // mozilla_jsipc_WrapperOwner_h__
diff --git a/js/ipc/moz.build b/js/ipc/moz.build
new file mode 100644
index 000000000..539b23f64
--- /dev/null
+++ b/js/ipc/moz.build
@@ -0,0 +1,41 @@
+# -*- 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/.
+
+UNIFIED_SOURCES += [
+ 'CPOWTimer.cpp',
+ 'JavaScriptChild.cpp',
+ 'JavaScriptParent.cpp',
+ 'JavaScriptShared.cpp',
+ 'WrapperAnswer.cpp',
+ 'WrapperOwner.cpp',
+]
+
+IPDL_SOURCES += [
+ 'JavaScriptTypes.ipdlh',
+ 'PJavaScript.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+DEFINES['BIN_SUFFIX'] = '"%s"' % CONFIG['BIN_SUFFIX']
+
+EXPORTS.mozilla.jsipc = [
+ 'CpowHolder.h',
+ 'CrossProcessObjectWrappers.h',
+]
+
+LOCAL_INCLUDES += [
+ '/dom/base',
+ '/js/ipc',
+ '/js/src',
+ '/js/xpconnect/src',
+ '/js/xpconnect/wrappers',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-shadow']