michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "base/basictypes.h" michael@0: michael@0: #include "jsfriendapi.h" michael@0: #include "jswrapper.h" michael@0: michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsJSNPRuntime.h" michael@0: #include "nsNPAPIPlugin.h" michael@0: #include "nsNPAPIPluginInstance.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsDOMJSUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIJSRuntimeService.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "prmem.h" michael@0: #include "nsIContent.h" michael@0: #include "nsPluginInstanceOwner.h" michael@0: #include "nsWrapperCacheInlines.h" michael@0: #include "js/HashTable.h" michael@0: #include "mozilla/HashFunctions.h" michael@0: michael@0: michael@0: #define NPRUNTIME_JSCLASS_NAME "NPObject JS wrapper class" michael@0: michael@0: using namespace mozilla::plugins::parent; michael@0: using namespace mozilla; michael@0: michael@0: #include "mozilla/plugins/PluginScriptableObjectParent.h" michael@0: using mozilla::plugins::PluginScriptableObjectParent; michael@0: using mozilla::plugins::ParentNPObject; michael@0: michael@0: struct JSObjWrapperHasher : public js::DefaultHasher michael@0: { michael@0: typedef nsJSObjWrapperKey Key; michael@0: typedef Key Lookup; michael@0: michael@0: static uint32_t hash(const Lookup &l) { michael@0: return HashGeneric(l.mJSObj, l.mNpp); michael@0: } michael@0: michael@0: static void rekey(Key &k, const Key& newKey) { michael@0: MOZ_ASSERT(k.mNpp == newKey.mNpp); michael@0: k.mJSObj = newKey.mJSObj; michael@0: } michael@0: }; michael@0: michael@0: // Hash of JSObject wrappers that wraps JSObjects as NPObjects. There michael@0: // will be one wrapper per JSObject per plugin instance, i.e. if two michael@0: // plugins access the JSObject x, two wrappers for x will be michael@0: // created. This is needed to be able to properly drop the wrappers michael@0: // when a plugin is torn down in case there's a leak in the plugin (we michael@0: // don't want to leak the world just because a plugin leaks an michael@0: // NPObject). michael@0: typedef js::HashMap JSObjWrapperTable; michael@0: static JSObjWrapperTable sJSObjWrappers; michael@0: michael@0: // Whether it's safe to iterate sJSObjWrappers. Set to true when sJSObjWrappers michael@0: // has been initialized and is not currently being enumerated. michael@0: static bool sJSObjWrappersAccessible = false; michael@0: michael@0: // Hash of NPObject wrappers that wrap NPObjects as JSObjects. michael@0: static PLDHashTable sNPObjWrappers; michael@0: michael@0: // Global wrapper count. This includes JSObject wrappers *and* michael@0: // NPObject wrappers. When this count goes to zero, there are no more michael@0: // wrappers and we can kill off hash tables etc. michael@0: static int32_t sWrapperCount; michael@0: michael@0: // The JSRuntime. Used to unroot JSObjects when no JSContext is michael@0: // reachable. michael@0: static JSRuntime *sJSRuntime; michael@0: michael@0: static nsTArray* sDelayedReleases; michael@0: michael@0: namespace { michael@0: michael@0: inline bool michael@0: NPObjectIsOutOfProcessProxy(NPObject *obj) michael@0: { michael@0: return obj->_class == PluginScriptableObjectParent::GetClass(); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: // Helper class that reports any JS exceptions that were thrown while michael@0: // the plugin executed JS. michael@0: michael@0: class AutoJSExceptionReporter michael@0: { michael@0: public: michael@0: AutoJSExceptionReporter(JSContext *cx) michael@0: : mCx(cx) michael@0: { michael@0: } michael@0: michael@0: ~AutoJSExceptionReporter() michael@0: { michael@0: JS_ReportPendingException(mCx); michael@0: } michael@0: michael@0: protected: michael@0: JSContext *mCx; michael@0: }; michael@0: michael@0: michael@0: NPClass nsJSObjWrapper::sJSObjWrapperNPClass = michael@0: { michael@0: NP_CLASS_STRUCT_VERSION, michael@0: nsJSObjWrapper::NP_Allocate, michael@0: nsJSObjWrapper::NP_Deallocate, michael@0: nsJSObjWrapper::NP_Invalidate, michael@0: nsJSObjWrapper::NP_HasMethod, michael@0: nsJSObjWrapper::NP_Invoke, michael@0: nsJSObjWrapper::NP_InvokeDefault, michael@0: nsJSObjWrapper::NP_HasProperty, michael@0: nsJSObjWrapper::NP_GetProperty, michael@0: nsJSObjWrapper::NP_SetProperty, michael@0: nsJSObjWrapper::NP_RemoveProperty, michael@0: nsJSObjWrapper::NP_Enumerate, michael@0: nsJSObjWrapper::NP_Construct michael@0: }; michael@0: michael@0: static bool michael@0: NPObjWrapper_AddProperty(JSContext *cx, JS::Handle obj, JS::Handle id, JS::MutableHandle vp); michael@0: michael@0: static bool michael@0: NPObjWrapper_DelProperty(JSContext *cx, JS::Handle obj, JS::Handle id, bool *succeeded); michael@0: michael@0: static bool michael@0: NPObjWrapper_SetProperty(JSContext *cx, JS::Handle obj, JS::Handle id, bool strict, michael@0: JS::MutableHandle vp); michael@0: michael@0: static bool michael@0: NPObjWrapper_GetProperty(JSContext *cx, JS::Handle obj, JS::Handle id, JS::MutableHandle vp); michael@0: michael@0: static bool michael@0: NPObjWrapper_newEnumerate(JSContext *cx, JS::Handle obj, JSIterateOp enum_op, michael@0: JS::Value *statep, jsid *idp); michael@0: michael@0: static bool michael@0: NPObjWrapper_NewResolve(JSContext *cx, JS::Handle obj, JS::Handle id, michael@0: JS::MutableHandle objp); michael@0: michael@0: static bool michael@0: NPObjWrapper_Convert(JSContext *cx, JS::Handle obj, JSType type, JS::MutableHandle vp); michael@0: michael@0: static void michael@0: NPObjWrapper_Finalize(JSFreeOp *fop, JSObject *obj); michael@0: michael@0: static bool michael@0: NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp); michael@0: michael@0: static bool michael@0: NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp); michael@0: michael@0: static bool michael@0: CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj, michael@0: JS::Handle id, NPVariant* getPropertyResult, michael@0: JS::MutableHandle vp); michael@0: michael@0: const JSClass sNPObjectJSWrapperClass = michael@0: { michael@0: NPRUNTIME_JSCLASS_NAME, michael@0: JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_NEW_RESOLVE | JSCLASS_NEW_ENUMERATE, michael@0: NPObjWrapper_AddProperty, michael@0: NPObjWrapper_DelProperty, michael@0: NPObjWrapper_GetProperty, michael@0: NPObjWrapper_SetProperty, michael@0: (JSEnumerateOp)NPObjWrapper_newEnumerate, michael@0: (JSResolveOp)NPObjWrapper_NewResolve, michael@0: NPObjWrapper_Convert, michael@0: NPObjWrapper_Finalize, michael@0: NPObjWrapper_Call, michael@0: nullptr, /* hasInstance */ michael@0: NPObjWrapper_Construct michael@0: }; michael@0: michael@0: typedef struct NPObjectMemberPrivate { michael@0: JS::Heap npobjWrapper; michael@0: JS::Heap fieldValue; michael@0: JS::Heap methodName; michael@0: NPP npp; michael@0: } NPObjectMemberPrivate; michael@0: michael@0: static bool michael@0: NPObjectMember_Convert(JSContext *cx, JS::Handle obj, JSType type, JS::MutableHandle vp); michael@0: michael@0: static void michael@0: NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj); michael@0: michael@0: static bool michael@0: NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp); michael@0: michael@0: static void michael@0: NPObjectMember_Trace(JSTracer *trc, JSObject *obj); michael@0: michael@0: static const JSClass sNPObjectMemberClass = michael@0: { michael@0: "NPObject Ambiguous Member class", JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS, michael@0: JS_PropertyStub, JS_DeletePropertyStub, michael@0: JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, michael@0: JS_ResolveStub, NPObjectMember_Convert, michael@0: NPObjectMember_Finalize, NPObjectMember_Call, michael@0: nullptr, nullptr, NPObjectMember_Trace michael@0: }; michael@0: michael@0: static void michael@0: OnWrapperDestroyed(); michael@0: michael@0: static void michael@0: DelayedReleaseGCCallback(JSGCStatus status) michael@0: { michael@0: if (JSGC_END == status) { michael@0: // Take ownership of sDelayedReleases and null it out now. The michael@0: // _releaseobject call below can reenter GC and double-free these objects. michael@0: nsAutoPtr > delayedReleases(sDelayedReleases); michael@0: sDelayedReleases = nullptr; michael@0: michael@0: if (delayedReleases) { michael@0: for (uint32_t i = 0; i < delayedReleases->Length(); ++i) { michael@0: NPObject* obj = (*delayedReleases)[i]; michael@0: if (obj) michael@0: _releaseobject(obj); michael@0: OnWrapperDestroyed(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: static void michael@0: OnWrapperCreated() michael@0: { michael@0: if (sWrapperCount++ == 0) { michael@0: static const char rtsvc_id[] = "@mozilla.org/js/xpc/RuntimeService;1"; michael@0: nsCOMPtr rtsvc(do_GetService(rtsvc_id)); michael@0: if (!rtsvc) michael@0: return; michael@0: michael@0: rtsvc->GetRuntime(&sJSRuntime); michael@0: NS_ASSERTION(sJSRuntime != nullptr, "no JSRuntime?!"); michael@0: michael@0: // Register our GC callback to perform delayed destruction of finalized michael@0: // NPObjects. Leave this callback around and don't ever unregister it. michael@0: rtsvc->RegisterGCCallback(DelayedReleaseGCCallback); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: OnWrapperDestroyed() michael@0: { michael@0: NS_ASSERTION(sWrapperCount, "Whaaa, unbalanced created/destroyed calls!"); michael@0: michael@0: if (--sWrapperCount == 0) { michael@0: if (sJSObjWrappersAccessible) { michael@0: MOZ_ASSERT(sJSObjWrappers.count() == 0); michael@0: michael@0: // No more wrappers, and our hash was initialized. Finish the michael@0: // hash to prevent leaking it. michael@0: sJSObjWrappers.finish(); michael@0: sJSObjWrappersAccessible = false; michael@0: } michael@0: michael@0: if (sNPObjWrappers.ops) { michael@0: MOZ_ASSERT(sNPObjWrappers.entryCount == 0); michael@0: michael@0: // No more wrappers, and our hash was initialized. Finish the michael@0: // hash to prevent leaking it. michael@0: PL_DHashTableFinish(&sNPObjWrappers); michael@0: michael@0: sNPObjWrappers.ops = nullptr; michael@0: } michael@0: michael@0: // No more need for this. michael@0: sJSRuntime = nullptr; michael@0: } michael@0: } michael@0: michael@0: namespace mozilla { michael@0: namespace plugins { michael@0: namespace parent { michael@0: michael@0: JSContext * michael@0: GetJSContext(NPP npp) michael@0: { michael@0: NS_ENSURE_TRUE(npp, nullptr); michael@0: michael@0: nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)npp->ndata; michael@0: NS_ENSURE_TRUE(inst, nullptr); michael@0: michael@0: nsRefPtr owner = inst->GetOwner(); michael@0: NS_ENSURE_TRUE(owner, nullptr); michael@0: michael@0: nsCOMPtr doc; michael@0: owner->GetDocument(getter_AddRefs(doc)); michael@0: NS_ENSURE_TRUE(doc, nullptr); michael@0: michael@0: nsCOMPtr documentContainer = doc->GetContainer(); michael@0: nsCOMPtr sgo(do_GetInterface(documentContainer)); michael@0: NS_ENSURE_TRUE(sgo, nullptr); michael@0: michael@0: nsIScriptContext *scx = sgo->GetContext(); michael@0: NS_ENSURE_TRUE(scx, nullptr); michael@0: michael@0: return scx->GetNativeContext(); michael@0: } michael@0: michael@0: } michael@0: } michael@0: } michael@0: michael@0: static NPP michael@0: LookupNPP(NPObject *npobj); michael@0: michael@0: michael@0: static JS::Value michael@0: NPVariantToJSVal(NPP npp, JSContext *cx, const NPVariant *variant) michael@0: { michael@0: switch (variant->type) { michael@0: case NPVariantType_Void : michael@0: return JSVAL_VOID; michael@0: case NPVariantType_Null : michael@0: return JSVAL_NULL; michael@0: case NPVariantType_Bool : michael@0: return BOOLEAN_TO_JSVAL(NPVARIANT_TO_BOOLEAN(*variant)); michael@0: case NPVariantType_Int32 : michael@0: { michael@0: // Don't use INT_TO_JSVAL directly to prevent bugs when dealing michael@0: // with ints larger than what fits in a integer JS::Value. michael@0: return ::JS_NumberValue(NPVARIANT_TO_INT32(*variant)); michael@0: } michael@0: case NPVariantType_Double : michael@0: { michael@0: return ::JS_NumberValue(NPVARIANT_TO_DOUBLE(*variant)); michael@0: } michael@0: case NPVariantType_String : michael@0: { michael@0: const NPString *s = &NPVARIANT_TO_STRING(*variant); michael@0: NS_ConvertUTF8toUTF16 utf16String(s->UTF8Characters, s->UTF8Length); michael@0: michael@0: JSString *str = michael@0: ::JS_NewUCStringCopyN(cx, utf16String.get(), utf16String.Length()); michael@0: michael@0: if (str) { michael@0: return STRING_TO_JSVAL(str); michael@0: } michael@0: michael@0: break; michael@0: } michael@0: case NPVariantType_Object: michael@0: { michael@0: if (npp) { michael@0: JSObject *obj = michael@0: nsNPObjWrapper::GetNewOrUsed(npp, cx, NPVARIANT_TO_OBJECT(*variant)); michael@0: michael@0: if (obj) { michael@0: return OBJECT_TO_JSVAL(obj); michael@0: } michael@0: } michael@0: michael@0: NS_ERROR("Error wrapping NPObject!"); michael@0: michael@0: break; michael@0: } michael@0: default: michael@0: NS_ERROR("Unknown NPVariant type!"); michael@0: } michael@0: michael@0: NS_ERROR("Unable to convert NPVariant to jsval!"); michael@0: michael@0: return JSVAL_VOID; michael@0: } michael@0: michael@0: bool michael@0: JSValToNPVariant(NPP npp, JSContext *cx, JS::Value val, NPVariant *variant) michael@0: { michael@0: NS_ASSERTION(npp, "Must have an NPP to wrap a jsval!"); michael@0: michael@0: if (JSVAL_IS_PRIMITIVE(val)) { michael@0: if (val == JSVAL_VOID) { michael@0: VOID_TO_NPVARIANT(*variant); michael@0: } else if (JSVAL_IS_NULL(val)) { michael@0: NULL_TO_NPVARIANT(*variant); michael@0: } else if (JSVAL_IS_BOOLEAN(val)) { michael@0: BOOLEAN_TO_NPVARIANT(JSVAL_TO_BOOLEAN(val), *variant); michael@0: } else if (JSVAL_IS_INT(val)) { michael@0: INT32_TO_NPVARIANT(JSVAL_TO_INT(val), *variant); michael@0: } else if (JSVAL_IS_DOUBLE(val)) { michael@0: double d = JSVAL_TO_DOUBLE(val); michael@0: int i; michael@0: if (JS_DoubleIsInt32(d, &i)) { michael@0: INT32_TO_NPVARIANT(i, *variant); michael@0: } else { michael@0: DOUBLE_TO_NPVARIANT(d, *variant); michael@0: } michael@0: } else if (JSVAL_IS_STRING(val)) { michael@0: JSString *jsstr = JSVAL_TO_STRING(val); michael@0: size_t length; michael@0: const jschar *chars = ::JS_GetStringCharsZAndLength(cx, jsstr, &length); michael@0: if (!chars) { michael@0: return false; michael@0: } michael@0: michael@0: nsDependentString str(chars, length); michael@0: michael@0: uint32_t len; michael@0: char *p = ToNewUTF8String(str, &len); michael@0: michael@0: if (!p) { michael@0: return false; michael@0: } michael@0: michael@0: STRINGN_TO_NPVARIANT(p, len, *variant); michael@0: } else { michael@0: NS_ERROR("Unknown primitive type!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // The reflected plugin object may be in another compartment if the plugin michael@0: // element has since been adopted into a new document. We don't bother michael@0: // transplanting the plugin objects, and just do a unwrap with security michael@0: // checks if we encounter one of them as an argument. If the unwrap fails, michael@0: // we run with the original wrapped object, since sometimes there are michael@0: // legitimate cases where a security wrapper ends up here (for example, michael@0: // Location objects, which are _always_ behind security wrappers). michael@0: JS::Rooted obj(cx, val.toObjectOrNull()); michael@0: obj = js::CheckedUnwrap(obj); michael@0: if (!obj) { michael@0: obj = JSVAL_TO_OBJECT(val); michael@0: } michael@0: michael@0: NPObject *npobj = nsJSObjWrapper::GetNewOrUsed(npp, cx, obj); michael@0: if (!npobj) { michael@0: return false; michael@0: } michael@0: michael@0: // Pass over ownership of npobj to *variant michael@0: OBJECT_TO_NPVARIANT(npobj, *variant); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: ThrowJSException(JSContext *cx, const char *message) michael@0: { michael@0: const char *ex = PeekException(); michael@0: michael@0: if (ex) { michael@0: nsAutoString ucex; michael@0: michael@0: if (message) { michael@0: AppendASCIItoUTF16(message, ucex); michael@0: michael@0: AppendASCIItoUTF16(" [plugin exception: ", ucex); michael@0: } michael@0: michael@0: AppendUTF8toUTF16(ex, ucex); michael@0: michael@0: if (message) { michael@0: AppendASCIItoUTF16("].", ucex); michael@0: } michael@0: michael@0: JSString *str = ::JS_NewUCStringCopyN(cx, ucex.get(), ucex.Length()); michael@0: michael@0: if (str) { michael@0: JS::Rooted exn(cx, JS::StringValue(str)); michael@0: ::JS_SetPendingException(cx, exn); michael@0: } michael@0: michael@0: PopException(); michael@0: } else { michael@0: ::JS_ReportError(cx, message); michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: ReportExceptionIfPending(JSContext *cx) michael@0: { michael@0: const char *ex = PeekException(); michael@0: michael@0: if (!ex) { michael@0: return true; michael@0: } michael@0: michael@0: ThrowJSException(cx, nullptr); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsJSObjWrapper::nsJSObjWrapper(NPP npp) michael@0: : mJSObj(GetJSContext(npp), nullptr), mNpp(npp) michael@0: { michael@0: MOZ_COUNT_CTOR(nsJSObjWrapper); michael@0: OnWrapperCreated(); michael@0: } michael@0: michael@0: nsJSObjWrapper::~nsJSObjWrapper() michael@0: { michael@0: MOZ_COUNT_DTOR(nsJSObjWrapper); michael@0: michael@0: // Invalidate first, since it relies on sJSRuntime and sJSObjWrappers. michael@0: NP_Invalidate(this); michael@0: michael@0: OnWrapperDestroyed(); michael@0: } michael@0: michael@0: // static michael@0: NPObject * michael@0: nsJSObjWrapper::NP_Allocate(NPP npp, NPClass *aClass) michael@0: { michael@0: NS_ASSERTION(aClass == &sJSObjWrapperNPClass, michael@0: "Huh, wrong class passed to NP_Allocate()!!!"); michael@0: michael@0: return new nsJSObjWrapper(npp); michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsJSObjWrapper::NP_Deallocate(NPObject *npobj) michael@0: { michael@0: // nsJSObjWrapper::~nsJSObjWrapper() will call NP_Invalidate(). michael@0: delete (nsJSObjWrapper *)npobj; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsJSObjWrapper::NP_Invalidate(NPObject *npobj) michael@0: { michael@0: nsJSObjWrapper *jsnpobj = (nsJSObjWrapper *)npobj; michael@0: michael@0: if (jsnpobj && jsnpobj->mJSObj) { michael@0: michael@0: if (sJSObjWrappersAccessible) { michael@0: // Remove the wrapper from the hash michael@0: nsJSObjWrapperKey key(jsnpobj->mJSObj, jsnpobj->mNpp); michael@0: JSObjWrapperTable::Ptr ptr = sJSObjWrappers.lookup(key); michael@0: MOZ_ASSERT(ptr.found()); michael@0: sJSObjWrappers.remove(ptr); michael@0: } michael@0: michael@0: // Forget our reference to the JSObject. michael@0: jsnpobj->mJSObj = nullptr; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: GetProperty(JSContext *cx, JSObject *objArg, NPIdentifier npid, JS::MutableHandle rval) michael@0: { michael@0: NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), michael@0: "id must be either string or int!\n"); michael@0: JS::Rooted obj(cx, objArg); michael@0: JS::Rooted id(cx, NPIdentifierToJSId(npid)); michael@0: return ::JS_GetPropertyById(cx, obj, id, rval); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsJSObjWrapper::NP_HasMethod(NPObject *npobj, NPIdentifier id) michael@0: { michael@0: NPP npp = NPPStack::Peek(); michael@0: JSContext *cx = GetJSContext(npp); michael@0: michael@0: if (!cx) { michael@0: return false; michael@0: } michael@0: michael@0: if (!npobj) { michael@0: ThrowJSException(cx, michael@0: "Null npobj in nsJSObjWrapper::NP_HasMethod!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; michael@0: michael@0: nsCxPusher pusher; michael@0: pusher.Push(cx); michael@0: JSAutoCompartment ac(cx, npjsobj->mJSObj); michael@0: michael@0: AutoJSExceptionReporter reporter(cx); michael@0: michael@0: JS::Rooted v(cx); michael@0: bool ok = GetProperty(cx, npjsobj->mJSObj, id, &v); michael@0: michael@0: return ok && !JSVAL_IS_PRIMITIVE(v) && michael@0: ::JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(v)); michael@0: } michael@0: michael@0: static bool michael@0: doInvoke(NPObject *npobj, NPIdentifier method, const NPVariant *args, michael@0: uint32_t argCount, bool ctorCall, NPVariant *result) michael@0: { michael@0: NPP npp = NPPStack::Peek(); michael@0: JSContext *cx = GetJSContext(npp); michael@0: michael@0: if (!cx) { michael@0: return false; michael@0: } michael@0: michael@0: if (!npobj || !result) { michael@0: ThrowJSException(cx, "Null npobj, or result in doInvoke!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Initialize *result michael@0: VOID_TO_NPVARIANT(*result); michael@0: michael@0: nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; michael@0: michael@0: nsCxPusher pusher; michael@0: pusher.Push(cx); michael@0: JS::Rooted jsobj(cx, npjsobj->mJSObj); michael@0: JSAutoCompartment ac(cx, jsobj); michael@0: JS::Rooted fv(cx); michael@0: michael@0: AutoJSExceptionReporter reporter(cx); michael@0: michael@0: if (method != NPIdentifier_VOID) { michael@0: if (!GetProperty(cx, jsobj, method, &fv) || michael@0: ::JS_TypeOfValue(cx, fv) != JSTYPE_FUNCTION) { michael@0: return false; michael@0: } michael@0: } else { michael@0: fv.setObject(*jsobj); michael@0: } michael@0: michael@0: // Convert args michael@0: JS::AutoValueVector jsargs(cx); michael@0: if (!jsargs.reserve(argCount)) { michael@0: ::JS_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: for (uint32_t i = 0; i < argCount; ++i) { michael@0: jsargs.infallibleAppend(NPVariantToJSVal(npp, cx, args + i)); michael@0: } michael@0: michael@0: JS::Rooted v(cx); michael@0: bool ok = false; michael@0: michael@0: if (ctorCall) { michael@0: JSObject *newObj = michael@0: ::JS_New(cx, jsobj, jsargs); michael@0: michael@0: if (newObj) { michael@0: v.setObject(*newObj); michael@0: ok = true; michael@0: } michael@0: } else { michael@0: ok = ::JS_CallFunctionValue(cx, jsobj, fv, jsargs, &v); michael@0: } michael@0: michael@0: if (ok) michael@0: ok = JSValToNPVariant(npp, cx, v, result); michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsJSObjWrapper::NP_Invoke(NPObject *npobj, NPIdentifier method, michael@0: const NPVariant *args, uint32_t argCount, michael@0: NPVariant *result) michael@0: { michael@0: if (method == NPIdentifier_VOID) { michael@0: return false; michael@0: } michael@0: michael@0: return doInvoke(npobj, method, args, argCount, false, result); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsJSObjWrapper::NP_InvokeDefault(NPObject *npobj, const NPVariant *args, michael@0: uint32_t argCount, NPVariant *result) michael@0: { michael@0: return doInvoke(npobj, NPIdentifier_VOID, args, argCount, false, michael@0: result); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsJSObjWrapper::NP_HasProperty(NPObject *npobj, NPIdentifier npid) michael@0: { michael@0: NPP npp = NPPStack::Peek(); michael@0: JSContext *cx = GetJSContext(npp); michael@0: michael@0: if (!cx) { michael@0: return false; michael@0: } michael@0: michael@0: if (!npobj) { michael@0: ThrowJSException(cx, michael@0: "Null npobj in nsJSObjWrapper::NP_HasProperty!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; michael@0: bool found, ok = false; michael@0: michael@0: nsCxPusher pusher; michael@0: pusher.Push(cx); michael@0: AutoJSExceptionReporter reporter(cx); michael@0: JS::Rooted jsobj(cx, npjsobj->mJSObj); michael@0: JSAutoCompartment ac(cx, jsobj); michael@0: michael@0: NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), michael@0: "id must be either string or int!\n"); michael@0: JS::Rooted id(cx, NPIdentifierToJSId(npid)); michael@0: ok = ::JS_HasPropertyById(cx, jsobj, id, &found); michael@0: return ok && found; michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsJSObjWrapper::NP_GetProperty(NPObject *npobj, NPIdentifier id, michael@0: NPVariant *result) michael@0: { michael@0: NPP npp = NPPStack::Peek(); michael@0: JSContext *cx = GetJSContext(npp); michael@0: michael@0: if (!cx) { michael@0: return false; michael@0: } michael@0: michael@0: if (!npobj) { michael@0: ThrowJSException(cx, michael@0: "Null npobj in nsJSObjWrapper::NP_GetProperty!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; michael@0: michael@0: nsCxPusher pusher; michael@0: pusher.Push(cx); michael@0: AutoJSExceptionReporter reporter(cx); michael@0: JSAutoCompartment ac(cx, npjsobj->mJSObj); michael@0: michael@0: JS::Rooted v(cx); michael@0: return (GetProperty(cx, npjsobj->mJSObj, id, &v) && michael@0: JSValToNPVariant(npp, cx, v, result)); michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsJSObjWrapper::NP_SetProperty(NPObject *npobj, NPIdentifier npid, michael@0: const NPVariant *value) michael@0: { michael@0: NPP npp = NPPStack::Peek(); michael@0: JSContext *cx = GetJSContext(npp); michael@0: michael@0: if (!cx) { michael@0: return false; michael@0: } michael@0: michael@0: if (!npobj) { michael@0: ThrowJSException(cx, michael@0: "Null npobj in nsJSObjWrapper::NP_SetProperty!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; michael@0: bool ok = false; michael@0: michael@0: nsCxPusher pusher; michael@0: pusher.Push(cx); michael@0: AutoJSExceptionReporter reporter(cx); michael@0: JS::Rooted jsObj(cx, npjsobj->mJSObj); michael@0: JSAutoCompartment ac(cx, jsObj); michael@0: michael@0: JS::Rooted v(cx, NPVariantToJSVal(npp, cx, value)); michael@0: michael@0: NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), michael@0: "id must be either string or int!\n"); michael@0: JS::Rooted id(cx, NPIdentifierToJSId(npid)); michael@0: ok = ::JS_SetPropertyById(cx, jsObj, id, v); michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: // static michael@0: bool michael@0: nsJSObjWrapper::NP_RemoveProperty(NPObject *npobj, NPIdentifier npid) michael@0: { michael@0: NPP npp = NPPStack::Peek(); michael@0: JSContext *cx = GetJSContext(npp); michael@0: michael@0: if (!cx) { michael@0: return false; michael@0: } michael@0: michael@0: if (!npobj) { michael@0: ThrowJSException(cx, michael@0: "Null npobj in nsJSObjWrapper::NP_RemoveProperty!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; michael@0: bool ok = false; michael@0: michael@0: nsCxPusher pusher; michael@0: pusher.Push(cx); michael@0: AutoJSExceptionReporter reporter(cx); michael@0: bool deleted = false; michael@0: JS::Rooted obj(cx, npjsobj->mJSObj); michael@0: JSAutoCompartment ac(cx, obj); michael@0: michael@0: NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), michael@0: "id must be either string or int!\n"); michael@0: JS::Rooted id(cx, NPIdentifierToJSId(npid)); michael@0: ok = ::JS_DeletePropertyById2(cx, obj, id, &deleted); michael@0: if (ok && deleted) { michael@0: // FIXME: See bug 425823, we shouldn't need to do this, and once michael@0: // that bug is fixed we can remove this code. michael@0: michael@0: bool hasProp; michael@0: ok = ::JS_HasPropertyById(cx, obj, id, &hasProp); michael@0: michael@0: if (ok && hasProp) { michael@0: // The property might have been deleted, but it got michael@0: // re-resolved, so no, it's not really deleted. michael@0: michael@0: deleted = false; michael@0: } michael@0: } michael@0: michael@0: return ok && deleted; michael@0: } michael@0: michael@0: //static michael@0: bool michael@0: nsJSObjWrapper::NP_Enumerate(NPObject *npobj, NPIdentifier **idarray, michael@0: uint32_t *count) michael@0: { michael@0: NPP npp = NPPStack::Peek(); michael@0: JSContext *cx = GetJSContext(npp); michael@0: michael@0: *idarray = 0; michael@0: *count = 0; michael@0: michael@0: if (!cx) { michael@0: return false; michael@0: } michael@0: michael@0: if (!npobj) { michael@0: ThrowJSException(cx, michael@0: "Null npobj in nsJSObjWrapper::NP_Enumerate!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; michael@0: michael@0: nsCxPusher pusher; michael@0: pusher.Push(cx); michael@0: AutoJSExceptionReporter reporter(cx); michael@0: JS::Rooted jsobj(cx, npjsobj->mJSObj); michael@0: JSAutoCompartment ac(cx, jsobj); michael@0: michael@0: JS::AutoIdArray ida(cx, JS_Enumerate(cx, jsobj)); michael@0: if (!ida) { michael@0: return false; michael@0: } michael@0: michael@0: *count = ida.length(); michael@0: *idarray = (NPIdentifier *)PR_Malloc(*count * sizeof(NPIdentifier)); michael@0: if (!*idarray) { michael@0: ThrowJSException(cx, "Memory allocation failed for NPIdentifier!"); michael@0: return false; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < *count; i++) { michael@0: JS::Rooted v(cx); michael@0: if (!JS_IdToValue(cx, ida[i], &v)) { michael@0: PR_Free(*idarray); michael@0: return false; michael@0: } michael@0: michael@0: NPIdentifier id; michael@0: if (v.isString()) { michael@0: JS::Rooted str(cx, v.toString()); michael@0: str = JS_InternJSString(cx, str); michael@0: if (!str) { michael@0: PR_Free(*idarray); michael@0: return false; michael@0: } michael@0: id = StringToNPIdentifier(cx, str); michael@0: } else { michael@0: NS_ASSERTION(JSVAL_IS_INT(v), michael@0: "The element in ida must be either string or int!\n"); michael@0: id = IntToNPIdentifier(JSVAL_TO_INT(v)); michael@0: } michael@0: michael@0: (*idarray)[i] = id; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: //static michael@0: bool michael@0: nsJSObjWrapper::NP_Construct(NPObject *npobj, const NPVariant *args, michael@0: uint32_t argCount, NPVariant *result) michael@0: { michael@0: return doInvoke(npobj, NPIdentifier_VOID, args, argCount, true, result); michael@0: } michael@0: michael@0: michael@0: michael@0: /* michael@0: * This function is called during minor GCs for each key in the sJSObjWrappers michael@0: * table that has been moved. michael@0: * michael@0: * Note that the wrapper may be dead at this point, and even the table may have michael@0: * been finalized if all wrappers have died. michael@0: */ michael@0: static void michael@0: JSObjWrapperKeyMarkCallback(JSTracer *trc, JSObject *obj, void *data) { michael@0: NPP npp = static_cast(data); michael@0: MOZ_ASSERT(sJSObjWrappersAccessible); michael@0: if (!sJSObjWrappers.initialized()) michael@0: return; michael@0: michael@0: JSObject *prior = obj; michael@0: nsJSObjWrapperKey oldKey(prior, npp); michael@0: JSObjWrapperTable::Ptr p = sJSObjWrappers.lookup(oldKey); michael@0: if (!p) michael@0: return; michael@0: michael@0: JS_CallObjectTracer(trc, &obj, "sJSObjWrappers key object"); michael@0: nsJSObjWrapperKey newKey(obj, npp); michael@0: sJSObjWrappers.rekeyIfMoved(oldKey, newKey); michael@0: } michael@0: michael@0: // Look up or create an NPObject that wraps the JSObject obj. michael@0: michael@0: // static michael@0: NPObject * michael@0: nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, JS::Handle obj) michael@0: { michael@0: if (!npp) { michael@0: NS_ERROR("Null NPP passed to nsJSObjWrapper::GetNewOrUsed()!"); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!cx) { michael@0: cx = GetJSContext(npp); michael@0: michael@0: if (!cx) { michael@0: NS_ERROR("Unable to find a JSContext in nsJSObjWrapper::GetNewOrUsed()!"); michael@0: michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: // No need to enter the right compartment here as we only get the michael@0: // class and private from the JSObject, neither of which cares about michael@0: // compartments. michael@0: michael@0: const JSClass *clazz = JS_GetClass(obj); michael@0: michael@0: if (clazz == &sNPObjectJSWrapperClass) { michael@0: // obj is one of our own, its private data is the NPObject we're michael@0: // looking for. michael@0: michael@0: NPObject *npobj = (NPObject *)::JS_GetPrivate(obj); michael@0: michael@0: // If the private is null, that means that the object has already been torn michael@0: // down, possible because the owning plugin was destroyed (there can be michael@0: // multiple plugins, so the fact that it was destroyed does not prevent one michael@0: // of its dead JS objects from being passed to another plugin). There's not michael@0: // much use in wrapping such a dead object, so we just return null, causing michael@0: // us to throw. michael@0: if (!npobj) michael@0: return nullptr; michael@0: michael@0: if (LookupNPP(npobj) == npp) michael@0: return _retainobject(npobj); michael@0: } michael@0: michael@0: if (!sJSObjWrappers.initialized()) { michael@0: // No hash yet (or any more), initialize it. michael@0: if (!sJSObjWrappers.init(16)) { michael@0: NS_ERROR("Error initializing PLDHashTable!"); michael@0: michael@0: return nullptr; michael@0: } michael@0: sJSObjWrappersAccessible = true; michael@0: } michael@0: MOZ_ASSERT(sJSObjWrappersAccessible); michael@0: michael@0: JSObjWrapperTable::Ptr p = sJSObjWrappers.lookupForAdd(nsJSObjWrapperKey(obj, npp)); michael@0: if (p) { michael@0: MOZ_ASSERT(p->value()); michael@0: // Found a live nsJSObjWrapper, return it. michael@0: michael@0: return _retainobject(p->value()); michael@0: } michael@0: michael@0: // No existing nsJSObjWrapper, create one. michael@0: michael@0: nsJSObjWrapper *wrapper = michael@0: (nsJSObjWrapper *)_createobject(npp, &sJSObjWrapperNPClass); michael@0: michael@0: if (!wrapper) { michael@0: // Out of memory, entry not yet added to table. michael@0: return nullptr; michael@0: } michael@0: michael@0: // Assign mJSObj, rooting the JSObject. Its lifetime is now tied to that of michael@0: // the NPObject. michael@0: wrapper->mJSObj = obj; michael@0: michael@0: nsJSObjWrapperKey key(obj, npp); michael@0: if (!sJSObjWrappers.putNew(key, wrapper)) { michael@0: // Out of memory, free the wrapper we created. michael@0: _releaseobject(wrapper); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Add postbarrier for the hashtable key michael@0: JS_StoreObjectPostBarrierCallback(cx, JSObjWrapperKeyMarkCallback, obj, wrapper->mNpp); michael@0: michael@0: return wrapper; michael@0: } michael@0: michael@0: // Climb the prototype chain, unwrapping as necessary until we find an NP object michael@0: // wrapper. michael@0: // michael@0: // Because this function unwraps, its return value must be wrapped for the cx michael@0: // compartment for callers that plan to hold onto the result or do anything michael@0: // substantial with it. michael@0: static JSObject * michael@0: GetNPObjectWrapper(JSContext *cx, JSObject *aObj, bool wrapResult = true) michael@0: { michael@0: JS::Rooted obj(cx, aObj); michael@0: while (obj && (obj = js::CheckedUnwrap(obj))) { michael@0: if (JS_GetClass(obj) == &sNPObjectJSWrapperClass) { michael@0: if (wrapResult && !JS_WrapObject(cx, &obj)) { michael@0: return nullptr; michael@0: } michael@0: return obj; michael@0: } michael@0: if (!::JS_GetPrototype(cx, obj, &obj)) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: static NPObject * michael@0: GetNPObject(JSContext *cx, JSObject *obj) michael@0: { michael@0: obj = GetNPObjectWrapper(cx, obj, /* wrapResult = */ false); michael@0: if (!obj) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return (NPObject *)::JS_GetPrivate(obj); michael@0: } michael@0: michael@0: michael@0: // Does not actually add a property because this is always followed by a michael@0: // SetProperty call. michael@0: static bool michael@0: NPObjWrapper_AddProperty(JSContext *cx, JS::Handle obj, JS::Handle id, JS::MutableHandle vp) michael@0: { michael@0: NPObject *npobj = GetNPObject(cx, obj); michael@0: michael@0: if (!npobj || !npobj->_class || !npobj->_class->hasProperty || michael@0: !npobj->_class->hasMethod) { michael@0: ThrowJSException(cx, "Bad NPObject as private data!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: if (NPObjectIsOutOfProcessProxy(npobj)) { michael@0: return true; michael@0: } michael@0: michael@0: PluginDestructionGuard pdg(LookupNPP(npobj)); michael@0: michael@0: NPIdentifier identifier = JSIdToNPIdentifier(id); michael@0: bool hasProperty = npobj->_class->hasProperty(npobj, identifier); michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: michael@0: if (hasProperty) michael@0: return true; michael@0: michael@0: // We must permit methods here since JS_DefineUCFunction() will add michael@0: // the function as a property michael@0: bool hasMethod = npobj->_class->hasMethod(npobj, identifier); michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: michael@0: if (!hasMethod) { michael@0: ThrowJSException(cx, "Trying to add unsupported property on NPObject!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: NPObjWrapper_DelProperty(JSContext *cx, JS::Handle obj, JS::Handle id, bool *succeeded) michael@0: { michael@0: NPObject *npobj = GetNPObject(cx, obj); michael@0: michael@0: if (!npobj || !npobj->_class || !npobj->_class->hasProperty || michael@0: !npobj->_class->removeProperty) { michael@0: ThrowJSException(cx, "Bad NPObject as private data!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: PluginDestructionGuard pdg(LookupNPP(npobj)); michael@0: michael@0: NPIdentifier identifier = JSIdToNPIdentifier(id); michael@0: michael@0: if (!NPObjectIsOutOfProcessProxy(npobj)) { michael@0: bool hasProperty = npobj->_class->hasProperty(npobj, identifier); michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: michael@0: if (!hasProperty) { michael@0: *succeeded = true; michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: if (!npobj->_class->removeProperty(npobj, identifier)) michael@0: *succeeded = false; michael@0: michael@0: return ReportExceptionIfPending(cx); michael@0: } michael@0: michael@0: static bool michael@0: NPObjWrapper_SetProperty(JSContext *cx, JS::Handle obj, JS::Handle id, bool strict, michael@0: JS::MutableHandle vp) michael@0: { michael@0: NPObject *npobj = GetNPObject(cx, obj); michael@0: michael@0: if (!npobj || !npobj->_class || !npobj->_class->hasProperty || michael@0: !npobj->_class->setProperty) { michael@0: ThrowJSException(cx, "Bad NPObject as private data!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Find out what plugin (NPP) is the owner of the object we're michael@0: // manipulating, and make it own any JSObject wrappers created here. michael@0: NPP npp = LookupNPP(npobj); michael@0: michael@0: if (!npp) { michael@0: ThrowJSException(cx, "No NPP found for NPObject!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: PluginDestructionGuard pdg(npp); michael@0: michael@0: NPIdentifier identifier = JSIdToNPIdentifier(id); michael@0: michael@0: if (!NPObjectIsOutOfProcessProxy(npobj)) { michael@0: bool hasProperty = npobj->_class->hasProperty(npobj, identifier); michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: michael@0: if (!hasProperty) { michael@0: ThrowJSException(cx, "Trying to set unsupported property on NPObject!"); michael@0: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: NPVariant npv; michael@0: if (!JSValToNPVariant(npp, cx, vp, &npv)) { michael@0: ThrowJSException(cx, "Error converting jsval to NPVariant!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: bool ok = npobj->_class->setProperty(npobj, identifier, &npv); michael@0: _releasevariantvalue(&npv); // Release the variant michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: michael@0: if (!ok) { michael@0: ThrowJSException(cx, "Error setting property on NPObject!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: NPObjWrapper_GetProperty(JSContext *cx, JS::Handle obj, JS::Handle id, JS::MutableHandle vp) michael@0: { michael@0: NPObject *npobj = GetNPObject(cx, obj); michael@0: michael@0: if (!npobj || !npobj->_class || !npobj->_class->hasProperty || michael@0: !npobj->_class->hasMethod || !npobj->_class->getProperty) { michael@0: ThrowJSException(cx, "Bad NPObject as private data!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Find out what plugin (NPP) is the owner of the object we're michael@0: // manipulating, and make it own any JSObject wrappers created here. michael@0: NPP npp = LookupNPP(npobj); michael@0: if (!npp) { michael@0: ThrowJSException(cx, "No NPP found for NPObject!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: PluginDestructionGuard pdg(npp); michael@0: michael@0: bool hasProperty, hasMethod; michael@0: michael@0: NPVariant npv; michael@0: VOID_TO_NPVARIANT(npv); michael@0: michael@0: NPIdentifier identifier = JSIdToNPIdentifier(id); michael@0: michael@0: if (NPObjectIsOutOfProcessProxy(npobj)) { michael@0: PluginScriptableObjectParent* actor = michael@0: static_cast(npobj)->parent; michael@0: michael@0: // actor may be null if the plugin crashed. michael@0: if (!actor) michael@0: return false; michael@0: michael@0: bool success = actor->GetPropertyHelper(identifier, &hasProperty, michael@0: &hasMethod, &npv); michael@0: if (!ReportExceptionIfPending(cx)) { michael@0: if (success) michael@0: _releasevariantvalue(&npv); michael@0: return false; michael@0: } michael@0: michael@0: if (success) { michael@0: // We return NPObject Member class here to support ambiguous members. michael@0: if (hasProperty && hasMethod) michael@0: return CreateNPObjectMember(npp, cx, obj, npobj, id, &npv, vp); michael@0: michael@0: if (hasProperty) { michael@0: vp.set(NPVariantToJSVal(npp, cx, &npv)); michael@0: _releasevariantvalue(&npv); michael@0: michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: hasProperty = npobj->_class->hasProperty(npobj, identifier); michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: michael@0: hasMethod = npobj->_class->hasMethod(npobj, identifier); michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: michael@0: // We return NPObject Member class here to support ambiguous members. michael@0: if (hasProperty && hasMethod) michael@0: return CreateNPObjectMember(npp, cx, obj, npobj, id, nullptr, vp); michael@0: michael@0: if (hasProperty) { michael@0: if (npobj->_class->getProperty(npobj, identifier, &npv)) michael@0: vp.set(NPVariantToJSVal(npp, cx, &npv)); michael@0: michael@0: _releasevariantvalue(&npv); michael@0: michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: CallNPMethodInternal(JSContext *cx, JS::Handle obj, unsigned argc, michael@0: JS::Value *argv, JS::Value *rval, bool ctorCall) michael@0: { michael@0: NPObject *npobj = GetNPObject(cx, obj); michael@0: michael@0: if (!npobj || !npobj->_class) { michael@0: ThrowJSException(cx, "Bad NPObject as private data!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Find out what plugin (NPP) is the owner of the object we're michael@0: // manipulating, and make it own any JSObject wrappers created here. michael@0: NPP npp = LookupNPP(npobj); michael@0: michael@0: if (!npp) { michael@0: ThrowJSException(cx, "Error finding NPP for NPObject!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: PluginDestructionGuard pdg(npp); michael@0: michael@0: NPVariant npargs_buf[8]; michael@0: NPVariant *npargs = npargs_buf; michael@0: michael@0: if (argc > (sizeof(npargs_buf) / sizeof(NPVariant))) { michael@0: // Our stack buffer isn't large enough to hold all arguments, michael@0: // malloc a buffer. michael@0: npargs = (NPVariant *)PR_Malloc(argc * sizeof(NPVariant)); michael@0: michael@0: if (!npargs) { michael@0: ThrowJSException(cx, "Out of memory!"); michael@0: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Convert arguments michael@0: uint32_t i; michael@0: for (i = 0; i < argc; ++i) { michael@0: if (!JSValToNPVariant(npp, cx, argv[i], npargs + i)) { michael@0: ThrowJSException(cx, "Error converting jsvals to NPVariants!"); michael@0: michael@0: if (npargs != npargs_buf) { michael@0: PR_Free(npargs); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: NPVariant v; michael@0: VOID_TO_NPVARIANT(v); michael@0: michael@0: JSObject *funobj = JSVAL_TO_OBJECT(argv[-2]); michael@0: bool ok; michael@0: const char *msg = "Error calling method on NPObject!"; michael@0: michael@0: if (ctorCall) { michael@0: // construct a new NPObject based on the NPClass in npobj. Fail if michael@0: // no construct method is available. michael@0: michael@0: if (NP_CLASS_STRUCT_VERSION_HAS_CTOR(npobj->_class) && michael@0: npobj->_class->construct) { michael@0: ok = npobj->_class->construct(npobj, npargs, argc, &v); michael@0: } else { michael@0: ok = false; michael@0: michael@0: msg = "Attempt to construct object from class with no constructor."; michael@0: } michael@0: } else if (funobj != obj) { michael@0: // A obj.function() style call is made, get the method name from michael@0: // the function object. michael@0: michael@0: if (npobj->_class->invoke) { michael@0: JSFunction *fun = ::JS_GetObjectFunction(funobj); michael@0: JS::Rooted funId(cx, ::JS_GetFunctionId(fun)); michael@0: JSString *name = ::JS_InternJSString(cx, funId); michael@0: NPIdentifier id = StringToNPIdentifier(cx, name); michael@0: michael@0: ok = npobj->_class->invoke(npobj, id, npargs, argc, &v); michael@0: } else { michael@0: ok = false; michael@0: michael@0: msg = "Attempt to call a method on object with no invoke method."; michael@0: } michael@0: } else { michael@0: if (npobj->_class->invokeDefault) { michael@0: // obj is a callable object that is being called, no method name michael@0: // available then. Invoke the default method. michael@0: michael@0: ok = npobj->_class->invokeDefault(npobj, npargs, argc, &v); michael@0: } else { michael@0: ok = false; michael@0: michael@0: msg = "Attempt to call a default method on object with no " michael@0: "invokeDefault method."; michael@0: } michael@0: } michael@0: michael@0: // Release arguments. michael@0: for (i = 0; i < argc; ++i) { michael@0: _releasevariantvalue(npargs + i); michael@0: } michael@0: michael@0: if (npargs != npargs_buf) { michael@0: PR_Free(npargs); michael@0: } michael@0: michael@0: if (!ok) { michael@0: // ReportExceptionIfPending returns a return value, which is true michael@0: // if no exception was thrown. In that case, throw our own. michael@0: if (ReportExceptionIfPending(cx)) michael@0: ThrowJSException(cx, msg); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: *rval = NPVariantToJSVal(npp, cx, &v); michael@0: michael@0: // *rval now owns the value, release our reference. michael@0: _releasevariantvalue(&v); michael@0: michael@0: return ReportExceptionIfPending(cx); michael@0: } michael@0: michael@0: static bool michael@0: CallNPMethod(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: JS::Rooted obj(cx, JS_THIS_OBJECT(cx, vp)); michael@0: if (!obj) michael@0: return false; michael@0: michael@0: return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false); michael@0: } michael@0: michael@0: struct NPObjectEnumerateState { michael@0: uint32_t index; michael@0: uint32_t length; michael@0: NPIdentifier *value; michael@0: }; michael@0: michael@0: static bool michael@0: NPObjWrapper_newEnumerate(JSContext *cx, JS::Handle obj, JSIterateOp enum_op, michael@0: JS::Value *statep, jsid *idp) michael@0: { michael@0: NPObject *npobj = GetNPObject(cx, obj); michael@0: NPIdentifier *enum_value; michael@0: uint32_t length; michael@0: NPObjectEnumerateState *state; michael@0: michael@0: if (!npobj || !npobj->_class) { michael@0: ThrowJSException(cx, "Bad NPObject as private data!"); michael@0: return false; michael@0: } michael@0: michael@0: PluginDestructionGuard pdg(LookupNPP(npobj)); michael@0: michael@0: NS_ASSERTION(statep, "Must have a statep to enumerate!"); michael@0: michael@0: switch(enum_op) { michael@0: case JSENUMERATE_INIT: michael@0: case JSENUMERATE_INIT_ALL: michael@0: state = new NPObjectEnumerateState(); michael@0: if (!state) { michael@0: ThrowJSException(cx, "Memory allocation failed for " michael@0: "NPObjectEnumerateState!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(npobj->_class) || michael@0: !npobj->_class->enumerate) { michael@0: enum_value = 0; michael@0: length = 0; michael@0: } else if (!npobj->_class->enumerate(npobj, &enum_value, &length)) { michael@0: delete state; michael@0: michael@0: if (ReportExceptionIfPending(cx)) { michael@0: // ReportExceptionIfPending returns a return value, which is true michael@0: // if no exception was thrown. In that case, throw our own. michael@0: ThrowJSException(cx, "Error enumerating properties on scriptable " michael@0: "plugin object"); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: state->value = enum_value; michael@0: state->length = length; michael@0: state->index = 0; michael@0: *statep = PRIVATE_TO_JSVAL(state); michael@0: if (idp) { michael@0: *idp = INT_TO_JSID(length); michael@0: } michael@0: michael@0: break; michael@0: michael@0: case JSENUMERATE_NEXT: michael@0: state = (NPObjectEnumerateState *)JSVAL_TO_PRIVATE(*statep); michael@0: enum_value = state->value; michael@0: length = state->length; michael@0: if (state->index != length) { michael@0: *idp = NPIdentifierToJSId(enum_value[state->index++]); michael@0: return true; michael@0: } michael@0: michael@0: // FALL THROUGH michael@0: michael@0: case JSENUMERATE_DESTROY: michael@0: state = (NPObjectEnumerateState *)JSVAL_TO_PRIVATE(*statep); michael@0: if (state->value) michael@0: PR_Free(state->value); michael@0: delete state; michael@0: *statep = JSVAL_NULL; michael@0: michael@0: break; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: NPObjWrapper_NewResolve(JSContext *cx, JS::Handle obj, JS::Handle id, michael@0: JS::MutableHandle objp) michael@0: { michael@0: NPObject *npobj = GetNPObject(cx, obj); michael@0: michael@0: if (!npobj || !npobj->_class || !npobj->_class->hasProperty || michael@0: !npobj->_class->hasMethod) { michael@0: ThrowJSException(cx, "Bad NPObject as private data!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: PluginDestructionGuard pdg(LookupNPP(npobj)); michael@0: michael@0: NPIdentifier identifier = JSIdToNPIdentifier(id); michael@0: michael@0: bool hasProperty = npobj->_class->hasProperty(npobj, identifier); michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: michael@0: if (hasProperty) { michael@0: NS_ASSERTION(JSID_IS_STRING(id) || JSID_IS_INT(id), michael@0: "id must be either string or int!\n"); michael@0: if (!::JS_DefinePropertyById(cx, obj, id, JSVAL_VOID, nullptr, michael@0: nullptr, JSPROP_ENUMERATE | JSPROP_SHARED)) { michael@0: return false; michael@0: } michael@0: michael@0: objp.set(obj); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool hasMethod = npobj->_class->hasMethod(npobj, identifier); michael@0: if (!ReportExceptionIfPending(cx)) michael@0: return false; michael@0: michael@0: if (hasMethod) { michael@0: NS_ASSERTION(JSID_IS_STRING(id) || JSID_IS_INT(id), michael@0: "id must be either string or int!\n"); michael@0: michael@0: JSFunction *fnc = ::JS_DefineFunctionById(cx, obj, id, CallNPMethod, 0, michael@0: JSPROP_ENUMERATE); michael@0: michael@0: objp.set(obj); michael@0: michael@0: return fnc != nullptr; michael@0: } michael@0: michael@0: // no property or method michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: NPObjWrapper_Convert(JSContext *cx, JS::Handle obj, JSType hint, JS::MutableHandle vp) michael@0: { michael@0: MOZ_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID); michael@0: michael@0: // Plugins do not simply use JS_ConvertStub, and the default [[DefaultValue]] michael@0: // behavior, because that behavior involves calling toString or valueOf on michael@0: // objects which weren't designed to accommodate this. Usually this wouldn't michael@0: // be a problem, because the absence of either property, or the presence of michael@0: // either property with a value that isn't callable, will cause that property michael@0: // to simply be ignored. But there is a problem in one specific case: Java, michael@0: // specifically java.lang.Integer. The Integer class has static valueOf michael@0: // methods, none of which are nullary, so the JS-reflected method will behave michael@0: // poorly when called with no arguments. We work around this problem by michael@0: // giving plugins a [[DefaultValue]] which uses only toString and not valueOf. michael@0: michael@0: JS::Rooted v(cx, JSVAL_VOID); michael@0: if (!JS_GetProperty(cx, obj, "toString", &v)) michael@0: return false; michael@0: if (!JSVAL_IS_PRIMITIVE(v) && JS_ObjectIsCallable(cx, JSVAL_TO_OBJECT(v))) { michael@0: if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), vp)) michael@0: return false; michael@0: if (JSVAL_IS_PRIMITIVE(vp)) michael@0: return true; michael@0: } michael@0: michael@0: JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO, michael@0: JS_GetClass(obj)->name, michael@0: hint == JSTYPE_VOID michael@0: ? "primitive type" michael@0: : hint == JSTYPE_NUMBER michael@0: ? "number" michael@0: : "string"); michael@0: return false; michael@0: } michael@0: michael@0: static void michael@0: NPObjWrapper_Finalize(JSFreeOp *fop, JSObject *obj) michael@0: { michael@0: NPObject *npobj = (NPObject *)::JS_GetPrivate(obj); michael@0: if (npobj) { michael@0: if (sNPObjWrappers.ops) { michael@0: PL_DHashTableOperate(&sNPObjWrappers, npobj, PL_DHASH_REMOVE); michael@0: } michael@0: } michael@0: michael@0: if (!sDelayedReleases) michael@0: sDelayedReleases = new nsTArray; michael@0: sDelayedReleases->AppendElement(npobj); michael@0: } michael@0: michael@0: static bool michael@0: NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: JS::Rooted obj(cx, &args.callee()); michael@0: return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false); michael@0: } michael@0: michael@0: static bool michael@0: NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: JS::Rooted obj(cx, &args.callee()); michael@0: return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, true); michael@0: } michael@0: michael@0: class NPObjWrapperHashEntry : public PLDHashEntryHdr michael@0: { michael@0: public: michael@0: NPObject *mNPObj; // Must be the first member for the PLDHash stubs to work michael@0: JSObject *mJSObj; michael@0: NPP mNpp; michael@0: }; michael@0: michael@0: michael@0: // An NPObject is going away, make sure we null out the JS object's michael@0: // private data in case this is an NPObject that came from a plugin michael@0: // and it's destroyed prematurely. michael@0: michael@0: // static michael@0: void michael@0: nsNPObjWrapper::OnDestroy(NPObject *npobj) michael@0: { michael@0: if (!npobj) { michael@0: return; michael@0: } michael@0: michael@0: if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { michael@0: // npobj is one of our own, no private data to clean up here. michael@0: michael@0: return; michael@0: } michael@0: michael@0: if (!sNPObjWrappers.ops) { michael@0: // No hash yet (or any more), no used wrappers available. michael@0: michael@0: return; michael@0: } michael@0: michael@0: NPObjWrapperHashEntry *entry = static_cast michael@0: (PL_DHashTableOperate(&sNPObjWrappers, npobj, PL_DHASH_LOOKUP)); michael@0: michael@0: if (PL_DHASH_ENTRY_IS_BUSY(entry) && entry->mJSObj) { michael@0: // Found a live NPObject wrapper, null out its JSObjects' private michael@0: // data. michael@0: michael@0: ::JS_SetPrivate(entry->mJSObj, nullptr); michael@0: michael@0: // Remove the npobj from the hash now that it went away. michael@0: PL_DHashTableRawRemove(&sNPObjWrappers, entry); michael@0: michael@0: // The finalize hook will call OnWrapperDestroyed(). michael@0: } michael@0: } michael@0: michael@0: // Look up or create a JSObject that wraps the NPObject npobj. michael@0: michael@0: // static michael@0: JSObject * michael@0: nsNPObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, NPObject *npobj) michael@0: { michael@0: if (!npobj) { michael@0: NS_ERROR("Null NPObject passed to nsNPObjWrapper::GetNewOrUsed()!"); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { michael@0: // npobj is one of our own, return its existing JSObject. michael@0: michael@0: JS::Rooted obj(cx, ((nsJSObjWrapper *)npobj)->mJSObj); michael@0: if (!JS_WrapObject(cx, &obj)) { michael@0: return nullptr; michael@0: } michael@0: return obj; michael@0: } michael@0: michael@0: if (!npp) { michael@0: NS_ERROR("No npp passed to nsNPObjWrapper::GetNewOrUsed()!"); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!sNPObjWrappers.ops) { michael@0: // No hash yet (or any more), initialize it. michael@0: PL_DHashTableInit(&sNPObjWrappers, PL_DHashGetStubOps(), nullptr, michael@0: sizeof(NPObjWrapperHashEntry), 16); michael@0: } michael@0: michael@0: NPObjWrapperHashEntry *entry = static_cast michael@0: (PL_DHashTableOperate(&sNPObjWrappers, npobj, PL_DHASH_ADD)); michael@0: michael@0: if (!entry) { michael@0: // Out of memory michael@0: JS_ReportOutOfMemory(cx); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: if (PL_DHASH_ENTRY_IS_BUSY(entry) && entry->mJSObj) { michael@0: // Found a live NPObject wrapper. It may not be in the same compartment michael@0: // as cx, so we need to wrap it before returning it. michael@0: JS::Rooted obj(cx, entry->mJSObj); michael@0: if (!JS_WrapObject(cx, &obj)) { michael@0: return nullptr; michael@0: } michael@0: return obj; michael@0: } michael@0: michael@0: entry->mNPObj = npobj; michael@0: entry->mNpp = npp; michael@0: michael@0: uint32_t generation = sNPObjWrappers.generation; michael@0: michael@0: // No existing JSObject, create one. michael@0: michael@0: JS::Rooted obj(cx, ::JS_NewObject(cx, &sNPObjectJSWrapperClass, JS::NullPtr(), michael@0: JS::NullPtr())); michael@0: michael@0: if (generation != sNPObjWrappers.generation) { michael@0: // Reload entry if the JS_NewObject call caused a GC and reallocated michael@0: // the table (see bug 445229). This is guaranteed to succeed. michael@0: michael@0: entry = static_cast michael@0: (PL_DHashTableOperate(&sNPObjWrappers, npobj, PL_DHASH_LOOKUP)); michael@0: NS_ASSERTION(entry && PL_DHASH_ENTRY_IS_BUSY(entry), michael@0: "Hashtable didn't find what we just added?"); michael@0: } michael@0: michael@0: if (!obj) { michael@0: // OOM? Remove the stale entry from the hash. michael@0: michael@0: PL_DHashTableRawRemove(&sNPObjWrappers, entry); michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: OnWrapperCreated(); michael@0: michael@0: entry->mJSObj = obj; michael@0: michael@0: ::JS_SetPrivate(obj, npobj); michael@0: michael@0: // The new JSObject now holds on to npobj michael@0: _retainobject(npobj); michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: michael@0: // Struct for passing an NPP and a JSContext to michael@0: // NPObjWrapperPluginDestroyedCallback michael@0: struct NppAndCx michael@0: { michael@0: NPP npp; michael@0: JSContext *cx; michael@0: }; michael@0: michael@0: static PLDHashOperator michael@0: NPObjWrapperPluginDestroyedCallback(PLDHashTable *table, PLDHashEntryHdr *hdr, michael@0: uint32_t number, void *arg) michael@0: { michael@0: NPObjWrapperHashEntry *entry = (NPObjWrapperHashEntry *)hdr; michael@0: NppAndCx *nppcx = reinterpret_cast(arg); michael@0: michael@0: if (entry->mNpp == nppcx->npp) { michael@0: // Prevent invalidate() and deallocate() from touching the hash michael@0: // we're enumerating. michael@0: const PLDHashTableOps *ops = table->ops; michael@0: table->ops = nullptr; michael@0: michael@0: NPObject *npobj = entry->mNPObj; michael@0: michael@0: if (npobj->_class && npobj->_class->invalidate) { michael@0: npobj->_class->invalidate(npobj); michael@0: } michael@0: michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: { michael@0: int32_t refCnt = npobj->referenceCount; michael@0: while (refCnt) { michael@0: --refCnt; michael@0: NS_LOG_RELEASE(npobj, refCnt, "BrowserNPObject"); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // Force deallocation of plugin objects since the plugin they came michael@0: // from is being torn down. michael@0: if (npobj->_class && npobj->_class->deallocate) { michael@0: npobj->_class->deallocate(npobj); michael@0: } else { michael@0: PR_Free(npobj); michael@0: } michael@0: michael@0: ::JS_SetPrivate(entry->mJSObj, nullptr); michael@0: michael@0: table->ops = ops; michael@0: michael@0: if (sDelayedReleases && sDelayedReleases->RemoveElement(npobj)) { michael@0: OnWrapperDestroyed(); michael@0: } michael@0: michael@0: return PL_DHASH_REMOVE; michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // static michael@0: void michael@0: nsJSNPRuntime::OnPluginDestroy(NPP npp) michael@0: { michael@0: if (sJSObjWrappersAccessible) { michael@0: michael@0: // Prevent modification of sJSObjWrappers table if we go reentrant. michael@0: sJSObjWrappersAccessible = false; michael@0: michael@0: for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) { michael@0: nsJSObjWrapper *npobj = e.front().value(); michael@0: MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass); michael@0: if (npobj->mNpp == npp) { michael@0: if (npobj->_class && npobj->_class->invalidate) { michael@0: npobj->_class->invalidate(npobj); michael@0: } michael@0: michael@0: _releaseobject(npobj); michael@0: michael@0: e.removeFront(); michael@0: } michael@0: } michael@0: michael@0: sJSObjWrappersAccessible = true; michael@0: } michael@0: michael@0: // Use the safe JSContext here as we're not always able to find the michael@0: // JSContext associated with the NPP any more. michael@0: AutoSafeJSContext cx; michael@0: if (sNPObjWrappers.ops) { michael@0: NppAndCx nppcx = { npp, cx }; michael@0: PL_DHashTableEnumerate(&sNPObjWrappers, michael@0: NPObjWrapperPluginDestroyedCallback, &nppcx); michael@0: } michael@0: } michael@0: michael@0: michael@0: // Find the NPP for a NPObject. michael@0: static NPP michael@0: LookupNPP(NPObject *npobj) michael@0: { michael@0: if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { michael@0: nsJSObjWrapper* o = static_cast(npobj); michael@0: return o->mNpp; michael@0: } michael@0: michael@0: NPObjWrapperHashEntry *entry = static_cast michael@0: (PL_DHashTableOperate(&sNPObjWrappers, npobj, PL_DHASH_ADD)); michael@0: michael@0: if (PL_DHASH_ENTRY_IS_FREE(entry)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: NS_ASSERTION(entry->mNpp, "Live NPObject entry w/o an NPP!"); michael@0: michael@0: return entry->mNpp; michael@0: } michael@0: michael@0: static bool michael@0: CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj, michael@0: JS::Handle id, NPVariant* getPropertyResult, michael@0: JS::MutableHandle vp) michael@0: { michael@0: if (!npobj || !npobj->_class || !npobj->_class->getProperty || michael@0: !npobj->_class->invoke) { michael@0: ThrowJSException(cx, "Bad NPObject"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: NPObjectMemberPrivate *memberPrivate = michael@0: (NPObjectMemberPrivate *)PR_Malloc(sizeof(NPObjectMemberPrivate)); michael@0: if (!memberPrivate) michael@0: return false; michael@0: michael@0: // Make sure to clear all members in case something fails here michael@0: // during initialization. michael@0: memset(memberPrivate, 0, sizeof(NPObjectMemberPrivate)); michael@0: michael@0: JSObject *memobj = ::JS_NewObject(cx, &sNPObjectMemberClass, JS::NullPtr(), JS::NullPtr()); michael@0: if (!memobj) { michael@0: PR_Free(memberPrivate); michael@0: return false; michael@0: } michael@0: michael@0: vp.setObject(*memobj); michael@0: michael@0: ::JS_SetPrivate(memobj, (void *)memberPrivate); michael@0: michael@0: NPIdentifier identifier = JSIdToNPIdentifier(id); michael@0: michael@0: JS::Rooted fieldValue(cx); michael@0: NPVariant npv; michael@0: michael@0: if (getPropertyResult) { michael@0: // Plugin has already handed us the value we want here. michael@0: npv = *getPropertyResult; michael@0: } michael@0: else { michael@0: VOID_TO_NPVARIANT(npv); michael@0: michael@0: NPBool hasProperty = npobj->_class->getProperty(npobj, identifier, michael@0: &npv); michael@0: if (!ReportExceptionIfPending(cx) || !hasProperty) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: fieldValue = NPVariantToJSVal(npp, cx, &npv); michael@0: michael@0: // npobjWrapper is the JSObject through which we make sure we don't michael@0: // outlive the underlying NPObject, so make sure it points to the michael@0: // real JSObject wrapper for the NPObject. michael@0: obj = GetNPObjectWrapper(cx, obj); michael@0: michael@0: memberPrivate->npobjWrapper = obj; michael@0: michael@0: memberPrivate->fieldValue = fieldValue; michael@0: memberPrivate->methodName = id; michael@0: memberPrivate->npp = npp; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: NPObjectMember_Convert(JSContext *cx, JS::Handle obj, JSType type, JS::MutableHandle vp) michael@0: { michael@0: NPObjectMemberPrivate *memberPrivate = michael@0: (NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, obj, michael@0: &sNPObjectMemberClass, michael@0: nullptr); michael@0: if (!memberPrivate) { michael@0: NS_ERROR("no Ambiguous Member Private data!"); michael@0: return false; michael@0: } michael@0: michael@0: switch (type) { michael@0: case JSTYPE_VOID: michael@0: case JSTYPE_STRING: michael@0: case JSTYPE_NUMBER: michael@0: vp.set(memberPrivate->fieldValue); michael@0: if (vp.isObject()) { michael@0: JS::Rooted objVal(cx, &vp.toObject()); michael@0: return JS_DefaultValue(cx, objVal, type, vp); michael@0: } michael@0: return true; michael@0: case JSTYPE_BOOLEAN: michael@0: case JSTYPE_OBJECT: michael@0: vp.set(memberPrivate->fieldValue); michael@0: return true; michael@0: case JSTYPE_FUNCTION: michael@0: // Leave this to NPObjectMember_Call. michael@0: return true; michael@0: default: michael@0: NS_ERROR("illegal operation on JSObject prototype object"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj) michael@0: { michael@0: NPObjectMemberPrivate *memberPrivate; michael@0: michael@0: memberPrivate = (NPObjectMemberPrivate *)::JS_GetPrivate(obj); michael@0: if (!memberPrivate) michael@0: return; michael@0: michael@0: PR_Free(memberPrivate); michael@0: } michael@0: michael@0: static bool michael@0: NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp) michael@0: { michael@0: JS::CallArgs args = JS::CallArgsFromVp(argc, vp); michael@0: JS::Rooted memobj(cx, &args.callee()); michael@0: NS_ENSURE_TRUE(memobj, false); michael@0: michael@0: NPObjectMemberPrivate *memberPrivate = michael@0: (NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, memobj, michael@0: &sNPObjectMemberClass, michael@0: &args); michael@0: if (!memberPrivate || !memberPrivate->npobjWrapper) michael@0: return false; michael@0: michael@0: NPObject *npobj = GetNPObject(cx, memberPrivate->npobjWrapper); michael@0: if (!npobj) { michael@0: ThrowJSException(cx, "Call on invalid member object"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: NPVariant npargs_buf[8]; michael@0: NPVariant *npargs = npargs_buf; michael@0: michael@0: if (args.length() > (sizeof(npargs_buf) / sizeof(NPVariant))) { michael@0: // Our stack buffer isn't large enough to hold all arguments, michael@0: // malloc a buffer. michael@0: npargs = (NPVariant *)PR_Malloc(args.length() * sizeof(NPVariant)); michael@0: michael@0: if (!npargs) { michael@0: ThrowJSException(cx, "Out of memory!"); michael@0: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Convert arguments michael@0: for (uint32_t i = 0; i < args.length(); ++i) { michael@0: if (!JSValToNPVariant(memberPrivate->npp, cx, args[i], npargs + i)) { michael@0: ThrowJSException(cx, "Error converting jsvals to NPVariants!"); michael@0: michael@0: if (npargs != npargs_buf) { michael@0: PR_Free(npargs); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: michael@0: NPVariant npv; michael@0: bool ok = npobj->_class->invoke(npobj, michael@0: JSIdToNPIdentifier(memberPrivate->methodName), michael@0: npargs, args.length(), &npv); michael@0: michael@0: // Release arguments. michael@0: for (uint32_t i = 0; i < args.length(); ++i) { michael@0: _releasevariantvalue(npargs + i); michael@0: } michael@0: michael@0: if (npargs != npargs_buf) { michael@0: PR_Free(npargs); michael@0: } michael@0: michael@0: if (!ok) { michael@0: // ReportExceptionIfPending returns a return value, which is true michael@0: // if no exception was thrown. In that case, throw our own. michael@0: if (ReportExceptionIfPending(cx)) michael@0: ThrowJSException(cx, "Error calling method on NPObject!"); michael@0: michael@0: return false; michael@0: } michael@0: michael@0: args.rval().set(NPVariantToJSVal(memberPrivate->npp, cx, &npv)); michael@0: michael@0: // *vp now owns the value, release our reference. michael@0: _releasevariantvalue(&npv); michael@0: michael@0: return ReportExceptionIfPending(cx); michael@0: } michael@0: michael@0: static void michael@0: NPObjectMember_Trace(JSTracer *trc, JSObject *obj) michael@0: { michael@0: NPObjectMemberPrivate *memberPrivate = michael@0: (NPObjectMemberPrivate *)::JS_GetPrivate(obj); michael@0: if (!memberPrivate) michael@0: return; michael@0: michael@0: // Our NPIdentifier is not always interned, so we must root it explicitly. michael@0: JS_CallHeapIdTracer(trc, &memberPrivate->methodName, "NPObjectMemberPrivate.methodName"); michael@0: michael@0: if (!JSVAL_IS_PRIMITIVE(memberPrivate->fieldValue)) { michael@0: JS_CallHeapValueTracer(trc, &memberPrivate->fieldValue, michael@0: "NPObject Member => fieldValue"); michael@0: } michael@0: michael@0: // There's no strong reference from our private data to the michael@0: // NPObject, so make sure to mark the NPObject wrapper to keep the michael@0: // NPObject alive as long as this NPObjectMember is alive. michael@0: if (memberPrivate->npobjWrapper) { michael@0: JS_CallHeapObjectTracer(trc, &memberPrivate->npobjWrapper, michael@0: "NPObject Member => npobjWrapper"); michael@0: } michael@0: }