/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * XPConnect allows JS code to manipulate C++ object and C++ code to manipulate * JS objects. JS manipulation of C++ objects tends to be significantly more * complex. This comment explains how it is orchestrated by XPConnect. * * For each C++ object to be manipulated in JS, there is a corresponding JS * object. This is called the "flattened JS object". By default, there is an * additional C++ object involved of type XPCWrappedNative. The XPCWrappedNative * holds pointers to the C++ object and the flat JS object. * * All XPCWrappedNative objects belong to an XPCWrappedNativeScope. These scopes * are essentially in 1:1 correspondence with JS compartments. The * XPCWrappedNativeScope has a pointer to the JS compartment. The global of a * flattened JS object is one of the globals in this compartment (the exception * to this rule is when a PreCreate hook asks for a different global; see * nsIXPCScriptable below). * * Some C++ objects (notably DOM objects) have information associated with them * that lists the interfaces implemented by these objects. A C++ object exposes * this information by implementing nsIClassInfo. If a C++ object implements * nsIClassInfo, then JS code can call its methods without needing to use * QueryInterface first. Typically, all instances of a C++ class share the same * nsIClassInfo instance. (That is, obj->QueryInterface(nsIClassInfo) returns * the same result for every obj of a given class.) * * XPConnect tracks nsIClassInfo information in an XPCWrappedNativeProto object. * A given XPCWrappedNativeScope will have one XPCWrappedNativeProto for each * nsIClassInfo instance being used. The XPCWrappedNativeProto has an associated * JS object, which is used as the prototype of all flattened JS objects created * for C++ objects with the given nsIClassInfo. * * Each XPCWrappedNativeProto has a pointer to its XPCWrappedNativeScope. If an * XPCWrappedNative wraps a C++ object with class info, then it points to its * XPCWrappedNativeProto. Otherwise it points to its XPCWrappedNativeScope. (The * pointers are smooshed together in a tagged union.) Either way it can reach * its scope. * * An XPCWrappedNativeProto keeps track of the set of interfaces implemented by * the C++ object in an XPCNativeSet. (The list of interfaces is obtained by * calling a method on the nsIClassInfo.) An XPCNativeSet is a collection of * XPCNativeInterfaces. Each interface stores the list of members, which can be * methods, constants, getters, or setters. * * An XPCWrappedNative also points to an XPCNativeSet. Initially this starts out * the same as the XPCWrappedNativeProto's set. If there is no proto, it starts * out as a singleton set containing nsISupports. If JS code QI's new interfaces * outside of the existing set, the set will grow. All QueryInterface results * are cached in XPCWrappedNativeTearOff objects, which are linked off of the * XPCWrappedNative. * * Besides having class info, a C++ object may be "scriptable" (i.e., implement * nsIXPCScriptable). This allows it to implement a more DOM-like interface, * besides just exposing XPCOM methods and constants. An nsIXPCScriptable * instance has hooks that correspond to all the normal JSClass hooks. Each * nsIXPCScriptable instance can have pointers from XPCWrappedNativeProto and * XPCWrappedNative (since C++ objects can have scriptable info without having * class info). */ /* All the XPConnect private declarations - only include locally. */ #ifndef xpcprivate_h___ #define xpcprivate_h___ #include "mozilla/Alignment.h" #include "mozilla/ArrayUtils.h" #include "mozilla/Assertions.h" #include "mozilla/Atomics.h" #include "mozilla/Attributes.h" #include "mozilla/BasePrincipal.h" #include "mozilla/CycleCollectedJSContext.h" #include "mozilla/CycleCollectedJSRuntime.h" #include "mozilla/DebugOnly.h" #include "mozilla/DefineEnum.h" #include "mozilla/LinkedList.h" #include "mozilla/Maybe.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Preferences.h" #include "mozilla/TimeStamp.h" #include "mozilla/UniquePtr.h" #include "mozilla/dom/ScriptSettings.h" #include #include #include #include #include "xpcpublic.h" #include "js/HashTable.h" #include "js/GCHashTable.h" #include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetPrivate #include "js/TracingAPI.h" #include "js/WeakMapPtr.h" #include "PLDHashTable.h" #include "nscore.h" #include "nsXPCOM.h" #include "nsCycleCollectionParticipant.h" #include "nsDebug.h" #include "nsISupports.h" #include "nsIServiceManager.h" #include "nsIClassInfoImpl.h" #include "nsIComponentManager.h" #include "nsIComponentRegistrar.h" #include "nsISupportsPrimitives.h" #include "nsISimpleEnumerator.h" #include "nsMemory.h" #include "nsIXPConnect.h" #include "nsIXPCScriptable.h" #include "nsIObserver.h" #include "nsWeakReference.h" #include "nsCOMPtr.h" #include "nsXPTCUtils.h" #include "xptinfo.h" #include "XPCForwards.h" #include "XPCLog.h" #include "xpccomponents.h" #include "prenv.h" #include "prcvar.h" #include "nsString.h" #include "nsReadableUtils.h" #include "MainThreadUtils.h" #include "nsIConsoleService.h" #include "nsVariant.h" #include "nsCOMArray.h" #include "nsTArray.h" #include "nsBaseHashtable.h" #include "nsHashKeys.h" #include "nsWrapperCache.h" #include "nsStringBuffer.h" #include "nsDataHashtable.h" #include "nsDeque.h" #include "nsIScriptSecurityManager.h" #include "nsIPrincipal.h" #include "nsJSPrincipals.h" #include "nsIScriptObjectPrincipal.h" #include "xpcObjectHelper.h" #include "SandboxPrivate.h" #include "BackstagePass.h" #ifdef XP_WIN // Nasty MS defines # ifdef GetClassInfo # undef GetClassInfo # endif # ifdef GetClassName # undef GetClassName # endif #endif /* XP_WIN */ namespace mozilla { namespace dom { class Exception; } // namespace dom } // namespace mozilla /***************************************************************************/ // data declarations... extern const char XPC_EXCEPTION_CONTRACTID[]; extern const char XPC_CONSOLE_CONTRACTID[]; extern const char XPC_SCRIPT_ERROR_CONTRACTID[]; extern const char XPC_XPCONNECT_CONTRACTID[]; /***************************************************************************/ // Useful macros... #define XPC_STRING_GETTER_BODY(dest, src) \ NS_ENSURE_ARG_POINTER(dest); \ *dest = src ? moz_xstrdup(src) : nullptr; \ return NS_OK // If IS_WN_CLASS for the JSClass of an object is true, the object is a // wrappednative wrapper, holding the XPCWrappedNative in its private slot. static inline bool IS_WN_CLASS(const JSClass* clazz) { return clazz->isWrappedNative(); } static inline bool IS_WN_REFLECTOR(JSObject* obj) { return IS_WN_CLASS(JS::GetClass(obj)); } /*************************************************************************** **************************************************************************** * * Core runtime and context classes... * **************************************************************************** ***************************************************************************/ // We have a general rule internally that getters that return addref'd interface // pointer generally do so using an 'out' parm. When interface pointers are // returned as function call result values they are not addref'd. Exceptions // to this rule are noted explicitly. class nsXPConnect final : public nsIXPConnect { public: // all the interface method declarations... NS_DECL_ISUPPORTS NS_DECL_NSIXPCONNECT // non-interface implementation public: static XPCJSRuntime* GetRuntimeInstance(); XPCJSContext* GetContext() { return mContext; } static nsIScriptSecurityManager* SecurityManager() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(gScriptSecurityManager); return gScriptSecurityManager; } static nsIPrincipal* SystemPrincipal() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(gSystemPrincipal); return gSystemPrincipal; } // Called by module code in dll startup static void InitStatics(); // Called by module code on dll shutdown. static void ReleaseXPConnectSingleton(); static void InitJSContext(); void RecordTraversal(void* p, nsISupports* s); protected: virtual ~nsXPConnect(); nsXPConnect(); private: // Singleton instance static nsXPConnect* gSelf; static bool gOnceAliveNowDead; XPCJSContext* mContext = nullptr; XPCJSRuntime* mRuntime = nullptr; bool mShuttingDown; friend class nsIXPConnect; public: static nsIScriptSecurityManager* gScriptSecurityManager; static nsIPrincipal* gSystemPrincipal; }; /***************************************************************************/ class XPCRootSetElem { public: XPCRootSetElem() : mNext(nullptr), mSelfp(nullptr) {} ~XPCRootSetElem() { MOZ_ASSERT(!mNext, "Must be unlinked"); MOZ_ASSERT(!mSelfp, "Must be unlinked"); } inline XPCRootSetElem* GetNextRoot() { return mNext; } void AddToRootSet(XPCRootSetElem** listHead); void RemoveFromRootSet(); private: XPCRootSetElem* mNext; XPCRootSetElem** mSelfp; }; /***************************************************************************/ // In the current xpconnect system there can only be one XPCJSContext. // So, xpconnect can only be used on one JSContext within the process. class WatchdogManager; // clang-format off MOZ_DEFINE_ENUM(WatchdogTimestampCategory, ( TimestampWatchdogWakeup, TimestampWatchdogHibernateStart, TimestampWatchdogHibernateStop, TimestampContextStateChange )); // clang-format on class AsyncFreeSnowWhite; class XPCWrappedNativeScope; using XPCWrappedNativeScopeList = mozilla::LinkedList; class XPCJSContext final : public mozilla::CycleCollectedJSContext, public mozilla::LinkedListElement { public: static XPCJSContext* NewXPCJSContext(); static XPCJSContext* Get(); XPCJSRuntime* Runtime() const; virtual mozilla::CycleCollectedJSRuntime* CreateRuntime( JSContext* aCx) override; XPCCallContext* GetCallContext() const { return mCallContext; } XPCCallContext* SetCallContext(XPCCallContext* ccx) { XPCCallContext* old = mCallContext; mCallContext = ccx; return old; } jsid GetResolveName() const { return mResolveName; } jsid SetResolveName(jsid name) { jsid old = mResolveName; mResolveName = name; return old; } XPCWrappedNative* GetResolvingWrapper() const { return mResolvingWrapper; } XPCWrappedNative* SetResolvingWrapper(XPCWrappedNative* w) { XPCWrappedNative* old = mResolvingWrapper; mResolvingWrapper = w; return old; } bool JSContextInitialized(JSContext* cx); virtual void BeforeProcessTask(bool aMightBlock) override; virtual void AfterProcessTask(uint32_t aNewRecursionDepth) override; ~XPCJSContext(); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); bool IsSystemCaller() const override; AutoMarkingPtr** GetAutoRootsAdr() { return &mAutoRoots; } nsresult GetPendingResult() { return mPendingResult; } void SetPendingResult(nsresult rv) { mPendingResult = rv; } PRTime GetWatchdogTimestamp(WatchdogTimestampCategory aCategory); static bool RecordScriptActivity(bool aActive); bool SetHasScriptActivity(bool aActive) { bool oldValue = mHasScriptActivity; mHasScriptActivity = aActive; return oldValue; } static bool InterruptCallback(JSContext* cx); // Mapping of often used strings to jsid atoms that live 'forever'. // // To add a new string: add to this list and to XPCJSRuntime::mStrings // at the top of XPCJSRuntime.cpp enum { IDX_CONSTRUCTOR = 0, IDX_TO_STRING, IDX_TO_SOURCE, IDX_LAST_RESULT, IDX_RETURN_CODE, IDX_VALUE, IDX_QUERY_INTERFACE, IDX_COMPONENTS, IDX_CC, IDX_CI, IDX_CR, IDX_CU, IDX_WRAPPED_JSOBJECT, IDX_OBJECT, IDX_FUNCTION, IDX_PROTOTYPE, IDX_CREATE_INSTANCE, IDX_ITEM, IDX_PROTO, IDX_EVAL, IDX_CONTROLLERS, IDX_CONTROLLERS_CLASS, IDX_REALFRAMEELEMENT, IDX_LENGTH, IDX_NAME, IDX_UNDEFINED, IDX_EMPTYSTRING, IDX_FILENAME, IDX_LINENUMBER, IDX_COLUMNNUMBER, IDX_STACK, IDX_MESSAGE, IDX_ERRORS, IDX_LASTINDEX, IDX_THEN, IDX_ISINSTANCE, IDX_INFINITY, IDX_NAN, IDX_CLASS_ID, IDX_INTERFACE_ID, IDX_INITIALIZER, IDX_PRINT, IDX_TOTAL_COUNT // just a count of the above }; inline JS::HandleId GetStringID(unsigned index) const; inline const char* GetStringName(unsigned index) const; private: XPCJSContext(); MOZ_IS_CLASS_INIT nsresult Initialize(); XPCCallContext* mCallContext; AutoMarkingPtr* mAutoRoots; jsid mResolveName; XPCWrappedNative* mResolvingWrapper; WatchdogManager* mWatchdogManager; // Number of XPCJSContexts currently alive. static uint32_t sInstanceCount; static mozilla::StaticAutoPtr sWatchdogInstance; static WatchdogManager* GetWatchdogManager(); // If we spend too much time running JS code in an event handler, then we // want to show the slow script UI. The timeout T is controlled by prefs. We // invoke the interrupt callback once after T/2 seconds and set // mSlowScriptSecondHalf to true. After another T/2 seconds, we invoke the // interrupt callback again. Since mSlowScriptSecondHalf is now true, it // shows the slow script UI. The reason we invoke the callback twice is to // ensure that putting the computer to sleep while running a script doesn't // cause the UI to be shown. If the laptop goes to sleep during one of the // timeout periods, the script still has the other T/2 seconds to complete // before the slow script UI is shown. bool mSlowScriptSecondHalf; // mSlowScriptCheckpoint is set to the time when: // 1. We started processing the current event, or // 2. mSlowScriptSecondHalf was set to true // (whichever comes later). We use it to determine whether the interrupt // callback needs to do anything. mozilla::TimeStamp mSlowScriptCheckpoint; // Accumulates total time we actually waited for telemetry mozilla::TimeDuration mSlowScriptActualWait; bool mTimeoutAccumulated; bool mExecutedChromeScript; bool mHasScriptActivity; // mPendingResult is used to implement Components.returnCode. Only really // meaningful while calling through XPCWrappedJS. nsresult mPendingResult; // These members must be accessed via WatchdogManager. enum { CONTEXT_ACTIVE, CONTEXT_INACTIVE } mActive; PRTime mLastStateChange; friend class XPCJSRuntime; friend class Watchdog; friend class WatchdogManager; friend class AutoLockWatchdog; }; class XPCJSRuntime final : public mozilla::CycleCollectedJSRuntime { public: static XPCJSRuntime* Get(); void RemoveWrappedJS(nsXPCWrappedJS* wrapper); void AssertInvalidWrappedJSNotInTable(nsXPCWrappedJS* wrapper) const; JSObject2WrappedJSMap* GetMultiCompartmentWrappedJSMap() const { return mWrappedJSMap.get(); } IID2NativeInterfaceMap* GetIID2NativeInterfaceMap() const { return mIID2NativeInterfaceMap.get(); } ClassInfo2NativeSetMap* GetClassInfo2NativeSetMap() const { return mClassInfo2NativeSetMap.get(); } NativeSetMap* GetNativeSetMap() const { return mNativeSetMap.get(); } XPCWrappedNativeProtoMap* GetDyingWrappedNativeProtoMap() const { return mDyingWrappedNativeProtoMap.get(); } XPCWrappedNativeScopeList& GetWrappedNativeScopes() { return mWrappedNativeScopes; } bool InitializeStrings(JSContext* cx); virtual bool DescribeCustomObjects(JSObject* aObject, const JSClass* aClasp, char (&aName)[72]) const override; virtual bool NoteCustomGCThingXPCOMChildren( const JSClass* aClasp, JSObject* aObj, nsCycleCollectionTraversalCallback& aCb) const override; /** * Infrastructure for classes that need to defer part of the finalization * until after the GC has run, for example for objects that we don't want to * destroy during the GC. */ public: bool GetDoingFinalization() const { return mDoingFinalization; } JS::HandleId GetStringID(unsigned index) const { MOZ_ASSERT(index < XPCJSContext::IDX_TOTAL_COUNT, "index out of range"); // fromMarkedLocation() is safe because the string is interned. return JS::HandleId::fromMarkedLocation(&mStrIDs[index]); } JS::HandleValue GetStringJSVal(unsigned index) const { MOZ_ASSERT(index < XPCJSContext::IDX_TOTAL_COUNT, "index out of range"); // fromMarkedLocation() is safe because the string is interned. return JS::HandleValue::fromMarkedLocation(&mStrJSVals[index]); } const char* GetStringName(unsigned index) const { MOZ_ASSERT(index < XPCJSContext::IDX_TOTAL_COUNT, "index out of range"); return mStrings[index]; } virtual bool UsefulToMergeZones() const override; void TraceNativeBlackRoots(JSTracer* trc) override; void TraceAdditionalNativeGrayRoots(JSTracer* aTracer) override; void TraverseAdditionalNativeRoots( nsCycleCollectionNoteRootCallback& cb) override; void UnmarkSkippableJSHolders(); void PrepareForForgetSkippable() override; void BeginCycleCollectionCallback() override; void EndCycleCollectionCallback( mozilla::CycleCollectorResults& aResults) override; void DispatchDeferredDeletion(bool aContinuation, bool aPurge = false) override; void CustomGCCallback(JSGCStatus status) override; void CustomOutOfMemoryCallback() override; void OnLargeAllocationFailure(); static void GCSliceCallback(JSContext* cx, JS::GCProgress progress, const JS::GCDescription& desc); static void DoCycleCollectionCallback(JSContext* cx); static void FinalizeCallback(JSFreeOp* fop, JSFinalizeStatus status, void* data); static void WeakPointerZonesCallback(JSContext* cx, void* data); static void WeakPointerCompartmentCallback(JSContext* cx, JS::Compartment* comp, void* data); inline void AddVariantRoot(XPCTraceableVariant* variant); inline void AddWrappedJSRoot(nsXPCWrappedJS* wrappedJS); void DebugDump(int16_t depth); bool GCIsRunning() const { return mGCIsRunning; } ~XPCJSRuntime(); void AddGCCallback(xpcGCCallback cb); void RemoveGCCallback(xpcGCCallback cb); JSObject* GetUAWidgetScope(JSContext* cx, nsIPrincipal* principal); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); /** * The unprivileged junk scope is an unprivileged sandbox global used for * convenience by certain operations which need an unprivileged global but * don't have one immediately handy. It should generally be avoided when * possible. * * The scope is created lazily when it is needed, and held weakly so that it * is destroyed when there are no longer any remaining external references to * it. This means that under low memory conditions, when the scope does not * already exist, we may not be able to create one. In these circumstances, * the infallible version of this API will abort, and the fallible version * will return null. Callers should therefore prefer the fallible version when * on a codepath which can already return failure, but may use the infallible * one otherwise. */ JSObject* UnprivilegedJunkScope(); JSObject* UnprivilegedJunkScope(const mozilla::fallible_t&); bool IsUnprivilegedJunkScope(JSObject*); JSObject* LoaderGlobal(); void DeleteSingletonScopes(); void SystemIsBeingShutDown(); private: explicit XPCJSRuntime(JSContext* aCx); MOZ_IS_CLASS_INIT void Initialize(JSContext* cx); void Shutdown(JSContext* cx) override; static const char* const mStrings[XPCJSContext::IDX_TOTAL_COUNT]; jsid mStrIDs[XPCJSContext::IDX_TOTAL_COUNT]; JS::Value mStrJSVals[XPCJSContext::IDX_TOTAL_COUNT]; struct Hasher { using Key = RefPtr; using Lookup = Key; static uint32_t hash(const Lookup& l) { return l->GetOriginNoSuffixHash(); } static bool match(const Key& k, const Lookup& l) { return k->FastEquals(l); } }; struct SweepPolicy { static bool needsSweep(RefPtr* /* unused */, JS::Heap* value) { return JS::GCPolicy>::needsSweep(value); } }; typedef JS::GCHashMap, JS::Heap, Hasher, js::SystemAllocPolicy, SweepPolicy> Principal2JSObjectMap; mozilla::UniquePtr mWrappedJSMap; mozilla::UniquePtr mIID2NativeInterfaceMap; mozilla::UniquePtr mClassInfo2NativeSetMap; mozilla::UniquePtr mNativeSetMap; Principal2JSObjectMap mUAWidgetScopeMap; XPCWrappedNativeScopeList mWrappedNativeScopes; mozilla::UniquePtr mDyingWrappedNativeProtoMap; bool mGCIsRunning; nsTArray mNativesToReleaseArray; bool mDoingFinalization; XPCRootSetElem* mVariantRoots; XPCRootSetElem* mWrappedJSRoots; nsTArray extraGCCallbacks; JS::GCSliceCallback mPrevGCSliceCallback; JS::DoCycleCollectionCallback mPrevDoCycleCollectionCallback; mozilla::WeakPtr mUnprivilegedJunkScope; JS::PersistentRootedObject mLoaderGlobal; RefPtr mAsyncSnowWhiteFreer; friend class XPCJSContext; friend class XPCIncrementalReleaseRunnable; }; inline JS::HandleId XPCJSContext::GetStringID(unsigned index) const { return Runtime()->GetStringID(index); } inline const char* XPCJSContext::GetStringName(unsigned index) const { return Runtime()->GetStringName(index); } /***************************************************************************/ // No virtuals // XPCCallContext is ALWAYS declared as a local variable in some function; // i.e. instance lifetime is always controled by some C++ function returning. // // These things are created frequently in many places. We *intentionally* do // not inialialize all members in order to save on construction overhead. // Some constructor pass more valid params than others. We init what must be // init'd and leave other members undefined. In debug builds the accessors // use a CHECK_STATE macro to track whether or not the object is in a valid // state to answer the question a caller might be asking. As long as this // class is maintained correctly it can do its job without a bunch of added // overhead from useless initializations and non-DEBUG error checking. // // Note that most accessors are inlined. class MOZ_STACK_CLASS XPCCallContext final { public: enum { NO_ARGS = (unsigned)-1 }; explicit XPCCallContext(JSContext* cx, JS::HandleObject obj = nullptr, JS::HandleObject funobj = nullptr, JS::HandleId id = JSID_VOIDHANDLE, unsigned argc = NO_ARGS, JS::Value* argv = nullptr, JS::Value* rval = nullptr); virtual ~XPCCallContext(); inline bool IsValid() const; inline XPCJSContext* GetContext() const; inline JSContext* GetJSContext() const; inline bool GetContextPopRequired() const; inline XPCCallContext* GetPrevCallContext() const; inline JSObject* GetFlattenedJSObject() const; inline XPCWrappedNative* GetWrapper() const; inline bool CanGetTearOff() const; inline XPCWrappedNativeTearOff* GetTearOff() const; inline nsIXPCScriptable* GetScriptable() const; inline XPCNativeSet* GetSet() const; inline bool CanGetInterface() const; inline XPCNativeInterface* GetInterface() const; inline XPCNativeMember* GetMember() const; inline bool HasInterfaceAndMember() const; inline bool GetStaticMemberIsLocal() const; inline unsigned GetArgc() const; inline JS::Value* GetArgv() const; inline uint16_t GetMethodIndex() const; inline jsid GetResolveName() const; inline jsid SetResolveName(JS::HandleId name); inline XPCWrappedNative* GetResolvingWrapper() const; inline XPCWrappedNative* SetResolvingWrapper(XPCWrappedNative* w); inline void SetRetVal(const JS::Value& val); void SetName(jsid name); void SetArgsAndResultPtr(unsigned argc, JS::Value* argv, JS::Value* rval); void SetCallInfo(XPCNativeInterface* iface, XPCNativeMember* member, bool isSetter); nsresult CanCallNow(); void SystemIsBeingShutDown(); operator JSContext*() const { return GetJSContext(); } private: // no copy ctor or assignment allowed XPCCallContext(const XPCCallContext& r) = delete; XPCCallContext& operator=(const XPCCallContext& r) = delete; private: // posible values for mState enum State { INIT_FAILED, SYSTEM_SHUTDOWN, HAVE_CONTEXT, HAVE_OBJECT, HAVE_NAME, HAVE_ARGS, READY_TO_CALL, CALL_DONE }; #ifdef DEBUG inline void CHECK_STATE(int s) const { MOZ_ASSERT(mState >= s, "bad state"); } #else # define CHECK_STATE(s) ((void)0) #endif private: State mState; nsCOMPtr mXPC; XPCJSContext* mXPCJSContext; JSContext* mJSContext; // ctor does not necessarily init the following. BEWARE! XPCCallContext* mPrevCallContext; XPCWrappedNative* mWrapper; XPCWrappedNativeTearOff* mTearOff; nsCOMPtr mScriptable; RefPtr mSet; RefPtr mInterface; XPCNativeMember* mMember; JS::RootedId mName; bool mStaticMemberIsLocal; unsigned mArgc; JS::Value* mArgv; JS::Value* mRetVal; uint16_t mMethodIndex; }; /*************************************************************************** **************************************************************************** * * Core classes for wrapped native objects for use from JavaScript... * **************************************************************************** ***************************************************************************/ // These are the various JSClasses and callbacks whose use that required // visibility from more than one .cpp file. extern const JSClass XPC_WN_NoHelper_JSClass; extern const JSClass XPC_WN_Proto_JSClass; extern const JSClass XPC_WN_Tearoff_JSClass; #define XPC_WN_TEAROFF_RESERVED_SLOTS 1 #define XPC_WN_TEAROFF_FLAT_OBJECT_SLOT 0 extern const JSClass XPC_WN_NoHelper_Proto_JSClass; extern bool XPC_WN_CallMethod(JSContext* cx, unsigned argc, JS::Value* vp); extern bool XPC_WN_GetterSetter(JSContext* cx, unsigned argc, JS::Value* vp); /***************************************************************************/ // XPCWrappedNativeScope is one-to-one with a JS compartment. class XPCWrappedNativeScope final : public mozilla::LinkedListElement { public: XPCJSRuntime* GetRuntime() const { return XPCJSRuntime::Get(); } Native2WrappedNativeMap* GetWrappedNativeMap() const { return mWrappedNativeMap.get(); } ClassInfo2WrappedNativeProtoMap* GetWrappedNativeProtoMap() const { return mWrappedNativeProtoMap.get(); } nsXPCComponents* GetComponents() const { return mComponents; } bool AttachComponentsObject(JSContext* aCx); // Returns the JS object reflection of the Components object. bool GetComponentsJSObject(JSContext* cx, JS::MutableHandleObject obj); JSObject* GetExpandoChain(JS::HandleObject target); JSObject* DetachExpandoChain(JS::HandleObject target); bool SetExpandoChain(JSContext* cx, JS::HandleObject target, JS::HandleObject chain); static void SystemIsBeingShutDown(); static void TraceWrappedNativesInAllScopes(XPCJSRuntime* xpcrt, JSTracer* trc); void TraceInside(JSTracer* trc) { if (mXrayExpandos.initialized()) { mXrayExpandos.trace(trc); } JS::TraceEdge(trc, &mIDProto, "XPCWrappedNativeScope::mIDProto"); JS::TraceEdge(trc, &mIIDProto, "XPCWrappedNativeScope::mIIDProto"); JS::TraceEdge(trc, &mCIDProto, "XPCWrappedNativeScope::mCIDProto"); } static void SuspectAllWrappers(nsCycleCollectionNoteRootCallback& cb); static void SweepAllWrappedNativeTearOffs(); void UpdateWeakPointersAfterGC(); static void DebugDumpAllScopes(int16_t depth); void DebugDump(int16_t depth); struct ScopeSizeInfo { explicit ScopeSizeInfo(mozilla::MallocSizeOf mallocSizeOf) : mMallocSizeOf(mallocSizeOf), mScopeAndMapSize(0), mProtoAndIfaceCacheSize(0) {} mozilla::MallocSizeOf mMallocSizeOf; size_t mScopeAndMapSize; size_t mProtoAndIfaceCacheSize; }; static void AddSizeOfAllScopesIncludingThis(JSContext* cx, ScopeSizeInfo* scopeSizeInfo); void AddSizeOfIncludingThis(JSContext* cx, ScopeSizeInfo* scopeSizeInfo); // Check whether our mAllowContentXBLScope state matches the given // principal. This is used to avoid sharing compartments on // mismatch. bool XBLScopeStateMatches(nsIPrincipal* aPrincipal); XPCWrappedNativeScope(JS::Compartment* aCompartment, JS::HandleObject aFirstGlobal); virtual ~XPCWrappedNativeScope(); mozilla::UniquePtr mWaiverWrapperMap; JS::Compartment* Compartment() const { return mCompartment; } // Returns the global to use for new WrappedNative objects allocated in this // compartment. This is better than using arbitrary globals we happen to be in // because it prevents leaks (objects keep their globals alive). JSObject* GetGlobalForWrappedNatives() { return js::GetFirstGlobalInCompartment(Compartment()); } bool AllowContentXBLScope(JS::Realm* aRealm); // ID Object prototype caches. JS::Heap mIDProto; JS::Heap mIIDProto; JS::Heap mCIDProto; protected: XPCWrappedNativeScope() = delete; private: mozilla::UniquePtr mWrappedNativeMap; mozilla::UniquePtr mWrappedNativeProtoMap; RefPtr mComponents; JS::Compartment* mCompartment; JS::WeakMapPtr mXrayExpandos; // For remote XUL domains, we run all XBL in the content scope for compat // reasons (though we sometimes pref this off for automation). We // track the result of this decision (mAllowContentXBLScope) for now. bool mAllowContentXBLScope; }; /***************************************************************************/ // Slots we use for our functions #define XPC_FUNCTION_NATIVE_MEMBER_SLOT 0 #define XPC_FUNCTION_PARENT_OBJECT_SLOT 1 /***************************************************************************/ // XPCNativeMember represents a single idl declared method, attribute or // constant. // Tight. No virtual methods. Can be bitwise copied (until any resolution done). class XPCNativeMember final { public: static bool GetCallInfo(JSObject* funobj, RefPtr* pInterface, XPCNativeMember** pMember); jsid GetName() const { return mName; } uint16_t GetIndex() const { return mIndex; } bool GetConstantValue(XPCCallContext& ccx, XPCNativeInterface* iface, JS::Value* pval) { MOZ_ASSERT(IsConstant(), "Only call this if you're sure this is a constant!"); return Resolve(ccx, iface, nullptr, pval); } bool NewFunctionObject(XPCCallContext& ccx, XPCNativeInterface* iface, JS::HandleObject parent, JS::Value* pval); bool IsMethod() const { return 0 != (mFlags & METHOD); } bool IsConstant() const { return 0 != (mFlags & CONSTANT); } bool IsAttribute() const { return 0 != (mFlags & GETTER); } bool IsWritableAttribute() const { return 0 != (mFlags & SETTER_TOO); } bool IsReadOnlyAttribute() const { return IsAttribute() && !IsWritableAttribute(); } void SetName(jsid a) { mName = a; } void SetMethod(uint16_t index) { mFlags = METHOD; mIndex = index; } void SetConstant(uint16_t index) { mFlags = CONSTANT; mIndex = index; } void SetReadOnlyAttribute(uint16_t index) { mFlags = GETTER; mIndex = index; } void SetWritableAttribute() { MOZ_ASSERT(mFlags == GETTER, "bad"); mFlags = GETTER | SETTER_TOO; } static uint16_t GetMaxIndexInInterface() { return (1 << 12) - 1; } inline XPCNativeInterface* GetInterface() const; void SetIndexInInterface(uint16_t index) { mIndexInInterface = index; } /* default ctor - leave random contents */ MOZ_COUNTED_DEFAULT_CTOR(XPCNativeMember) MOZ_COUNTED_DTOR(XPCNativeMember) private: bool Resolve(XPCCallContext& ccx, XPCNativeInterface* iface, JS::HandleObject parent, JS::Value* vp); enum { METHOD = 0x01, CONSTANT = 0x02, GETTER = 0x04, SETTER_TOO = 0x08 // If you add a flag here, you may need to make mFlags wider and either // make mIndexInInterface narrower (and adjust // XPCNativeInterface::NewInstance accordingly) or make this object // bigger. }; private: // our only data... jsid mName; uint16_t mIndex; // mFlags needs to be wide enough to hold the flags in the above enum. uint16_t mFlags : 4; // mIndexInInterface is the index of this in our XPCNativeInterface's // mMembers. In theory our XPCNativeInterface could have as many as 2^15-1 // members (since mMemberCount is 15-bit) but in practice we prevent // creation of XPCNativeInterfaces which have more than 2^12 members. // If the width of this field changes, update GetMaxIndexInInterface. uint16_t mIndexInInterface : 12; } JS_HAZ_NON_GC_POINTER; // Only stores a pinned string /***************************************************************************/ // XPCNativeInterface represents a single idl declared interface. This is // primarily the set of XPCNativeMembers. // Tight. No virtual methods. class XPCNativeInterface final { public: NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(XPCNativeInterface, DestroyInstance(this)) static already_AddRefed GetNewOrUsed(JSContext* cx, const nsIID* iid); static already_AddRefed GetNewOrUsed( JSContext* cx, const nsXPTInterfaceInfo* info); static already_AddRefed GetNewOrUsed(JSContext* cx, const char* name); static already_AddRefed GetISupports(JSContext* cx); inline const nsXPTInterfaceInfo* GetInterfaceInfo() const { return mInfo; } inline jsid GetName() const { return mName; } inline const nsIID* GetIID() const; inline const char* GetNameString() const; inline XPCNativeMember* FindMember(jsid name) const; static inline size_t OffsetOfMembers(); uint16_t GetMemberCount() const { return mMemberCount; } XPCNativeMember* GetMemberAt(uint16_t i) { MOZ_ASSERT(i < mMemberCount, "bad index"); return &mMembers[i]; } void DebugDump(int16_t depth); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); protected: static already_AddRefed NewInstance( JSContext* cx, const nsXPTInterfaceInfo* aInfo); XPCNativeInterface() = delete; XPCNativeInterface(const nsXPTInterfaceInfo* aInfo, jsid aName) : mInfo(aInfo), mName(aName), mMemberCount(0) {} ~XPCNativeInterface(); void* operator new(size_t, void* p) noexcept(true) { return p; } XPCNativeInterface(const XPCNativeInterface& r) = delete; XPCNativeInterface& operator=(const XPCNativeInterface& r) = delete; static void DestroyInstance(XPCNativeInterface* inst); private: const nsXPTInterfaceInfo* mInfo; jsid mName; uint16_t mMemberCount; XPCNativeMember mMembers[1]; // always last - object sized for array }; /***************************************************************************/ // XPCNativeSetKey is used to key a XPCNativeSet in a NativeSetMap. // It represents a new XPCNativeSet we are considering constructing, without // requiring that the set actually be built. class MOZ_STACK_CLASS XPCNativeSetKey final { public: // This represents an existing set |baseSet|. explicit XPCNativeSetKey(XPCNativeSet* baseSet) : mCx(nullptr), mBaseSet(baseSet), mAddition(nullptr) { MOZ_ASSERT(baseSet); } // This represents a new set containing only nsISupports and // |addition|. This needs a JSContext because it may need to // construct some data structures that need one to construct them. explicit XPCNativeSetKey(JSContext* cx, XPCNativeInterface* addition) : mCx(cx), mBaseSet(nullptr), mAddition(addition) { MOZ_ASSERT(cx); MOZ_ASSERT(addition); } // This represents the existing set |baseSet| with the interface // |addition| inserted after existing interfaces. |addition| must // not already be present in |baseSet|. explicit XPCNativeSetKey(XPCNativeSet* baseSet, XPCNativeInterface* addition); ~XPCNativeSetKey() = default; XPCNativeSet* GetBaseSet() const { return mBaseSet; } XPCNativeInterface* GetAddition() const { return mAddition; } PLDHashNumber Hash() const; // Allow shallow copy private: JSContext* mCx; RefPtr mBaseSet; RefPtr mAddition; }; /***************************************************************************/ // XPCNativeSet represents an ordered collection of XPCNativeInterface pointers. class XPCNativeSet final { public: NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(XPCNativeSet, DestroyInstance(this)) static already_AddRefed GetNewOrUsed(JSContext* cx, const nsIID* iid); static already_AddRefed GetNewOrUsed(JSContext* cx, nsIClassInfo* classInfo); static already_AddRefed GetNewOrUsed(JSContext* cx, XPCNativeSetKey* key); // This generates a union set. // // If preserveFirstSetOrder is true, the elements from |firstSet| come first, // followed by any non-duplicate items from |secondSet|. If false, the same // algorithm is applied; but if we detect that |secondSet| is a superset of // |firstSet|, we return |secondSet| without worrying about whether the // ordering might differ from |firstSet|. static already_AddRefed GetNewOrUsed( JSContext* cx, XPCNativeSet* firstSet, XPCNativeSet* secondSet, bool preserveFirstSetOrder); static void ClearCacheEntryForClassInfo(nsIClassInfo* classInfo); inline bool FindMember(jsid name, XPCNativeMember** pMember, uint16_t* pInterfaceIndex) const; inline bool FindMember(jsid name, XPCNativeMember** pMember, RefPtr* pInterface) const; inline bool FindMember(JS::HandleId name, XPCNativeMember** pMember, RefPtr* pInterface, XPCNativeSet* protoSet, bool* pIsLocal) const; inline bool HasInterface(XPCNativeInterface* aInterface) const; uint16_t GetInterfaceCount() const { return mInterfaceCount; } XPCNativeInterface** GetInterfaceArray() { return mInterfaces; } XPCNativeInterface* GetInterfaceAt(uint16_t i) { MOZ_ASSERT(i < mInterfaceCount, "bad index"); return mInterfaces[i]; } inline bool MatchesSetUpToInterface(const XPCNativeSet* other, XPCNativeInterface* iface) const; void DebugDump(int16_t depth); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); protected: static already_AddRefed NewInstance( JSContext* cx, nsTArray>&& array); static already_AddRefed NewInstanceMutate(XPCNativeSetKey* key); XPCNativeSet() : mInterfaceCount(0) {} ~XPCNativeSet(); void* operator new(size_t, void* p) noexcept(true) { return p; } static void DestroyInstance(XPCNativeSet* inst); private: uint16_t mInterfaceCount; // Always last - object sized for array. // These are strong references. XPCNativeInterface* mInterfaces[1]; }; /***********************************************/ // XPCWrappedNativeProtos hold the additional shared wrapper data for // XPCWrappedNative whose native objects expose nsIClassInfo. // The XPCWrappedNativeProto is owned by its mJSProtoObject, until that object // is finalized. After that, it is owned by XPCJSRuntime's // mDyingWrappedNativeProtoMap. See // XPCWrappedNativeProto::JSProtoObjectFinalized and // XPCJSRuntime::FinalizeCallback. class XPCWrappedNativeProto final { public: static XPCWrappedNativeProto* GetNewOrUsed(JSContext* cx, XPCWrappedNativeScope* scope, nsIClassInfo* classInfo, nsIXPCScriptable* scriptable); XPCWrappedNativeScope* GetScope() const { return mScope; } XPCJSRuntime* GetRuntime() const { return mScope->GetRuntime(); } JSObject* GetJSProtoObject() const { return mJSProtoObject; } JSObject* GetJSProtoObjectPreserveColor() const { return mJSProtoObject.unbarrieredGet(); } nsIClassInfo* GetClassInfo() const { return mClassInfo; } XPCNativeSet* GetSet() const { return mSet; } nsIXPCScriptable* GetScriptable() const { return mScriptable; } void JSProtoObjectFinalized(JSFreeOp* fop, JSObject* obj); void JSProtoObjectMoved(JSObject* obj, const JSObject* old); void SystemIsBeingShutDown(); void DebugDump(int16_t depth); void TraceSelf(JSTracer* trc) { if (mJSProtoObject) { TraceEdge(trc, &mJSProtoObject, "XPCWrappedNativeProto::mJSProtoObject"); } } void TraceJS(JSTracer* trc) { TraceSelf(trc); } /* void WriteBarrierPre(JSContext* cx) { if (JS::IsIncrementalBarrierNeeded(cx) && mJSProtoObject) { mJSProtoObject.writeBarrierPre(cx); } } */ // NOP. This is just here to make the AutoMarkingPtr code compile. void Mark() const {} inline void AutoTrace(JSTracer* trc) {} ~XPCWrappedNativeProto(); protected: // disable copy ctor and assignment XPCWrappedNativeProto(const XPCWrappedNativeProto& r) = delete; XPCWrappedNativeProto& operator=(const XPCWrappedNativeProto& r) = delete; // hide ctor XPCWrappedNativeProto(XPCWrappedNativeScope* Scope, nsIClassInfo* ClassInfo, RefPtr&& Set); bool Init(JSContext* cx, nsIXPCScriptable* scriptable); private: #ifdef DEBUG static int32_t gDEBUG_LiveProtoCount; #endif private: XPCWrappedNativeScope* mScope; JS::Heap mJSProtoObject; nsCOMPtr mClassInfo; RefPtr mSet; nsCOMPtr mScriptable; }; /***********************************************/ // XPCWrappedNativeTearOff represents the info needed to make calls to one // interface on the underlying native object of a XPCWrappedNative. class XPCWrappedNativeTearOff final { public: bool IsAvailable() const { return mInterface == nullptr; } bool IsReserved() const { return mInterface == (XPCNativeInterface*)1; } bool IsValid() const { return !IsAvailable() && !IsReserved(); } void SetReserved() { mInterface = (XPCNativeInterface*)1; } XPCNativeInterface* GetInterface() const { return mInterface; } nsISupports* GetNative() const { return mNative; } JSObject* GetJSObject(); JSObject* GetJSObjectPreserveColor() const; void SetInterface(XPCNativeInterface* Interface) { mInterface = Interface; } void SetNative(nsISupports* Native) { mNative = Native; } already_AddRefed TakeNative() { return mNative.forget(); } void SetJSObject(JSObject* JSObj); void JSObjectFinalized() { SetJSObject(nullptr); } void JSObjectMoved(JSObject* obj, const JSObject* old); XPCWrappedNativeTearOff() : mInterface(nullptr), mJSObject(nullptr) { MOZ_COUNT_CTOR(XPCWrappedNativeTearOff); } ~XPCWrappedNativeTearOff(); // NOP. This is just here to make the AutoMarkingPtr code compile. inline void TraceJS(JSTracer* trc) {} inline void AutoTrace(JSTracer* trc) {} void Mark() { mJSObject.setFlags(1); } void Unmark() { mJSObject.unsetFlags(1); } bool IsMarked() const { return mJSObject.hasFlag(1); } XPCWrappedNativeTearOff* AddTearOff() { MOZ_ASSERT(!mNextTearOff); mNextTearOff = mozilla::MakeUnique(); return mNextTearOff.get(); } XPCWrappedNativeTearOff* GetNextTearOff() { return mNextTearOff.get(); } private: XPCWrappedNativeTearOff(const XPCWrappedNativeTearOff& r) = delete; XPCWrappedNativeTearOff& operator=(const XPCWrappedNativeTearOff& r) = delete; private: XPCNativeInterface* mInterface; // mNative is an nsRefPtr not an nsCOMPtr because it may not be the canonical // nsISupports pointer. RefPtr mNative; JS::TenuredHeap mJSObject; mozilla::UniquePtr mNextTearOff; }; /***************************************************************************/ // XPCWrappedNative the wrapper around one instance of a native xpcom object // to be used from JavaScript. class XPCWrappedNative final : public nsIXPConnectWrappedNative { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_NSIXPCONNECTJSOBJECTHOLDER NS_DECL_NSIXPCONNECTWRAPPEDNATIVE NS_DECL_CYCLE_COLLECTION_CLASS(XPCWrappedNative) bool IsValid() const { return mFlatJSObject.hasFlag(FLAT_JS_OBJECT_VALID); } #define XPC_SCOPE_WORD(s) (intptr_t(s)) #define XPC_SCOPE_MASK (intptr_t(0x3)) #define XPC_SCOPE_TAG (intptr_t(0x1)) #define XPC_WRAPPER_EXPIRED (intptr_t(0x2)) static inline bool IsTaggedScope(XPCWrappedNativeScope* s) { return XPC_SCOPE_WORD(s) & XPC_SCOPE_TAG; } static inline XPCWrappedNativeScope* TagScope(XPCWrappedNativeScope* s) { MOZ_ASSERT(!IsTaggedScope(s), "bad pointer!"); return (XPCWrappedNativeScope*)(XPC_SCOPE_WORD(s) | XPC_SCOPE_TAG); } static inline XPCWrappedNativeScope* UnTagScope(XPCWrappedNativeScope* s) { return (XPCWrappedNativeScope*)(XPC_SCOPE_WORD(s) & ~XPC_SCOPE_TAG); } inline bool IsWrapperExpired() const { return XPC_SCOPE_WORD(mMaybeScope) & XPC_WRAPPER_EXPIRED; } bool HasProto() const { return !IsTaggedScope(mMaybeScope); } XPCWrappedNativeProto* GetProto() const { return HasProto() ? (XPCWrappedNativeProto*)(XPC_SCOPE_WORD(mMaybeProto) & ~XPC_SCOPE_MASK) : nullptr; } XPCWrappedNativeScope* GetScope() const { return GetProto() ? GetProto()->GetScope() : (XPCWrappedNativeScope*)(XPC_SCOPE_WORD(mMaybeScope) & ~XPC_SCOPE_MASK); } nsISupports* GetIdentityObject() const { return mIdentity; } /** * This getter clears the gray bit before handing out the JSObject which * means that the object is guaranteed to be kept alive past the next CC. */ JSObject* GetFlatJSObject() const { return mFlatJSObject; } /** * This getter does not change the color of the JSObject meaning that the * object returned is not guaranteed to be kept alive past the next CC. * * This should only be called if you are certain that the return value won't * be passed into a JS API function and that it won't be stored without * being rooted (or otherwise signaling the stored value to the CC). */ JSObject* GetFlatJSObjectPreserveColor() const { return mFlatJSObject.unbarrieredGetPtr(); } XPCNativeSet* GetSet() const { return mSet; } void SetSet(already_AddRefed set) { mSet = set; } static XPCWrappedNative* Get(JSObject* obj) { MOZ_ASSERT(IS_WN_REFLECTOR(obj)); return (XPCWrappedNative*)JS::GetPrivate(obj); } private: void SetFlatJSObject(JSObject* object); void UnsetFlatJSObject(); inline void ExpireWrapper() { mMaybeScope = (XPCWrappedNativeScope*)(XPC_SCOPE_WORD(mMaybeScope) | XPC_WRAPPER_EXPIRED); } public: nsIXPCScriptable* GetScriptable() const { return mScriptable; } nsIClassInfo* GetClassInfo() const { return IsValid() && HasProto() ? GetProto()->GetClassInfo() : nullptr; } bool HasMutatedSet() const { return IsValid() && (!HasProto() || GetSet() != GetProto()->GetSet()); } XPCJSRuntime* GetRuntime() const { XPCWrappedNativeScope* scope = GetScope(); return scope ? scope->GetRuntime() : nullptr; } static nsresult WrapNewGlobal(JSContext* cx, xpcObjectHelper& nativeHelper, nsIPrincipal* principal, bool initStandardClasses, JS::RealmOptions& aOptions, XPCWrappedNative** wrappedGlobal); static nsresult GetNewOrUsed(JSContext* cx, xpcObjectHelper& helper, XPCWrappedNativeScope* Scope, XPCNativeInterface* Interface, XPCWrappedNative** wrapper); void FlatJSObjectFinalized(); void FlatJSObjectMoved(JSObject* obj, const JSObject* old); void SystemIsBeingShutDown(); enum CallMode { CALL_METHOD, CALL_GETTER, CALL_SETTER }; static bool CallMethod(XPCCallContext& ccx, CallMode mode = CALL_METHOD); static bool GetAttribute(XPCCallContext& ccx) { return CallMethod(ccx, CALL_GETTER); } static bool SetAttribute(XPCCallContext& ccx) { return CallMethod(ccx, CALL_SETTER); } XPCWrappedNativeTearOff* FindTearOff(JSContext* cx, XPCNativeInterface* aInterface, bool needJSObject = false, nsresult* pError = nullptr); XPCWrappedNativeTearOff* FindTearOff(JSContext* cx, const nsIID& iid); void Mark() const {} inline void TraceInside(JSTracer* trc) { if (HasProto()) { GetProto()->TraceSelf(trc); } JSObject* obj = mFlatJSObject.unbarrieredGetPtr(); if (obj && JS_IsGlobalObject(obj)) { xpc::TraceXPCGlobal(trc, obj); } } void TraceJS(JSTracer* trc) { TraceInside(trc); } void TraceSelf(JSTracer* trc) { // If this got called, we're being kept alive by someone who really // needs us alive and whole. Do not let our mFlatJSObject go away. // This is the only time we should be tracing our mFlatJSObject, // normally somebody else is doing that. JS::TraceEdge(trc, &mFlatJSObject, "XPCWrappedNative::mFlatJSObject"); } static void Trace(JSTracer* trc, JSObject* obj); void AutoTrace(JSTracer* trc) { TraceSelf(trc); } inline void SweepTearOffs(); // Returns a string that should be freed with js_free, or nullptr on // failure. char* ToString(XPCWrappedNativeTearOff* to = nullptr) const; static nsIXPCScriptable* GatherProtoScriptable(nsIClassInfo* classInfo); bool HasExternalReference() const { return mRefCnt > 1; } void Suspect(nsCycleCollectionNoteRootCallback& cb); void NoteTearoffs(nsCycleCollectionTraversalCallback& cb); // Make ctor and dtor protected (rather than private) to placate nsCOMPtr. protected: XPCWrappedNative() = delete; // This ctor is used if this object will have a proto. XPCWrappedNative(nsCOMPtr&& aIdentity, XPCWrappedNativeProto* aProto); // This ctor is used if this object will NOT have a proto. XPCWrappedNative(nsCOMPtr&& aIdentity, XPCWrappedNativeScope* aScope, RefPtr&& aSet); virtual ~XPCWrappedNative(); void Destroy(); private: enum { // Flags bits for mFlatJSObject: FLAT_JS_OBJECT_VALID = js::Bit(0) }; bool Init(JSContext* cx, nsIXPCScriptable* scriptable); bool FinishInit(JSContext* cx); bool ExtendSet(JSContext* aCx, XPCNativeInterface* aInterface); nsresult InitTearOff(JSContext* cx, XPCWrappedNativeTearOff* aTearOff, XPCNativeInterface* aInterface, bool needJSObject); bool InitTearOffJSObject(JSContext* cx, XPCWrappedNativeTearOff* to); public: static void GatherScriptable(nsISupports* obj, nsIClassInfo* classInfo, nsIXPCScriptable** scrProto, nsIXPCScriptable** scrWrapper); private: union { XPCWrappedNativeScope* mMaybeScope; XPCWrappedNativeProto* mMaybeProto; }; RefPtr mSet; JS::TenuredHeap mFlatJSObject; nsCOMPtr mScriptable; XPCWrappedNativeTearOff mFirstTearOff; }; /*************************************************************************** **************************************************************************** * * Core classes for wrapped JSObject for use from native code... * **************************************************************************** ***************************************************************************/ /*************************/ // nsXPCWrappedJS is a wrapper for a single JSObject for use from native code. // nsXPCWrappedJS objects are chained together to represent the various // interface on the single underlying (possibly aggregate) JSObject. class nsXPCWrappedJS final : protected nsAutoXPTCStub, public nsIXPConnectWrappedJSUnmarkGray, public nsSupportsWeakReference, public XPCRootSetElem { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_NSIXPCONNECTJSOBJECTHOLDER NS_DECL_NSIXPCONNECTWRAPPEDJS NS_DECL_NSIXPCONNECTWRAPPEDJSUNMARKGRAY NS_DECL_NSISUPPORTSWEAKREFERENCE NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(nsXPCWrappedJS, nsIXPConnectWrappedJS) // This method is defined in XPCWrappedJSClass.cpp to preserve VCS blame. NS_IMETHOD CallMethod(uint16_t methodIndex, const nsXPTMethodInfo* info, nsXPTCMiniVariant* nativeParams) override; /* * This is rarely called directly. Instead one usually calls * XPCConvert::JSObject2NativeInterface which will handles cases where the * JS object is already a wrapped native or a DOM object. */ static nsresult GetNewOrUsed(JSContext* cx, JS::HandleObject aJSObj, REFNSIID aIID, nsXPCWrappedJS** wrapper); nsISomeInterface* GetXPTCStub() { return mXPTCStub; } /** * This getter does not change the color of the JSObject meaning that the * object returned is not guaranteed to be kept alive past the next CC. * * This should only be called if you are certain that the return value won't * be passed into a JS API function and that it won't be stored without * being rooted (or otherwise signaling the stored value to the CC). */ JSObject* GetJSObjectPreserveColor() const { return mJSObj.unbarrieredGet(); } // Returns true if the wrapper chain contains references to multiple // compartments. If the wrapper chain contains references to multiple // compartments, then it must be registered on the XPCJSContext. Otherwise, // it should be registered in the CompartmentPrivate for the compartment of // the root's JS object. This will only return correct results when called // on the root wrapper and will assert if not called on a root wrapper. bool IsMultiCompartment() const; const nsXPTInterfaceInfo* GetInfo() const { return mInfo; } REFNSIID GetIID() const { return mInfo->IID(); } nsXPCWrappedJS* GetRootWrapper() const { return mRoot; } nsXPCWrappedJS* GetNextWrapper() const { return mNext; } nsXPCWrappedJS* Find(REFNSIID aIID); nsXPCWrappedJS* FindInherited(REFNSIID aIID); nsXPCWrappedJS* FindOrFindInherited(REFNSIID aIID) { nsXPCWrappedJS* wrapper = Find(aIID); if (wrapper) { return wrapper; } return FindInherited(aIID); } bool IsRootWrapper() const { return mRoot == this; } bool IsValid() const { return bool(mJSObj); } void SystemIsBeingShutDown(); // These two methods are used by JSObject2WrappedJSMap::FindDyingJSObjects // to find non-rooting wrappers for dying JS objects. See the top of // XPCWrappedJS.cpp for more details. bool IsSubjectToFinalization() const { return IsValid() && mRefCnt == 1; } void UpdateObjectPointerAfterGC() { MOZ_ASSERT(IsRootWrapper()); JS_UpdateWeakPointerAfterGC(&mJSObj); } bool IsAggregatedToNative() const { return mRoot->mOuter != nullptr; } nsISupports* GetAggregatedNativeObject() const { return mRoot->mOuter; } void SetAggregatedNativeObject(nsISupports* aNative) { MOZ_ASSERT(aNative); if (mRoot->mOuter) { MOZ_ASSERT(mRoot->mOuter == aNative, "Only one aggregated native can be set"); return; } mRoot->mOuter = aNative; } void TraceJS(JSTracer* trc); // This method is defined in XPCWrappedJSClass.cpp to preserve VCS blame. static void DebugDumpInterfaceInfo(const nsXPTInterfaceInfo* aInfo, int16_t depth); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; virtual ~nsXPCWrappedJS(); protected: nsXPCWrappedJS() = delete; nsXPCWrappedJS(JSContext* cx, JSObject* aJSObj, const nsXPTInterfaceInfo* aInfo, nsXPCWrappedJS* root, nsresult* rv); bool CanSkip(); void Destroy(); void Unlink(); private: JS::Compartment* Compartment() const { return JS::GetCompartment(mJSObj.unbarrieredGet()); } // These methods are defined in XPCWrappedJSClass.cpp to preserve VCS blame. static const nsXPTInterfaceInfo* GetInterfaceInfo(REFNSIID aIID); nsresult DelegatedQueryInterface(REFNSIID aIID, void** aInstancePtr); static JSObject* GetRootJSObject(JSContext* cx, JSObject* aJSObj); static JSObject* CallQueryInterfaceOnJSObject(JSContext* cx, JSObject* jsobj, JS::HandleObject scope, REFNSIID aIID); // aObj is the nsXPCWrappedJS's object. We used this as the callee (or |this| // if getter or setter). // aSyntheticException, if not null, is the exception we should be using. // If null, look for an exception on the JSContext hanging off the // XPCCallContext. static nsresult CheckForException( XPCCallContext& ccx, mozilla::dom::AutoEntryScript& aes, JS::HandleObject aObj, const char* aPropertyName, const char* anInterfaceName, mozilla::dom::Exception* aSyntheticException = nullptr); static bool GetArraySizeFromParam(const nsXPTMethodInfo* method, const nsXPTType& type, nsXPTCMiniVariant* params, uint32_t* result); static bool GetInterfaceTypeFromParam(const nsXPTMethodInfo* method, const nsXPTType& type, nsXPTCMiniVariant* params, nsID* result); static void CleanupOutparams(const nsXPTMethodInfo* info, nsXPTCMiniVariant* nativeParams, bool inOutOnly, uint8_t count); JS::Heap mJSObj; const nsXPTInterfaceInfo* const mInfo; nsXPCWrappedJS* mRoot; // If mRoot != this, it is an owning pointer. nsXPCWrappedJS* mNext; nsCOMPtr mOuter; // only set in root }; /*************************************************************************** **************************************************************************** * * All manner of utility classes follow... * **************************************************************************** ***************************************************************************/ namespace xpc { // A wrapper around JS iterators which presents an equivalent // nsISimpleEnumerator interface for their contents. class XPCWrappedJSIterator final : public nsISimpleEnumerator { public: NS_DECL_CYCLE_COLLECTION_CLASS(XPCWrappedJSIterator) NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_NSISIMPLEENUMERATOR NS_DECL_NSISIMPLEENUMERATORBASE explicit XPCWrappedJSIterator(nsIJSEnumerator* aEnum); private: ~XPCWrappedJSIterator() = default; nsCOMPtr mEnum; nsCOMPtr mGlobal; nsCOMPtr mNext; mozilla::Maybe mHasNext; }; } // namespace xpc /***************************************************************************/ // class here just for static methods class XPCConvert { public: /** * Convert a native object into a JS::Value. * * @param cx the JSContext representing the global we want the value in * @param d [out] the resulting JS::Value * @param s the native object we're working with * @param type the type of object that s is * @param iid the interface of s that we want * @param scope the default scope to put on the new JSObject's parent * chain * @param pErr [out] relevant error code, if any. */ static bool NativeData2JS(JSContext* cx, JS::MutableHandleValue d, const void* s, const nsXPTType& type, const nsID* iid, uint32_t arrlen, nsresult* pErr); static bool JSData2Native(JSContext* cx, void* d, JS::HandleValue s, const nsXPTType& type, const nsID* iid, uint32_t arrlen, nsresult* pErr); /** * Convert a native nsISupports into a JSObject. * * @param cx the JSContext representing the global we want the object in. * @param dest [out] the resulting JSObject * @param src the native object we're working with * @param iid the interface of src that we want (may be null) * @param cache the wrapper cache for src (may be null, in which case src * will be QI'ed to get the cache) * @param allowNativeWrapper if true, this method may wrap the resulting * JSObject in an XPCNativeWrapper and return that, as needed. * @param pErr [out] relevant error code, if any. * @param src_is_identity optional performance hint. Set to true only * if src is the identity pointer. */ static bool NativeInterface2JSObject(JSContext* cx, JS::MutableHandleValue dest, xpcObjectHelper& aHelper, const nsID* iid, bool allowNativeWrapper, nsresult* pErr); static bool GetNativeInterfaceFromJSObject(void** dest, JSObject* src, const nsID* iid, nsresult* pErr); static bool JSObject2NativeInterface(JSContext* cx, void** dest, JS::HandleObject src, const nsID* iid, nsISupports* aOuter, nsresult* pErr); // Note - This return the XPCWrappedNative, rather than the native itself, // for the WN case. You probably want UnwrapReflectorToISupports. static bool GetISupportsFromJSObject(JSObject* obj, nsISupports** iface); static nsresult JSValToXPCException(JSContext* cx, JS::MutableHandleValue s, const char* ifaceName, const char* methodName, mozilla::dom::Exception** exception); static nsresult ConstructException(nsresult rv, const char* message, const char* ifaceName, const char* methodName, nsISupports* data, mozilla::dom::Exception** exception, JSContext* cx, JS::Value* jsExceptionPtr); private: /** * Convert a native array into a JS::Value. * * @param cx the JSContext we're working with and in whose global the array * should be created. * @param d [out] the resulting JS::Value * @param buf the native buffer containing input values * @param type the type of objects in the array * @param iid the interface of each object in the array that we want * @param count the number of items in the array * @param scope the default scope to put on the new JSObjects' parent chain * @param pErr [out] relevant error code, if any. */ static bool NativeArray2JS(JSContext* cx, JS::MutableHandleValue d, const void* buf, const nsXPTType& type, const nsID* iid, uint32_t count, nsresult* pErr); using ArrayAllocFixupLen = std::function; /** * Convert a JS::Value into a native array. * * @param cx the JSContext we're working with * @param aJSVal the JS::Value to convert * @param aEltType the type of objects in the array * @param aIID the interface of each object in the array * @param pErr [out] relevant error code, if any * @param aAllocFixupLen function called with the JS Array's length to * allocate the backing buffer. This function may * modify the length of array to be converted. */ static bool JSArray2Native(JSContext* cx, JS::HandleValue aJSVal, const nsXPTType& aEltType, const nsIID* aIID, nsresult* pErr, const ArrayAllocFixupLen& aAllocFixupLen); XPCConvert() = delete; }; /***************************************************************************/ // code for throwing exceptions into JS class nsXPCException; class XPCThrower { public: static void Throw(nsresult rv, JSContext* cx); static void Throw(nsresult rv, XPCCallContext& ccx); static void ThrowBadResult(nsresult rv, nsresult result, XPCCallContext& ccx); static void ThrowBadParam(nsresult rv, unsigned paramNum, XPCCallContext& ccx); static bool SetVerbosity(bool state) { bool old = sVerbose; sVerbose = state; return old; } static bool CheckForPendingException(nsresult result, JSContext* cx); private: static void Verbosify(XPCCallContext& ccx, char** psz, bool own); private: static bool sVerbose; }; /***************************************************************************/ class nsXPCException { public: static bool NameAndFormatForNSResult(nsresult rv, const char** name, const char** format); static const void* IterateNSResults(nsresult* rv, const char** name, const char** format, const void** iterp); static uint32_t GetNSResultCount(); }; /***************************************************************************/ // 'Components' object implementation. class nsXPCComponents final : public nsIXPCComponents { public: NS_DECL_ISUPPORTS NS_DECL_NSIXPCCOMPONENTS public: void SystemIsBeingShutDown() { ClearMembers(); } XPCWrappedNativeScope* GetScope() { return mScope; } protected: ~nsXPCComponents(); explicit nsXPCComponents(XPCWrappedNativeScope* aScope); void ClearMembers(); XPCWrappedNativeScope* mScope; RefPtr mInterfaces; RefPtr mResults; RefPtr mClasses; RefPtr mID; RefPtr mException; RefPtr mConstructor; RefPtr mUtils; friend class XPCWrappedNativeScope; }; /****************************************************************************** * Handles pre/post script processing. */ class MOZ_RAII AutoScriptEvaluate { public: /** * Saves the JSContext as well as initializing our state * @param cx The JSContext, this can be null, we don't do anything then */ explicit AutoScriptEvaluate(JSContext* cx) : mJSContext(cx), mEvaluated(false) {} /** * Does the pre script evaluation. * This function should only be called once, and will assert if called * more than once */ bool StartEvaluating(JS::HandleObject scope); /** * Does the post script evaluation. */ ~AutoScriptEvaluate(); private: JSContext* mJSContext; mozilla::Maybe mState; bool mEvaluated; mozilla::Maybe mAutoRealm; // No copying or assignment allowed AutoScriptEvaluate(const AutoScriptEvaluate&) = delete; AutoScriptEvaluate& operator=(const AutoScriptEvaluate&) = delete; }; /***************************************************************************/ class MOZ_RAII AutoResolveName { public: AutoResolveName(XPCCallContext& ccx, JS::HandleId name) : mContext(ccx.GetContext()), mOld(ccx, mContext->SetResolveName(name)) #ifdef DEBUG , mCheck(ccx, name) #endif { } ~AutoResolveName() { mozilla::DebugOnly old = mContext->SetResolveName(mOld); MOZ_ASSERT(old == mCheck, "Bad Nesting!"); } private: XPCJSContext* mContext; JS::RootedId mOld; #ifdef DEBUG JS::RootedId mCheck; #endif }; /***************************************************************************/ // AutoMarkingPtr is the base class for the various AutoMarking pointer types // below. This system allows us to temporarily protect instances of our garbage // collected types after they are constructed but before they are safely // attached to other rooted objects. // This base class has pure virtual support for marking. class AutoMarkingPtr { public: explicit AutoMarkingPtr(JSContext* cx) { mRoot = XPCJSContext::Get()->GetAutoRootsAdr(); mNext = *mRoot; *mRoot = this; } virtual ~AutoMarkingPtr() { if (mRoot) { MOZ_ASSERT(*mRoot == this); *mRoot = mNext; } } void TraceJSAll(JSTracer* trc) { for (AutoMarkingPtr* cur = this; cur; cur = cur->mNext) { cur->TraceJS(trc); } } void MarkAfterJSFinalizeAll() { for (AutoMarkingPtr* cur = this; cur; cur = cur->mNext) { cur->MarkAfterJSFinalize(); } } protected: virtual void TraceJS(JSTracer* trc) = 0; virtual void MarkAfterJSFinalize() = 0; private: AutoMarkingPtr** mRoot; AutoMarkingPtr* mNext; }; template class TypedAutoMarkingPtr : public AutoMarkingPtr { public: explicit TypedAutoMarkingPtr(JSContext* cx) : AutoMarkingPtr(cx), mPtr(nullptr) {} TypedAutoMarkingPtr(JSContext* cx, T* ptr) : AutoMarkingPtr(cx), mPtr(ptr) {} T* get() const { return mPtr; } operator T*() const { return mPtr; } T* operator->() const { return mPtr; } TypedAutoMarkingPtr& operator=(T* ptr) { mPtr = ptr; return *this; } protected: virtual void TraceJS(JSTracer* trc) override { if (mPtr) { mPtr->TraceJS(trc); mPtr->AutoTrace(trc); } } virtual void MarkAfterJSFinalize() override { if (mPtr) { mPtr->Mark(); } } private: T* mPtr; }; using AutoMarkingWrappedNativePtr = TypedAutoMarkingPtr; using AutoMarkingWrappedNativeTearOffPtr = TypedAutoMarkingPtr; using AutoMarkingWrappedNativeProtoPtr = TypedAutoMarkingPtr; /***************************************************************************/ // in xpcvariant.cpp... // {1809FD50-91E8-11d5-90F9-0010A4E73D9A} #define XPCVARIANT_IID \ { \ 0x1809fd50, 0x91e8, 0x11d5, { \ 0x90, 0xf9, 0x0, 0x10, 0xa4, 0xe7, 0x3d, 0x9a \ } \ } // {DC524540-487E-4501-9AC7-AAA784B17C1C} #define XPCVARIANT_CID \ { \ 0xdc524540, 0x487e, 0x4501, { \ 0x9a, 0xc7, 0xaa, 0xa7, 0x84, 0xb1, 0x7c, 0x1c \ } \ } class XPCVariant : public nsIVariant { public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_NSIVARIANT NS_DECL_CYCLE_COLLECTION_CLASS(XPCVariant) // If this class ever implements nsIWritableVariant, take special care with // the case when mJSVal is JSVAL_STRING, since we don't own the data in // that case. // We #define and iid so that out module local code can use QI to detect // if a given nsIVariant is in fact an XPCVariant. NS_DECLARE_STATIC_IID_ACCESSOR(XPCVARIANT_IID) static already_AddRefed newVariant(JSContext* cx, const JS::Value& aJSVal); /** * This getter clears the gray bit before handing out the Value if the Value * represents a JSObject. That means that the object is guaranteed to be * kept alive past the next CC. */ JS::Value GetJSVal() const { return mJSVal; } /** * This getter does not change the color of the Value (if it represents a * JSObject) meaning that the value returned is not guaranteed to be kept * alive past the next CC. * * This should only be called if you are certain that the return value won't * be passed into a JS API function and that it won't be stored without * being rooted (or otherwise signaling the stored value to the CC). */ JS::Value GetJSValPreserveColor() const { return mJSVal.unbarrieredGet(); } XPCVariant(JSContext* cx, const JS::Value& aJSVal); /** * Convert a variant into a JS::Value. * * @param cx the context for the whole procedure * @param variant the variant to convert * @param scope the default scope to put on the new JSObject's parent chain * @param pErr [out] relevant error code, if any. * @param pJSVal [out] the resulting jsval. */ static bool VariantDataToJS(JSContext* cx, nsIVariant* variant, nsresult* pErr, JS::MutableHandleValue pJSVal); bool IsPurple() { return mRefCnt.IsPurple(); } void RemovePurple() { mRefCnt.RemovePurple(); } protected: virtual ~XPCVariant() = default; bool InitializeData(JSContext* cx); protected: nsDiscriminatedUnion mData; JS::Heap mJSVal; bool mReturnRawObject; }; NS_DEFINE_STATIC_IID_ACCESSOR(XPCVariant, XPCVARIANT_IID) class XPCTraceableVariant : public XPCVariant, public XPCRootSetElem { public: XPCTraceableVariant(JSContext* cx, const JS::Value& aJSVal) : XPCVariant(cx, aJSVal) { nsXPConnect::GetRuntimeInstance()->AddVariantRoot(this); } virtual ~XPCTraceableVariant(); void TraceJS(JSTracer* trc); }; /***************************************************************************/ // Utilities inline void* xpc_GetJSPrivate(JSObject* obj) { return JS::GetPrivate(obj); } inline JSContext* xpc_GetSafeJSContext() { return XPCJSContext::Get()->Context(); } namespace xpc { // JSNatives to expose atob and btoa in various non-DOM XPConnect scopes. bool Atob(JSContext* cx, unsigned argc, JS::Value* vp); bool Btoa(JSContext* cx, unsigned argc, JS::Value* vp); // Helper function that creates a JSFunction that wraps a native function that // forwards the call to the original 'callable'. class FunctionForwarderOptions; bool NewFunctionForwarder(JSContext* cx, JS::HandleId id, JS::HandleObject callable, FunctionForwarderOptions& options, JS::MutableHandleValue vp); // Old fashioned xpc error reporter. Try to use JS_ReportError instead. nsresult ThrowAndFail(nsresult errNum, JSContext* cx, bool* retval); struct GlobalProperties { GlobalProperties() { mozilla::PodZero(this); } bool Parse(JSContext* cx, JS::HandleObject obj); bool DefineInXPCComponents(JSContext* cx, JS::HandleObject obj); bool DefineInSandbox(JSContext* cx, JS::HandleObject obj); // Interface objects we can expose. bool Blob : 1; bool ChromeUtils : 1; bool CSS : 1; bool CSSRule : 1; bool Directory : 1; bool Document : 1; bool DOMException : 1; bool DOMParser : 1; bool DOMTokenList : 1; bool Element : 1; bool Event : 1; bool File : 1; bool FileReader : 1; bool FormData : 1; bool Headers : 1; bool InspectorUtils : 1; bool MessageChannel : 1; bool Node : 1; bool NodeFilter : 1; bool Performance : 1; bool PromiseDebugging : 1; bool Range : 1; bool Selection : 1; bool TextDecoder : 1; bool TextEncoder : 1; bool URL : 1; bool URLSearchParams : 1; bool XMLHttpRequest : 1; bool XMLSerializer : 1; // Ad-hoc property names we implement. bool atob : 1; bool btoa : 1; bool caches : 1; bool crypto : 1; bool fetch : 1; bool indexedDB : 1; bool isSecureContext : 1; bool rtcIdentityProvider : 1; #ifdef MOZ_GLEAN bool glean : 1; bool gleanPings : 1; #endif private: bool Define(JSContext* cx, JS::HandleObject obj); }; // Infallible. already_AddRefed NewSandboxConstructor(); // Returns true if class of 'obj' is SandboxClass. bool IsSandbox(JSObject* obj); class MOZ_STACK_CLASS OptionsBase { public: explicit OptionsBase(JSContext* cx = xpc_GetSafeJSContext(), JSObject* options = nullptr) : mCx(cx), mObject(cx, options) {} virtual bool Parse() = 0; protected: bool ParseValue(const char* name, JS::MutableHandleValue prop, bool* found = nullptr); bool ParseBoolean(const char* name, bool* prop); bool ParseObject(const char* name, JS::MutableHandleObject prop); bool ParseJSString(const char* name, JS::MutableHandleString prop); bool ParseString(const char* name, nsCString& prop); bool ParseString(const char* name, nsString& prop); bool ParseId(const char* name, JS::MutableHandleId id); bool ParseUInt32(const char* name, uint32_t* prop); JSContext* mCx; JS::RootedObject mObject; }; class MOZ_STACK_CLASS SandboxOptions : public OptionsBase { public: explicit SandboxOptions(JSContext* cx = xpc_GetSafeJSContext(), JSObject* options = nullptr) : OptionsBase(cx, options), wantXrays(true), allowWaivers(true), wantComponents(true), wantExportHelpers(false), isWebExtensionContentScript(false), proto(cx), sameZoneAs(cx), forceSecureContext(false), freshCompartment(false), freshZone(false), isUAWidgetScope(false), invisibleToDebugger(false), discardSource(false), metadata(cx), userContextId(0), originAttributes(cx) {} virtual bool Parse() override; bool wantXrays; bool allowWaivers; bool wantComponents; bool wantExportHelpers; bool isWebExtensionContentScript; JS::RootedObject proto; nsCString sandboxName; JS::RootedObject sameZoneAs; bool forceSecureContext; bool freshCompartment; bool freshZone; bool isUAWidgetScope; bool invisibleToDebugger; bool discardSource; GlobalProperties globalProperties; JS::RootedValue metadata; uint32_t userContextId; JS::RootedObject originAttributes; protected: bool ParseGlobalProperties(); }; class MOZ_STACK_CLASS CreateObjectInOptions : public OptionsBase { public: explicit CreateObjectInOptions(JSContext* cx = xpc_GetSafeJSContext(), JSObject* options = nullptr) : OptionsBase(cx, options), defineAs(cx, JSID_VOID) {} virtual bool Parse() override { return ParseId("defineAs", &defineAs); } JS::RootedId defineAs; }; class MOZ_STACK_CLASS ExportFunctionOptions : public OptionsBase { public: explicit ExportFunctionOptions(JSContext* cx = xpc_GetSafeJSContext(), JSObject* options = nullptr) : OptionsBase(cx, options), defineAs(cx, JSID_VOID), allowCrossOriginArguments(false) {} virtual bool Parse() override { return ParseId("defineAs", &defineAs) && ParseBoolean("allowCrossOriginArguments", &allowCrossOriginArguments); } JS::RootedId defineAs; bool allowCrossOriginArguments; }; class MOZ_STACK_CLASS FunctionForwarderOptions : public OptionsBase { public: explicit FunctionForwarderOptions(JSContext* cx = xpc_GetSafeJSContext(), JSObject* options = nullptr) : OptionsBase(cx, options), allowCrossOriginArguments(false) {} JSObject* ToJSObject(JSContext* cx) { JS::RootedObject obj(cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); if (!obj) { return nullptr; } JS::RootedValue val(cx); unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT; val = JS::BooleanValue(allowCrossOriginArguments); if (!JS_DefineProperty(cx, obj, "allowCrossOriginArguments", val, attrs)) { return nullptr; } return obj; } virtual bool Parse() override { return ParseBoolean("allowCrossOriginArguments", &allowCrossOriginArguments); } bool allowCrossOriginArguments; }; class MOZ_STACK_CLASS StackScopedCloneOptions : public OptionsBase { public: explicit StackScopedCloneOptions(JSContext* cx = xpc_GetSafeJSContext(), JSObject* options = nullptr) : OptionsBase(cx, options), wrapReflectors(false), cloneFunctions(false), deepFreeze(false) {} virtual bool Parse() override { return ParseBoolean("wrapReflectors", &wrapReflectors) && ParseBoolean("cloneFunctions", &cloneFunctions) && ParseBoolean("deepFreeze", &deepFreeze); } // When a reflector is encountered, wrap it rather than aborting the clone. bool wrapReflectors; // When a function is encountered, clone it (exportFunction-style) rather than // aborting the clone. bool cloneFunctions; // If true, the resulting object is deep-frozen after being cloned. bool deepFreeze; }; JSObject* CreateGlobalObject(JSContext* cx, const JSClass* clasp, nsIPrincipal* principal, JS::RealmOptions& aOptions); // Modify the provided compartment options, consistent with |aPrincipal| and // with globally-cached values of various preferences. // // Call this function *before* |aOptions| is used to create the corresponding // global object, as not all of the options it sets can be modified on an // existing global object. (The type system should make this obvious, because // you can't get a *mutable* JS::RealmOptions& from an existing global // object.) void InitGlobalObjectOptions(JS::RealmOptions& aOptions, nsIPrincipal* aPrincipal); // Finish initializing an already-created, not-yet-exposed-to-script global // object. This will attach a Components object (if necessary) and call // |JS_FireOnNewGlobalObject| (if necessary). // // If you must modify compartment options, see InitGlobalObjectOptions above. bool InitGlobalObject(JSContext* aJSContext, JS::Handle aGlobal, uint32_t aFlags); // Helper for creating a sandbox object to use for evaluating // untrusted code completely separated from all other code in the // system using EvalInSandbox(). Takes the JSContext on which to // do setup etc on, puts the sandbox object in *vp (which must be // rooted by the caller), and uses the principal that's either // directly passed in prinOrSop or indirectly as an // nsIScriptObjectPrincipal holding the principal. If no principal is // reachable through prinOrSop, a new null principal will be created // and used. nsresult CreateSandboxObject(JSContext* cx, JS::MutableHandleValue vp, nsISupports* prinOrSop, xpc::SandboxOptions& options); // Helper for evaluating scripts in a sandbox object created with // CreateSandboxObject(). The caller is responsible of ensuring // that *rval doesn't get collected during the call or usage after the // call. This helper will use filename and lineNo for error reporting, // and if no filename is provided it will use the codebase from the // principal and line number 1 as a fallback. nsresult EvalInSandbox(JSContext* cx, JS::HandleObject sandbox, const nsAString& source, const nsACString& filename, int32_t lineNo, bool enforceFilenameRestrictions, JS::MutableHandleValue rval); // Helper for retrieving metadata stored in a reserved slot. The metadata // is set during the sandbox creation using the "metadata" option. nsresult GetSandboxMetadata(JSContext* cx, JS::HandleObject sandboxArg, JS::MutableHandleValue rval); nsresult SetSandboxMetadata(JSContext* cx, JS::HandleObject sandboxArg, JS::HandleValue metadata); bool CreateObjectIn(JSContext* cx, JS::HandleValue vobj, CreateObjectInOptions& options, JS::MutableHandleValue rval); bool EvalInWindow(JSContext* cx, const nsAString& source, JS::HandleObject scope, JS::MutableHandleValue rval); bool ExportFunction(JSContext* cx, JS::HandleValue vscope, JS::HandleValue vfunction, JS::HandleValue voptions, JS::MutableHandleValue rval); bool CloneInto(JSContext* cx, JS::HandleValue vobj, JS::HandleValue vscope, JS::HandleValue voptions, JS::MutableHandleValue rval); bool StackScopedClone(JSContext* cx, StackScopedCloneOptions& options, JS::HandleObject sourceScope, JS::MutableHandleValue val); } /* namespace xpc */ /***************************************************************************/ // Inlined utilities. inline bool xpc_ForcePropertyResolve(JSContext* cx, JS::HandleObject obj, jsid id); inline jsid GetJSIDByIndex(JSContext* cx, unsigned index); namespace xpc { enum WrapperDenialType { WrapperDenialForXray = 0, WrapperDenialForCOW, WrapperDenialTypeCount }; bool ReportWrapperDenial(JSContext* cx, JS::HandleId id, WrapperDenialType type, const char* reason); class CompartmentOriginInfo { public: CompartmentOriginInfo(const CompartmentOriginInfo&) = delete; CompartmentOriginInfo(mozilla::BasePrincipal* aOrigin, const mozilla::SiteIdentifier& aSite) : mOrigin(aOrigin), mSite(aSite) { MOZ_ASSERT(aOrigin); MOZ_ASSERT(aSite.IsInitialized()); } bool IsSameOrigin(nsIPrincipal* aOther) const; // Does the principal of compartment a subsume the principal of compartment b? static bool Subsumes(JS::Compartment* aCompA, JS::Compartment* aCompB); static bool SubsumesIgnoringFPD(JS::Compartment* aCompA, JS::Compartment* aCompB); bool MightBeWebContent() const; // Note: this principal must not be used for subsumes/equality checks // considering document.domain. See mOrigin. mozilla::BasePrincipal* GetPrincipalIgnoringDocumentDomain() const { return mOrigin; } const mozilla::SiteIdentifier& SiteRef() const { return mSite; } bool HasChangedDocumentDomain() const { return mChangedDocumentDomain; } void SetChangedDocumentDomain() { mChangedDocumentDomain = true; } private: // All globals in the compartment must have this origin. Note that // individual globals and principals can have their domain changed via // document.domain, so this principal must not be used for things like // subsumesConsideringDomain or equalsConsideringDomain. Use the realm's // principal for that. RefPtr mOrigin; // In addition to the origin we also store the SiteIdentifier. When realms // in different compartments can become same-origin (via document.domain), // these compartments must have equal SiteIdentifiers. (This is derived from // mOrigin but we cache it here for performance reasons.) mozilla::SiteIdentifier mSite; // True if any global in this compartment mutated document.domain. bool mChangedDocumentDomain = false; }; // The CompartmentPrivate contains XPConnect-specific stuff related to each JS // compartment. Since compartments are trust domains, this means mostly // information needed to select the right security policy for cross-compartment // wrappers. class CompartmentPrivate { CompartmentPrivate() = delete; CompartmentPrivate(const CompartmentPrivate&) = delete; public: CompartmentPrivate(JS::Compartment* c, mozilla::UniquePtr scope, mozilla::BasePrincipal* origin, const mozilla::SiteIdentifier& site); ~CompartmentPrivate(); static CompartmentPrivate* Get(JS::Compartment* compartment) { MOZ_ASSERT(compartment); void* priv = JS_GetCompartmentPrivate(compartment); return static_cast(priv); } static CompartmentPrivate* Get(JS::Realm* realm) { MOZ_ASSERT(realm); JS::Compartment* compartment = JS::GetCompartmentForRealm(realm); return Get(compartment); } static CompartmentPrivate* Get(JSObject* object) { JS::Compartment* compartment = JS::GetCompartment(object); return Get(compartment); } bool CanShareCompartmentWith(nsIPrincipal* principal) { // Only share if we're same-origin with the principal. if (!originInfo.IsSameOrigin(principal)) { return false; } // Don't share if we have any weird state set. return !wantXrays && !isWebExtensionContentScript && !isUAWidgetCompartment && mScope->XBLScopeStateMatches(principal); } CompartmentOriginInfo originInfo; // Controls whether this compartment gets Xrays to same-origin. This behavior // is deprecated, but is still the default for sandboxes for compatibity // reasons. bool wantXrays; // Controls whether this compartment is allowed to waive Xrays to content // that it subsumes. This should generally be true, except in cases where we // want to prevent code from depending on Xray Waivers (which might make it // more portable to other browser architectures). bool allowWaivers; // This compartment corresponds to a WebExtension content script, and // receives various bits of special compatibility behavior. bool isWebExtensionContentScript; // True if this compartment is a UA widget compartment. bool isUAWidgetCompartment; // See CompartmentHasExclusiveExpandos. bool hasExclusiveExpandos; // Whether SystemIsBeingShutDown has been called on this compartment. bool wasShutdown; JSObject2WrappedJSMap* GetWrappedJSMap() const { return mWrappedJSMap.get(); } void UpdateWeakPointersAfterGC(); void SystemIsBeingShutDown(); size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); struct SweepPolicy { static bool needsSweep(const void* /* unused */, JS::Heap* value) { return JS::GCPolicy>::needsSweep(value); } }; typedef JS::GCHashMap, mozilla::PointerHasher, js::SystemAllocPolicy, SweepPolicy> RemoteProxyMap; RemoteProxyMap& GetRemoteProxyMap() { return mRemoteProxies; } XPCWrappedNativeScope* GetScope() { return mScope.get(); } private: mozilla::UniquePtr mWrappedJSMap; // Cache holding proxy objects for Window objects (and their Location object) // that are loaded in a different process. RemoteProxyMap mRemoteProxies; // Our XPCWrappedNativeScope. mozilla::UniquePtr mScope; }; inline void CrashIfNotInAutomation() { MOZ_RELEASE_ASSERT(IsInAutomation()); } // XPConnect-specific data associated with each JavaScript realm. Per-Window // settings live here; security-wrapper-related settings live in the // CompartmentPrivate. // // Following the ECMAScript spec, a realm contains a global (e.g. an inner // Window) and its associated scripts and objects; a compartment may contain // several same-origin realms. class RealmPrivate { RealmPrivate() = delete; RealmPrivate(const RealmPrivate&) = delete; public: enum LocationHint { LocationHintRegular, LocationHintAddon }; explicit RealmPrivate(JS::Realm* realm); // Creates the RealmPrivate and CompartmentPrivate (if needed) for a new // global. static void Init(JS::HandleObject aGlobal, const mozilla::SiteIdentifier& aSite); static RealmPrivate* Get(JS::Realm* realm) { MOZ_ASSERT(realm); void* priv = JS::GetRealmPrivate(realm); return static_cast(priv); } // Get the RealmPrivate for a given object. `object` must not be a // cross-compartment wrapper, as CCWs aren't dedicated to a particular // realm. static RealmPrivate* Get(JSObject* object) { JS::Realm* realm = JS::GetObjectRealmOrNull(object); return Get(realm); } // The scriptability of this realm. Scriptability scriptability; // Whether we've emitted a warning about a property that was filtered out // by a security wrapper. See XrayWrapper.cpp. bool wrapperDenialWarnings[WrapperDenialTypeCount]; const nsACString& GetLocation() { if (location.IsEmpty() && locationURI) { nsCOMPtr jsLocationURI = do_QueryInterface(locationURI); if (jsLocationURI) { // We cannot call into JS-implemented nsIURI objects, because // we are iterating over the JS heap at this point. location = ""_ns; } else if (NS_FAILED(locationURI->GetSpec(location))) { location = ""_ns; } } return location; } bool GetLocationURI(LocationHint aLocationHint, nsIURI** aURI) { if (locationURI) { nsCOMPtr rval = locationURI; rval.forget(aURI); return true; } return TryParseLocationURI(aLocationHint, aURI); } bool GetLocationURI(nsIURI** aURI) { return GetLocationURI(LocationHintRegular, aURI); } void SetLocation(const nsACString& aLocation) { if (aLocation.IsEmpty()) { return; } if (!location.IsEmpty() || locationURI) { return; } location = aLocation; } void SetLocationURI(nsIURI* aLocationURI) { if (!aLocationURI) { return; } if (locationURI) { return; } locationURI = aLocationURI; } // JSStackFrames are tracked on a per-realm basis so they // can be cleared when the associated window goes away. void RegisterStackFrame(JSStackFrameBase* aFrame); void UnregisterStackFrame(JSStackFrameBase* aFrame); void NukeJSStackFrames(); private: nsCString location; nsCOMPtr locationURI; bool TryParseLocationURI(LocationHint aType, nsIURI** aURI); nsTHashtable> mJSStackFrames; }; inline XPCWrappedNativeScope* ObjectScope(JSObject* obj) { return CompartmentPrivate::Get(obj)->GetScope(); } JSObject* NewOutObject(JSContext* cx); bool IsOutObject(JSContext* cx, JSObject* obj); nsresult HasInstance(JSContext* cx, JS::HandleObject objArg, const nsID* iid, bool* bp); // Returns the principal associated with |obj|'s realm. The object must not be a // cross-compartment wrapper. nsIPrincipal* GetObjectPrincipal(JSObject* obj); // Attempt to clean up the passed in value pointer. The pointer `value` must be // a pointer to a value described by the type `nsXPTType`. // // This method expects a value of the following types: // TD_NSIDPTR // value : nsID* (free) // TD_ASTRING, TD_CSTRING, TD_UTF8STRING // value : ns[C]String* (truncate) // TD_PSTRING, TD_PWSTRING, TD_PSTRING_SIZE_IS, TD_PWSTRING_SIZE_IS // value : char[16_t]** (free) // TD_INTERFACE_TYPE, TD_INTERFACE_IS_TYPE // value : nsISupports** (release) // TD_LEGACY_ARRAY (NOTE: aArrayLen should be passed) // value : void** (destroy elements & free) // TD_ARRAY // value : nsTArray* (destroy elements & Clear) // TD_DOMOBJECT // value : T** (cleanup) // TD_PROMISE // value : dom::Promise** (release) // // Other types are ignored. inline void CleanupValue(const nsXPTType& aType, void* aValue, uint32_t aArrayLen = 0); // Out-of-line internals for xpc::CleanupValue. Defined in XPCConvert.cpp. void InnerCleanupValue(const nsXPTType& aType, void* aValue, uint32_t aArrayLen); // In order to be able to safely call CleanupValue on a generated value, the // data behind it needs to be initialized to a safe value. This method handles // initializing the backing data to a safe value to use as an argument to // XPCConvert methods, or xpc::CleanupValue. // // The pointer `aValue` must point to a block of memory at least aType.Stride() // bytes large, and correctly aligned. // // This method accepts the same types as xpc::CleanupValue. void InitializeValue(const nsXPTType& aType, void* aValue); // If a value was initialized with InitializeValue, it should be destroyed with // DestructValue. This method acts like CleanupValue, except that destructors // for complex types are also invoked, leaving them in an invalid state. // // This method should be called when destroying types initialized with // InitializeValue. // // The pointer 'aValue' must point to a valid value of type 'aType'. void DestructValue(const nsXPTType& aType, void* aValue, uint32_t aArrayLen = 0); } // namespace xpc namespace mozilla { namespace dom { extern bool DefineStaticJSVals(JSContext* cx); } // namespace dom } // namespace mozilla bool xpc_LocalizeRuntime(JSRuntime* rt); void xpc_DelocalizeRuntime(JSRuntime* rt); /***************************************************************************/ // Inlines use the above - include last. #include "XPCInlines.h" /***************************************************************************/ // Maps have inlines that use the above - include last. #include "XPCMaps.h" /***************************************************************************/ #endif /* xpcprivate_h___ */