Bug 1141916 - Part 2: Support [ChromeOnly] on dictionary members. r=peterv
authorCameron McCormack <cam@mcc.id.au>
Thu, 02 Jun 2016 12:14:24 -0400
changeset 339232 b4555556428ee4ded0b94eaa43997f20098f9e63
parent 339231 f34508f1a4a0f2c5a3f1a3faba15a51dbd8f4ee8
child 339233 00d30d448554e802975a76a9572911b902a1cb7f
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1141916
milestone49.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 1141916 - Part 2: Support [ChromeOnly] on dictionary members. r=peterv
dom/bindings/Codegen.py
dom/bindings/parser/WebIDL.py
dom/bindings/parser/tests/test_conditional_dictionary_member.py
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -12753,19 +12753,28 @@ class CGDictionary(CGThing):
             "prop": self.makeMemberName(member.identifier.name),
             "convert": string.Template(conversionInfo.template).substitute(replacements),
             "propGet": propGet
         }
         # The conversion code will only run where a default value or a value passed
         # by the author needs to get converted, so we can remember if we have any
         # members present here.
         conversionReplacements["convert"] += "mIsAnyMemberPresent = true;\n"
-        conversion = ("if (!isNull && !${propGet}) {\n"
-                      "  return false;\n"
-                      "}\n")
+        if isChromeOnly(member):
+            conversion = ("if (!isNull) {\n"
+                          "  if (!nsContentUtils::ThreadsafeIsCallerChrome()) {\n"
+                          "    temp->setUndefined();\n"
+                          "  } else if (!${propGet}) {\n"
+                          "    return false;\n"
+                          "  }\n"
+                          "}\n")
+        else:
+            conversion = ("if (!isNull && !${propGet}) {\n"
+                          "  return false;\n"
+                          "}\n")
         if member.defaultValue:
             if (member.type.isUnion() and
                 (not member.type.nullable() or
                  not isinstance(member.defaultValue, IDLNullValue))):
                 # Since this has a default value, it might have been initialized
                 # already.  Go ahead and uninit it before we try to init it
                 # again.
                 memberName = self.makeMemberName(member.identifier.name)
@@ -12865,16 +12874,18 @@ class CGDictionary(CGThing):
         conversion = CGWrapper(
             CGIndenter(conversion),
             pre=("do {\n"
                  "  // block for our 'break' successCode and scope for 'temp' and 'currentValue'\n"),
             post="} while(0);\n")
         if member.canHaveMissingValue():
             # Only do the conversion if we have a value
             conversion = CGIfWrapper(conversion, "%s.WasPassed()" % memberLoc)
+        if isChromeOnly(member):
+            conversion = CGIfWrapper(conversion, "nsContentUtils::ThreadsafeIsCallerChrome()")
         return conversion
 
     def getMemberTrace(self, member):
         type = member.type
         assert typeNeedsRooting(type)
         memberLoc = self.makeMemberName(member.identifier.name)
         if not member.canHaveMissingValue():
             memberData = memberLoc
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -4075,26 +4075,63 @@ class IDLAttribute(IDLInterfaceMember):
             raise WebIDLError("An attribute with [PutForwards] must have an "
                               "interface type as its type", [self.location])
 
         if not self.type.isInterface() and self.getExtendedAttribute("SameObject"):
             raise WebIDLError("An attribute with [SameObject] must have an "
                               "interface type as its type", [self.location])
 
     def validate(self):
+        def typeContainsChromeOnlyDictionaryMember(type):
+            if (type.nullable() or
+                type.isSequence() or
+                type.isMozMap()):
+                return typeContainsChromeOnlyDictionaryMember(type.inner)
+
+            if type.isUnion():
+                for memberType in type.flatMemberTypes:
+                    (contains, location) = typeContainsChromeOnlyDictionaryMember(memberType)
+                    if contains:
+                        return (True, location)
+
+            if type.isDictionary():
+                dictionary = type.inner
+                while dictionary:
+                    (contains, location) = dictionaryContainsChromeOnlyMember(dictionary)
+                    if contains:
+                        return (True, location)
+                    dictionary = dictionary.parent
+
+            return (False, None)
+
+        def dictionaryContainsChromeOnlyMember(dictionary):
+            for member in dictionary.members:
+                if member.getExtendedAttribute("ChromeOnly"):
+                    return (True, member.location)
+                (contains, location) = typeContainsChromeOnlyDictionaryMember(member.type)
+                if contains:
+                    return (True, location)
+            return (False, None)
+
         IDLInterfaceMember.validate(self)
 
-        if ((self.getExtendedAttribute("Cached") or
-             self.getExtendedAttribute("StoreInSlot")) and
-            not self.affects == "Nothing"):
-            raise WebIDLError("Cached attributes and attributes stored in "
-                              "slots must be Constant or Pure or "
-                              "Affects=Nothing, since the getter won't always "
-                              "be called.",
-                              [self.location])
+        if (self.getExtendedAttribute("Cached") or
+            self.getExtendedAttribute("StoreInSlot")):
+            if not self.affects == "Nothing":
+                raise WebIDLError("Cached attributes and attributes stored in "
+                                  "slots must be Constant or Pure or "
+                                  "Affects=Nothing, since the getter won't always "
+                                  "be called.",
+                                  [self.location])
+            (contains, location) = typeContainsChromeOnlyDictionaryMember(self.type)
+            if contains:
+                raise WebIDLError("[Cached] and [StoreInSlot] must not be used "
+                                  "on an attribute whose type contains a "
+                                  "[ChromeOnly] dictionary member",
+                                  [self.location, location])
         if self.getExtendedAttribute("Frozen"):
             if (not self.type.isSequence() and not self.type.isDictionary() and
                 not self.type.isMozMap()):
                 raise WebIDLError("[Frozen] is only allowed on "
                                   "sequence-valued, dictionary-valued, and "
                                   "MozMap-valued attributes",
                                   [self.location])
         if not self.type.unroll().isExposedInAllOf(self.exposureSet):
@@ -4360,16 +4397,21 @@ class IDLArgument(IDLObjectWithIdentifie
                     raise WebIDLError("[EnforceRange] must take no arguments",
                                       [attribute.location])
                 if self.clamp:
                     raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive",
                                       [self.location])
                 self.enforceRange = True
             elif identifier == "TreatNonCallableAsNull":
                 self._allowTreatNonCallableAsNull = True
+            elif self.dictionaryMember and identifier == "ChromeOnly":
+                if not self.optional:
+                    raise WebIDLError("[ChromeOnly] must not be used on a required "
+                                      "dictionary member",
+                                      [attribute.location])
             else:
                 raise WebIDLError("Unhandled extended attribute on %s" %
                                   ("a dictionary member" if self.dictionaryMember else
                                    "an argument"),
                                   [attribute.location])
             attrlist = attribute.listValue()
             self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
 
new file mode 100644
--- /dev/null
+++ b/dom/bindings/parser/tests/test_conditional_dictionary_member.py
@@ -0,0 +1,110 @@
+def WebIDLTest(parser, harness):
+    parser.parse("""
+      dictionary Dict {
+        any foo;
+        [ChromeOnly] any bar;
+      };
+    """)
+    results = parser.finish()
+    harness.check(len(results), 1, "Should have a dictionary")
+    members = results[0].members;
+    harness.check(len(members), 2, "Should have two members")
+    # Note that members are ordered lexicographically, so "bar" comes
+    # before "foo".
+    harness.ok(members[0].getExtendedAttribute("ChromeOnly"),
+               "First member is not ChromeOnly")
+    harness.ok(not members[1].getExtendedAttribute("ChromeOnly"),
+               "Second member is ChromeOnly")
+
+    parser = parser.reset()
+    parser.parse("""
+      dictionary Dict {
+        any foo;
+        any bar;
+      };
+
+      interface Iface {
+        [Constant, Cached] readonly attribute Dict dict;
+      };
+    """)
+    results = parser.finish()
+    harness.check(len(results), 2, "Should have a dictionary and an interface")
+
+    parser = parser.reset()
+    exception = None
+    try:
+      parser.parse("""
+        dictionary Dict {
+          any foo;
+          [ChromeOnly] any bar;
+        };
+
+        interface Iface {
+          [Constant, Cached] readonly attribute Dict dict;
+        };
+      """)
+      results = parser.finish()
+    except Exception, exception:
+        pass
+
+    harness.ok(exception, "Should have thrown.")
+    harness.check(exception.message,
+                  "[Cached] and [StoreInSlot] must not be used on an attribute "
+                  "whose type contains a [ChromeOnly] dictionary member",
+                  "Should have thrown the right exception")
+
+    parser = parser.reset()
+    exception = None
+    try:
+      parser.parse("""
+        dictionary ParentDict {
+          [ChromeOnly] any bar;
+        };
+
+        dictionary Dict : ParentDict {
+          any foo;
+        };
+
+        interface Iface {
+          [Constant, Cached] readonly attribute Dict dict;
+        };
+      """)
+      results = parser.finish()
+    except Exception, exception:
+        pass
+
+    harness.ok(exception, "Should have thrown (2).")
+    harness.check(exception.message,
+                  "[Cached] and [StoreInSlot] must not be used on an attribute "
+                  "whose type contains a [ChromeOnly] dictionary member",
+                  "Should have thrown the right exception (2)")
+
+    parser = parser.reset()
+    exception = None
+    try:
+      parser.parse("""
+        dictionary GrandParentDict {
+          [ChromeOnly] any baz;
+        };
+
+        dictionary ParentDict : GrandParentDict {
+          any bar;
+        };
+
+        dictionary Dict : ParentDict {
+          any foo;
+        };
+
+        interface Iface {
+          [Constant, Cached] readonly attribute Dict dict;
+        };
+      """)
+      results = parser.finish()
+    except Exception, exception:
+        pass
+
+    harness.ok(exception, "Should have thrown (3).")
+    harness.check(exception.message,
+                  "[Cached] and [StoreInSlot] must not be used on an attribute "
+                  "whose type contains a [ChromeOnly] dictionary member",
+                  "Should have thrown the right exception (3)")