Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/HashFunctions.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/mozalloc.h"
#include "mozilla/Preferences.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Vector.h"
#include "mozilla/dom/ScriptSettings.h"
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "xpcpublic.h"
#include "js/HashTable.h"
#include "js/GCHashTable.h"
#include "js/Object.h" // JS::GetClass, JS::GetCompartment
#include "js/PropertyAndElement.h" // JS_DefineProperty
#include "js/TracingAPI.h"
#include "js/WeakMapPtr.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 "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 "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 AutoEntryScript;
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[];
/***************************************************************************/
// Helper function.
namespace xpc {
inline bool IsWrappedNativeReflector(JSObject* obj) {
return JS::GetClass(obj)->isWrappedNative();
}
} // namespace xpc
/***************************************************************************
****************************************************************************
*
* 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
// 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;
friend class nsIXPConnect;
public:
static nsIScriptSecurityManager* gScriptSecurityManager;
static nsIPrincipal* gSystemPrincipal;
};
/***************************************************************************/
// 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<XPCWrappedNativeScope>;
class XPCJSContext final : public mozilla::CycleCollectedJSContext,
public mozilla::LinkedListElement<XPCJSContext> {
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;
// Relay to the CCGCScheduler instead of queuing up an idle runnable
// (as is done for workers in CycleCollectedJSContext).
virtual void MaybePokeGC() 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_VALUE,
IDX_QUERY_INTERFACE,
IDX_COMPONENTS,
IDX_CC,
IDX_CI,
IDX_CR,
IDX_CU,
IDX_SERVICES,
IDX_WRAPPED_JSOBJECT,
IDX_PROTOTYPE,
IDX_EVAL,
IDX_CONTROLLERS,
IDX_CONTROLLERS_CLASS,
IDX_LENGTH,
IDX_NAME,
IDX_UNDEFINED,
IDX_EMPTYSTRING,
IDX_FILENAME,
IDX_LINENUMBER,
IDX_COLUMNNUMBER,
IDX_STACK,
IDX_MESSAGE,
IDX_CAUSE,
IDX_ERRORS,
IDX_LASTINDEX,
IDX_THEN,
IDX_ISINSTANCE,
IDX_INFINITY,
IDX_NAN,
IDX_CLASS_ID,
IDX_INTERFACE_ID,
IDX_INITIALIZER,
IDX_PRINT,
IDX_FETCH,
IDX_CRYPTO,
IDX_INDEXEDDB,
IDX_STRUCTUREDCLONE,
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<WatchdogManager> 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(); }
using WrappedNativeProtoVector =
mozilla::Vector<mozilla::UniquePtr<XPCWrappedNativeProto>, 0,
InfallibleAllocPolicy>;
WrappedNativeProtoVector& GetDyingWrappedNativeProtos() {
return mDyingWrappedNativeProtos;
}
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]);
}
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(mozilla::CCReason aReason) 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(JS::GCContext* gcx, JSFinalizeStatus status,
void* data);
static void WeakPointerZonesCallback(JSTracer* trc, void* data);
static void WeakPointerCompartmentCallback(JSTracer* trc,
JS::Compartment* comp, void* data);
inline void AddSubjectToFinalizationWJS(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*);
void DeleteSingletonScopes();
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];
struct Hasher {
using Key = RefPtr<mozilla::BasePrincipal>;
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 MapEntryGCPolicy {
static bool traceWeak(JSTracer* trc,
RefPtr<mozilla::BasePrincipal>* /* unused */,
JS::Heap<JSObject*>* value) {
return JS::GCPolicy<JS::Heap<JSObject*>>::traceWeak(trc, value);
}
};
typedef JS::GCHashMap<RefPtr<mozilla::BasePrincipal>, JS::Heap<JSObject*>,
Hasher, js::SystemAllocPolicy, MapEntryGCPolicy>
Principal2JSObjectMap;
mozilla::UniquePtr<JSObject2WrappedJSMap> mWrappedJSMap;
mozilla::UniquePtr<IID2NativeInterfaceMap> mIID2NativeInterfaceMap;
mozilla::UniquePtr<ClassInfo2NativeSetMap> mClassInfo2NativeSetMap;
mozilla::UniquePtr<NativeSetMap> mNativeSetMap;
Principal2JSObjectMap mUAWidgetScopeMap;
XPCWrappedNativeScopeList mWrappedNativeScopes;
WrappedNativeProtoVector mDyingWrappedNativeProtos;
bool mGCIsRunning;
nsTArray<nsISupports*> mNativesToReleaseArray;
bool mDoingFinalization;
mozilla::LinkedList<nsXPCWrappedJS> mSubjectToFinalizationWJS;
nsTArray<xpcGCCallback> extraGCCallbacks;
JS::GCSliceCallback mPrevGCSliceCallback;
JS::DoCycleCollectionCallback mPrevDoCycleCollectionCallback;
mozilla::WeakPtr<SandboxPrivate> mUnprivilegedJunkScope;
RefPtr<AsyncFreeSnowWhite> 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 : unsigned { NO_ARGS = (unsigned)-1 };
explicit XPCCallContext(JSContext* cx, JS::HandleObject obj = nullptr,
JS::HandleObject funobj = nullptr,
JS::HandleId id = JS::VoidHandlePropertyKey,
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<nsIXPConnect> mXPC;
XPCJSContext* mXPCJSContext;
JSContext* mJSContext;
// ctor does not necessarily init the following. BEWARE!
XPCCallContext* mPrevCallContext;
XPCWrappedNative* mWrapper;
XPCWrappedNativeTearOff* mTearOff;
nsCOMPtr<nsIXPCScriptable> mScriptable;
RefPtr<XPCNativeSet> mSet;
RefPtr<XPCNativeInterface> 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;
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<XPCWrappedNativeScope> {
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);
bool AttachJSServices(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(JSTracer* trc);
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<JSObject2JSObjectMap> 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<JSObject*> mIDProto;
JS::Heap<JSObject*> mIIDProto;
JS::Heap<JSObject*> mCIDProto;
protected:
XPCWrappedNativeScope() = delete;
private:
mozilla::UniquePtr<Native2WrappedNativeMap> mWrappedNativeMap;
mozilla::UniquePtr<ClassInfo2WrappedNativeProtoMap> mWrappedNativeProtoMap;
RefPtr<nsXPCComponents> mComponents;
JS::Compartment* mCompartment;
JS::WeakMapPtr<JSObject*, JSObject*> 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<XPCNativeInterface>* 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)
XPCNativeMember(const XPCNativeMember& other)
: mName(other.mName),
mIndex(other.mIndex),
mFlags(other.mFlags),
mIndexInInterface(other.mIndexInInterface) {
MOZ_COUNT_CTOR(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;
};
/***************************************************************************/
// 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<XPCNativeInterface> GetNewOrUsed(JSContext* cx,
const nsIID* iid);
static already_AddRefed<XPCNativeInterface> GetNewOrUsed(
JSContext* cx, const nsXPTInterfaceInfo* info);
static already_AddRefed<XPCNativeInterface> GetNewOrUsed(JSContext* cx,
const char* name);
static already_AddRefed<XPCNativeInterface> 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);
void Trace(JSTracer* trc);
protected:
static already_AddRefed<XPCNativeInterface> NewInstance(
JSContext* cx, IID2NativeInterfaceMap* aMap,
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; }
mozilla::HashNumber Hash() const;
// Allow shallow copy
private:
JSContext* mCx;
RefPtr<XPCNativeSet> mBaseSet;
RefPtr<XPCNativeInterface> 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<XPCNativeSet> GetNewOrUsed(JSContext* cx,
const nsIID* iid);
static already_AddRefed<XPCNativeSet> GetNewOrUsed(JSContext* cx,
nsIClassInfo* classInfo);
static already_AddRefed<XPCNativeSet> 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<XPCNativeSet> 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<XPCNativeInterface>* pInterface) const;
inline bool FindMember(JS::HandleId name, XPCNativeMember** pMember,
RefPtr<XPCNativeInterface>* 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<XPCNativeSet> NewInstance(
JSContext* cx, nsTArray<RefPtr<XPCNativeInterface>>&& array);
static already_AddRefed<XPCNativeSet> 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
// mDyingWrappedNativeProtos. See XPCWrappedNativeProto::JSProtoObjectFinalized
// and XPCJSRuntime::FinalizeCallback.
class XPCWrappedNativeProto final {
public:
enum Slots { ProtoSlot, SlotCount };
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(JS::GCContext* gcx, JSObject* obj);
void JSProtoObjectMoved(JSObject* obj, const JSObject* old);
static XPCWrappedNativeProto* Get(JSObject* obj);
void SystemIsBeingShutDown();
void DebugDump(int16_t depth);
void TraceSelf(JSTracer* trc) {
if (mJSProtoObject) {
TraceEdge(trc, &mJSProtoObject, "XPCWrappedNativeProto::mJSProtoObject");
}
}
void TraceJS(JSTracer* trc) { TraceSelf(trc); }
// 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<XPCNativeSet>&& Set);
bool Init(JSContext* cx, nsIXPCScriptable* scriptable);
private:
#ifdef DEBUG
static int32_t gDEBUG_LiveProtoCount;
#endif
private:
XPCWrappedNativeScope* mScope;
JS::Heap<JSObject*> mJSProtoObject;
nsCOMPtr<nsIClassInfo> mClassInfo;
RefPtr<XPCNativeSet> mSet;
nsCOMPtr<nsIXPCScriptable> mScriptable;
};
/***********************************************/
// XPCWrappedNativeTearOff represents the info needed to make calls to one
// interface on the underlying native object of a XPCWrappedNative.
class XPCWrappedNativeTearOff final {
public:
enum Slots { FlatObjectSlot, TearOffSlot, SlotCount };
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<nsISupports> TakeNative() { return mNative.forget(); }
void SetJSObject(JSObject* JSObj);
void JSObjectFinalized() { SetJSObject(nullptr); }
void JSObjectMoved(JSObject* obj, const JSObject* old);
static XPCWrappedNativeTearOff* Get(JSObject* obj);
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<XPCWrappedNativeTearOff>();
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<nsISupports> mNative;
JS::TenuredHeap<JSObject*> mJSObject;
mozilla::UniquePtr<XPCWrappedNativeTearOff> 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_CYCLE_COLLECTION_CLASS(XPCWrappedNative)
JSObject* GetJSObject() override;
bool IsValid() const { return mFlatJSObject.hasFlag(FLAT_JS_OBJECT_VALID); }
nsresult DebugDump(int16_t depth);
#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<XPCNativeSet> set) { mSet = set; }
static XPCWrappedNative* Get(JSObject* obj) {
MOZ_ASSERT(xpc::IsWrappedNativeReflector(obj));
return JS::GetObjectISupports<XPCWrappedNative>(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,
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<nsISupports>&& aIdentity,
XPCWrappedNativeProto* aProto);
// This ctor is used if this object will NOT have a proto.
XPCWrappedNative(nsCOMPtr<nsISupports>&& aIdentity,
XPCWrappedNativeScope* aScope, RefPtr<XPCNativeSet>&& 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<XPCNativeSet> mSet;
JS::TenuredHeap<JSObject*> mFlatJSObject;
nsCOMPtr<nsIXPCScriptable> 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 mozilla::LinkedListElement<nsXPCWrappedJS> {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_NSISUPPORTSWEAKREFERENCE
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(
nsXPCWrappedJS, nsIXPConnectWrappedJS)
JSObject* GetJSObject() override;
// 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; }
nsresult DebugDump(int16_t depth);
/**
* 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(JSTracer* trc) {
MOZ_ASSERT(IsRootWrapper());
JS_UpdateWeakPointerAfterGC(trc, &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;
}
// 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:
friend class nsIXPConnectWrappedJS;
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<JSObject*> mJSObj;
const nsXPTInterfaceInfo* const mInfo;
nsXPCWrappedJS* mRoot; // If mRoot != this, it is an owning pointer.
nsXPCWrappedJS* mNext;
nsCOMPtr<nsISupports> 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<nsIJSEnumerator> mEnum;
nsCOMPtr<nsIGlobalObject> mGlobal;
nsCOMPtr<nsISupports> mNext;
mozilla::Maybe<bool> 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<void*(uint32_t*)>;
/**
* 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<nsXPCComponents_Interfaces> mInterfaces;
RefPtr<nsXPCComponents_Results> mResults;
RefPtr<nsXPCComponents_Classes> mClasses;
RefPtr<nsXPCComponents_ID> mID;
RefPtr<nsXPCComponents_Exception> mException;
RefPtr<nsXPCComponents_Constructor> mConstructor;
RefPtr<nsXPCComponents_Utils> 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<JS::AutoSaveExceptionState> mState;
bool mEvaluated;
mozilla::Maybe<JSAutoRealm> 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<jsid> 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 T>
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<T>& 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<XPCWrappedNative>;
using AutoMarkingWrappedNativeTearOffPtr =
TypedAutoMarkingPtr<XPCWrappedNativeTearOff>;
using AutoMarkingWrappedNativeProtoPtr =
TypedAutoMarkingPtr<XPCWrappedNativeProto>;
/***************************************************************************/
// Definitions 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_SCRIPT_HOLDER_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<XPCVariant> 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; }
protected:
/**
* 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);
public:
/**
* 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);
protected:
virtual ~XPCVariant();
bool InitializeData(JSContext* cx);
void Cleanup();
nsDiscriminatedUnion mData;
JS::Heap<JS::Value> mJSVal;
bool mReturnRawObject;
};
NS_DEFINE_STATIC_IID_ACCESSOR(XPCVariant, XPCVARIANT_IID)
/***************************************************************************/
// Utilities
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 AbortController : 1;
bool Blob : 1;
bool ChromeUtils : 1;
bool CSS : 1;
bool CSSRule : 1;
bool CustomStateSet : 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 IOUtils : 1;
bool InspectorCSSParser : 1;
bool InspectorUtils : 1;
bool MessageChannel : 1;
bool MIDIInputMap : 1;
bool MIDIOutputMap : 1;
bool Node : 1;
bool NodeFilter : 1;
bool PathUtils : 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 WebSocket : 1;
bool Window : 1;
bool XMLSerializer : 1;
bool ReadableStream : 1;
// Ad-hoc property names we implement.
bool atob : 1;
bool btoa : 1;
bool caches : 1;
bool crypto : 1;
bool fetch : 1;
bool storage : 1;
bool structuredClone : 1;
bool indexedDB : 1;
bool isSecureContext : 1;
bool rtcIdentityProvider : 1;
private:
bool Define(JSContext* cx, JS::HandleObject obj);
};
// Infallible.
already_AddRefed<nsIXPCComponents_utils_Sandbox> 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, JS::PropertyKey::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, JS::PropertyKey::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);
// 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<JSObject*> 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<mozilla::BasePrincipal> 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<XPCWrappedNativeScope> 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<CompartmentPrivate*>(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(JSTracer* trc);
void SystemIsBeingShutDown();
size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
struct MapEntryGCPolicy {
static bool traceWeak(JSTracer* trc, const void* /* unused */,
JS::Heap<JSObject*>* value) {
return JS::GCPolicy<JS::Heap<JSObject*>>::traceWeak(trc, value);
}
};
typedef JS::GCHashMap<const void*, JS::Heap<JSObject*>,
mozilla::PointerHasher<const void*>,
js::SystemAllocPolicy, MapEntryGCPolicy>
RemoteProxyMap;
RemoteProxyMap& GetRemoteProxyMap() { return mRemoteProxies; }
XPCWrappedNativeScope* GetScope() { return mScope.get(); }
private:
mozilla::UniquePtr<JSObject2WrappedJSMap> 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<XPCWrappedNativeScope> 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<RealmPrivate*>(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<nsIXPConnectWrappedJS> 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 = "<JS-implemented nsIURI location>"_ns;
} else if (NS_FAILED(locationURI->GetSpec(location))) {
location = "<unknown location>"_ns;
}
}
return location;
}
bool GetLocationURI(LocationHint aLocationHint, nsIURI** aURI) {
if (locationURI) {
nsCOMPtr<nsIURI> 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<nsIURI> locationURI;
bool TryParseLocationURI(LocationHint aType, nsIURI** aURI);
nsTHashtable<nsPtrHashKey<JSStackFrameBase>> 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<T>* (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);
bool SandboxCreateCrypto(JSContext* cx, JS::Handle<JSObject*> obj);
bool SandboxCreateFetch(JSContext* cx, JS::Handle<JSObject*> obj);
bool SandboxCreateStructuredClone(JSContext* cx, JS::Handle<JSObject*> obj);
} // 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"
/***************************************************************************/
#endif /* xpcprivate_h___ */