diff options
Diffstat (limited to 'js/ipc')
-rw-r--r-- | js/ipc/CPOWTimer.cpp | 43 | ||||
-rw-r--r-- | js/ipc/CPOWTimer.h | 44 | ||||
-rw-r--r-- | js/ipc/CpowHolder.h | 25 | ||||
-rw-r--r-- | js/ipc/CrossProcessObjectWrappers.h | 101 | ||||
-rw-r--r-- | js/ipc/JavaScriptBase.h | 230 | ||||
-rw-r--r-- | js/ipc/JavaScriptChild.cpp | 100 | ||||
-rw-r--r-- | js/ipc/JavaScriptChild.h | 50 | ||||
-rw-r--r-- | js/ipc/JavaScriptLogging.h | 231 | ||||
-rw-r--r-- | js/ipc/JavaScriptParent.cpp | 212 | ||||
-rw-r--r-- | js/ipc/JavaScriptParent.h | 43 | ||||
-rw-r--r-- | js/ipc/JavaScriptShared.cpp | 760 | ||||
-rw-r--r-- | js/ipc/JavaScriptShared.h | 236 | ||||
-rw-r--r-- | js/ipc/JavaScriptTypes.ipdlh | 161 | ||||
-rw-r--r-- | js/ipc/PJavaScript.ipdl | 60 | ||||
-rw-r--r-- | js/ipc/WrapperAnswer.cpp | 798 | ||||
-rw-r--r-- | js/ipc/WrapperAnswer.h | 80 | ||||
-rw-r--r-- | js/ipc/WrapperOwner.cpp | 1236 | ||||
-rw-r--r-- | js/ipc/WrapperOwner.h | 167 | ||||
-rw-r--r-- | js/ipc/moz.build | 41 |
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'] |