Bug 1155946 part 2. Add mayResolve methods to DOM classes with resolve hooks. r=peterv
authorBoris Zbarsky <bzbarsky@mit.edu>
Tue, 28 Apr 2015 12:25:55 -0400
changeset 241432 bc969d3718580d59b6363fa1aed6f71d6aa39609
parent 241431 d53d91a3fae77790b06a401c4441aa415cddbfaf
child 241433 b9f8b3184c001dffa7581cd9c260dea427720649
push id59117
push userbzbarsky@mozilla.com
push dateTue, 28 Apr 2015 16:26:12 +0000
treeherdermozilla-inbound@b9f8b3184c00 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1155946
milestone40.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 1155946 part 2. Add mayResolve methods to DOM classes with resolve hooks. r=peterv
dom/base/Navigator.cpp
dom/base/Navigator.h
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
dom/base/nsJSEnvironment.cpp
dom/base/nsJSEnvironment.h
dom/base/nsObjectLoadingContent.cpp
dom/base/nsObjectLoadingContent.h
dom/bindings/BindingUtils.cpp
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
js/src/devtools/rootAnalysis/annotations.js
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -2082,16 +2082,17 @@ Navigator::GetMozAudioChannelManager(Err
 }
 #endif
 
 bool
 Navigator::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObject,
                      JS::Handle<jsid> aId,
                      JS::MutableHandle<JSPropertyDescriptor> aDesc)
 {
+  // Note: Keep this in sync with MayResolve.
   if (!JSID_IS_STRING(aId)) {
     return true;
   }
 
   nsScriptNameSpaceManager* nameSpaceManager = GetNameSpaceManager();
   if (!nameSpaceManager) {
     return Throw(aCx, NS_ERROR_NOT_INITIALIZED);
   }
@@ -2233,16 +2234,38 @@ Navigator::DoResolve(JSContext* aCx, JS:
   if (!JS_WrapValue(aCx, &prop_val)) {
     return Throw(aCx, NS_ERROR_UNEXPECTED);
   }
 
   FillPropertyDescriptor(aDesc, aObject, prop_val, false);
   return true;
 }
 
+/* static */
+bool
+Navigator::MayResolve(jsid aId)
+{
+  // Note: This function does not fail and may not have any side-effects.
+  // Note: Keep this in sync with DoResolve.
+  if (!JSID_IS_STRING(aId)) {
+    return false;
+  }
+
+  nsScriptNameSpaceManager *nameSpaceManager = PeekNameSpaceManager();
+  if (!nameSpaceManager) {
+    // Really shouldn't happen here.  Fail safe.
+    return true;
+  }
+
+  nsAutoString name;
+  AssignJSFlatString(name, JSID_TO_FLAT_STRING(aId));
+
+  return nameSpaceManager->LookupNavigatorName(name);
+}
+
 struct NavigatorNameEnumeratorClosure
 {
   NavigatorNameEnumeratorClosure(JSContext* aCx, JSObject* aWrapper,
                                  nsTArray<nsString>& aNames)
     : mCx(aCx),
       mWrapper(aCx, aWrapper),
       mNames(aNames)
   {
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -276,16 +276,19 @@ public:
                               ErrorResult& aRv);
 #endif // MOZ_MEDIA_NAVIGATOR
 
   already_AddRefed<ServiceWorkerContainer> ServiceWorker();
 
   bool DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObject,
                  JS::Handle<jsid> aId,
                  JS::MutableHandle<JSPropertyDescriptor> aDesc);
+  // The return value is whether DoResolve might end up resolving the given id.
+  // If in doubt, return true.
+  static bool MayResolve(jsid aId);
   void GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames,
                            ErrorResult& aRv);
   void GetLanguages(nsTArray<nsString>& aLanguages);
 
   bool MozE10sEnabled();
 
   static void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -4261,29 +4261,69 @@ nsGlobalWindow::GetSupportedNames(nsTArr
 
 bool
 nsGlobalWindow::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
                           JS::Handle<jsid> aId,
                           JS::MutableHandle<JSPropertyDescriptor> aDesc)
 {
   MOZ_ASSERT(IsInnerWindow());
 
+  // Note: Keep this in sync with MayResolve.
+
   // Note: The infallibleInit call in GlobalResolve depends on this check.
   if (!JSID_IS_STRING(aId)) {
     return true;
   }
 
   nsresult rv = nsWindowSH::GlobalResolve(this, aCx, aObj, aId, aDesc);
   if (NS_FAILED(rv)) {
     return Throw(aCx, rv);
   }
 
   return true;
 }
 
+/* static */
+bool
+nsGlobalWindow::MayResolve(jsid aId)
+{
+  // Note: This function does not fail and may not have any side-effects.
+  // Note: Keep this in sync with DoResolve.
+  if (!JSID_IS_STRING(aId)) {
+    return false;
+  }
+
+  if (aId == XPCJSRuntime::Get()->GetStringID(XPCJSRuntime::IDX_COMPONENTS)) {
+    return true;
+  }
+
+  if (aId == XPCJSRuntime::Get()->GetStringID(XPCJSRuntime::IDX_CONTROLLERS)) {
+    // We only resolve .controllers in release builds and on non-chrome windows,
+    // but let's not worry about any of that stuff.
+    return true;
+  }
+
+  nsScriptNameSpaceManager *nameSpaceManager = PeekNameSpaceManager();
+  if (!nameSpaceManager) {
+    // Really shouldn't happen.  Fail safe.
+    return true;
+  }
+
+  nsAutoString name;
+  AssignJSFlatString(name, JSID_TO_FLAT_STRING(aId));
+
+  const nsGlobalNameStruct *name_struct =
+    nameSpaceManager->LookupName(name);
+
+  // LookupName only returns structs for the global.
+  MOZ_ASSERT_IF(name_struct,
+                name_struct->mType != nsGlobalNameStruct::eTypeNavigatorProperty);
+  return name_struct;
+}
+
 struct GlobalNameEnumeratorClosure
 {
   GlobalNameEnumeratorClosure(JSContext* aCx, nsGlobalWindow* aWindow,
                               nsTArray<nsString>& aNames)
     : mCx(aCx),
       mWindow(aWindow),
       mWrapper(aCx, aWindow->GetWrapper()),
       mNames(aNames)
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -497,16 +497,19 @@ public:
   static bool IsPrivilegedChromeWindow(JSContext* /* unused */, JSObject* aObj);
 
   static bool IsShowModalDialogEnabled(JSContext* /* unused */ = nullptr,
                                        JSObject* /* unused */ = nullptr);
 
   bool DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
                  JS::Handle<jsid> aId,
                  JS::MutableHandle<JSPropertyDescriptor> aDesc);
+  // The return value is whether DoResolve might end up resolving the given id.
+  // If in doubt, return true.
+  static bool MayResolve(jsid aId);
 
   void GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames,
                            mozilla::ErrorResult& aRv);
 
   // Object Management
   static already_AddRefed<nsGlobalWindow> Create(nsGlobalWindow *aOuterWindow);
 
   static nsGlobalWindow *FromSupports(nsISupports *supports)
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2821,16 +2821,22 @@ mozilla::dom::GetNameSpaceManager()
 
     nsresult rv = gNameSpaceManager->Init();
     NS_ENSURE_SUCCESS(rv, nullptr);
   }
 
   return gNameSpaceManager;
 }
 
+nsScriptNameSpaceManager*
+mozilla::dom::PeekNameSpaceManager()
+{
+  return gNameSpaceManager;
+}
+
 void
 mozilla::dom::ShutdownJSEnvironment()
 {
   KillTimers();
 
   NS_IF_RELEASE(gNameSpaceManager);
 
   if (!sContextCount) {
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -177,16 +177,19 @@ namespace mozilla {
 namespace dom {
 
 void StartupJSEnvironment();
 void ShutdownJSEnvironment();
 
 // Get the NameSpaceManager, creating if necessary
 nsScriptNameSpaceManager* GetNameSpaceManager();
 
+// Peek the NameSpaceManager, without creating it.
+nsScriptNameSpaceManager* PeekNameSpaceManager();
+
 // Runnable that's used to do async error reporting
 class AsyncErrorReporter : public nsRunnable
 {
 public:
   // aWindow may be null if this error report is not associated with a window
   AsyncErrorReporter(JSRuntime* aRuntime, xpc::ErrorReport* aReport)
     : mReport(aReport)
   {}
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -3662,16 +3662,24 @@ nsObjectLoadingContent::DoResolve(JSCont
   nsRefPtr<nsNPAPIPluginInstance> pi;
   nsresult rv = ScriptRequestPluginInstance(aCx, getter_AddRefs(pi));
   if (NS_FAILED(rv)) {
     return mozilla::dom::Throw(aCx, rv);
   }
   return true;
 }
 
+/* static */
+bool
+nsObjectLoadingContent::MayResolve(jsid aId)
+{
+  // We can resolve anything, really.
+  return true;
+}
+
 void
 nsObjectLoadingContent::GetOwnPropertyNames(JSContext* aCx,
                                             nsTArray<nsString>& /* unused */,
                                             ErrorResult& aRv)
 {
   // Just like DoResolve, just make sure we're instantiated.  That will do
   // the work our Enumerate hook needs to do.  This purposefully does not fire
   // for xray resolves, see bug 967694
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -166,16 +166,20 @@ class nsObjectLoadingContent : public ns
 
     // Remove plugin from protochain
     void TeardownProtoChain();
 
     // Helper for WebIDL NeedResolve
     bool DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObject,
                    JS::Handle<jsid> aId,
                    JS::MutableHandle<JSPropertyDescriptor> aDesc);
+    // The return value is whether DoResolve might end up resolving the given
+    // id.  If in doubt, return true.
+    static bool MayResolve(jsid aId);
+
     // Helper for WebIDL enumeration
     void GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& /* unused */,
                              mozilla::ErrorResult& aRv);
 
     // WebIDL API
     nsIDocument* GetContentDocument();
     void GetActualType(nsAString& aType) const
     {
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -2432,16 +2432,22 @@ ResolveGlobal(JSContext* aCx, JS::Handle
   MOZ_ASSERT(JS_IsGlobalObject(aObj),
              "Should have a global here, since we plan to resolve standard "
              "classes!");
 
   return JS_ResolveStandardClass(aCx, aObj, aId, aResolvedp);
 }
 
 bool
+MayResolveGlobal(const JSAtomState& aNames, jsid aId, JSObject* aMaybeObj)
+{
+  return JS_MayResolveStandardClass(aNames, aId, aMaybeObj);
+}
+
+bool
 EnumerateGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj)
 {
   MOZ_ASSERT(JS_IsGlobalObject(aObj),
              "Should have a global here, since we plan to enumerate standard "
              "classes!");
 
   return JS_EnumerateStandardClasses(aCx, aObj);
 }
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -2964,16 +2964,19 @@ IsInCertifiedApp(JSContext* aCx, JSObjec
 void
 FinalizeGlobal(JSFreeOp* aFop, JSObject* aObj);
 
 bool
 ResolveGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj,
               JS::Handle<jsid> aId, bool* aResolvedp);
 
 bool
+MayResolveGlobal(const JSAtomState& aNames, jsid aId, JSObject* aMaybeObj);
+
+bool
 EnumerateGlobal(JSContext* aCx, JS::Handle<JSObject*> aObj);
 
 template <class T>
 struct CreateGlobalOptions
 {
   static MOZ_CONSTEXPR_VAR ProtoAndIfaceCache::Kind ProtoAndIfaceCacheKind =
     ProtoAndIfaceCache::NonWindowLike;
   // Intl API is broken and makes JS_InitStandardClasses fail intermittently,
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -18,16 +18,17 @@ AUTOGENERATED_WARNING_COMMENT = \
     "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n\n"
 ADDPROPERTY_HOOK_NAME = '_addProperty'
 FINALIZE_HOOK_NAME = '_finalize'
 OBJECT_MOVED_HOOK_NAME = '_objectMoved'
 CONSTRUCT_HOOK_NAME = '_constructor'
 LEGACYCALLER_HOOK_NAME = '_legacycaller'
 HASINSTANCE_HOOK_NAME = '_hasInstance'
 RESOLVE_HOOK_NAME = '_resolve'
+MAY_RESOLVE_HOOK_NAME = '_mayResolve'
 ENUMERATE_HOOK_NAME = '_enumerate'
 ENUM_ENTRY_VARIABLE_NAME = 'strings'
 INSTANCE_RESERVED_SLOTS = 1
 
 
 def memberReservedSlot(member):
     return "(DOM_INSTANCE_RESERVED_SLOTS + %d)" % member.slotIndex
 
@@ -452,36 +453,39 @@ class CGDOMJSClass(CGThing):
                     }
                     """,
                     objectMoved=objectMovedHook)
         else:
             classFlags += "JSCLASS_HAS_RESERVED_SLOTS(%d)" % slotCount
             reservedSlots = slotCount
         if self.descriptor.interface.getExtendedAttribute("NeedResolve"):
             resolveHook = RESOLVE_HOOK_NAME
+            mayResolveHook = MAY_RESOLVE_HOOK_NAME
             enumerateHook = ENUMERATE_HOOK_NAME
         elif self.descriptor.isGlobal():
             resolveHook = "mozilla::dom::ResolveGlobal"
+            mayResolveHook = "mozilla::dom::MayResolveGlobal"
             enumerateHook = "mozilla::dom::EnumerateGlobal"
         else:
             resolveHook = "nullptr"
+            mayResolveHook = "nullptr"
             enumerateHook = "nullptr"
 
         return fill(
             """
             static const DOMJSClass Class = {
               { "${name}",
                 ${flags},
                 ${addProperty}, /* addProperty */
                 nullptr,               /* delProperty */
                 nullptr,               /* getProperty */
                 nullptr,               /* setProperty */
                 ${enumerate}, /* enumerate */
                 ${resolve}, /* resolve */
-                nullptr,               /* mayResolve */
+                ${mayResolve}, /* mayResolve */
                 nullptr,               /* convert */
                 ${finalize}, /* finalize */
                 ${call}, /* call */
                 nullptr,               /* hasInstance */
                 nullptr,               /* construct */
                 ${trace}, /* trace */
                 JS_NULL_CLASS_SPEC,
                 $*{classExtensionAndObjectOps}
@@ -493,16 +497,17 @@ class CGDOMJSClass(CGThing):
             static_assert(${reservedSlots} >= ${slotCount},
                           "Must have enough reserved slots.");
             """,
             name=self.descriptor.interface.identifier.name,
             flags=classFlags,
             addProperty=ADDPROPERTY_HOOK_NAME if wantsAddProperty(self.descriptor) else 'nullptr',
             enumerate=enumerateHook,
             resolve=resolveHook,
+            mayResolve=mayResolveHook,
             finalize=FINALIZE_HOOK_NAME,
             call=callHook,
             trace=traceHook,
             classExtensionAndObjectOps=classExtensionAndObjectOps,
             descriptor=DOMClass(self.descriptor),
             instanceReservedSlots=INSTANCE_RESERVED_SLOTS,
             reservedSlots=reservedSlots,
             slotCount=slotCount)
@@ -7764,67 +7769,93 @@ class CGLegacyCallHook(CGAbstractBinding
 
     def generate_code(self):
         name = self._legacycaller.identifier.name
         nativeName = MakeNativeName(self.descriptor.binaryNameFor(name))
         return CGMethodCall(nativeName, False, self.descriptor,
                             self._legacycaller)
 
 
-class CGResolveHook(CGAbstractBindingMethod):
+class CGResolveHook(CGAbstractClassHook):
     """
     Resolve hook for objects that have the NeedResolve extended attribute.
     """
     def __init__(self, descriptor):
         assert descriptor.interface.getExtendedAttribute("NeedResolve")
 
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'obj'),
                 Argument('JS::Handle<jsid>', 'id'),
                 Argument('bool*', 'resolvedp')]
-        # Our "self" is actually the "obj" argument in this case, not the thisval.
-        CGAbstractBindingMethod.__init__(
-            self, descriptor, RESOLVE_HOOK_NAME,
-            args, getThisObj="", callArgs="")
+        CGAbstractClassHook.__init__(self, descriptor, RESOLVE_HOOK_NAME,
+                                     "bool", args)
 
     def generate_code(self):
-        return CGGeneric(dedent("""
+        return dedent("""
             JS::Rooted<JSPropertyDescriptor> desc(cx);
             if (!self->DoResolve(cx, obj, id, &desc)) {
               return false;
             }
             if (!desc.object()) {
               return true;
             }
             // If desc.value() is undefined, then the DoResolve call
             // has already defined it on the object.  Don't try to also
             // define it.
             if (!desc.value().isUndefined() &&
                 !JS_DefinePropertyById(cx, obj, id, desc)) {
               return false;
             }
             *resolvedp = true;
             return true;
-            """))
+            """)
 
     def definition_body(self):
         if self.descriptor.isGlobal():
             # Resolve standard classes
             prefix = dedent("""
                 if (!ResolveGlobal(cx, obj, id, resolvedp)) {
                   return false;
                 }
                 if (*resolvedp) {
                   return true;
                 }
 
                 """)
         else:
             prefix = ""
-        return prefix + CGAbstractBindingMethod.definition_body(self)
+        return prefix + CGAbstractClassHook.definition_body(self)
+
+
+class CGMayResolveHook(CGAbstractStaticMethod):
+    """
+    Resolve hook for objects that have the NeedResolve extended attribute.
+    """
+    def __init__(self, descriptor):
+        assert descriptor.interface.getExtendedAttribute("NeedResolve")
+
+        args = [Argument('const JSAtomState&', 'names'),
+                Argument('jsid', 'id'),
+                Argument('JSObject*', 'maybeObj')]
+        CGAbstractStaticMethod.__init__(self, descriptor, MAY_RESOLVE_HOOK_NAME,
+                                        "bool", args)
+
+    def definition_body(self):
+        if self.descriptor.isGlobal():
+            # Check whether this would resolve as a standard class.
+            prefix = dedent("""
+                if (MayResolveGlobal(names, id, maybeObj)) {
+                  return true;
+                }
+
+                """)
+        else:
+            prefix = ""
+        return (prefix +
+                "return %s::MayResolve(id);\n" % self.descriptor.nativeType)
 
 
 class CGEnumerateHook(CGAbstractBindingMethod):
     """
     Enumerate hook for objects with custom hooks.
     """
     def __init__(self, descriptor):
         assert descriptor.interface.getExtendedAttribute("NeedResolve")
@@ -11343,16 +11374,17 @@ class CGDescriptor(CGThing):
             cgThings.append(CGInterfaceObjectJSClass(descriptor, properties))
             if descriptor.needsConstructHookHolder():
                 cgThings.append(CGClassConstructHookHolder(descriptor))
             cgThings.append(CGNamedConstructors(descriptor))
 
         cgThings.append(CGLegacyCallHook(descriptor))
         if descriptor.interface.getExtendedAttribute("NeedResolve"):
             cgThings.append(CGResolveHook(descriptor))
+            cgThings.append(CGMayResolveHook(descriptor))
             cgThings.append(CGEnumerateHook(descriptor))
 
         if descriptor.hasNamedPropertiesObject:
             cgThings.append(CGGetNamedPropertiesObjectMethod(descriptor))
 
         if descriptor.interface.hasInterfacePrototypeObject():
             cgThings.append(CGPrototypeJSClass(descriptor, properties))
 
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -169,16 +169,22 @@ var ignoreFunctions = {
     // AutoSuppressGCAnalysis. AutoSafeJSContext is the same thing, just with
     // a different value for the 'aSafe' parameter.
     "void mozilla::AutoJSContext::AutoJSContext(mozilla::detail::GuardObjectNotifier*)" : true,
     "void mozilla::AutoSafeJSContext::~AutoSafeJSContext(int32)" : true,
 
     // And these are workarounds to avoid even more analysis work,
     // which would sadly still be needed even with bug 898815.
     "void js::AutoCompartment::AutoCompartment(js::ExclusiveContext*, JSCompartment*)": true,
+
+    // The nsScriptNameSpaceManager functions can't actually GC.  They
+    // just use a pldhash which has function pointers, which makes the
+    // analysis think maybe they can.
+    "nsGlobalNameStruct* nsScriptNameSpaceManager::LookupNavigatorName(nsAString_internal*)": true,
+    "nsGlobalNameStruct* nsScriptNameSpaceManager::LookupName(nsAString_internal*, uint16**)": true,
 };
 
 function ignoreGCFunction(mangled)
 {
     assert(mangled in readableNames);
     var fun = readableNames[mangled][0];
 
     if (fun in ignoreFunctions)