diff options
Diffstat (limited to 'dom/plugins/base/nsJSNPRuntime.cpp')
-rw-r--r-- | dom/plugins/base/nsJSNPRuntime.cpp | 2328 |
1 files changed, 2328 insertions, 0 deletions
diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp new file mode 100644 index 000000000..05e0ec4ba --- /dev/null +++ b/dom/plugins/base/nsJSNPRuntime.cpp @@ -0,0 +1,2328 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "base/basictypes.h" + +#include "jsfriendapi.h" +#include "jswrapper.h" + +#include "nsAutoPtr.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsJSNPRuntime.h" +#include "nsNPAPIPlugin.h" +#include "nsNPAPIPluginInstance.h" +#include "nsIGlobalObject.h" +#include "nsIScriptGlobalObject.h" +#include "nsIScriptContext.h" +#include "nsDOMJSUtils.h" +#include "nsJSUtils.h" +#include "nsIDocument.h" +#include "nsIXPConnect.h" +#include "xpcpublic.h" +#include "nsIDOMElement.h" +#include "prmem.h" +#include "nsIContent.h" +#include "nsPluginInstanceOwner.h" +#include "nsWrapperCacheInlines.h" +#include "js/GCHashTable.h" +#include "js/TracingAPI.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/plugins/PluginAsyncSurrogate.h" + +using mozilla::plugins::AsyncNPObject; +using mozilla::plugins::PluginAsyncSurrogate; + +#define NPRUNTIME_JSCLASS_NAME "NPObject JS wrapper class" + +using namespace mozilla::plugins::parent; +using namespace mozilla; + +#include "mozilla/plugins/PluginScriptableObjectParent.h" +using mozilla::plugins::PluginScriptableObjectParent; +using mozilla::plugins::ParentNPObject; + +struct JSObjWrapperHasher +{ + typedef nsJSObjWrapperKey Key; + typedef Key Lookup; + + static uint32_t hash(const Lookup &l) { + return js::MovableCellHasher<JS::Heap<JSObject*>>::hash(l.mJSObj) ^ + HashGeneric(l.mNpp); + } + + static bool match(const Key& k, const Lookup &l) { + return js::MovableCellHasher<JS::Heap<JSObject*>>::match(k.mJSObj, l.mJSObj) && + k.mNpp == l.mNpp; + } +}; + +namespace JS { +template <> +struct GCPolicy<nsJSObjWrapper*> { + static void trace(JSTracer* trc, nsJSObjWrapper** wrapper, const char* name) { + MOZ_ASSERT(wrapper); + MOZ_ASSERT(*wrapper); + (*wrapper)->trace(trc); + } +}; +} // namespace JS + +class NPObjWrapperHashEntry : public PLDHashEntryHdr +{ +public: + NPObject *mNPObj; // Must be the first member for the PLDHash stubs to work + JS::TenuredHeap<JSObject*> mJSObj; + NPP mNpp; +}; + +// Hash of JSObject wrappers that wraps JSObjects as NPObjects. There +// will be one wrapper per JSObject per plugin instance, i.e. if two +// plugins access the JSObject x, two wrappers for x will be +// created. This is needed to be able to properly drop the wrappers +// when a plugin is torn down in case there's a leak in the plugin (we +// don't want to leak the world just because a plugin leaks an +// NPObject). +typedef JS::GCHashMap<nsJSObjWrapperKey, + nsJSObjWrapper*, + JSObjWrapperHasher, + js::SystemAllocPolicy> JSObjWrapperTable; +static JSObjWrapperTable sJSObjWrappers; + +// Whether it's safe to iterate sJSObjWrappers. Set to true when sJSObjWrappers +// has been initialized and is not currently being enumerated. +static bool sJSObjWrappersAccessible = false; + +// Hash of NPObject wrappers that wrap NPObjects as JSObjects. +static PLDHashTable* sNPObjWrappers; + +// Global wrapper count. This includes JSObject wrappers *and* +// NPObject wrappers. When this count goes to zero, there are no more +// wrappers and we can kill off hash tables etc. +static int32_t sWrapperCount; + +static bool sCallbackIsRegistered = false; + +static nsTArray<NPObject*>* sDelayedReleases; + +namespace { + +inline void +CastNPObject(NPObject *aObj, PluginScriptableObjectParent*& aActor, + PluginAsyncSurrogate*& aSurrogate) +{ + aActor = nullptr; + aSurrogate = nullptr; + if (aObj->_class == PluginScriptableObjectParent::GetClass()) { + aActor = static_cast<ParentNPObject*>(aObj)->parent; + } else if (aObj->_class == PluginAsyncSurrogate::GetClass()) { + aSurrogate = static_cast<AsyncNPObject*>(aObj)->mSurrogate; + } +} + +inline bool +NPObjectIsOutOfProcessProxy(NPObject *obj) +{ + return obj->_class == PluginScriptableObjectParent::GetClass() || + obj->_class == PluginAsyncSurrogate::GetClass(); +} + +} // namespace + +// Helper class that suppresses any JS exceptions that were thrown while +// the plugin executed JS, if the nsJSObjWrapper has a destroy pending. +// Note that this class is the product (vestige?) of a long evolution in how +// error reporting worked, and hence the mIsDestroyPending check, and hence this +// class in general, may or may not actually be necessary. + +class MOZ_STACK_CLASS AutoJSExceptionSuppressor +{ +public: + AutoJSExceptionSuppressor(dom::AutoEntryScript& aes, nsJSObjWrapper* aWrapper) + : mAes(aes) + , mIsDestroyPending(aWrapper->mDestroyPending) + { + } + + ~AutoJSExceptionSuppressor() + { + if (mIsDestroyPending) { + mAes.ClearException(); + } + } + +protected: + dom::AutoEntryScript& mAes; + bool mIsDestroyPending; +}; + + +NPClass nsJSObjWrapper::sJSObjWrapperNPClass = + { + NP_CLASS_STRUCT_VERSION, + nsJSObjWrapper::NP_Allocate, + nsJSObjWrapper::NP_Deallocate, + nsJSObjWrapper::NP_Invalidate, + nsJSObjWrapper::NP_HasMethod, + nsJSObjWrapper::NP_Invoke, + nsJSObjWrapper::NP_InvokeDefault, + nsJSObjWrapper::NP_HasProperty, + nsJSObjWrapper::NP_GetProperty, + nsJSObjWrapper::NP_SetProperty, + nsJSObjWrapper::NP_RemoveProperty, + nsJSObjWrapper::NP_Enumerate, + nsJSObjWrapper::NP_Construct + }; + +static bool +NPObjWrapper_AddProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::Handle<JS::Value> v); + +static bool +NPObjWrapper_DelProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::ObjectOpResult &result); + +static bool +NPObjWrapper_SetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp, JS::ObjectOpResult &result); + +static bool +NPObjWrapper_GetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp); + +static bool +NPObjWrapper_Enumerate(JSContext *cx, JS::Handle<JSObject*> obj, JS::AutoIdVector &properties, + bool enumerableOnly); + +static bool +NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + bool *resolvedp); + +static void +NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj); + +static void +NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old); + +static bool +NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp); + +static bool +NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp); + +static bool +NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp); + +static bool +CreateNPObjectMember(NPP npp, JSContext *cx, + JS::Handle<JSObject*> obj, NPObject* npobj, + JS::Handle<jsid> id, NPVariant* getPropertyResult, + JS::MutableHandle<JS::Value> vp); + +const static js::ClassOps sNPObjectJSWrapperClassOps = { + NPObjWrapper_AddProperty, + NPObjWrapper_DelProperty, + NPObjWrapper_GetProperty, + NPObjWrapper_SetProperty, + nullptr, + NPObjWrapper_Resolve, + nullptr, /* mayResolve */ + NPObjWrapper_Finalize, + NPObjWrapper_Call, + nullptr, /* hasInstance */ + NPObjWrapper_Construct, + nullptr, /* trace */ +}; + +const static js::ClassExtension sNPObjectJSWrapperClassExtension = { + nullptr, /* weakmapKeyDelegateOp */ + NPObjWrapper_ObjectMoved +}; + +const static js::ObjectOps sNPObjectJSWrapperObjectOps = { + nullptr, // lookupProperty + nullptr, // defineProperty + nullptr, // hasProperty + nullptr, // getProperty + nullptr, // setProperty + nullptr, // getOwnPropertyDescriptor + nullptr, // deleteProperty + nullptr, nullptr, // watch/unwatch + nullptr, // getElements + NPObjWrapper_Enumerate, + nullptr, +}; + +const static js::Class sNPObjectJSWrapperClass = { + NPRUNTIME_JSCLASS_NAME, + JSCLASS_HAS_PRIVATE | + JSCLASS_FOREGROUND_FINALIZE, + &sNPObjectJSWrapperClassOps, + JS_NULL_CLASS_SPEC, + &sNPObjectJSWrapperClassExtension, + &sNPObjectJSWrapperObjectOps +}; + +typedef struct NPObjectMemberPrivate { + JS::Heap<JSObject *> npobjWrapper; + JS::Heap<JS::Value> fieldValue; + JS::Heap<jsid> methodName; + NPP npp; +} NPObjectMemberPrivate; + +static bool +NPObjectMember_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id, + JS::MutableHandleValue vp); + +static void +NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj); + +static bool +NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp); + +static void +NPObjectMember_Trace(JSTracer *trc, JSObject *obj); + +static bool +NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp); + +static const JSClassOps sNPObjectMemberClassOps = { + nullptr, nullptr, NPObjectMember_GetProperty, nullptr, + nullptr, nullptr, nullptr, + NPObjectMember_Finalize, NPObjectMember_Call, + nullptr, nullptr, NPObjectMember_Trace +}; + +static const JSClass sNPObjectMemberClass = { + "NPObject Ambiguous Member class", + JSCLASS_HAS_PRIVATE | + JSCLASS_FOREGROUND_FINALIZE, + &sNPObjectMemberClassOps +}; + +static void +OnWrapperDestroyed(); + +static void +TraceJSObjWrappers(JSTracer *trc, void *data) +{ + if (sJSObjWrappers.initialized()) { + sJSObjWrappers.trace(trc); + } +} + +static void +DelayedReleaseGCCallback(JSGCStatus status) +{ + if (JSGC_END == status) { + // Take ownership of sDelayedReleases and null it out now. The + // _releaseobject call below can reenter GC and double-free these objects. + nsAutoPtr<nsTArray<NPObject*> > delayedReleases(sDelayedReleases); + sDelayedReleases = nullptr; + + if (delayedReleases) { + for (uint32_t i = 0; i < delayedReleases->Length(); ++i) { + NPObject* obj = (*delayedReleases)[i]; + if (obj) + _releaseobject(obj); + OnWrapperDestroyed(); + } + } + } +} + +static bool +RegisterGCCallbacks() +{ + if (sCallbackIsRegistered) { + return true; + } + + // Register a callback to trace wrapped JSObjects. + JSContext* cx = dom::danger::GetJSContext(); + if (!JS_AddExtraGCRootsTracer(cx, TraceJSObjWrappers, nullptr)) { + return false; + } + + // Register our GC callback to perform delayed destruction of finalized + // NPObjects. + xpc::AddGCCallback(DelayedReleaseGCCallback); + + sCallbackIsRegistered = true; + + return true; +} + +static void +UnregisterGCCallbacks() +{ + MOZ_ASSERT(sCallbackIsRegistered); + + // Remove tracing callback. + JSContext* cx = dom::danger::GetJSContext(); + JS_RemoveExtraGCRootsTracer(cx, TraceJSObjWrappers, nullptr); + + // Remove delayed destruction callback. + if (sCallbackIsRegistered) { + xpc::RemoveGCCallback(DelayedReleaseGCCallback); + sCallbackIsRegistered = false; + } +} + +static bool +CreateJSObjWrapperTable() +{ + MOZ_ASSERT(!sJSObjWrappersAccessible); + MOZ_ASSERT(!sJSObjWrappers.initialized()); + + if (!RegisterGCCallbacks()) { + return false; + } + + if (!sJSObjWrappers.init(16)) { + NS_ERROR("Error initializing PLDHashTable sJSObjWrappers!"); + return false; + } + + sJSObjWrappersAccessible = true; + return true; +} + +static void +DestroyJSObjWrapperTable() +{ + MOZ_ASSERT(sJSObjWrappersAccessible); + MOZ_ASSERT(sJSObjWrappers.initialized()); + MOZ_ASSERT(sJSObjWrappers.count() == 0); + + // No more wrappers, and our hash was initialized. Finish the + // hash to prevent leaking it. + sJSObjWrappers.finish(); + sJSObjWrappersAccessible = false; +} + +static bool +CreateNPObjWrapperTable() +{ + MOZ_ASSERT(!sNPObjWrappers); + + if (!RegisterGCCallbacks()) { + return false; + } + + sNPObjWrappers = + new PLDHashTable(PLDHashTable::StubOps(), sizeof(NPObjWrapperHashEntry)); + return true; +} + +static void +DestroyNPObjWrapperTable() +{ + MOZ_ASSERT(sNPObjWrappers->EntryCount() == 0); + + delete sNPObjWrappers; + sNPObjWrappers = nullptr; +} + +static void +OnWrapperCreated() +{ + ++sWrapperCount; +} + +static void +OnWrapperDestroyed() +{ + NS_ASSERTION(sWrapperCount, "Whaaa, unbalanced created/destroyed calls!"); + + if (--sWrapperCount == 0) { + if (sJSObjWrappersAccessible) { + DestroyJSObjWrapperTable(); + } + + if (sNPObjWrappers) { + // No more wrappers, and our hash was initialized. Finish the + // hash to prevent leaking it. + DestroyNPObjWrapperTable(); + } + + UnregisterGCCallbacks(); + } +} + +namespace mozilla { +namespace plugins { +namespace parent { + +static nsIGlobalObject* +GetGlobalObject(NPP npp) +{ + NS_ENSURE_TRUE(npp, nullptr); + + nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)npp->ndata; + NS_ENSURE_TRUE(inst, nullptr); + + RefPtr<nsPluginInstanceOwner> owner = inst->GetOwner(); + NS_ENSURE_TRUE(owner, nullptr); + + nsCOMPtr<nsIDocument> doc; + owner->GetDocument(getter_AddRefs(doc)); + NS_ENSURE_TRUE(doc, nullptr); + + return doc->GetScopeObject(); +} + +} // namespace parent +} // namespace plugins +} // namespace mozilla + +static NPP +LookupNPP(NPObject *npobj); + + +static JS::Value +NPVariantToJSVal(NPP npp, JSContext *cx, const NPVariant *variant) +{ + switch (variant->type) { + case NPVariantType_Void : + return JS::UndefinedValue(); + case NPVariantType_Null : + return JS::NullValue(); + case NPVariantType_Bool : + return JS::BooleanValue(NPVARIANT_TO_BOOLEAN(*variant)); + case NPVariantType_Int32 : + { + // Don't use INT_TO_JSVAL directly to prevent bugs when dealing + // with ints larger than what fits in a integer JS::Value. + return ::JS_NumberValue(NPVARIANT_TO_INT32(*variant)); + } + case NPVariantType_Double : + { + return ::JS_NumberValue(NPVARIANT_TO_DOUBLE(*variant)); + } + case NPVariantType_String : + { + const NPString *s = &NPVARIANT_TO_STRING(*variant); + NS_ConvertUTF8toUTF16 utf16String(s->UTF8Characters, s->UTF8Length); + + JSString *str = + ::JS_NewUCStringCopyN(cx, utf16String.get(), utf16String.Length()); + + if (str) { + return JS::StringValue(str); + } + + break; + } + case NPVariantType_Object: + { + if (npp) { + JSObject *obj = + nsNPObjWrapper::GetNewOrUsed(npp, cx, NPVARIANT_TO_OBJECT(*variant)); + + if (obj) { + return JS::ObjectValue(*obj); + } + } + + NS_ERROR("Error wrapping NPObject!"); + + break; + } + default: + NS_ERROR("Unknown NPVariant type!"); + } + + NS_ERROR("Unable to convert NPVariant to jsval!"); + + return JS::UndefinedValue(); +} + +bool +JSValToNPVariant(NPP npp, JSContext *cx, const JS::Value& val, NPVariant *variant) +{ + NS_ASSERTION(npp, "Must have an NPP to wrap a jsval!"); + + if (val.isPrimitive()) { + if (val.isUndefined()) { + VOID_TO_NPVARIANT(*variant); + } else if (val.isNull()) { + NULL_TO_NPVARIANT(*variant); + } else if (val.isBoolean()) { + BOOLEAN_TO_NPVARIANT(val.toBoolean(), *variant); + } else if (val.isInt32()) { + INT32_TO_NPVARIANT(val.toInt32(), *variant); + } else if (val.isDouble()) { + double d = val.toDouble(); + int i; + if (JS_DoubleIsInt32(d, &i)) { + INT32_TO_NPVARIANT(i, *variant); + } else { + DOUBLE_TO_NPVARIANT(d, *variant); + } + } else if (val.isString()) { + JSString *jsstr = val.toString(); + + nsAutoJSString str; + if (!str.init(cx, jsstr)) { + return false; + } + + uint32_t len; + char *p = ToNewUTF8String(str, &len); + + if (!p) { + return false; + } + + STRINGN_TO_NPVARIANT(p, len, *variant); + } else { + NS_ERROR("Unknown primitive type!"); + + return false; + } + + return true; + } + + // The reflected plugin object may be in another compartment if the plugin + // element has since been adopted into a new document. We don't bother + // transplanting the plugin objects, and just do a unwrap with security + // checks if we encounter one of them as an argument. If the unwrap fails, + // we run with the original wrapped object, since sometimes there are + // legitimate cases where a security wrapper ends up here (for example, + // Location objects, which are _always_ behind security wrappers). + JS::Rooted<JSObject*> obj(cx, val.toObjectOrNull()); + obj = js::CheckedUnwrap(obj); + if (!obj) { + obj = val.toObjectOrNull(); + } + + NPObject* npobj = nsJSObjWrapper::GetNewOrUsed(npp, obj); + if (!npobj) { + return false; + } + + // Pass over ownership of npobj to *variant + OBJECT_TO_NPVARIANT(npobj, *variant); + + return true; +} + +static void +ThrowJSExceptionASCII(JSContext *cx, const char *message) +{ + const char *ex = PeekException(); + + if (ex) { + nsAutoString ucex; + + if (message) { + AppendASCIItoUTF16(message, ucex); + + AppendASCIItoUTF16(" [plugin exception: ", ucex); + } + + AppendUTF8toUTF16(ex, ucex); + + if (message) { + AppendASCIItoUTF16("].", ucex); + } + + JSString *str = ::JS_NewUCStringCopyN(cx, ucex.get(), ucex.Length()); + + if (str) { + JS::Rooted<JS::Value> exn(cx, JS::StringValue(str)); + ::JS_SetPendingException(cx, exn); + } + + PopException(); + } else { + ::JS_ReportErrorASCII(cx, "%s", message); + } +} + +static bool +ReportExceptionIfPending(JSContext *cx) +{ + const char *ex = PeekException(); + + if (!ex) { + return true; + } + + ThrowJSExceptionASCII(cx, nullptr); + + return false; +} + +nsJSObjWrapper::nsJSObjWrapper(NPP npp) + : mJSObj(nullptr), mNpp(npp), mDestroyPending(false) +{ + MOZ_COUNT_CTOR(nsJSObjWrapper); + OnWrapperCreated(); +} + +nsJSObjWrapper::~nsJSObjWrapper() +{ + MOZ_COUNT_DTOR(nsJSObjWrapper); + + // Invalidate first, since it relies on sJSObjWrappers. + NP_Invalidate(this); + + OnWrapperDestroyed(); +} + +// static +NPObject * +nsJSObjWrapper::NP_Allocate(NPP npp, NPClass *aClass) +{ + NS_ASSERTION(aClass == &sJSObjWrapperNPClass, + "Huh, wrong class passed to NP_Allocate()!!!"); + + return new nsJSObjWrapper(npp); +} + +// static +void +nsJSObjWrapper::NP_Deallocate(NPObject *npobj) +{ + // nsJSObjWrapper::~nsJSObjWrapper() will call NP_Invalidate(). + delete (nsJSObjWrapper *)npobj; +} + +// static +void +nsJSObjWrapper::NP_Invalidate(NPObject *npobj) +{ + nsJSObjWrapper *jsnpobj = (nsJSObjWrapper *)npobj; + + if (jsnpobj && jsnpobj->mJSObj) { + + if (sJSObjWrappersAccessible) { + // Remove the wrapper from the hash + nsJSObjWrapperKey key(jsnpobj->mJSObj, jsnpobj->mNpp); + JSObjWrapperTable::Ptr ptr = sJSObjWrappers.lookup(key); + MOZ_ASSERT(ptr.found()); + sJSObjWrappers.remove(ptr); + } + + // Forget our reference to the JSObject. + jsnpobj->mJSObj = nullptr; + } +} + +static bool +GetProperty(JSContext *cx, JSObject *objArg, NPIdentifier npid, JS::MutableHandle<JS::Value> rval) +{ + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<JSObject *> obj(cx, objArg); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + return ::JS_GetPropertyById(cx, obj, id, rval); +} + +// static +bool +nsJSObjWrapper::NP_HasMethod(NPObject *npobj, NPIdentifier id) +{ + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI HasMethod"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_HasMethod!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + + JSAutoCompartment ac(cx, npjsobj->mJSObj); + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + + JS::Rooted<JS::Value> v(cx); + bool ok = GetProperty(cx, npjsobj->mJSObj, id, &v); + + return ok && !v.isPrimitive() && + ::JS_ObjectIsFunction(cx, v.toObjectOrNull()); +} + +static bool +doInvoke(NPObject *npobj, NPIdentifier method, const NPVariant *args, + uint32_t argCount, bool ctorCall, NPVariant *result) +{ + NPP npp = NPPStack::Peek(); + + nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + // We're about to run script via JS_CallFunctionValue, so we need an + // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec. + dom::AutoEntryScript aes(globalObject, "NPAPI doInvoke"); + JSContext *cx = aes.cx(); + + if (!npobj || !result) { + ThrowJSExceptionASCII(cx, "Null npobj, or result in doInvoke!"); + + return false; + } + + // Initialize *result + VOID_TO_NPVARIANT(*result); + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsobj); + JS::Rooted<JS::Value> fv(cx); + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + + if (method != NPIdentifier_VOID) { + if (!GetProperty(cx, jsobj, method, &fv) || + ::JS_TypeOfValue(cx, fv) != JSTYPE_FUNCTION) { + return false; + } + } else { + fv.setObject(*jsobj); + } + + // Convert args + JS::AutoValueVector jsargs(cx); + if (!jsargs.reserve(argCount)) { + ::JS_ReportOutOfMemory(cx); + return false; + } + for (uint32_t i = 0; i < argCount; ++i) { + jsargs.infallibleAppend(NPVariantToJSVal(npp, cx, args + i)); + } + + JS::Rooted<JS::Value> v(cx); + bool ok = false; + + if (ctorCall) { + JSObject *newObj = + ::JS_New(cx, jsobj, jsargs); + + if (newObj) { + v.setObject(*newObj); + ok = true; + } + } else { + ok = ::JS_CallFunctionValue(cx, jsobj, fv, jsargs, &v); + } + + if (ok) + ok = JSValToNPVariant(npp, cx, v, result); + + return ok; +} + +// static +bool +nsJSObjWrapper::NP_Invoke(NPObject *npobj, NPIdentifier method, + const NPVariant *args, uint32_t argCount, + NPVariant *result) +{ + if (method == NPIdentifier_VOID) { + return false; + } + + return doInvoke(npobj, method, args, argCount, false, result); +} + +// static +bool +nsJSObjWrapper::NP_InvokeDefault(NPObject *npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result) +{ + return doInvoke(npobj, NPIdentifier_VOID, args, argCount, false, + result); +} + +// static +bool +nsJSObjWrapper::NP_HasProperty(NPObject *npobj, NPIdentifier npid) +{ + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI HasProperty"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_HasProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + bool found, ok = false; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsobj); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_HasPropertyById(cx, jsobj, id, &found); + return ok && found; +} + +// static +bool +nsJSObjWrapper::NP_GetProperty(NPObject *npobj, NPIdentifier id, + NPVariant *result) +{ + NPP npp = NPPStack::Peek(); + + nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + // We're about to run script via JS_CallFunctionValue, so we need an + // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec. + dom::AutoEntryScript aes(globalObject, "NPAPI get"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_GetProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JSAutoCompartment ac(cx, npjsobj->mJSObj); + + JS::Rooted<JS::Value> v(cx); + return (GetProperty(cx, npjsobj->mJSObj, id, &v) && + JSValToNPVariant(npp, cx, v, result)); +} + +// static +bool +nsJSObjWrapper::NP_SetProperty(NPObject *npobj, NPIdentifier npid, + const NPVariant *value) +{ + NPP npp = NPPStack::Peek(); + + nsCOMPtr<nsIGlobalObject> globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + // We're about to run script via JS_CallFunctionValue, so we need an + // AutoEntryScript. NPAPI plugins are Gecko-specific and not in any spec. + dom::AutoEntryScript aes(globalObject, "NPAPI set"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_SetProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + bool ok = false; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsObj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsObj); + + JS::Rooted<JS::Value> v(cx, NPVariantToJSVal(npp, cx, value)); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_SetPropertyById(cx, jsObj, id, v); + + return ok; +} + +// static +bool +nsJSObjWrapper::NP_RemoveProperty(NPObject *npobj, NPIdentifier npid) +{ + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI RemoveProperty"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_RemoveProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::ObjectOpResult result; + JS::Rooted<JSObject*> obj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, obj); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + if (!::JS_DeletePropertyById(cx, obj, id, result)) + return false; + + if (result) { + // FIXME: See bug 425823, we shouldn't need to do this, and once + // that bug is fixed we can remove this code. + bool hasProp; + if (!::JS_HasPropertyById(cx, obj, id, &hasProp)) + return false; + if (!hasProp) + return true; + + // The property might have been deleted, but it got + // re-resolved, so no, it's not really deleted. + result.failCantDelete(); + } + + return result.reportError(cx, obj, id); +} + +//static +bool +nsJSObjWrapper::NP_Enumerate(NPObject *npobj, NPIdentifier **idarray, + uint32_t *count) +{ + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI Enumerate"); + JSContext *cx = aes.cx(); + + *idarray = 0; + *count = 0; + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_Enumerate!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsobj); + + JS::Rooted<JS::IdVector> ida(cx, JS::IdVector(cx)); + if (!JS_Enumerate(cx, jsobj, &ida)) { + return false; + } + + *count = ida.length(); + *idarray = (NPIdentifier *)PR_Malloc(*count * sizeof(NPIdentifier)); + if (!*idarray) { + ThrowJSExceptionASCII(cx, "Memory allocation failed for NPIdentifier!"); + return false; + } + + for (uint32_t i = 0; i < *count; i++) { + JS::Rooted<JS::Value> v(cx); + if (!JS_IdToValue(cx, ida[i], &v)) { + PR_Free(*idarray); + return false; + } + + NPIdentifier id; + if (v.isString()) { + JS::Rooted<JSString*> str(cx, v.toString()); + str = JS_AtomizeAndPinJSString(cx, str); + if (!str) { + PR_Free(*idarray); + return false; + } + id = StringToNPIdentifier(cx, str); + } else { + NS_ASSERTION(v.isInt32(), + "The element in ida must be either string or int!\n"); + id = IntToNPIdentifier(v.toInt32()); + } + + (*idarray)[i] = id; + } + + return true; +} + +//static +bool +nsJSObjWrapper::NP_Construct(NPObject *npobj, const NPVariant *args, + uint32_t argCount, NPVariant *result) +{ + return doInvoke(npobj, NPIdentifier_VOID, args, argCount, true, result); +} + +// Look up or create an NPObject that wraps the JSObject obj. + +// static +NPObject * +nsJSObjWrapper::GetNewOrUsed(NPP npp, JS::Handle<JSObject*> obj) +{ + if (!npp) { + NS_ERROR("Null NPP passed to nsJSObjWrapper::GetNewOrUsed()!"); + + return nullptr; + } + + // If we're running out-of-process and initializing asynchronously, and if + // the plugin has been asked to destroy itself during initialization, + // don't return any new NPObjects. + nsNPAPIPluginInstance* inst = static_cast<nsNPAPIPluginInstance*>(npp->ndata); + if (inst->GetPlugin()->GetLibrary()->IsOOP()) { + PluginAsyncSurrogate* surrogate = PluginAsyncSurrogate::Cast(npp); + if (surrogate && surrogate->IsDestroyPending()) { + return nullptr; + } + } + + // No need to enter the right compartment here as we only get the + // class and private from the JSObject, neither of which cares about + // compartments. + + if (nsNPObjWrapper::IsWrapper(obj)) { + // obj is one of our own, its private data is the NPObject we're + // looking for. + + NPObject *npobj = (NPObject *)::JS_GetPrivate(obj); + + // If the private is null, that means that the object has already been torn + // down, possible because the owning plugin was destroyed (there can be + // multiple plugins, so the fact that it was destroyed does not prevent one + // of its dead JS objects from being passed to another plugin). There's not + // much use in wrapping such a dead object, so we just return null, causing + // us to throw. + if (!npobj) + return nullptr; + + if (LookupNPP(npobj) == npp) + return _retainobject(npobj); + } + + if (!sJSObjWrappers.initialized()) { + // No hash yet (or any more), initialize it. + if (!CreateJSObjWrapperTable()) + return nullptr; + } + MOZ_ASSERT(sJSObjWrappersAccessible); + + JSObjWrapperTable::Ptr p = sJSObjWrappers.lookupForAdd(nsJSObjWrapperKey(obj, npp)); + if (p) { + MOZ_ASSERT(p->value()); + // Found a live nsJSObjWrapper, return it. + + return _retainobject(p->value()); + } + + // No existing nsJSObjWrapper, create one. + + nsJSObjWrapper *wrapper = + (nsJSObjWrapper *)_createobject(npp, &sJSObjWrapperNPClass); + + if (!wrapper) { + // Out of memory, entry not yet added to table. + return nullptr; + } + + wrapper->mJSObj = obj; + + // Insert the new wrapper into the hashtable, rooting the JSObject. Its + // lifetime is now tied to that of the NPObject. + if (!sJSObjWrappers.putNew(nsJSObjWrapperKey(obj, npp), wrapper)) { + // Out of memory, free the wrapper we created. + _releaseobject(wrapper); + return nullptr; + } + + return wrapper; +} + +// Climb the prototype chain, unwrapping as necessary until we find an NP object +// wrapper. +// +// Because this function unwraps, its return value must be wrapped for the cx +// compartment for callers that plan to hold onto the result or do anything +// substantial with it. +static JSObject * +GetNPObjectWrapper(JSContext *cx, JS::Handle<JSObject*> aObj, bool wrapResult = true) +{ + JS::Rooted<JSObject*> obj(cx, aObj); + while (obj && (obj = js::CheckedUnwrap(obj))) { + if (nsNPObjWrapper::IsWrapper(obj)) { + if (wrapResult && !JS_WrapObject(cx, &obj)) { + return nullptr; + } + return obj; + } + + JSAutoCompartment ac(cx, obj); + if (!::JS_GetPrototype(cx, obj, &obj)) { + return nullptr; + } + } + return nullptr; +} + +static NPObject * +GetNPObject(JSContext *cx, JS::Handle<JSObject*> aObj) +{ + JS::Rooted<JSObject*> obj(cx, aObj); + obj = GetNPObjectWrapper(cx, obj, /* wrapResult = */ false); + if (!obj) { + return nullptr; + } + + return (NPObject *)::JS_GetPrivate(obj); +} + + +// Does not actually add a property because this is always followed by a +// SetProperty call. +static bool +NPObjWrapper_AddProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::Handle<JS::Value> v) +{ + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->hasMethod) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + if (NPObjectIsOutOfProcessProxy(npobj)) { + return true; + } + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (hasProperty) + return true; + + // We must permit methods here since JS_DefineUCFunction() will add + // the function as a property + bool hasMethod = npobj->_class->hasMethod(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (!hasMethod) { + ThrowJSExceptionASCII(cx, "Trying to add unsupported property on NPObject!"); + + return false; + } + + return true; +} + +static bool +NPObjWrapper_DelProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::ObjectOpResult &result) +{ + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->removeProperty) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + if (!NPObjectIsOutOfProcessProxy(npobj)) { + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (!hasProperty) + return result.succeed(); + } + + // This removeProperty hook may throw an exception and return false; or just + // return false without an exception pending, which behaves like `delete + // obj.prop` returning false: in strict mode it becomes a TypeError. Legacy + // code---nothing else that uses the JSAPI works this way anymore. + bool succeeded = npobj->_class->removeProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + return succeeded ? result.succeed() : result.failCantDelete(); +} + +static bool +NPObjWrapper_SetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp, JS::ObjectOpResult &result) +{ + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->setProperty) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + // Find out what plugin (NPP) is the owner of the object we're + // manipulating, and make it own any JSObject wrappers created here. + NPP npp = LookupNPP(npobj); + + if (!npp) { + ThrowJSExceptionASCII(cx, "No NPP found for NPObject!"); + + return false; + } + + PluginDestructionGuard pdg(npp); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + if (!NPObjectIsOutOfProcessProxy(npobj)) { + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (!hasProperty) { + ThrowJSExceptionASCII(cx, "Trying to set unsupported property on NPObject!"); + + return false; + } + } + + NPVariant npv; + if (!JSValToNPVariant(npp, cx, vp, &npv)) { + ThrowJSExceptionASCII(cx, "Error converting jsval to NPVariant!"); + + return false; + } + + bool ok = npobj->_class->setProperty(npobj, identifier, &npv); + _releasevariantvalue(&npv); // Release the variant + if (!ReportExceptionIfPending(cx)) + return false; + + if (!ok) { + ThrowJSExceptionASCII(cx, "Error setting property on NPObject!"); + + return false; + } + + return result.succeed(); +} + +static bool +NPObjWrapper_GetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp) +{ + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->hasMethod || !npobj->_class->getProperty) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + if (JSID_IS_SYMBOL(id)) { + JS::RootedSymbol sym(cx, JSID_TO_SYMBOL(id)); + if (JS::GetSymbolCode(sym) == JS::SymbolCode::toPrimitive) { + JS::RootedObject obj(cx, JS_GetFunctionObject( + JS_NewFunction( + cx, NPObjWrapper_toPrimitive, 1, 0, + "Symbol.toPrimitive"))); + if (!obj) + return false; + vp.setObject(*obj); + return true; + } + + if (JS::GetSymbolCode(sym) == JS::SymbolCode::toStringTag) { + JS::RootedString tag(cx, JS_NewStringCopyZ(cx, NPRUNTIME_JSCLASS_NAME)); + if (!tag) { + return false; + } + + vp.setString(tag); + return true; + } + + vp.setUndefined(); + return true; + } + + // Find out what plugin (NPP) is the owner of the object we're + // manipulating, and make it own any JSObject wrappers created here. + NPP npp = LookupNPP(npobj); + if (!npp) { + ThrowJSExceptionASCII(cx, "No NPP found for NPObject!"); + + return false; + } + + PluginDestructionGuard pdg(npp); + + bool hasProperty, hasMethod; + + NPVariant npv; + VOID_TO_NPVARIANT(npv); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + if (NPObjectIsOutOfProcessProxy(npobj)) { + PluginScriptableObjectParent* actor = nullptr; + PluginAsyncSurrogate* surrogate = nullptr; + CastNPObject(npobj, actor, surrogate); + + // actor and surrogate may be null if the plugin crashed. + if (!actor && !surrogate) + return false; + + bool success = false; + if (surrogate) { + success = surrogate->GetPropertyHelper(npobj, identifier, &hasProperty, + &hasMethod, &npv); + } else if (actor) { + success = actor->GetPropertyHelper(identifier, &hasProperty, &hasMethod, + &npv); + } + + if (!ReportExceptionIfPending(cx)) { + if (success) + _releasevariantvalue(&npv); + return false; + } + + if (success) { + // We return NPObject Member class here to support ambiguous members. + if (hasProperty && hasMethod) + return CreateNPObjectMember(npp, cx, obj, npobj, id, &npv, vp); + + if (hasProperty) { + vp.set(NPVariantToJSVal(npp, cx, &npv)); + _releasevariantvalue(&npv); + + if (!ReportExceptionIfPending(cx)) + return false; + } + } + return true; + } + + hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + hasMethod = npobj->_class->hasMethod(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + // We return NPObject Member class here to support ambiguous members. + if (hasProperty && hasMethod) + return CreateNPObjectMember(npp, cx, obj, npobj, id, nullptr, vp); + + if (hasProperty) { + if (npobj->_class->getProperty(npobj, identifier, &npv)) + vp.set(NPVariantToJSVal(npp, cx, &npv)); + + _releasevariantvalue(&npv); + + if (!ReportExceptionIfPending(cx)) + return false; + } + + return true; +} + +static bool +CallNPMethodInternal(JSContext *cx, JS::Handle<JSObject*> obj, unsigned argc, + JS::Value *argv, JS::Value *rval, bool ctorCall) +{ + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + // Find out what plugin (NPP) is the owner of the object we're + // manipulating, and make it own any JSObject wrappers created here. + NPP npp = LookupNPP(npobj); + + if (!npp) { + ThrowJSExceptionASCII(cx, "Error finding NPP for NPObject!"); + + return false; + } + + PluginDestructionGuard pdg(npp); + + NPVariant npargs_buf[8]; + NPVariant *npargs = npargs_buf; + + if (argc > (sizeof(npargs_buf) / sizeof(NPVariant))) { + // Our stack buffer isn't large enough to hold all arguments, + // malloc a buffer. + npargs = (NPVariant *)PR_Malloc(argc * sizeof(NPVariant)); + + if (!npargs) { + ThrowJSExceptionASCII(cx, "Out of memory!"); + + return false; + } + } + + // Convert arguments + uint32_t i; + for (i = 0; i < argc; ++i) { + if (!JSValToNPVariant(npp, cx, argv[i], npargs + i)) { + ThrowJSExceptionASCII(cx, "Error converting jsvals to NPVariants!"); + + if (npargs != npargs_buf) { + PR_Free(npargs); + } + + return false; + } + } + + NPVariant v; + VOID_TO_NPVARIANT(v); + + JSObject *funobj = argv[-2].toObjectOrNull(); + bool ok; + const char *msg = "Error calling method on NPObject!"; + + if (ctorCall) { + // construct a new NPObject based on the NPClass in npobj. Fail if + // no construct method is available. + + if (NP_CLASS_STRUCT_VERSION_HAS_CTOR(npobj->_class) && + npobj->_class->construct) { + ok = npobj->_class->construct(npobj, npargs, argc, &v); + } else { + ok = false; + + msg = "Attempt to construct object from class with no constructor."; + } + } else if (funobj != obj) { + // A obj.function() style call is made, get the method name from + // the function object. + + if (npobj->_class->invoke) { + JSFunction *fun = ::JS_GetObjectFunction(funobj); + JS::Rooted<JSString*> funId(cx, ::JS_GetFunctionId(fun)); + JSString *name = ::JS_AtomizeAndPinJSString(cx, funId); + NPIdentifier id = StringToNPIdentifier(cx, name); + + ok = npobj->_class->invoke(npobj, id, npargs, argc, &v); + } else { + ok = false; + + msg = "Attempt to call a method on object with no invoke method."; + } + } else { + if (npobj->_class->invokeDefault) { + // obj is a callable object that is being called, no method name + // available then. Invoke the default method. + + ok = npobj->_class->invokeDefault(npobj, npargs, argc, &v); + } else { + ok = false; + + msg = "Attempt to call a default method on object with no " + "invokeDefault method."; + } + } + + // Release arguments. + for (i = 0; i < argc; ++i) { + _releasevariantvalue(npargs + i); + } + + if (npargs != npargs_buf) { + PR_Free(npargs); + } + + if (!ok) { + // ReportExceptionIfPending returns a return value, which is true + // if no exception was thrown. In that case, throw our own. + if (ReportExceptionIfPending(cx)) + ThrowJSExceptionASCII(cx, msg); + + return false; + } + + *rval = NPVariantToJSVal(npp, cx, &v); + + // *rval now owns the value, release our reference. + _releasevariantvalue(&v); + + return ReportExceptionIfPending(cx); +} + +static bool +CallNPMethod(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + + return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false); +} + +static bool +NPObjWrapper_Enumerate(JSContext *cx, JS::Handle<JSObject*> obj, + JS::AutoIdVector &properties, bool enumerableOnly) +{ + NPObject *npobj = GetNPObject(cx, obj); + if (!npobj || !npobj->_class) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + return false; + } + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(npobj->_class) || + !npobj->_class->enumerate) { + return true; + } + + NPIdentifier *identifiers; + uint32_t length; + if (!npobj->_class->enumerate(npobj, &identifiers, &length)) { + if (ReportExceptionIfPending(cx)) { + // ReportExceptionIfPending returns a return value, which is true + // if no exception was thrown. In that case, throw our own. + ThrowJSExceptionASCII(cx, "Error enumerating properties on scriptable " + "plugin object"); + } + return false; + } + + if (!properties.reserve(length)) + return false; + + JS::Rooted<jsid> id(cx); + for (uint32_t i = 0; i < length; i++) { + id = NPIdentifierToJSId(identifiers[i]); + properties.infallibleAppend(id); + } + + PR_Free(identifiers); + return true; +} + +static bool +NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, + bool *resolvedp) +{ + if (JSID_IS_SYMBOL(id)) + return true; + + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::JS); + + NPObject *npobj = GetNPObject(cx, obj); + + if (!npobj || !npobj->_class || !npobj->_class->hasProperty || + !npobj->_class->hasMethod) { + ThrowJSExceptionASCII(cx, "Bad NPObject as private data!"); + + return false; + } + + PluginDestructionGuard pdg(LookupNPP(npobj)); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + bool hasProperty = npobj->_class->hasProperty(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (hasProperty) { + NS_ASSERTION(JSID_IS_STRING(id) || JSID_IS_INT(id), + "id must be either string or int!\n"); + if (!::JS_DefinePropertyById(cx, obj, id, JS::UndefinedHandleValue, + JSPROP_ENUMERATE | JSPROP_SHARED)) { + return false; + } + + *resolvedp = true; + + return true; + } + + bool hasMethod = npobj->_class->hasMethod(npobj, identifier); + if (!ReportExceptionIfPending(cx)) + return false; + + if (hasMethod) { + NS_ASSERTION(JSID_IS_STRING(id) || JSID_IS_INT(id), + "id must be either string or int!\n"); + + JSFunction *fnc = ::JS_DefineFunctionById(cx, obj, id, CallNPMethod, 0, + JSPROP_ENUMERATE); + + *resolvedp = true; + + return fnc != nullptr; + } + + // no property or method + return true; +} + +static void +NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj) +{ + NPObject *npobj = (NPObject *)::JS_GetPrivate(obj); + if (npobj) { + if (sNPObjWrappers) { + sNPObjWrappers->Remove(npobj); + } + } + + if (!sDelayedReleases) + sDelayedReleases = new nsTArray<NPObject*>; + sDelayedReleases->AppendElement(npobj); +} + +static void +NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old) +{ + // The wrapper JSObject has been moved, so we need to update the entry in the + // sNPObjWrappers hash table, if present. + + if (!sNPObjWrappers) { + return; + } + + NPObject *npobj = (NPObject *)::JS_GetPrivate(obj); + if (!npobj) { + return; + } + + // Calling PLDHashTable::Search() will not result in GC. + JS::AutoSuppressGCAnalysis nogc; + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj)); + MOZ_ASSERT(entry && entry->mJSObj); + MOZ_ASSERT(entry->mJSObj.unbarrieredGetPtr() == old); + entry->mJSObj = obj; +} + +static bool +NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> obj(cx, &args.callee()); + return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false); +} + +static bool +NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> obj(cx, &args.callee()); + return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, true); +} + +static bool +NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp) +{ + // Plugins do not simply use the default OrdinaryToPrimitive behavior, + // because that behavior involves calling toString or valueOf on objects + // which weren't designed to accommodate this. Usually this wouldn't be a + // problem, because the absence of either property, or the presence of either + // property with a value that isn't callable, will cause that property to + // simply be ignored. But there is a problem in one specific case: Java, + // specifically java.lang.Integer. The Integer class has static valueOf + // methods, none of which are nullary, so the JS-reflected method will behave + // poorly when called with no arguments. We work around this problem by + // giving plugins a [Symbol.toPrimitive]() method which uses only toString + // and not valueOf. + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedValue thisv(cx, args.thisv()); + if (thisv.isPrimitive()) + return true; + + JS::RootedObject obj(cx, &thisv.toObject()); + JS::RootedValue v(cx); + if (!JS_GetProperty(cx, obj, "toString", &v)) + return false; + if (v.isObject() && JS::IsCallable(&v.toObject())) { + if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), args.rval())) + return false; + if (args.rval().isPrimitive()) + return true; + } + + JS_ReportErrorNumberASCII(cx, js::GetErrorMessage, nullptr, + JSMSG_CANT_CONVERT_TO, + JS_GetClass(obj)->name, "primitive type"); + return false; +} + +bool +nsNPObjWrapper::IsWrapper(JSObject *obj) +{ + return js::GetObjectClass(obj) == &sNPObjectJSWrapperClass; +} + +// An NPObject is going away, make sure we null out the JS object's +// private data in case this is an NPObject that came from a plugin +// and it's destroyed prematurely. + +// static +void +nsNPObjWrapper::OnDestroy(NPObject *npobj) +{ + if (!npobj) { + return; + } + + if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { + // npobj is one of our own, no private data to clean up here. + + return; + } + + if (!sNPObjWrappers) { + // No hash yet (or any more), no used wrappers available. + + return; + } + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj)); + + if (entry && entry->mJSObj) { + // Found a live NPObject wrapper, null out its JSObjects' private + // data. + + ::JS_SetPrivate(entry->mJSObj, nullptr); + + // Remove the npobj from the hash now that it went away. + sNPObjWrappers->RawRemove(entry); + + // The finalize hook will call OnWrapperDestroyed(). + } +} + +// Look up or create a JSObject that wraps the NPObject npobj. + +// static +JSObject * +nsNPObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, NPObject *npobj) +{ + if (!npobj) { + NS_ERROR("Null NPObject passed to nsNPObjWrapper::GetNewOrUsed()!"); + + return nullptr; + } + + if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { + // npobj is one of our own, return its existing JSObject. + + JS::Rooted<JSObject*> obj(cx, ((nsJSObjWrapper *)npobj)->mJSObj); + if (!JS_WrapObject(cx, &obj)) { + return nullptr; + } + return obj; + } + + if (!npp) { + NS_ERROR("No npp passed to nsNPObjWrapper::GetNewOrUsed()!"); + + return nullptr; + } + + if (!sNPObjWrappers) { + // No hash yet (or any more), initialize it. + if (!CreateNPObjWrapperTable()) { + return nullptr; + } + } + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Add(npobj, fallible)); + + if (!entry) { + // Out of memory + JS_ReportOutOfMemory(cx); + + return nullptr; + } + + if (entry->mJSObj) { + // Found a live NPObject wrapper. It may not be in the same compartment + // as cx, so we need to wrap it before returning it. + JS::Rooted<JSObject*> obj(cx, entry->mJSObj); + if (!JS_WrapObject(cx, &obj)) { + return nullptr; + } + return obj; + } + + entry->mNPObj = npobj; + entry->mNpp = npp; + + uint32_t generation = sNPObjWrappers->Generation(); + + // No existing JSObject, create one. + + JS::Rooted<JSObject*> obj(cx, ::JS_NewObject(cx, js::Jsvalify(&sNPObjectJSWrapperClass))); + + if (generation != sNPObjWrappers->Generation()) { + // Reload entry if the JS_NewObject call caused a GC and reallocated + // the table (see bug 445229). This is guaranteed to succeed. + + entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj)); + NS_ASSERTION(entry, "Hashtable didn't find what we just added?"); + } + + if (!obj) { + // OOM? Remove the stale entry from the hash. + + sNPObjWrappers->RawRemove(entry); + + return nullptr; + } + + OnWrapperCreated(); + + entry->mJSObj = obj; + + ::JS_SetPrivate(obj, npobj); + + // The new JSObject now holds on to npobj + _retainobject(npobj); + + return obj; +} + +// static +void +nsJSNPRuntime::OnPluginDestroy(NPP npp) +{ + if (sJSObjWrappersAccessible) { + + // Prevent modification of sJSObjWrappers table if we go reentrant. + sJSObjWrappersAccessible = false; + + for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) { + nsJSObjWrapper *npobj = e.front().value(); + MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass); + if (npobj->mNpp == npp) { + if (npobj->_class && npobj->_class->invalidate) { + npobj->_class->invalidate(npobj); + } + + _releaseobject(npobj); + + e.removeFront(); + } + } + + sJSObjWrappersAccessible = true; + } + + if (sNPObjWrappers) { + for (auto i = sNPObjWrappers->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast<NPObjWrapperHashEntry*>(i.Get()); + + if (entry->mNpp == npp) { + // HACK: temporarily hide the table we're enumerating so that + // invalidate() and deallocate() don't touch it. + PLDHashTable *tmp = sNPObjWrappers; + sNPObjWrappers = nullptr; + + NPObject *npobj = entry->mNPObj; + + if (npobj->_class && npobj->_class->invalidate) { + npobj->_class->invalidate(npobj); + } + +#ifdef NS_BUILD_REFCNT_LOGGING + { + int32_t refCnt = npobj->referenceCount; + while (refCnt) { + --refCnt; + NS_LOG_RELEASE(npobj, refCnt, "BrowserNPObject"); + } + } +#endif + + // Force deallocation of plugin objects since the plugin they came + // from is being torn down. + if (npobj->_class && npobj->_class->deallocate) { + npobj->_class->deallocate(npobj); + } else { + PR_Free(npobj); + } + + ::JS_SetPrivate(entry->mJSObj, nullptr); + + sNPObjWrappers = tmp; + + if (sDelayedReleases && sDelayedReleases->RemoveElement(npobj)) { + OnWrapperDestroyed(); + } + + i.Remove(); + } + } + } +} + +// static +void +nsJSNPRuntime::OnPluginDestroyPending(NPP npp) +{ + if (sJSObjWrappersAccessible) { + // Prevent modification of sJSObjWrappers table if we go reentrant. + sJSObjWrappersAccessible = false; + for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) { + nsJSObjWrapper *npobj = e.front().value(); + MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass); + if (npobj->mNpp == npp) { + npobj->mDestroyPending = true; + } + } + sJSObjWrappersAccessible = true; + } +} + +// Find the NPP for a NPObject. +static NPP +LookupNPP(NPObject *npobj) +{ + if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) { + nsJSObjWrapper* o = static_cast<nsJSObjWrapper*>(npobj); + return o->mNpp; + } + + auto entry = + static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Add(npobj, fallible)); + + if (!entry) { + return nullptr; + } + + NS_ASSERTION(entry->mNpp, "Live NPObject entry w/o an NPP!"); + + return entry->mNpp; +} + +static bool +CreateNPObjectMember(NPP npp, JSContext *cx, + JS::Handle<JSObject*> aObj, NPObject* npobj, + JS::Handle<jsid> id, NPVariant* getPropertyResult, + JS::MutableHandle<JS::Value> vp) +{ + if (!npobj || !npobj->_class || !npobj->_class->getProperty || + !npobj->_class->invoke) { + ThrowJSExceptionASCII(cx, "Bad NPObject"); + + return false; + } + + NPObjectMemberPrivate *memberPrivate = + (NPObjectMemberPrivate *)PR_Malloc(sizeof(NPObjectMemberPrivate)); + if (!memberPrivate) + return false; + + // Make sure to clear all members in case something fails here + // during initialization. + memset(memberPrivate, 0, sizeof(NPObjectMemberPrivate)); + + JS::Rooted<JSObject*> obj(cx, aObj); + + JSObject *memobj = ::JS_NewObject(cx, &sNPObjectMemberClass); + if (!memobj) { + PR_Free(memberPrivate); + return false; + } + + vp.setObject(*memobj); + + ::JS_SetPrivate(memobj, (void *)memberPrivate); + + NPIdentifier identifier = JSIdToNPIdentifier(id); + + JS::Rooted<JS::Value> fieldValue(cx); + NPVariant npv; + + if (getPropertyResult) { + // Plugin has already handed us the value we want here. + npv = *getPropertyResult; + } + else { + VOID_TO_NPVARIANT(npv); + + NPBool hasProperty = npobj->_class->getProperty(npobj, identifier, + &npv); + if (!ReportExceptionIfPending(cx) || !hasProperty) { + return false; + } + } + + fieldValue = NPVariantToJSVal(npp, cx, &npv); + + // npobjWrapper is the JSObject through which we make sure we don't + // outlive the underlying NPObject, so make sure it points to the + // real JSObject wrapper for the NPObject. + obj = GetNPObjectWrapper(cx, obj); + + memberPrivate->npobjWrapper = obj; + + memberPrivate->fieldValue = fieldValue; + memberPrivate->methodName = id; + memberPrivate->npp = npp; + + return true; +} + +static bool +NPObjectMember_GetProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id, + JS::MutableHandleValue vp) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + + if (JSID_IS_SYMBOL(id)) { + JS::RootedSymbol sym(cx, JSID_TO_SYMBOL(id)); + if (JS::GetSymbolCode(sym) == JS::SymbolCode::toPrimitive) { + JS::RootedObject obj(cx, JS_GetFunctionObject( + JS_NewFunction( + cx, NPObjectMember_toPrimitive, 1, 0, + "Symbol.toPrimitive"))); + if (!obj) + return false; + vp.setObject(*obj); + return true; + } + } + + return true; +} + +static void +NPObjectMember_Finalize(JSFreeOp *fop, JSObject *obj) +{ + NPObjectMemberPrivate *memberPrivate; + + memberPrivate = (NPObjectMemberPrivate *)::JS_GetPrivate(obj); + if (!memberPrivate) + return; + + PR_Free(memberPrivate); +} + +static bool +NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::Rooted<JSObject*> memobj(cx, &args.callee()); + NS_ENSURE_TRUE(memobj, false); + + NPObjectMemberPrivate *memberPrivate = + (NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, memobj, + &sNPObjectMemberClass, + &args); + if (!memberPrivate || !memberPrivate->npobjWrapper) + return false; + + JS::Rooted<JSObject*> objWrapper(cx, memberPrivate->npobjWrapper); + NPObject *npobj = GetNPObject(cx, objWrapper); + if (!npobj) { + ThrowJSExceptionASCII(cx, "Call on invalid member object"); + + return false; + } + + NPVariant npargs_buf[8]; + NPVariant *npargs = npargs_buf; + + if (args.length() > (sizeof(npargs_buf) / sizeof(NPVariant))) { + // Our stack buffer isn't large enough to hold all arguments, + // malloc a buffer. + npargs = (NPVariant *)PR_Malloc(args.length() * sizeof(NPVariant)); + + if (!npargs) { + ThrowJSExceptionASCII(cx, "Out of memory!"); + + return false; + } + } + + // Convert arguments + for (uint32_t i = 0; i < args.length(); ++i) { + if (!JSValToNPVariant(memberPrivate->npp, cx, args[i], npargs + i)) { + ThrowJSExceptionASCII(cx, "Error converting jsvals to NPVariants!"); + + if (npargs != npargs_buf) { + PR_Free(npargs); + } + + return false; + } + } + + + NPVariant npv; + bool ok = npobj->_class->invoke(npobj, + JSIdToNPIdentifier(memberPrivate->methodName), + npargs, args.length(), &npv); + + // Release arguments. + for (uint32_t i = 0; i < args.length(); ++i) { + _releasevariantvalue(npargs + i); + } + + if (npargs != npargs_buf) { + PR_Free(npargs); + } + + if (!ok) { + // ReportExceptionIfPending returns a return value, which is true + // if no exception was thrown. In that case, throw our own. + if (ReportExceptionIfPending(cx)) + ThrowJSExceptionASCII(cx, "Error calling method on NPObject!"); + + return false; + } + + args.rval().set(NPVariantToJSVal(memberPrivate->npp, cx, &npv)); + + // *vp now owns the value, release our reference. + _releasevariantvalue(&npv); + + return ReportExceptionIfPending(cx); +} + +static void +NPObjectMember_Trace(JSTracer *trc, JSObject *obj) +{ + NPObjectMemberPrivate *memberPrivate = + (NPObjectMemberPrivate *)::JS_GetPrivate(obj); + if (!memberPrivate) + return; + + // Our NPIdentifier is not always interned, so we must trace it. + JS::TraceEdge(trc, &memberPrivate->methodName, "NPObjectMemberPrivate.methodName"); + + JS::TraceEdge(trc, &memberPrivate->fieldValue, "NPObject Member => fieldValue"); + + // There's no strong reference from our private data to the + // NPObject, so make sure to mark the NPObject wrapper to keep the + // NPObject alive as long as this NPObjectMember is alive. + JS::TraceEdge(trc, &memberPrivate->npobjWrapper, + "NPObject Member => npobjWrapper"); +} + +static bool +NPObjectMember_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + JS::RootedValue thisv(cx, args.thisv()); + if (thisv.isPrimitive()) { + args.rval().set(thisv); + return true; + } + + JS::RootedObject obj(cx, &thisv.toObject()); + NPObjectMemberPrivate *memberPrivate = + (NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, obj, + &sNPObjectMemberClass, + &args); + if (!memberPrivate) + return false; + + JSType hint; + if (!JS::GetFirstArgumentAsTypeHint(cx, args, &hint)) + return false; + + args.rval().set(memberPrivate->fieldValue); + if (args.rval().isObject()) { + JS::Rooted<JSObject*> objVal(cx, &args.rval().toObject()); + return JS::ToPrimitive(cx, objVal, hint, args.rval()); + } + return true; +} + +// static +bool +nsJSObjWrapper::HasOwnProperty(NPObject *npobj, NPIdentifier npid) +{ + NPP npp = NPPStack::Peek(); + nsIGlobalObject* globalObject = GetGlobalObject(npp); + if (NS_WARN_IF(!globalObject)) { + return false; + } + + dom::AutoEntryScript aes(globalObject, "NPAPI HasOwnProperty"); + JSContext *cx = aes.cx(); + + if (!npobj) { + ThrowJSExceptionASCII(cx, + "Null npobj in nsJSObjWrapper::NP_HasOwnProperty!"); + + return false; + } + + nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj; + bool found, ok = false; + + AutoJSExceptionSuppressor suppressor(aes, npjsobj); + JS::Rooted<JSObject*> jsobj(cx, npjsobj->mJSObj); + JSAutoCompartment ac(cx, jsobj); + + NS_ASSERTION(NPIdentifierIsInt(npid) || NPIdentifierIsString(npid), + "id must be either string or int!\n"); + JS::Rooted<jsid> id(cx, NPIdentifierToJSId(npid)); + ok = ::JS_AlreadyHasOwnPropertyById(cx, jsobj, id, &found); + return ok && found; +} |