Bug 1389949 - Make NPObjWrapper a JS proxy object. r=billm
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 18 Aug 2017 10:22:20 +0200
changeset 375626 09ece3c8484a17d955d1a882120e945ad5b1e817
parent 375625 4e2421fbfbd36829df559ec155c6f377e3431274
child 375627 d1449101425eaab6aaecbdd7da5d0433a5156a2e
push id32358
push userkwierso@gmail.com
push dateFri, 18 Aug 2017 22:53:28 +0000
treeherdermozilla-central@4f4487cc2d30 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbillm
bugs1389949
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1389949 - Make NPObjWrapper a JS proxy object. r=billm
dom/plugins/base/nsJSNPRuntime.cpp
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -154,88 +154,117 @@ NPClass nsJSObjWrapper::sJSObjWrapperNPC
     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_NewEnumerate(JSContext *cx, JS::Handle<JSObject*> obj, JS::AutoIdVector &properties,
-                          bool enumerableOnly);
+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::AutoIdVector& 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;
+};
+
+const char NPObjWrapperProxyHandler::family = 0;
+const NPObjWrapperProxyHandler NPObjWrapperProxyHandler::singleton;
 
 static bool
 NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
-                     bool *resolvedp);
-
-static void
-NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj);
+                     bool* resolved, JS::MutableHandle<JSObject*> method);
 
 static void
 NPObjWrapper_ObjectMoved(JSObject *obj, const JSObject *old);
 
 static bool
-NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp);
-
-static bool
-NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp);
-
-static bool
 NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
 
 static bool
 CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
                      JS::Handle<jsid> id,  NPVariant* getPropertyResult,
                      JS::MutableHandle<JS::Value> vp);
 
-const static js::ClassOps sNPObjectJSWrapperClassOps = {
-    NPObjWrapper_AddProperty,
-    NPObjWrapper_DelProperty,
-    NPObjWrapper_GetProperty,
-    NPObjWrapper_SetProperty,
-    nullptr,                    /* enumerate */
-    NPObjWrapper_NewEnumerate,
-    NPObjWrapper_Resolve,
-    nullptr,                    /* mayResolve */
-    NPObjWrapper_Finalize,
-    NPObjWrapper_Call,
-    nullptr,                    /* hasInstance */
-    NPObjWrapper_Construct,
-    nullptr,                    /* trace */
-};
-
-const static js::ClassExtension sNPObjectJSWrapperClassExtension = {
-    nullptr,                    /* weakmapKeyDelegateOp */
+static const js::ClassExtension sNPObjWrapperProxyClassExtension = PROXY_MAKE_EXT(
     NPObjWrapper_ObjectMoved
-};
-
-const static js::Class sNPObjectJSWrapperClass = {
+);
+
+const js::Class sNPObjWrapperProxyClass = PROXY_CLASS_WITH_EXT(
     NPRUNTIME_JSCLASS_NAME,
-    JSCLASS_HAS_PRIVATE |
-    JSCLASS_FOREGROUND_FINALIZE,
-    &sNPObjectJSWrapperClassOps,
-    JS_NULL_CLASS_SPEC,
-    &sNPObjectJSWrapperClassExtension,
-    JS_NULL_OBJECT_OPS
-};
+    JSCLASS_HAS_RESERVED_SLOTS(1),
+    &sNPObjWrapperProxyClassExtension);
 
 typedef struct NPObjectMemberPrivate {
     JS::Heap<JSObject *> npobjWrapper;
     JS::Heap<JS::Value> fieldValue;
     JS::Heap<jsid> methodName;
     NPP   npp;
 } NPObjectMemberPrivate;
 
@@ -1079,17 +1108,17 @@ nsJSObjWrapper::GetNewOrUsed(NPP npp, JS
   // 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);
+    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)
@@ -1166,76 +1195,55 @@ GetNPObjectWrapper(JSContext *cx, JSObje
 static NPObject *
 GetNPObject(JSContext *cx, JSObject *obj)
 {
   obj = GetNPObjectWrapper(cx, obj, /* wrapResult = */ false);
   if (!obj) {
     return nullptr;
   }
 
-  return (NPObject *)::JS_GetPrivate(obj);
+  return (NPObject *)js::GetProxyPrivate(obj).toPrivate();
 }
 
-
-// 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)
+static JSObject*
+NPObjWrapper_GetResolvedProps(JSContext* cx, JS::Handle<JSObject*> obj)
 {
-  NPObject *npobj = GetNPObject(cx, obj);
-
-  if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
-      !npobj->_class->hasMethod) {
-    ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
-
-    return false;
-  }
-
-  if (NPObjectIsOutOfProcessProxy(npobj)) {
-    return true;
-  }
-
-  PluginDestructionGuard pdg(LookupNPP(npobj));
-
-  NPIdentifier identifier = JSIdToNPIdentifier(id);
-  bool hasProperty = npobj->_class->hasProperty(npobj, identifier);
-  if (!ReportExceptionIfPending(cx))
-    return false;
-
-  if (hasProperty)
-    return true;
-
-  // We must permit methods here since JS_DefineUCFunction() will add
-  // the function as a property
-  bool hasMethod = npobj->_class->hasMethod(npobj, identifier);
-  if (!ReportExceptionIfPending(cx))
-    return false;
-
-  if (!hasMethod) {
-    ThrowJSExceptionASCII(cx, "Trying to add unsupported property on NPObject!");
-
-    return false;
-  }
-
-  return true;
+  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;
 }
 
-static bool
-NPObjWrapper_DelProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
-                         JS::ObjectOpResult &result)
+bool
+NPObjWrapperProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+                                  JS::ObjectOpResult& result) const
 {
-  NPObject *npobj = GetNPObject(cx, obj);
+  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;
@@ -1249,21 +1257,22 @@ NPObjWrapper_DelProperty(JSContext *cx, 
   // 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)
+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, obj);
+  NPObject *npobj = GetNPObject(cx, proxy);
 
   if (!npobj || !npobj->_class || !npobj->_class->hasProperty ||
       !npobj->_class->setProperty) {
     ThrowJSExceptionASCII(cx, "Bad NPObject as private data!");
 
     return false;
   }
 
@@ -1272,16 +1281,28 @@ NPObjWrapper_SetProperty(JSContext *cx, 
   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;
@@ -1310,19 +1331,23 @@ NPObjWrapper_SetProperty(JSContext *cx, 
 
     return false;
   }
 
   return result.succeed();
 }
 
 static bool
-NPObjWrapper_GetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp)
+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, obj);
+  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;
   }
 
@@ -1344,29 +1369,44 @@ NPObjWrapper_GetProperty(JSContext *cx, 
       if (!tag) {
         return false;
       }
 
       vp.setString(tag);
       return true;
     }
 
-    vp.setUndefined();
-    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);
@@ -1386,52 +1426,54 @@ NPObjWrapper_GetProperty(JSContext *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);
+        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 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, obj, npobj, id, nullptr, vp);
+    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 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);
 
@@ -1563,21 +1605,46 @@ CallNPMethod(JSContext *cx, unsigned arg
   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_NewEnumerate(JSContext *cx, JS::Handle<JSObject*> obj,
-                          JS::AutoIdVector &properties, bool enumerableOnly)
+bool
+NPObjWrapperProxyHandler::getOwnPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> proxy,
+                                                   JS::Handle<jsid> id,
+                                                   JS::MutableHandle<JS::PropertyDescriptor> desc) const
 {
-  NPObject *npobj = GetNPObject(cx, obj);
+  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::AutoIdVector& 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) ||
@@ -1605,88 +1672,113 @@ NPObjWrapper_NewEnumerate(JSContext *cx,
     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)
+                     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) {
-    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;
-    }
-
+    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, obj, id, CallNPMethod, 0,
+    JSFunction *fnc = ::JS_DefineFunctionById(cx, resolvedProps, id, CallNPMethod, 0,
                                               JSPROP_ENUMERATE);
-
+    if (!fnc)
+      return false;
+
+    method.set(JS_GetFunctionObject(fnc));
     *resolvedp = true;
-
-    return fnc != nullptr;
+    return true;
   }
 
   // no property or method
   return true;
 }
 
-static void
-NPObjWrapper_Finalize(js::FreeOp *fop, JSObject *obj)
+void
+NPObjWrapperProxyHandler::finalize(JSFreeOp* fop, JSObject* proxy) const
 {
   JS::AutoAssertGCCallback inCallback;
 
-  NPObject *npobj = (NPObject *)::JS_GetPrivate(obj);
+  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 == obj) {
+      if (entry && entry->mJSObj == proxy) {
         sNPObjWrappers->Remove(npobj);
       }
     }
   }
 
   if (!sDelayedReleases)
     sDelayedReleases = new nsTArray<NPObject*>;
   sDelayedReleases->AppendElement(npobj);
@@ -1697,45 +1789,45 @@ NPObjWrapper_ObjectMoved(JSObject *obj, 
 {
   // 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);
+  NPObject *npobj = (NPObject *)js::GetProxyPrivate(obj).toPrivate();
   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)
+bool
+NPObjWrapperProxyHandler::call(JSContext* cx, JS::Handle<JSObject*> proxy,
+                               const JS::CallArgs& args) const
 {
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  JS::Rooted<JSObject*> obj(cx, &args.callee());
-  return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false);
+  return CallNPMethodInternal(cx, proxy, args.length(), args.array(),
+                              args.rval().address(), false);
 }
 
-static bool
-NPObjWrapper_Construct(JSContext *cx, unsigned argc, JS::Value *vp)
+bool
+NPObjWrapperProxyHandler::construct(JSContext* cx, JS::Handle<JSObject*> proxy,
+                                    const JS::CallArgs& args) const
 {
-  JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
-  JS::Rooted<JSObject*> obj(cx, &args.callee());
-  return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, true);
+  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
@@ -1768,17 +1860,17 @@ NPObjWrapper_toPrimitive(JSContext *cx, 
                             JSMSG_CANT_CONVERT_TO,
                             JS_GetClass(obj)->name, "primitive type");
   return false;
 }
 
 bool
 nsNPObjWrapper::IsWrapper(JSObject *obj)
 {
-  return js::GetObjectClass(obj) == &sNPObjectJSWrapperClass;
+  return js::GetObjectClass(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
@@ -1802,17 +1894,17 @@ nsNPObjWrapper::OnDestroy(NPObject *npob
 
   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);
+    js::SetProxyPrivate(entry->mJSObj, JS::PrivateValue(nullptr));
 
     // Remove the npobj from the hash now that it went away.
     sNPObjWrappers->RawRemove(entry);
 
     // The finalize hook will call OnWrapperDestroyed().
   }
 }
 
@@ -1883,17 +1975,21 @@ nsNPObjWrapper::GetNewOrUsed(NPP npp, JS
 
   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)));
+  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?");
@@ -1906,17 +2002,17 @@ nsNPObjWrapper::GetNewOrUsed(NPP npp, JS
 
     return nullptr;
   }
 
   OnWrapperCreated();
 
   entry->mJSObj = obj;
 
-  ::JS_SetPrivate(obj, npobj);
+  js::SetProxyPrivate(obj, JS::PrivateValue(npobj));
 
   // The new JSObject now holds on to npobj
   _retainobject(npobj);
 
   return obj;
 }
 
 // static
@@ -1974,17 +2070,17 @@ nsJSNPRuntime::OnPluginDestroy(NPP npp)
         // 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_SetPrivate(entry->mJSObj, nullptr);
+        js::SetProxyPrivate(entry->mJSObj, JS::PrivateValue(nullptr));
 
         sNPObjWrappers = tmp;
 
         if (sDelayedReleases && sDelayedReleases->RemoveElement(npobj)) {
           OnWrapperDestroyed();
         }
 
         i.Remove();