Bug 1646145: Use lazy property resolution for constants on JS IID reflectors. r=nika
authorKris Maglione <maglione.k@gmail.com>
Wed, 17 Jun 2020 17:45:35 +0000
changeset 536161 c9871e6a258ba553f642eb87b7628b5a1babef20
parent 536160 f0d33891982a2288471a6de3640352bfaffb609a
child 536162 b506158b8759db4bd7a44a8a364ec7dcd3956655
push id37517
push usermalexandru@mozilla.com
push dateThu, 18 Jun 2020 04:43:29 +0000
treeherdermozilla-central@7f0b0cbecd94 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika
bugs1646145
milestone79.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 1646145: Use lazy property resolution for constants on JS IID reflectors. r=nika Differential Revision: https://phabricator.services.mozilla.com/D79904
js/xpconnect/src/XPCJSID.cpp
--- a/js/xpconnect/src/XPCJSID.cpp
+++ b/js/xpconnect/src/XPCJSID.cpp
@@ -5,17 +5,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* An xpcom implementation of the JavaScript nsIID and nsCID objects. */
 
 #include "xpcprivate.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/Attributes.h"
 #include "js/Symbol.h"
+#include "nsContentUtils.h"
 
+using namespace mozilla;
 using namespace mozilla::dom;
 using namespace JS;
 
 namespace xpc {
 
 /******************************************************************************
  * # Generic IDs #
  *
@@ -42,21 +44,43 @@ static const JSClass sID_Class = {
  *
  * In addition to the properties exposed by Generic ID objects, IID supports
  * 'instanceof', exposes constant properties defined on the class, and exposes
  * the interface name as the 'name' and 'toString()' values.
  */
 static bool IID_HasInstance(JSContext* aCx, unsigned aArgc, Value* aVp);
 static bool IID_GetName(JSContext* aCx, unsigned aArgc, Value* aVp);
 
+static bool IID_NewEnumerate(JSContext* cx, HandleObject obj,
+                             MutableHandleIdVector properties,
+                             bool enumerableOnly);
+static bool IID_Resolve(JSContext* cx, HandleObject obj, HandleId id,
+                        bool* resolvedp);
+static bool IID_MayResolve(const JSAtomState& names, jsid id,
+                           JSObject* maybeObj);
+
+static const JSClassOps sIID_ClassOps = {
+    nullptr,           // addProperty
+    nullptr,           // delProperty
+    nullptr,           // enumerate
+    IID_NewEnumerate,  // newEnumerate
+    IID_Resolve,       // resolve
+    IID_MayResolve,    // mayResolve
+    nullptr,           // finalize
+    nullptr,           // call
+    nullptr,           // hasInstance
+    nullptr,           // construct
+    nullptr,           // trace
+};
+
 // Interface ID objects use a single reserved slot containing a pointer to the
 // nsXPTInterfaceInfo object for the interface in question.
 enum { kIID_InfoSlot, kIID_SlotCount };
 static const JSClass sIID_Class = {
-    "nsJSIID", JSCLASS_HAS_RESERVED_SLOTS(kIID_SlotCount), JS_NULL_CLASS_OPS};
+    "nsJSIID", JSCLASS_HAS_RESERVED_SLOTS(kIID_SlotCount), &sIID_ClassOps};
 
 /******************************************************************************
  * # Contract IDs #
  *
  * In addition to the properties exposed by Generic ID objects, Contract IDs
  * expose 'getService' and 'createInstance' methods, and expose the contractID
  * string as '.name' and '.toString()'.
  */
@@ -150,16 +174,22 @@ static JSObject* GetIDObject(HandleValue
     JSObject* obj = js::CheckedUnwrapStatic(&aVal.toObject());
     if (obj && js::GetObjectClass(obj) == aClass) {
       return obj;
     }
   }
   return nullptr;
 }
 
+static const nsXPTInterfaceInfo* GetInterfaceInfo(JSObject* obj) {
+  MOZ_ASSERT(js::GetObjectClass(obj) == &sIID_Class);
+  return static_cast<const nsXPTInterfaceInfo*>(
+      js::GetReservedSlot(obj, kIID_InfoSlot).toPrivate());
+}
+
 /**
  * Unwrap an nsID object from a JSValue.
  *
  * For Generic ID objects, this function will extract the nsID from reserved
  * slots. For IfaceID objects, it will be extracted from the nsXPTInterfaceInfo,
  * and for ContractID objects, the ContractID's corresponding CID will be looked
  * up.
  */
@@ -182,18 +212,17 @@ Maybe<nsID> JSValue2ID(JSContext* aCx, H
                         js::GetReservedSlot(obj, kID_Slot2).toPrivateUint32(),
                         js::GetReservedSlot(obj, kID_Slot3).toPrivateUint32()};
 
     // Construct a nsID inside the Maybe, and copy the rawid into it.
     id.emplace();
     memcpy(id.ptr(), &rawid, sizeof(nsID));
   } else if (js::GetObjectClass(obj) == &sIID_Class) {
     // IfaceID objects store a nsXPTInterfaceInfo* pointer.
-    auto* info = static_cast<const nsXPTInterfaceInfo*>(
-        js::GetReservedSlot(obj, kIID_InfoSlot).toPrivate());
+    const nsXPTInterfaceInfo* info = GetInterfaceInfo(obj);
     id.emplace(info->IID());
   } else if (js::GetObjectClass(obj) == &sCID_Class) {
     // ContractID objects store a ContractID string.
     JS::UniqueChars contractId = JS_EncodeStringToLatin1(
         aCx, js::GetReservedSlot(obj, kCID_ContractSlot).toString());
 
     // NOTE(nika): If we directly access the nsComponentManager, we can do
     // this with a more-basic pointer lookup:
@@ -248,32 +277,16 @@ bool ID2JSValue(JSContext* aCx, const ns
 
 bool IfaceID2JSValue(JSContext* aCx, const nsXPTInterfaceInfo& aInfo,
                      MutableHandleValue aVal) {
   RootedObject obj(aCx, NewIDObjectHelper(aCx, &sIID_Class));
   if (!obj) {
     return false;
   }
 
-  // Define any constants defined on the interface on the ID object.
-  //
-  // NOTE: When InterfaceIDs were implemented using nsIXPCScriptable and
-  // XPConnect, this was implemented using a 'resolve' hook. It has been
-  // changed to happen at creation-time as most interfaces shouldn't have many
-  // constants, and this is likely to turn out cheaper.
-  RootedValue constant(aCx);
-  for (uint16_t i = 0; i < aInfo.ConstantCount(); ++i) {
-    constant.set(aInfo.Constant(i).JSValue());
-    if (!JS_DefineProperty(
-            aCx, obj, aInfo.Constant(i).Name(), constant,
-            JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT)) {
-      return false;
-    }
-  }
-
   // The InterfaceInfo is stored in a reserved slot.
   js::SetReservedSlot(obj, kIID_InfoSlot, PrivateValue((void*)&aInfo));
   aVal.setObject(*obj);
   return true;
 }
 
 bool ContractID2JSValue(JSContext* aCx, JSString* aContract,
                         MutableHandleValue aVal) {
@@ -458,30 +471,95 @@ static bool IID_HasInstance(JSContext* a
 static bool IID_GetName(JSContext* aCx, unsigned aArgc, Value* aVp) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
 
   RootedObject obj(aCx, GetIDObject(args.thisv(), &sIID_Class));
   if (!obj) {
     return Throw(aCx, NS_ERROR_XPC_BAD_CONVERT_JS);
   }
 
-  auto* info =
-      (const nsXPTInterfaceInfo*)js::GetReservedSlot(obj, kIID_InfoSlot)
-          .toPrivate();
+  const nsXPTInterfaceInfo* info = GetInterfaceInfo(obj);
 
   // Name property is the name of the interface this nsIID was created from.
   JSString* name = JS_NewStringCopyZ(aCx, info->Name());
   if (!name) {
     return Throw(aCx, NS_ERROR_OUT_OF_MEMORY);
   }
 
   args.rval().setString(name);
   return true;
 }
 
+static bool IID_NewEnumerate(JSContext* cx, HandleObject obj,
+                             MutableHandleIdVector properties,
+                             bool enumerableOnly) {
+  const nsXPTInterfaceInfo* info = GetInterfaceInfo(obj);
+
+  if (!properties.reserve(info->ConstantCount())) {
+    JS_ReportOutOfMemory(cx);
+    return false;
+  }
+
+  RootedId id(cx);
+  RootedString name(cx);
+  for (uint16_t i = 0; i < info->ConstantCount(); ++i) {
+    name = JS_AtomizeString(cx, info->Constant(i).Name());
+    if (!name || !JS_StringToId(cx, name, &id)) {
+      return false;
+    }
+    properties.infallibleAppend(id);
+  }
+
+  return true;
+}
+
+static bool IID_Resolve(JSContext* cx, HandleObject obj, HandleId id,
+                        bool* resolvedp) {
+  *resolvedp = false;
+  if (!JSID_IS_STRING(id)) {
+    return true;
+  }
+
+  JSLinearString* name = JSID_TO_LINEAR_STRING(id);
+  const nsXPTInterfaceInfo* info = GetInterfaceInfo(obj);
+  for (uint16_t i = 0; i < info->ConstantCount(); ++i) {
+    if (JS_LinearStringEqualsAscii(name, info->Constant(i).Name())) {
+      *resolvedp = true;
+
+      RootedValue constant(cx, info->Constant(i).JSValue());
+      return JS_DefinePropertyById(
+          cx, obj, id, constant,
+          JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT);
+    }
+  }
+  return true;
+}
+
+static bool IID_MayResolve(const JSAtomState& names, jsid id,
+                           JSObject* maybeObj) {
+  if (!JSID_IS_STRING(id)) {
+    return false;
+  }
+
+  if (!maybeObj) {
+    // Each interface object has its own set of constants, so if we don't know
+    // the object, assume any string property may be resolved.
+    return true;
+  }
+
+  JSLinearString* name = JSID_TO_LINEAR_STRING(id);
+  const nsXPTInterfaceInfo* info = GetInterfaceInfo(maybeObj);
+  for (uint16_t i = 0; i < info->ConstantCount(); ++i) {
+    if (JS_LinearStringEqualsAscii(name, info->Constant(i).Name())) {
+      return true;
+    }
+  }
+  return false;
+}
+
 // Common code for CID_CreateInstance and CID_GetService
 static bool CIGSHelper(JSContext* aCx, unsigned aArgc, Value* aVp,
                        bool aGetService) {
   CallArgs args = CallArgsFromVp(aArgc, aVp);
 
   // Extract the ContractID string from our reserved slot. Don't use
   // JSValue2ID as this method should only be defined on Contract ID objects,
   // and it allows us to avoid a duplicate hashtable lookup.