/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ /* vim: set ts=2 sw=2 et tw=79: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_dom_BindingUtils_h__ #define mozilla_dom_BindingUtils_h__ #include "mozilla/dom/DOMJSClass.h" #include "mozilla/dom/workers/Workers.h" #include "mozilla/ErrorResult.h" #include "jsapi.h" #include "jsfriendapi.h" #include "jswrapper.h" #include "nsIXPConnect.h" #include "qsObjectHelper.h" #include "xpcpublic.h" #include "nsTraceRefcnt.h" #include "nsWrapperCacheInlines.h" #include "mozilla/Likely.h" // nsGlobalWindow implements nsWrapperCache, but doesn't always use it. Don't // try to use it without fixing that first. class nsGlobalWindow; namespace mozilla { namespace dom { enum ErrNum { #define MSG_DEF(_name, _argc, _str) \ _name, #include "mozilla/dom/Errors.msg" #undef MSG_DEF Err_Limit }; bool ThrowErrorMessage(JSContext* aCx, const ErrNum aErrorNumber, ...); template inline bool Throw(JSContext* cx, nsresult rv) { using mozilla::dom::workers::exceptions::ThrowDOMExceptionForNSResult; // XXX Introduce exception machinery. if (mainThread) { xpc::Throw(cx, rv); } else { if (!JS_IsExceptionPending(cx)) { ThrowDOMExceptionForNSResult(cx, rv); } } return false; } template inline bool ThrowMethodFailedWithDetails(JSContext* cx, const ErrorResult& rv, const char* /* ifaceName */, const char* /* memberName */) { return Throw(cx, rv.ErrorCode()); } inline bool IsDOMClass(const JSClass* clasp) { return clasp->flags & JSCLASS_IS_DOMJSCLASS; } inline bool IsDOMClass(const js::Class* clasp) { return IsDOMClass(Jsvalify(clasp)); } template inline T* UnwrapDOMObject(JSObject* obj) { MOZ_ASSERT(IsDOMClass(JS_GetClass(obj))); JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT); // XXXbz/khuey worker code tries to unwrap interface objects (which have // nothing here). That needs to stop. // XXX We don't null-check UnwrapObject's result; aren't we going to crash // anyway? if (val.isUndefined()) { return NULL; } return static_cast(val.toPrivate()); } // Some callers don't want to set an exception when unwrappin fails // (for example, overload resolution uses unwrapping to tell what sort // of thing it's looking at). // U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr). template inline nsresult UnwrapObject(JSContext* cx, JSObject* obj, U& value) { /* First check to see whether we have a DOM object */ JSClass* clasp = js::GetObjectJSClass(obj); if (!IsDOMClass(clasp)) { /* Maybe we have a security wrapper or outer window? */ if (!js::IsWrapper(obj)) { /* Not a DOM object, not a wrapper, just bail */ return NS_ERROR_XPC_BAD_CONVERT_JS; } obj = xpc::Unwrap(cx, obj, false); if (!obj) { return NS_ERROR_XPC_SECURITY_MANAGER_VETO; } MOZ_ASSERT(!js::IsWrapper(obj)); clasp = js::GetObjectJSClass(obj); if (!IsDOMClass(clasp)) { /* We don't have a DOM object */ return NS_ERROR_XPC_BAD_CONVERT_JS; } } MOZ_ASSERT(IsDOMClass(clasp)); /* This object is a DOM object. Double-check that it is safely castable to T by checking whether it claims to inherit from the class identified by protoID. */ DOMJSClass* domClass = DOMJSClass::FromJSClass(clasp); if (domClass->mInterfaceChain[PrototypeTraits::Depth] == PrototypeID) { value = UnwrapDOMObject(obj); return NS_OK; } /* It's the wrong sort of DOM object */ return NS_ERROR_XPC_BAD_CONVERT_JS; } inline bool IsArrayLike(JSContext* cx, JSObject* obj) { MOZ_ASSERT(obj); // For simplicity, check for security wrappers up front. In case we // have a security wrapper, don't forget to enter the compartment of // the underlying object after unwrapping. JSAutoEnterCompartment ac; if (js::IsWrapper(obj)) { obj = xpc::Unwrap(cx, obj, false); if (!obj) { // Let's say it's not return false; } if (!ac.enter(cx, obj)) { return false; } } // XXXbz need to detect platform objects (including listbinding // ones) with indexGetters here! return JS_IsArrayObject(cx, obj) || JS_IsTypedArrayObject(obj, cx); } inline bool IsPlatformObject(JSContext* cx, JSObject* obj) { // XXXbz Should be treating list-binding objects as platform objects // too? The one consumer so far wants non-array-like platform // objects, so listbindings that have an indexGetter should test // false from here. Maybe this function should have a different // name? MOZ_ASSERT(obj); // Fast-path the common case JSClass* clasp = js::GetObjectJSClass(obj); if (IsDOMClass(clasp)) { return true; } // Now for simplicity check for security wrappers before anything else if (js::IsWrapper(obj)) { obj = xpc::Unwrap(cx, obj, false); if (!obj) { // Let's say it's not return false; } clasp = js::GetObjectJSClass(obj); } return IS_WRAPPER_CLASS(js::Valueify(clasp)) || IsDOMClass(clasp) || JS_IsArrayBufferObject(obj, cx); } // U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr). template inline nsresult UnwrapObject(JSContext* cx, JSObject* obj, U& value) { return UnwrapObject( PrototypeIDMap::PrototypeID), T>(cx, obj, value); } const size_t kProtoOrIfaceCacheCount = prototypes::id::_ID_Count + constructors::id::_ID_Count; inline void AllocateProtoOrIfaceCache(JSObject* obj) { MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); MOZ_ASSERT(js::GetReservedSlot(obj, DOM_PROTOTYPE_SLOT).isUndefined()); // Important: The () at the end ensure zero-initialization JSObject** protoOrIfaceArray = new JSObject*[kProtoOrIfaceCacheCount](); js::SetReservedSlot(obj, DOM_PROTOTYPE_SLOT, JS::PrivateValue(protoOrIfaceArray)); } inline void TraceProtoOrIfaceCache(JSTracer* trc, JSObject* obj) { MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); if (!HasProtoOrIfaceArray(obj)) return; JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(obj); for (size_t i = 0; i < kProtoOrIfaceCacheCount; ++i) { JSObject* proto = protoOrIfaceArray[i]; if (proto) { JS_CALL_OBJECT_TRACER(trc, proto, "protoOrIfaceArray[i]"); } } } inline void DestroyProtoOrIfaceCache(JSObject* obj) { MOZ_ASSERT(js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL); JSObject** protoOrIfaceArray = GetProtoOrIfaceArray(obj); delete [] protoOrIfaceArray; } struct ConstantSpec { const char* name; JS::Value value; }; /** * Add constants to an object. */ bool DefineConstants(JSContext* cx, JSObject* obj, ConstantSpec* cs); template struct Prefable { // A boolean indicating whether this set of specs is enabled bool enabled; // Array of specs, terminated in whatever way is customary for T. // Null to indicate a end-of-array for Prefable, when such an // indicator is needed. T* specs; }; /* * Create a DOM interface object (if constructorClass is non-null) and/or a * DOM interface prototype object (if protoClass is non-null). * * global is used as the parent of the interface object and the interface * prototype object * receiver is the object on which we need to define the interface object as a * property * protoProto is the prototype to use for the interface prototype object. * protoClass is the JSClass to use for the interface prototype object. * This is null if we should not create an interface prototype * object. * constructorClass is the JSClass to use for the interface object. * This is null if we should not create an interface object or * if it should be a function object. * constructor is the JSNative to use as a constructor. If this is non-null, it * should be used as a JSNative to back the interface object, which * should be a Function. If this is null, then we should create an * object of constructorClass, unless that's also null, in which * case we should not create an interface object at all. * ctorNargs is the length of the constructor function; 0 if no constructor * instanceClass is the JSClass of instance objects for this class. This can * be null if this is not a concrete proto. * methods and properties are to be defined on the interface prototype object; * these arguments are allowed to be null if there are no * methods or properties respectively. * constants are to be defined on the interface object and on the interface * prototype object; allowed to be null if there are no constants. * staticMethods are to be defined on the interface object; allowed to be null * if there are no static methods. * * At least one of protoClass and constructorClass should be non-null. * If constructorClass is non-null, the resulting interface object will be * defined on the given global with property name |name|, which must also be * non-null. * * returns the interface prototype object if protoClass is non-null, else it * returns the interface object. */ JSObject* CreateInterfaceObjects(JSContext* cx, JSObject* global, JSObject* receiver, JSObject* protoProto, JSClass* protoClass, JSClass* constructorClass, JSNative constructor, unsigned ctorNargs, JSClass* instanceClass, Prefable* methods, Prefable* properties, Prefable* constants, Prefable* staticMethods, const char* name); template inline bool WrapNewBindingObject(JSContext* cx, JSObject* scope, T* value, JS::Value* vp) { JSObject* obj = value->GetWrapper(); if (obj && js::GetObjectCompartment(obj) == js::GetObjectCompartment(scope)) { *vp = JS::ObjectValue(*obj); return true; } if (!obj) { bool triedToWrap; obj = value->WrapObject(cx, scope, &triedToWrap); if (!obj) { // At this point, obj is null, so just return false. We could // try to communicate triedToWrap to the caller, but in practice // callers seem to be testing JS_IsExceptionPending(cx) to // figure out whether WrapObject() threw instead. return false; } } // When called via XrayWrapper, we end up here while running in the // chrome compartment. But the obj we have would be created in // whatever the content compartment is. So at this point we need to // make sure it's correctly wrapped for the compartment of |scope|. // cx should already be in the compartment of |scope| here. MOZ_ASSERT(js::IsObjectInContextCompartment(scope, cx)); *vp = JS::ObjectValue(*obj); return JS_WrapValue(cx, vp); } // Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr). template