/* -*- 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/HashFunctions.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 (isInvalidSerialNumber(serialNumber)) 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 Maybe deserialize(uint64_t data) { if (isInvalidSerialNumber(data >> FLAG_BITS)) { return Nothing(); } return Some(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) {} static bool isInvalidSerialNumber(uint64_t aSerialNumber) { return aSerialNumber == 0 || aSerialNumber > SERIAL_NUMBER_MAX; } uint64_t serialNumber_ : SERIAL_NUMBER_BITS; bool hasXrayWaiver_ : 1; }; class JavaScriptShared; // DefaultHasher 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 mozilla::HashGeneric(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, ObjectIdHasher, js::SystemAllocPolicy> Table; public: IdToObjectMap(); void trace(JSTracer* trc, uint64_t minimumId = 0); void sweep(); bool add(ObjectId id, JSObject* obj); JSObject* find(ObjectId id); JSObject* findPreserveColor(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>; using Table = JS::GCHashMap, ObjectId, Hasher, js::SystemAllocPolicy>; public: ObjectToIdMap(); 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(); void decref(); void incref(); bool Unwrap(JSContext* cx, const InfallibleTArray& aCpows, JS::MutableHandleObject objp) override; bool Wrap(JSContext* cx, JS::HandleObject aObj, InfallibleTArray* outCpows) override; 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 desc, PPropertyDescriptor* out); bool toDescriptor(JSContext* cx, const PPropertyDescriptor& in, JS::MutableHandle 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); JSObject* findCPOWByIdPreserveColor(const ObjectId& objId); JSObject* findObjectById(JSContext* cx, const ObjectId& objId); #ifdef DEBUG bool hasCPOW(const ObjectId& objId, const JSObject* obj) { MOZ_ASSERT(obj); return findCPOWByIdPreserveColor(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