Files
tubestation/dom/plugins/base/nsJSNPRuntime.cpp
Wes Kocher acbbcd2aca Backed out 7 changesets (bug 1206168, bug 1177318, bug 1054756) for hazard build failures CLOSED TREE
Backed out changeset e892727a373a (bug 1206168)
Backed out changeset 6c93d1044b7e (bug 1054756)
Backed out changeset 105433ce195b (bug 1054756)
Backed out changeset 13128a88f2b9 (bug 1054756)
Backed out changeset c250abf4fd17 (bug 1054756)
Backed out changeset fc9fef646a97 (bug 1054756)
Backed out changeset c8897f109a08 (bug 1177318)
2015-09-23 12:31:19 -07:00

2309 lines
61 KiB
C++

/* -*- 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 "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/HashTable.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 : public js::DefaultHasher<nsJSObjWrapperKey>
{
typedef nsJSObjWrapperKey Key;
typedef Key Lookup;
static uint32_t hash(const Lookup &l) {
return HashGeneric(l.mJSObj, l.mNpp);
}
static void rekey(Key &k, const Key& newKey) {
MOZ_ASSERT(k.mNpp == newKey.mNpp);
k.mJSObj = newKey.mJSObj;
}
};
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::HashMap<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 reports any JS exceptions that were thrown while
// the plugin executed JS.
class MOZ_STACK_CLASS AutoJSExceptionReporter
{
public:
AutoJSExceptionReporter(dom::AutoJSAPI& jsapi, nsJSObjWrapper* aWrapper)
: mJsapi(jsapi)
, mIsDestroyPending(aWrapper->mDestroyPending)
{
jsapi.TakeOwnershipOfErrorReporting();
}
~AutoJSExceptionReporter()
{
if (mIsDestroyPending) {
mJsapi.ClearException();
}
}
protected:
dom::AutoJSAPI& mJsapi;
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 bool
NPObjWrapper_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> vp);
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
CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
JS::Handle<jsid> id, NPVariant* getPropertyResult,
JS::MutableHandle<JS::Value> vp);
const static js::Class sNPObjectJSWrapperClass =
{
NPRUNTIME_JSCLASS_NAME,
JSCLASS_HAS_PRIVATE,
NPObjWrapper_AddProperty,
NPObjWrapper_DelProperty,
NPObjWrapper_GetProperty,
NPObjWrapper_SetProperty,
nullptr,
NPObjWrapper_Resolve,
nullptr, /* mayResolve */
NPObjWrapper_Convert,
NPObjWrapper_Finalize,
NPObjWrapper_Call,
nullptr, /* hasInstance */
NPObjWrapper_Construct,
nullptr, /* trace */
JS_NULL_CLASS_SPEC,
{
nullptr, /* outerObject */
nullptr, /* innerObject */
false, /* isWrappedNative */
nullptr, /* weakmapKeyDelegateOp */
NPObjWrapper_ObjectMoved
},
{
nullptr, // lookupProperty
nullptr, // defineProperty
nullptr, // hasProperty
nullptr, // getProperty
nullptr, // setProperty
nullptr, // getOwnPropertyDescriptor
nullptr, // deleteProperty
nullptr, nullptr, // watch/unwatch
nullptr, // getElements
NPObjWrapper_Enumerate,
nullptr,
}
};
typedef struct NPObjectMemberPrivate {
JS::Heap<JSObject *> npobjWrapper;
JS::Heap<JS::Value> fieldValue;
JS::Heap<jsid> methodName;
NPP npp;
} NPObjectMemberPrivate;
static bool
NPObjectMember_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> 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 const JSClass sNPObjectMemberClass =
{
"NPObject Ambiguous Member class", JSCLASS_HAS_PRIVATE,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, NPObjectMember_Convert,
NPObjectMember_Finalize, NPObjectMember_Call,
nullptr, nullptr, NPObjectMember_Trace
};
static void
OnWrapperDestroyed();
static void
TraceJSObjWrappers(JSTracer *trc, void *data)
{
if (!sJSObjWrappers.initialized()) {
return;
}
// Trace all JSObjects in the sJSObjWrappers table and rekey the entries if
// any of them moved.
for (JSObjWrapperTable::Enum e(sJSObjWrappers); !e.empty(); e.popFront()) {
nsJSObjWrapperKey key = e.front().key();
JS_CallUnbarrieredObjectTracer(trc, &key.mJSObj, "sJSObjWrappers key object");
nsJSObjWrapper *wrapper = e.front().value();
if (wrapper->mJSObj) {
JS_CallObjectTracer(trc, &wrapper->mJSObj, "sJSObjWrappers wrapper object");
}
if (key != e.front().key()) {
e.rekeyFront(key);
}
}
}
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;
}
JSRuntime *jsRuntime = xpc::GetJSRuntime();
MOZ_ASSERT(jsRuntime != nullptr);
// Register a callback to trace wrapped JSObjects.
if (!JS_AddExtraGCRootsTracer(jsRuntime, 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);
JSRuntime *jsRuntime = xpc::GetJSRuntime();
MOZ_ASSERT(jsRuntime != nullptr);
// Remove tracing callback.
JS_RemoveExtraGCRootsTracer(jsRuntime, 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);
nsRefPtr<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();
}
JSContext *
GetJSContext(NPP npp)
{
nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(GetGlobalObject(npp));
NS_ENSURE_TRUE(sgo, nullptr);
nsIScriptContext *scx = sgo->GetContext();
NS_ENSURE_TRUE(scx, nullptr);
return scx->GetNativeContext();
}
} // 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, 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, cx, obj);
if (!npobj) {
return false;
}
// Pass over ownership of npobj to *variant
OBJECT_TO_NPVARIANT(npobj, *variant);
return true;
}
static void
ThrowJSException(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_ReportError(cx, message);
}
}
static bool
ReportExceptionIfPending(JSContext *cx)
{
const char *ex = PeekException();
if (!ex) {
return true;
}
ThrowJSException(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();
dom::AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(GetGlobalObject(npp)))) {
return false;
}
JSContext *cx = jsapi.cx();
if (!npobj) {
ThrowJSException(cx,
"Null npobj in nsJSObjWrapper::NP_HasMethod!");
return false;
}
nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
JSAutoCompartment ac(cx, npjsobj->mJSObj);
AutoJSExceptionReporter reporter(jsapi, 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) {
ThrowJSException(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);
AutoJSExceptionReporter reporter(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();
dom::AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(GetGlobalObject(npp)))) {
return false;
}
JSContext *cx = jsapi.cx();
if (!npobj) {
ThrowJSException(cx,
"Null npobj in nsJSObjWrapper::NP_HasProperty!");
return false;
}
nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
bool found, ok = false;
AutoJSExceptionReporter reporter(jsapi, 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) {
ThrowJSException(cx,
"Null npobj in nsJSObjWrapper::NP_GetProperty!");
return false;
}
nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
AutoJSExceptionReporter reporter(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) {
ThrowJSException(cx,
"Null npobj in nsJSObjWrapper::NP_SetProperty!");
return false;
}
nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
bool ok = false;
AutoJSExceptionReporter reporter(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();
dom::AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(GetGlobalObject(npp)))) {
return false;
}
JSContext *cx = jsapi.cx();
if (!npobj) {
ThrowJSException(cx,
"Null npobj in nsJSObjWrapper::NP_RemoveProperty!");
return false;
}
nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
AutoJSExceptionReporter reporter(jsapi, 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();
dom::AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(GetGlobalObject(npp)))) {
return false;
}
JSContext *cx = jsapi.cx();
*idarray = 0;
*count = 0;
if (!npobj) {
ThrowJSException(cx,
"Null npobj in nsJSObjWrapper::NP_Enumerate!");
return false;
}
nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
AutoJSExceptionReporter reporter(jsapi, 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) {
ThrowJSException(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);
}
/*
* This function is called during minor GCs for each key in the sJSObjWrappers
* table that has been moved.
*
* Note that the wrapper may be dead at this point, and even the table may have
* been finalized if all wrappers have died.
*/
static void
JSObjWrapperKeyMarkCallback(JSTracer *trc, JSObject *obj, void *data) {
NPP npp = static_cast<NPP>(data);
MOZ_ASSERT(sJSObjWrappersAccessible);
if (!sJSObjWrappers.initialized())
return;
JSObject *prior = obj;
nsJSObjWrapperKey oldKey(prior, npp);
JSObjWrapperTable::Ptr p = sJSObjWrappers.lookup(oldKey);
if (!p)
return;
JS_CallUnbarrieredObjectTracer(trc, &obj, "sJSObjWrappers key object");
nsJSObjWrapperKey newKey(obj, npp);
sJSObjWrappers.rekeyIfMoved(oldKey, newKey);
}
// Look up or create an NPObject that wraps the JSObject obj.
// static
NPObject *
nsJSObjWrapper::GetNewOrUsed(NPP npp, JSContext *cx, 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;
}
}
if (!cx) {
cx = GetJSContext(npp);
if (!cx) {
NS_ERROR("Unable to find a JSContext in nsJSObjWrapper::GetNewOrUsed()!");
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.
nsJSObjWrapperKey key(obj, npp);
if (!sJSObjWrappers.putNew(key, wrapper)) {
// Out of memory, free the wrapper we created.
_releaseobject(wrapper);
return nullptr;
}
// Add postbarrier for the hashtable key
JS_StoreObjectPostBarrierCallback(cx, JSObjWrapperKeyMarkCallback, obj, wrapper->mNpp);
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, 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;
}
if (!::JS_GetPrototype(cx, obj, &obj)) {
return nullptr;
}
}
return nullptr;
}
static NPObject *
GetNPObject(JSContext *cx, JSObject *obj)
{
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) {
ThrowJSException(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) {
ThrowJSException(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) {
ThrowJSException(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) {
ThrowJSException(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) {
ThrowJSException(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) {
ThrowJSException(cx, "Trying to set unsupported property on NPObject!");
return false;
}
}
NPVariant npv;
if (!JSValToNPVariant(npp, cx, vp, &npv)) {
ThrowJSException(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) {
ThrowJSException(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) {
ThrowJSException(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) {
ThrowJSException(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) {
ThrowJSException(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) {
ThrowJSException(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) {
ThrowJSException(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)) {
ThrowJSException(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))
ThrowJSException(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) {
ThrowJSException(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.
ThrowJSException(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;
NPObject *npobj = GetNPObject(cx, obj);
if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
!npobj->_class->hasMethod) {
ThrowJSException(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 bool
NPObjWrapper_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType hint, JS::MutableHandle<JS::Value> vp)
{
MOZ_ASSERT(hint == JSTYPE_NUMBER || hint == JSTYPE_STRING || hint == JSTYPE_VOID);
// Plugins do not simply use the default [[DefaultValue]] 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
// [[DefaultValue]] which uses only toString and not valueOf.
JS::Rooted<JS::Value> v(cx, JS::UndefinedValue());
if (!JS_GetProperty(cx, obj, "toString", &v))
return false;
if (!v.isPrimitive() && JS::IsCallable(v.toObjectOrNull())) {
if (!JS_CallFunctionValue(cx, obj, v, JS::HandleValueArray::empty(), vp))
return false;
if (vp.isPrimitive())
return true;
}
JS_ReportErrorNumber(cx, js::GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
JS_GetClass(obj)->name,
hint == JSTYPE_VOID
? "primitive type"
: hint == JSTYPE_NUMBER
? "number"
: "string");
return false;
}
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 == 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);
}
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.
NS_ASSERTION(sNPObjWrappers->Search(npobj),
"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, JSObject *obj, NPObject* npobj,
JS::Handle<jsid> id, NPVariant* getPropertyResult,
JS::MutableHandle<JS::Value> vp)
{
if (!npobj || !npobj->_class || !npobj->_class->getProperty ||
!npobj->_class->invoke) {
ThrowJSException(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));
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_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> vp)
{
NPObjectMemberPrivate *memberPrivate =
(NPObjectMemberPrivate *)::JS_GetInstancePrivate(cx, obj,
&sNPObjectMemberClass,
nullptr);
if (!memberPrivate) {
NS_ERROR("no Ambiguous Member Private data!");
return false;
}
switch (type) {
case JSTYPE_VOID:
case JSTYPE_STRING:
case JSTYPE_NUMBER:
vp.set(memberPrivate->fieldValue);
if (vp.isObject()) {
JS::Rooted<JSObject*> objVal(cx, &vp.toObject());
return JS_DefaultValue(cx, objVal, type, vp);
}
return true;
case JSTYPE_BOOLEAN:
case JSTYPE_OBJECT:
vp.set(memberPrivate->fieldValue);
return true;
case JSTYPE_FUNCTION:
// Leave this to NPObjectMember_Call.
return true;
default:
NS_ERROR("illegal operation on JSObject prototype object");
return false;
}
}
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;
NPObject *npobj = GetNPObject(cx, memberPrivate->npobjWrapper);
if (!npobj) {
ThrowJSException(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) {
ThrowJSException(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)) {
ThrowJSException(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))
ThrowJSException(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 root it explicitly.
JS_CallIdTracer(trc, &memberPrivate->methodName, "NPObjectMemberPrivate.methodName");
if (!memberPrivate->fieldValue.isPrimitive()) {
JS_CallValueTracer(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.
if (memberPrivate->npobjWrapper) {
JS_CallObjectTracer(trc, &memberPrivate->npobjWrapper,
"NPObject Member => npobjWrapper");
}
}
// static
bool
nsJSObjWrapper::HasOwnProperty(NPObject *npobj, NPIdentifier npid)
{
NPP npp = NPPStack::Peek();
dom::AutoJSAPI jsapi;
if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(GetGlobalObject(npp)))) {
return false;
}
JSContext *cx = jsapi.cx();
if (!npobj) {
ThrowJSException(cx,
"Null npobj in nsJSObjWrapper::NP_HasOwnProperty!");
return false;
}
nsJSObjWrapper *npjsobj = (nsJSObjWrapper *)npobj;
bool found, ok = false;
AutoJSExceptionReporter reporter(jsapi, 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;
}