summaryrefslogtreecommitdiffstats
path: root/dom/plugins/base/nsJSNPRuntime.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /dom/plugins/base/nsJSNPRuntime.cpp
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/plugins/base/nsJSNPRuntime.cpp2183
1 files changed, 2183 insertions, 0 deletions
diff --git a/dom/plugins/base/nsJSNPRuntime.cpp b/dom/plugins/base/nsJSNPRuntime.cpp
new file mode 100644
index 0000000000..f4d38002fd
--- /dev/null
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -0,0 +1,2183 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "jsfriendapi.h"
+
+#include "GeckoProfiler.h"
+#include "mozilla/UniquePtr.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 "mozilla/dom/Document.h"
+#include "xpcpublic.h"
+#include "nsIContent.h"
+#include "nsPluginInstanceOwner.h"
+#include "nsWrapperCacheInlines.h"
+#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
+#include "js/GCHashTable.h"
+#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetPrivate, JS::SetPrivate
+#include "js/Symbol.h"
+#include "js/TracingAPI.h"
+#include "js/Wrapper.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/ScriptSettings.h"
+
+#define NPRUNTIME_JSCLASS_NAME "NPObject JS wrapper class"
+
+using namespace mozilla::plugins::parent;
+using namespace mozilla;
+
+#include "mozilla/plugins/PluginScriptableObjectParent.h"
+using mozilla::plugins::ParentNPObject;
+using mozilla::plugins::PluginScriptableObjectParent;
+
+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);
+ }
+
+ static bool isValid(const nsJSObjWrapper*& wrapper) { return true; }
+};
+} // 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 UniquePtr<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 bool NPObjectIsOutOfProcessProxy(NPObject* obj) {
+ return obj->_class == PluginScriptableObjectParent::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};
+
+class NPObjWrapperProxyHandler : public js::BaseProxyHandler {
+ static const char family;
+
+ public:
+ static const NPObjWrapperProxyHandler singleton;
+
+ constexpr NPObjWrapperProxyHandler() : BaseProxyHandler(&family) {}
+
+ bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> desc,
+ JS::ObjectOpResult& result) const override {
+ ::JS_ReportErrorASCII(cx,
+ "Trying to add unsupported property on NPObject!");
+ return false;
+ }
+
+ bool getPrototypeIfOrdinary(
+ JSContext* cx, JS::Handle<JSObject*> proxy, bool* isOrdinary,
+ JS::MutableHandle<JSObject*> proto) const override {
+ *isOrdinary = true;
+ proto.set(js::GetStaticPrototype(proxy));
+ return true;
+ }
+
+ bool isExtensible(JSContext* cx, JS::Handle<JSObject*> proxy,
+ bool* extensible) const override {
+ // Needs to be extensible so nsObjectLoadingContent can mutate our
+ // __proto__.
+ *extensible = true;
+ return true;
+ }
+
+ bool preventExtensions(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::ObjectOpResult& result) const override {
+ result.succeed();
+ return true;
+ }
+
+ bool getOwnPropertyDescriptor(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
+
+ bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> properties) const override;
+
+ bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::ObjectOpResult& result) const override;
+
+ bool get(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp) const override;
+
+ bool set(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::Handle<JS::Value> vp, JS::Handle<JS::Value> receiver,
+ JS::ObjectOpResult& result) const override;
+
+ bool isCallable(JSObject* obj) const override { return true; }
+ bool call(JSContext* cx, JS::Handle<JSObject*> proxy,
+ const JS::CallArgs& args) const override;
+
+ bool isConstructor(JSObject* obj) const override { return true; }
+ bool construct(JSContext* cx, JS::Handle<JSObject*> proxy,
+ const JS::CallArgs& args) const override;
+
+ bool finalizeInBackground(const JS::Value& priv) const override {
+ return false;
+ }
+ void finalize(JSFreeOp* fop, JSObject* proxy) const override;
+
+ size_t objectMoved(JSObject* obj, JSObject* old) const override;
+};
+
+const char NPObjWrapperProxyHandler::family = 0;
+const NPObjWrapperProxyHandler NPObjWrapperProxyHandler::singleton;
+
+static bool NPObjWrapper_Resolve(JSContext* cx, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id, bool* resolved,
+ JS::MutableHandle<JSObject*> method);
+
+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 JSClass sNPObjWrapperProxyClass =
+ PROXY_CLASS_DEF(NPRUNTIME_JSCLASS_NAME, JSCLASS_HAS_RESERVED_SLOTS(1));
+
+typedef struct NPObjectMemberPrivate {
+ JS::Heap<JSObject*> npobjWrapper;
+ JS::Heap<JS::Value> fieldValue;
+ JS::Heap<jsid> methodName;
+ NPP npp = nullptr;
+} NPObjectMemberPrivate;
+
+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,
+ 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) {
+ 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.
+ UniquePtr<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);
+
+ if (!RegisterGCCallbacks()) {
+ return false;
+ }
+
+ sJSObjWrappers = MakeUnique<JSObjWrapperTable>();
+ sJSObjWrappersAccessible = true;
+ return true;
+}
+
+static void DestroyJSObjWrapperTable() {
+ MOZ_ASSERT(sJSObjWrappersAccessible);
+ MOZ_ASSERT(sJSObjWrappers);
+ MOZ_ASSERT(sJSObjWrappers->count() == 0);
+
+ // No more wrappers. Delete the table.
+ sJSObjWrappers = nullptr;
+ 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::plugins::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<dom::Document> doc;
+ owner->GetDocument(getter_AddRefs(doc));
+ NS_ENSURE_TRUE(doc, nullptr);
+
+ return doc->GetScopeObject();
+}
+
+} // namespace mozilla::plugins::parent
+
+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.toObject());
+ JS::Rooted<JSObject*> global(cx);
+ // CheckedUnwrapStatic is fine here; if we get a Location or WindowProxy,
+ // we'll just use the current global instead.
+ obj = js::CheckedUnwrapStatic(obj);
+ if (obj) {
+ global = JS::GetNonCCWObjectGlobal(obj);
+ } else {
+ obj = &val.toObject();
+ global = JS::CurrentGlobalOrNull(cx);
+ }
+
+ NPObject* npobj = nsJSObjWrapper::GetNewOrUsed(npp, obj, global);
+ 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(mozilla::MakeStringSpan(message), ucex);
+
+ ucex.AppendLiteral(" [plugin exception: ");
+ }
+
+ AppendUTF8toUTF16(mozilla::MakeStringSpan(ex), ucex);
+
+ if (message) {
+ ucex.AppendLiteral("].");
+ }
+
+ 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),
+ mJSObjGlobal(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;
+ jsnpobj->mJSObjGlobal = 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 void MarkCrossZoneNPIdentifier(JSContext* cx, NPIdentifier npid) {
+ JS_MarkCrossZoneId(cx, NPIdentifierToJSId(npid));
+}
+
+// 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;
+
+ JSAutoRealm ar(cx, npjsobj->mJSObjGlobal);
+ MarkCrossZoneNPIdentifier(cx, id);
+
+ AutoJSExceptionSuppressor suppressor(aes, npjsobj);
+
+ JS::Rooted<JS::Value> v(cx);
+ bool ok = GetProperty(cx, npjsobj->mJSObj, id, &v);
+
+ return ok && !v.isPrimitive() && ::JS_ObjectIsFunction(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;
+ }
+
+ AutoAllowLegacyScriptExecution exemption;
+
+ // 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);
+ JSAutoRealm ar(cx, npjsobj->mJSObjGlobal);
+ MarkCrossZoneNPIdentifier(cx, method);
+ 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::RootedVector<JS::Value> 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);
+ JSAutoRealm ar(cx, npjsobj->mJSObjGlobal);
+ MarkCrossZoneNPIdentifier(cx, npid);
+
+ 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);
+ JSAutoRealm ar(cx, npjsobj->mJSObjGlobal);
+ MarkCrossZoneNPIdentifier(cx, id);
+
+ 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);
+ JSAutoRealm ar(cx, npjsobj->mJSObjGlobal);
+ MarkCrossZoneNPIdentifier(cx, npid);
+
+ 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);
+ JSAutoRealm ar(cx, npjsobj->mJSObjGlobal);
+ MarkCrossZoneNPIdentifier(cx, npid);
+
+ 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);
+ JSAutoRealm ar(cx, npjsobj->mJSObjGlobal);
+
+ JS::Rooted<JS::IdVector> ida(cx, JS::IdVector(cx));
+ if (!JS_Enumerate(cx, jsobj, &ida)) {
+ return false;
+ }
+
+ *count = ida.length();
+ *idarray = (NPIdentifier*)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)) {
+ free(*idarray);
+ return false;
+ }
+
+ NPIdentifier id;
+ if (v.isString()) {
+ JS::Rooted<JSString*> str(cx, v.toString());
+ str = JS_AtomizeAndPinJSString(cx, str);
+ if (!str) {
+ 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,
+ JS::Handle<JSObject*> objGlobal) {
+ if (!npp) {
+ NS_ERROR("Null NPP passed to nsJSObjWrapper::GetNewOrUsed()!");
+
+ return nullptr;
+ }
+
+ MOZ_ASSERT(JS_IsGlobalObject(objGlobal));
+ MOZ_RELEASE_ASSERT(JS::GetCompartment(obj) == JS::GetCompartment(objGlobal));
+
+ // 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::GetProxyPrivate(obj).toPrivate();
+
+ // 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) {
+ // No hash yet (or any more), initialize it.
+ if (!CreateJSObjWrapperTable()) return nullptr;
+ }
+ MOZ_ASSERT(sJSObjWrappersAccessible);
+
+ JSObjWrapperTable::Ptr p =
+ sJSObjWrappers->lookup(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;
+ wrapper->mJSObjGlobal = objGlobal;
+
+ // 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);
+
+ // We can't have WindowProxy or Location objects with NP object wrapper
+ // objects on their proto chain, since they have immutable prototypes. So
+ // CheckedUnwrapStatic is ok here.
+ while (obj && (obj = js::CheckedUnwrapStatic(obj))) {
+ if (nsNPObjWrapper::IsWrapper(obj)) {
+ if (wrapResult && !JS_WrapObject(cx, &obj)) {
+ return nullptr;
+ }
+ return obj;
+ }
+
+ JSAutoRealm ar(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::GetProxyPrivate(obj).toPrivate();
+}
+
+static JSObject* NPObjWrapper_GetResolvedProps(JSContext* cx,
+ JS::Handle<JSObject*> obj) {
+ JS::Value slot = js::GetProxyReservedSlot(obj, 0);
+ if (slot.isObject()) return &slot.toObject();
+
+ MOZ_ASSERT(slot.isUndefined());
+
+ JSObject* res = JS_NewObject(cx, nullptr);
+ if (!res) return nullptr;
+
+ SetProxyReservedSlot(obj, 0, JS::ObjectValue(*res));
+ return res;
+}
+
+bool NPObjWrapperProxyHandler::delete_(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::ObjectOpResult& result) const {
+ NPObject* npobj = GetNPObject(cx, proxy);
+
+ if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
+ !npobj->_class->removeProperty) {
+ ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
+
+ return false;
+ }
+
+ JS::Rooted<JSObject*> resolvedProps(cx,
+ NPObjWrapper_GetResolvedProps(cx, proxy));
+ if (!resolvedProps) return false;
+ if (!JS_DeletePropertyById(cx, resolvedProps, id, result)) 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();
+}
+
+bool NPObjWrapperProxyHandler::set(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::Value> vp,
+ JS::Handle<JS::Value> receiver,
+ JS::ObjectOpResult& result) const {
+ NPObject* npobj = GetNPObject(cx, proxy);
+
+ 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;
+ }
+
+ {
+ bool resolved = false;
+ JS::Rooted<JSObject*> method(cx);
+ if (!NPObjWrapper_Resolve(cx, proxy, id, &resolved, &method)) return false;
+ if (!resolved) {
+ // We don't have a property/method with this id. Forward to the prototype
+ // chain.
+ return js::BaseProxyHandler::set(cx, proxy, id, vp, receiver, result);
+ }
+ }
+
+ 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 CallNPMethod(JSContext* cx, unsigned argc, JS::Value* vp);
+
+bool NPObjWrapperProxyHandler::get(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<JS::Value> receiver,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp) const {
+ NPObject* npobj = GetNPObject(cx, proxy);
+
+ if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
+ !npobj->_class->hasMethod || !npobj->_class->getProperty) {
+ ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
+
+ return false;
+ }
+
+ if (id.isSymbol()) {
+ if (id.isWellKnownSymbol(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 (id.isWellKnownSymbol(JS::SymbolCode::toStringTag)) {
+ JS::RootedString tag(cx, JS_NewStringCopyZ(cx, NPRUNTIME_JSCLASS_NAME));
+ if (!tag) {
+ return false;
+ }
+
+ vp.setString(tag);
+ return true;
+ }
+
+ return js::BaseProxyHandler::get(cx, proxy, receiver, id, vp);
+ }
+
+ // 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;
+ }
+
+ {
+ bool resolved = false;
+ JS::Rooted<JSObject*> method(cx);
+ if (!NPObjWrapper_Resolve(cx, proxy, id, &resolved, &method)) return false;
+ if (method) {
+ vp.setObject(*method);
+ return true;
+ }
+ if (!resolved) {
+ // We don't have a property/method with this id. Forward to the prototype
+ // chain.
+ return js::BaseProxyHandler::get(cx, proxy, receiver, id, vp);
+ }
+ }
+
+ PluginDestructionGuard pdg(npp);
+
+ bool hasProperty, hasMethod;
+
+ NPVariant npv;
+ VOID_TO_NPVARIANT(npv);
+
+ NPIdentifier identifier = JSIdToNPIdentifier(id);
+
+ if (NPObjectIsOutOfProcessProxy(npobj)) {
+ PluginScriptableObjectParent* actor =
+ static_cast<ParentNPObject*>(npobj)->parent;
+
+ // actor may be null if the plugin crashed.
+ if (!actor) return false;
+
+ bool 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, proxy, npobj, id, &npv, vp);
+
+ if (hasProperty) {
+ vp.set(NPVariantToJSVal(npp, cx, &npv));
+ _releasevariantvalue(&npv);
+
+ if (!ReportExceptionIfPending(cx)) return false;
+ return true;
+ }
+ }
+ return js::BaseProxyHandler::get(cx, proxy, receiver, id, vp);
+ }
+
+ 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, proxy, 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;
+ }
+
+ return js::BaseProxyHandler::get(cx, proxy, receiver, id, vp);
+}
+
+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*)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) {
+ 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) {
+ 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);
+ if (!args.thisv().isObject()) {
+ ThrowJSExceptionASCII(cx,
+ "plug-in method called on incompatible non-object");
+ return false;
+ }
+ JS::Rooted<JSObject*> obj(cx, &args.thisv().toObject());
+ return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false);
+}
+
+bool NPObjWrapperProxyHandler::getOwnPropertyDescriptor(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::MutableHandle<JS::PropertyDescriptor> desc) const {
+ bool resolved = false;
+ JS::Rooted<JSObject*> method(cx);
+ if (!NPObjWrapper_Resolve(cx, proxy, id, &resolved, &method)) return false;
+ if (!resolved) {
+ // No such property.
+ desc.object().set(nullptr);
+ return true;
+ }
+
+ // This returns a descriptor with |null| JS value if this is a plugin
+ // property (as opposed to a method). That should be fine, hopefully, as the
+ // previous code had very inconsistent behavior in this case as well. The main
+ // reason for returning a descriptor here is to make property enumeration work
+ // correctly (it will call getOwnPropertyDescriptor to check enumerability).
+ JS::Rooted<JS::Value> val(cx, JS::ObjectOrNullValue(method));
+ desc.initFields(proxy, val, JSPROP_ENUMERATE, nullptr, nullptr);
+ return true;
+}
+
+bool NPObjWrapperProxyHandler::ownPropertyKeys(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> properties) const {
+ NPObject* npobj = GetNPObject(cx, proxy);
+ 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);
+ }
+
+ free(identifiers);
+ return true;
+}
+
+// This function is very similar to a resolve hook for native objects. Instead
+// of defining properties on the object, it defines them on a resolvedProps
+// object (a plain JS object that's never exposed to script) that's stored in
+// the NPObjWrapper proxy's reserved slot. The behavior is as follows:
+//
+// - *resolvedp is set to true iff the plugin object has a property or method
+// (or both) with this id.
+//
+// - If the plugin object has a *property* with this id, the caller is
+// responsible for getting/setting its value. In this case we assign |null|
+// to resolvedProps[id] so we don't have to call hasProperty each time.
+//
+// - If the plugin object has a *method* with this id, we create a JSFunction to
+// call it and assign it to resolvedProps[id]. This function is also assigned
+// to the |method| outparam so callers can return it directly if we're doing a
+// |get|.
+static bool NPObjWrapper_Resolve(JSContext* cx, JS::Handle<JSObject*> obj,
+ JS::Handle<jsid> id, bool* resolvedp,
+ JS::MutableHandle<JSObject*> method) {
+ if (JSID_IS_SYMBOL(id)) return true;
+
+ AUTO_PROFILER_LABEL("NPObjWrapper_Resolve", 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;
+ }
+
+ JS::Rooted<JSObject*> resolvedProps(cx,
+ NPObjWrapper_GetResolvedProps(cx, obj));
+ if (!resolvedProps) return false;
+ JS::Rooted<JS::Value> res(cx);
+ if (!JS_GetPropertyById(cx, resolvedProps, id, &res)) return false;
+ if (res.isObjectOrNull()) {
+ method.set(res.toObjectOrNull());
+ *resolvedp = true;
+ return true;
+ }
+
+ PluginDestructionGuard pdg(LookupNPP(npobj));
+
+ NPIdentifier identifier = JSIdToNPIdentifier(id);
+
+ bool hasProperty = npobj->_class->hasProperty(npobj, identifier);
+ if (!ReportExceptionIfPending(cx)) return false;
+
+ if (hasProperty) {
+ if (!JS_SetPropertyById(cx, resolvedProps, id, JS::NullHandleValue))
+ 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, resolvedProps, id, CallNPMethod, 0, JSPROP_ENUMERATE);
+ if (!fnc) return false;
+
+ method.set(JS_GetFunctionObject(fnc));
+ *resolvedp = true;
+ return true;
+ }
+
+ // no property or method
+ return true;
+}
+
+void NPObjWrapperProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const {
+ JS::AutoAssertGCCallback inCallback;
+
+ NPObject* npobj = (NPObject*)js::GetProxyPrivate(proxy).toPrivate();
+ if (npobj) {
+ if (sNPObjWrappers) {
+ // If the sNPObjWrappers map contains an entry that refers to this
+ // wrapper, remove it.
+ auto entry =
+ static_cast<NPObjWrapperHashEntry*>(sNPObjWrappers->Search(npobj));
+ if (entry && entry->mJSObj == proxy) {
+ sNPObjWrappers->Remove(npobj);
+ }
+ }
+ }
+
+ if (!sDelayedReleases) sDelayedReleases = new nsTArray<NPObject*>;
+ sDelayedReleases->AppendElement(npobj);
+}
+
+size_t NPObjWrapperProxyHandler::objectMoved(JSObject* obj,
+ JSObject* old) const {
+ // The wrapper JSObject has been moved, so we need to update the entry in the
+ // sNPObjWrappers hash table, if present.
+
+ if (!sNPObjWrappers) {
+ return 0;
+ }
+
+ NPObject* npobj = (NPObject*)js::GetProxyPrivate(obj).toPrivate();
+ if (!npobj) {
+ return 0;
+ }
+
+ // 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;
+ return 0;
+}
+
+bool NPObjWrapperProxyHandler::call(JSContext* cx, JS::Handle<JSObject*> proxy,
+ const JS::CallArgs& args) const {
+ return CallNPMethodInternal(cx, proxy, args.length(), args.array(),
+ args.rval().address(), false);
+}
+
+bool NPObjWrapperProxyHandler::construct(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ const JS::CallArgs& args) const {
+ return CallNPMethodInternal(cx, proxy, args.length(), args.array(),
+ args.rval().address(), 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::GetClass(obj) == &sNPObjWrapperProxyClass;
+}
+
+// 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 an NPObject wrapper, null out its JSObjects' private data.
+ js::SetProxyPrivate(entry->mJSObj.unbarrieredGetPtr(),
+ JS::PrivateValue(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. The return value
+// is always in the compartment of the passed-in JSContext (it might be a CCW).
+
+// 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 NPObject wrapper. First check it is still alive.
+ JSObject* obj = entry->mJSObj.unbarrieredGetPtr();
+ if (js::gc::EdgeNeedsSweepUnbarriered(&obj)) {
+ // The object is dead (finalization will happen at a later time). By the
+ // time we leave this function, this entry will either be updated with a
+ // new wrapper or removed if that fails. Clear it anyway to make sure
+ // nothing touches the dead object.
+ entry->mJSObj = nullptr;
+ } else {
+ // 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::RootedValue priv(cx, JS::PrivateValue(nullptr));
+ js::ProxyOptions options;
+ options.setClass(&sNPObjWrapperProxyClass);
+ JS::Rooted<JSObject*> obj(
+ cx, js::NewProxyObject(cx, &NPObjWrapperProxyHandler::singleton, priv,
+ nullptr, options));
+
+ 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::SetProxyPrivate(obj, JS::PrivateValue(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 (auto iter = sJSObjWrappers->modIter(); !iter.done(); iter.next()) {
+ nsJSObjWrapper* npobj = iter.get().value();
+ MOZ_ASSERT(npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass);
+ if (npobj->mNpp == npp) {
+ if (npobj->_class && npobj->_class->invalidate) {
+ npobj->_class->invalidate(npobj);
+ }
+
+ _releaseobject(npobj);
+
+ iter.remove();
+ }
+ }
+
+ 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 {
+ free(npobj);
+ }
+
+ js::SetProxyPrivate(entry->mJSObj.unbarrieredGetPtr(),
+ JS::PrivateValue(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 (auto iter = sJSObjWrappers->iter(); !iter.done(); iter.next()) {
+ nsJSObjWrapper* npobj = iter.get().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 = new NPObjectMemberPrivate;
+
+ JS::Rooted<JSObject*> obj(cx, aObj);
+
+ JS::Rooted<JSObject*> memobj(cx, ::JS_NewObject(cx, &sNPObjectMemberClass));
+ if (!memobj) {
+ delete 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;
+
+ // Finally, define the Symbol.toPrimitive property on |memobj|.
+
+ JS::Rooted<jsid> toPrimitiveId(cx);
+ toPrimitiveId =
+ SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::toPrimitive));
+
+ JSFunction* fun = JS_NewFunction(cx, NPObjectMember_toPrimitive, 1, 0,
+ "Symbol.toPrimitive");
+ if (!fun) return false;
+
+ JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(fun));
+ if (!JS_DefinePropertyById(cx, memobj, toPrimitiveId, funObj, 0))
+ return false;
+
+ return true;
+}
+
+static void NPObjectMember_Finalize(JSFreeOp* fop, JSObject* obj) {
+ NPObjectMemberPrivate* memberPrivate;
+
+ memberPrivate = (NPObjectMemberPrivate*)JS::GetPrivate(obj);
+ if (!memberPrivate) return;
+
+ delete 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*)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) {
+ 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) {
+ 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) {
+ auto* 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;
+}