/* -*- 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/. */
/** * A common base class for representing WebIDL callback function and * callback interface types in C++. * * This class implements common functionality like lifetime * management, initialization with the JS object, and setup of the * call environment. Subclasses are responsible for providing methods * that do the call into JS as needed.
*/
// The caller may pass a global object which will act as an override for the // incumbent script settings object when the callback is invoked (overriding // the entry point computed from aCallback). If no override is required, the // caller should pass null. |aCx| is used to capture the current // stack, which is later used as an async parent when the callback // is invoked. aCx can be nullptr, in which case no stack is // captured. explicit CallbackObject(JSContext* aCx, JS::Handle<JSObject*> aCallback,
JS::Handle<JSObject*> aCallbackGlobal,
nsIGlobalObject* aIncumbentGlobal) { if (aCx && JS::IsAsyncStackCaptureEnabledForRealm(aCx)) {
JS::Rooted<JSObject*> stack(aCx); if (!JS::CaptureCurrentStack(aCx, &stack)) {
JS_ClearPendingException(aCx);
}
Init(aCallback, aCallbackGlobal, stack, aIncumbentGlobal);
} else {
Init(aCallback, aCallbackGlobal, nullptr, aIncumbentGlobal);
}
}
// Instead of capturing the current stack to use as an async parent when the // callback is invoked, the caller can use this overload to pass in a stack // for that purpose. explicit CallbackObject(JSObject* aCallback, JSObject* aCallbackGlobal,
JSObject* aAsyncStack,
nsIGlobalObject* aIncumbentGlobal) {
Init(aCallback, aCallbackGlobal, aAsyncStack, aIncumbentGlobal);
}
// This is guaranteed to be non-null from the time the CallbackObject is // created until JavaScript has had a chance to run. It will only return null // after a JavaScript caller has called nukeSandbox on a Sandbox object and // the cycle collector has had a chance to run, unless Reset() is explicitly // called (see below). // // This means that any native callee which receives a CallbackObject as an // argument can safely rely on the callback being non-null so long as it // doesn't trigger any scripts before it accesses it.
JSObject* CallbackOrNull() const {
mCallback.exposeToActiveJS(); return CallbackPreserveColor();
}
// Like CallbackOrNull(), but will return a new dead proxy object in the // caller's realm if the callback is null.
JSObject* Callback(JSContext* aCx);
/* * 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.
*/
JSObject* CallbackPreserveColor() const { return mCallback.unbarrieredGet(); }
JSObject* CallbackGlobalPreserveColor() const { return mCallbackGlobal.unbarrieredGet();
}
/* * If the callback is known to be non-gray, then this method can be * used instead of CallbackOrNull() to avoid the overhead of * ExposeObjectToActiveJS().
*/
JSObject* CallbackKnownNotGray() const {
JS::AssertObjectIsNotGray(mCallback); return CallbackPreserveColor();
}
enum ExceptionHandling { // Report any exception and don't throw it to the caller code.
eReportExceptions, // Throw any exception to the caller code and don't report it.
eRethrowExceptions, // Throw an exception to the caller code if the thrown exception is a // binding object for a DOMException from the caller's scope, otherwise // report it.
eRethrowContentExceptions
};
// Append a UTF-8 string to aOutString that describes the callback function, // for use in logging or profiler markers. // The string contains the function name and its source location, if // available, in the following format: // "<functionName> (<sourceURL>:<lineNumber>)" void GetDescription(nsACString& aOutString);
// Used for cycle collection optimization. Should return true only if all our // outgoing edges are to known-live objects. In that case, there's no point // traversing our edges to them, because we know they can't be collected // anyway. bool IsBlackForCC() const { // Play it safe in case this gets called after unlink. return (!mCallback || !JS::ObjectIsMarkedGray(mCallback)) &&
(!mCallbackGlobal || !JS::ObjectIsMarkedGray(mCallbackGlobal)) &&
(!mCreationStack || !JS::ObjectIsMarkedGray(mCreationStack)) &&
(!mIncumbentJSGlobal ||
!JS::ObjectIsMarkedGray(mIncumbentJSGlobal)) && // mIncumbentGlobal is known-live if we have a known-live // mIncumbentJSGlobal, since mIncumbentJSGlobal will keep a ref to // it. At this point if mIncumbentJSGlobal is not null, it's // known-live.
(!mIncumbentGlobal || mIncumbentJSGlobal);
}
private: inlinevoid InitNoHold(JSObject* aCallback, JSObject* aCallbackGlobal,
JSObject* aCreationStack,
nsIGlobalObject* aIncumbentGlobal) {
MOZ_ASSERT(aCallback && !mCallback);
MOZ_ASSERT(aCallbackGlobal);
MOZ_DIAGNOSTIC_ASSERT(JS::GetCompartment(aCallback) ==
JS::GetCompartment(aCallbackGlobal));
MOZ_ASSERT(JS_IsGlobalObject(aCallbackGlobal));
mCallback = aCallback;
mCallbackGlobal = aCallbackGlobal;
mCreationStack = aCreationStack; if (aIncumbentGlobal) {
mIncumbentGlobal = aIncumbentGlobal; // We don't want to expose to JS here (change the color). If someone ever // reads mIncumbentJSGlobal, that will expose. If not, no need to expose // here.
mIncumbentJSGlobal = aIncumbentGlobal->GetGlobalJSObjectPreserveColor();
}
}
inlinevoid Init(JSObject* aCallback, JSObject* aCallbackGlobal,
JSObject* aCreationStack,
nsIGlobalObject* aIncumbentGlobal) { // Set script objects before we hold, on the off chance that a GC could // somehow happen in there... (which would be pretty odd, granted).
InitNoHold(aCallback, aCallbackGlobal, aCreationStack, aIncumbentGlobal);
mozilla::HoldJSObjects(this);
}
// Provide a way to clear this object's pointers to GC things after the // callback has been run. Note that CallbackOrNull() will return null after // this point. This should only be called if the object is known not to be // used again, and no handles (e.g. those returned by CallbackPreserveColor) // are in use. void Reset() { ClearJSReferences(); } friendclass mozilla::PromiseJobRunnable;
// For use from subclasses that want to be usable with Rooted. void Trace(JSTracer* aTracer);
// For use from subclasses that want to be traced for a bit then possibly // switch to HoldJSObjects and do other slow JS-related init work we might do. // If we have more than one owner, this will HoldJSObjects and do said slow // init work; otherwise it will just forget all our JS references. void FinishSlowJSInitIfMoreThanOneOwner(JSContext* aCx);
// Struct used as a way to force a CallbackObject constructor to not call // HoldJSObjects. We're putting it here so that CallbackObject subclasses will // have access to it, but outside code will not. // // Places that use this need to ensure that the callback is traced (e.g. via a // Rooted) until the HoldJSObjects call happens. struct FastCallbackConstructor {};
// Just like the public version without the FastCallbackConstructor argument, // except for not calling HoldJSObjects and not capturing async stacks (on the // assumption that we will do that last whenever we decide to actually // HoldJSObjects; see FinishSlowJSInitIfMoreThanOneOwner). If you use this, // you MUST ensure that the object is traced until the HoldJSObjects happens!
CallbackObject(JSObject* aCallback, JSObject* aCallbackGlobal, const FastCallbackConstructor&) {
InitNoHold(aCallback, aCallbackGlobal, nullptr, nullptr);
}
// mCallback is not unwrapped, so it can be a cross-compartment-wrapper. // This is done to ensure that, if JS code can't call a callback f(), or get // its members, directly itself, this code won't call f(), or get its members, // on the code's behalf.
JS::Heap<JSObject*> mCallback; // mCallbackGlobal is the global that we were in when we created the // callback. In particular, it is guaranteed to be same-compartment with // aCallback. We store it separately, because we have no way to recover the // global if mCallback is a cross-compartment wrapper.
JS::Heap<JSObject*> mCallbackGlobal;
JS::Heap<JSObject*> mCreationStack; // Ideally, we'd just hold a reference to the nsIGlobalObject, since that's // what we need to pass to AutoIncumbentScript. Unfortunately, that doesn't // hold the actual JS global alive. So we maintain an additional pointer to // the JS global itself so that we can trace it. // // At some point we should consider trying to make native globals hold their // scripted global alive, at which point we can get rid of the duplication // here.
nsCOMPtr<nsIGlobalObject> mIncumbentGlobal;
JS::TenuredHeap<JSObject*> mIncumbentJSGlobal;
class MOZ_STACK_CLASS CallSetup { /** * A class that performs whatever setup we need to safely make a * call while this class is on the stack, After the constructor * returns, the call is safe to make if GetContext() returns * non-null.
*/ public: // If aExceptionHandling == eRethrowContentExceptions then aRealm // needs to be set to the realm in which exceptions will be rethrown. // // If aExceptionHandling == eRethrowExceptions then aRealm may be set // to the realm in which exceptions will be rethrown. In that case // they will only be rethrown if that realm's principal subsumes the // principal of our (unwrapped) callback.
CallSetup(CallbackObject* aCallback, ErrorResult& aRv, constchar* aExecutionReason,
ExceptionHandling aExceptionHandling, JS::Realm* aRealm = nullptr, bool aIsJSImplementedWebIDL = false);
MOZ_CAN_RUN_SCRIPT ~CallSetup();
JSContext* GetContext() const { return mCx; }
// Safe to call this after the constructor has run without throwing on the // ErrorResult it was handed.
BindingCallContext& GetCallContext() { return *mCallContext; }
private: // We better not get copy-constructed
CallSetup(const CallSetup&) = delete;
// Members which can go away whenever
JSContext* mCx;
// Caller's realm. This will only have a sensible value if // mExceptionHandling == eRethrowContentExceptions.
JS::Realm* mRealm;
// And now members whose construction/destruction order we need to control.
Maybe<AutoEntryScript> mAutoEntryScript;
Maybe<AutoIncumbentScript> mAutoIncumbentScript;
Maybe<JS::Rooted<JSObject*>> mRootedCallable; // The global of mRootedCallable.
Maybe<JS::Rooted<JSObject*>> mRootedCallableGlobal;
// Members which are used to set the async stack.
Maybe<JS::Rooted<JSObject*>> mAsyncStack;
Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
// Can't construct a JSAutoRealm without a JSContext either. Also, // Put mAr after mAutoEntryScript so that we exit the realm before we // pop the script settings stack. Though in practice we'll often manually // order those two things.
Maybe<JSAutoRealm> mAr;
// Our BindingCallContext. This is a Maybe so we can avoid constructing it // until after we have a JSContext to construct it with.
Maybe<BindingCallContext> mCallContext;
// An ErrorResult to possibly re-throw exceptions on and whether // we should re-throw them.
ErrorResult& mErrorResult; const ExceptionHandling mExceptionHandling; constbool mIsMainThread;
};
};
template <class WebIDLCallbackT, class XPCOMCallbackT> class CallbackObjectHolder;
template <class T, class U> void ImplCycleCollectionUnlink(CallbackObjectHolder<T, U>& aField);
class CallbackObjectHolderBase { protected: // Returns null on all failures
already_AddRefed<nsISupports> ToXPCOMCallback(CallbackObject* aCallback, const nsIID& aIID) const;
};
template <class WebIDLCallbackT, class XPCOMCallbackT> class CallbackObjectHolder : CallbackObjectHolderBase { /** * A class which stores either a WebIDLCallbackT* or an XPCOMCallbackT*. Both * types must inherit from nsISupports. The pointer that's stored can be * null. * * When storing a WebIDLCallbackT*, mPtrBits is set to the pointer value. * When storing an XPCOMCallbackT*, mPtrBits is the pointer value with low bit * set.
*/ public: explicit CallbackObjectHolder(WebIDLCallbackT* aCallback)
: mPtrBits(reinterpret_cast<uintptr_t>(aCallback)) {
NS_IF_ADDREF(aCallback);
}
CallbackObjectHolder(CallbackObjectHolder&& aOther)
: mPtrBits(aOther.mPtrBits) {
aOther.mPtrBits = 0;
static_assert(sizeof(CallbackObjectHolder) == sizeof(void*), "This object is expected to be as small as a pointer, and it " "is currently passed by value in various places. If it is " "bloating, we may want to pass it by reference then.");
}
already_AddRefed<nsISupports> Forget() { // This can be called from random threads. Make sure to not refcount things // in here!
nsISupports* supp = GetISupports();
mPtrBits = 0; return dont_AddRef(supp);
}
// Boolean conversion operator so people can use this in boolean tests explicitoperatorbool() const { return GetISupports(); }
booloperator==(WebIDLCallbackT* aOtherCallback) const { if (!aOtherCallback) { // If other is null, then we must be null to be equal. return !GetISupports();
}
if (!HasWebIDLCallback() || !GetWebIDLCallback()) { // If other is non-null, then we can't be equal if we have a // non-WebIDL callback or a null callback. returnfalse;
}
// Try to return an XPCOMCallbackT version of this object.
already_AddRefed<XPCOMCallbackT> ToXPCOMCallback() const { if (!HasWebIDLCallback()) {
RefPtr<XPCOMCallbackT> callback = GetXPCOMCallback(); return callback.forget();
}
nsCOMPtr<nsISupports> supp = CallbackObjectHolderBase::ToXPCOMCallback(
GetWebIDLCallback(), NS_GET_TEMPLATE_IID(XPCOMCallbackT)); if (supp) { // ToXPCOMCallback already did the right QI for us. return supp.forget().downcast<XPCOMCallbackT>();
} return nullptr;
}
// Try to return a WebIDLCallbackT version of this object.
already_AddRefed<WebIDLCallbackT> ToWebIDLCallback() const { if (HasWebIDLCallback()) {
RefPtr<WebIDLCallbackT> callback = GetWebIDLCallback(); return callback.forget();
} return nullptr;
}
void UnlinkSelf() { // NS_IF_RELEASE because we might have been unlinked before
nsISupports* ptr = GetISupports(); // Clear mPtrBits before the release to prevent reentrance.
mPtrBits = 0;
NS_IF_RELEASE(ptr);
}
// T is expected to be a RefPtr or OwningNonNull around a CallbackObject // subclass. This class is used in bindings to safely handle Fast* callbacks; // it ensures that the callback is traced, and that if something is holding onto // the callback when we're done with it HoldJSObjects is called. // // Since we effectively hold a ref to a refcounted thing (like RefPtr or // OwningNonNull), we are also MOZ_IS_SMARTPTR_TO_REFCOUNTED for static analysis // purposes. template <typename T> class MOZ_RAII MOZ_IS_SMARTPTR_TO_REFCOUNTED RootedCallback
: public JS::Rooted<T> { public: explicit RootedCallback(JSContext* cx) : JS::Rooted<T>(cx), mCx(cx) {}
// We need a way to make assignment from pointers (how we're normally used) // work. template <typename S> voidoperator=(S* arg) {
this->get().operator=(arg);
}
// But nullptr can't use the above template, because it doesn't know which S // to select. So we need a special overload for nullptr. voidoperator=(decltype(nullptr) arg) { this->get().operator=(arg); }
// Codegen relies on being able to do CallbackOrNull() and Callback() on us.
JSObject* CallbackOrNull() const { return this->get()->CallbackOrNull(); }
~RootedCallback() { // Ensure that our callback starts holding on to its own JS objects as // needed. We really do need to check that things are initialized even when // T is OwningNonNull, because we might be running before the OwningNonNull // ever got assigned to! if (IsInitialized(this->get())) {
this->get()->FinishSlowJSInitIfMoreThanOneOwner(mCx);
}
}
private: template <typename U> staticbool IsInitialized(U& aArg); // Not implemented
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.