Bug 792137. [TreatNonCallableAsNull] lives on the callback type now. r=peterv
authorBoris Zbarsky <bzbarsky@mit.edu>
Wed, 10 Oct 2012 15:53:02 -0400
changeset 109879 c24f40ee97cb316c5a9e21e3aa3b6fe6c282d690
parent 109878 ea3394d0280cfbbf1f120a2fdd3d0f1aea5ffb2f
child 109880 2803709dc340deb32dc82ac4d67433cf01f7b00b
push id16254
push userbzbarsky@mozilla.com
push dateWed, 10 Oct 2012 19:58:50 +0000
treeherdermozilla-inbound@2803709dc340 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs792137
milestone19.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 792137. [TreatNonCallableAsNull] lives on the callback type now. r=peterv
dom/bindings/Codegen.py
dom/bindings/Errors.msg
dom/bindings/parser/WebIDL.py
dom/bindings/parser/tests/test_callback.py
dom/bindings/parser/tests/test_treatNonCallableAsNull.py
dom/bindings/test/TestBindingHeader.h
dom/bindings/test/TestCodeGen.webidl
dom/webidl/EventHandler.webidl
dom/webidl/WebIDL.mk
dom/webidl/WebSocket.webidl
dom/webidl/XMLHttpRequest.webidl
dom/webidl/XMLHttpRequestEventTarget.webidl
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1765,16 +1765,20 @@ def getJSToNativeConversionTemplate(type
     def onFailureNotAnObject(failureCode):
         return CGWrapper(CGGeneric(
                 failureCode or
                 'return ThrowErrorMessage(cx, MSG_NOT_OBJECT);'), post="\n")
     def onFailureBadType(failureCode, typeName):
         return CGWrapper(CGGeneric(
                 failureCode or
                 'return ThrowErrorMessage(cx, MSG_DOES_NOT_IMPLEMENT_INTERFACE, "%s");' % typeName), post="\n")
+    def onFailureNotCallable(failureCode):
+        return CGWrapper(CGGeneric(
+                failureCode or
+                'return ThrowErrorMessage(cx, MSG_NOT_CALLABLE);'), post="\n")
 
     # A helper function for handling default values.  Takes a template
     # body and the C++ code to set the default value and wraps the
     # given template body in handling for the default value.
     def handleDefault(template, setDefault):
         if defaultValue is None:
             return template
         return CGWrapper(
@@ -2388,33 +2392,51 @@ for (uint32_t i = 0; i < length; ++i) {
             template = handleDefault(template,
                                      ("${declName} = %sValues::%s" %
                                       (enum,
                                        getEnumValueName(defaultValue.value))))
         return (template, CGGeneric(enum), None, isOptional)
 
     if type.isCallback():
         assert not isEnforceRange and not isClamp
+        assert not type.treatNonCallableAsNull() or type.nullable()
 
         if isMember:
             raise TypeError("Can't handle member callbacks; need to sort out "
                             "rooting issues")
-        # XXXbz we're going to assume that callback types are always
-        # nullable and always have [TreatNonCallableAsNull] for now.
-        haveCallable = "${val}.isObject() && JS_ObjectIsCallable(cx, &${val}.toObject())"
-        if defaultValue is not None:
-            assert(isinstance(defaultValue, IDLNullValue))
-            haveCallable = "${haveValue} && " + haveCallable
-        return (
-            "if (%s) {\n"
-            "  ${declName} = &${val}.toObject();\n"
-            "} else {\n"
-            "  ${declName} = NULL;\n"
-            "}" % haveCallable,
-            CGGeneric("JSObject*"), None, isOptional)
+
+        if type.nullable():
+            declType = CGGeneric("JSObject*")
+        else:
+            declType = CGGeneric("NonNull<JSObject>")
+
+        if type.treatNonCallableAsNull():
+            haveCallable = "JS_ObjectIsCallable(cx, &${val}.toObject())"
+            if not isDefinitelyObject:
+                haveCallable = "${val}.isObject() && " + haveCallable
+            if defaultValue is not None:
+                assert(isinstance(defaultValue, IDLNullValue))
+                haveCallable = "${haveValue} && " + haveCallable
+            template = (
+                "if (%s) {\n"
+                "  ${declName} = &${val}.toObject();\n"
+                "} else {\n"
+                "  ${declName} = nullptr;\n"
+                "}" % haveCallable)
+        else:
+            template = wrapObjectTemplate(
+                "if (JS_ObjectIsCallable(cx, &${val}.toObject())) {\n"
+                "  ${declName} = &${val}.toObject();\n"
+                "} else {\n"
+                "%s"
+                "}" % CGIndenter(onFailureNotCallable(failureCode)).define(),
+                isDefinitelyObject, type,
+                "${declName} = nullptr",
+                failureCode)
+        return (template, declType, None, isOptional)
 
     if type.isAny():
         assert not isEnforceRange and not isClamp
 
         if isMember:
             raise TypeError("Can't handle member 'any'; need to sort out "
                             "rooting issues")
         templateBody = "${declName} = ${val};"
@@ -3844,17 +3866,19 @@ def getUnionAccessorSignatureType(type, 
 
     if type.isEnum():
         if type.nullable():
             raise TypeError("We don't support nullable enumerated arguments or "
                             "union members yet")
         return CGGeneric(type.inner.identifier.name)
 
     if type.isCallback():
-        return CGGeneric("JSObject*")
+        if type.nullable():
+            return CGGeneric("JSObject*")
+        return CGGeneric("JSObject&")
 
     if type.isAny():
         return CGGeneric("JS::Value")
 
     if type.isObject():
         typeName = CGGeneric("JSObject")
         if type.nullable():
             typeName = CGWrapper(typeName, post="*")
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -17,14 +17,15 @@
  * <FORMAT_STRING> is a string literal, containing <ARGUMENT_COUNT> sequences
  * {X} where X  is an integer representing the argument number that will
  * be replaced with a string value when the error is reported.
  */
 
 MSG_DEF(MSG_INVALID_ENUM_VALUE, 2, "Value '{0}' is not a valid value for enumeration {1}.")
 MSG_DEF(MSG_MISSING_ARGUMENTS, 1, "Not enough arguments to {0}.")
 MSG_DEF(MSG_NOT_OBJECT, 0, "Value not an object.")
+MSG_DEF(MSG_NOT_CALLABLE, 0, "Value is not callable.")
 MSG_DEF(MSG_DOES_NOT_IMPLEMENT_INTERFACE, 1, "Value does not implement interface {0}.")
 MSG_DEF(MSG_NOT_IN_UNION, 1, "Value could not be converted to any of: {0}.")
 MSG_DEF(MSG_ILLEGAL_CONSTRUCTOR, 0, "Illegal constructor.")
 MSG_DEF(MSG_NO_PROPERTY_SETTER, 1, "{0} doesn't have an indexed property setter.")
 MSG_DEF(MSG_ENFORCE_RANGE_NON_FINITE, 1, "Non-finite value is out of range for {0}.")
 MSG_DEF(MSG_ENFORCE_RANGE_OUT_OF_RANGE, 1, "Value is out of range for {0}.")
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -958,25 +958,18 @@ class IDLType(IDLObject):
 
     def isComplete(self):
         return True
 
     def tag(self):
         assert False # Override me!
 
     def treatNonCallableAsNull(self):
-        if not (self.nullable() and self.tag() == IDLType.Tags.callback):
-            raise WebIDLError("Type %s cannot be TreatNonCallableAsNull" % self,
-                              [self.location])
-
-        return hasattr(self, "_treatNonCallableAsNull")
-
-    def markTreatNonCallableAsNull(self):
-        assert not self.treatNonCallableAsNull()
-        self._treatNonCallableAsNull = True
+        assert self.tag() == IDLType.Tags.callback
+        return self.nullable() and self.inner._treatNonCallableAsNull
 
     def addExtendedAttributes(self, attrs):
         assert len(attrs) == 0
 
     def resolveType(self, parentScope):
         pass
 
     def unroll(self):
@@ -1994,17 +1987,18 @@ class IDLAttribute(IDLInterfaceMember):
                                       "type", [self.location, f.location])
 
     def validate(self):
         pass
 
     def handleExtendedAttribute(self, attr):
         identifier = attr.identifier()
         if identifier == "TreatNonCallableAsNull":
-            self.type.markTreatNonCallableAsNull();
+            raise WebIDLError("TreatNonCallableAsNull cannot be specified on attributes",
+                              [attr.location, self.location])
         elif identifier == "SetterInfallible" and self.readonly:
             raise WebIDLError("Readonly attributes must not be flagged as "
                               "[SetterInfallible]",
                               [self.location])
         elif identifier == "LenientThis":
             if not attr.noArguments():
                 raise WebIDLError("[LenientThis] must take no arguments",
                                   [attr.location])
@@ -2109,16 +2103,18 @@ class IDLCallbackType(IDLType, IDLObject
         self._arguments = list(arguments)
 
         IDLObjectWithScope.__init__(self, location, parentScope, identifier)
 
         for (returnType, arguments) in self.signatures():
             for argument in arguments:
                 argument.resolve(self)
 
+        self._treatNonCallableAsNull = False
+
     def isCallback(self):
         return True
 
     def signatures(self):
         return [(self._returnType, self._arguments)]
 
     def tag(self):
         return IDLType.Tags.callback
@@ -2148,16 +2144,26 @@ class IDLCallbackType(IDLType, IDLObject
 
     def isDistinguishableFrom(self, other):
         if other.isUnion():
             # Just forward to the union; it'll deal
             return other.isDistinguishableFrom(self)
         return (other.isPrimitive() or other.isString() or other.isEnum() or
                 other.isNonCallbackInterface() or other.isDate())
 
+    def addExtendedAttributes(self, attrs):
+        unhandledAttrs = []
+        for attr in attrs:
+            if attr.identifier() == "TreatNonCallableAsNull":
+                self._treatNonCallableAsNull = True
+            else:
+                unhandledAttrs.append(attr)
+        if len(unhandledAttrs) != 0:
+            IDLType.addExtendedAttributes(self, unhandledAttrs)
+
 class IDLMethodOverload:
     """
     A class that represents a single overload of a WebIDL method.  This is not
     quite the same as an element of the "effective overload set" in the spec,
     because separate IDLMethodOverloads are not created based on arguments being
     optional.  Rather, when multiple methods have the same name, there is an
     IDLMethodOverload for each one, all hanging off an IDLMethod representing
     the full set of overloads.
--- a/dom/bindings/parser/tests/test_callback.py
+++ b/dom/bindings/parser/tests/test_callback.py
@@ -7,17 +7,17 @@ def WebIDLTest(parser, harness):
         };
 
         callback CallbackType = boolean (unsigned long arg);
     """)
 
     results = parser.finish()
 
     harness.ok(True, "TestCallback interface parsed without error.")
-    harness.check(len(results), 2, "Should be one production.")
+    harness.check(len(results), 2, "Should be two productions.")
     iface = results[0]
     harness.ok(isinstance(iface, WebIDL.IDLInterface),
                "Should be an IDLInterface")
     harness.check(iface.identifier.QName(), "::TestCallback", "Interface has the right QName")
     harness.check(iface.identifier.name, "TestCallback", "Interface has the right name")
     harness.check(len(iface.members), 1, "Expect %s members" % 1)
 
     attr = iface.members[0]
--- a/dom/bindings/parser/tests/test_treatNonCallableAsNull.py
+++ b/dom/bindings/parser/tests/test_treatNonCallableAsNull.py
@@ -1,17 +1,17 @@
 import WebIDL
 
 def WebIDLTest(parser, harness):
     parser.parse("""
-        callback Function = any(any... arguments);
+        [TreatNonCallableAsNull] callback Function = any(any... arguments);
 
         interface TestTreatNonCallableAsNull1 {
-          [TreatNonCallableAsNull] attribute Function? onfoo;
-          attribute Function? onbar;
+          attribute Function? onfoo;
+          attribute Function onbar;
         };
     """)
 
     results = parser.finish()
 
     iface = results[1]
     attr = iface.members[0]
     harness.check(attr.type.treatNonCallableAsNull(), True, "Got the expected value")
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -329,23 +329,26 @@ public:
   void PassOptionalEnum(const Optional<TestEnum>&);
   void PassEnumWithDefault(TestEnum);
   TestEnum ReceiveEnum();
   TestEnum EnumAttribute();
   TestEnum ReadonlyEnumAttribute();
   void SetEnumAttribute(TestEnum);
 
   // Callback types
-  void PassCallback(JSContext*, JSObject*);
+  void PassCallback(JSContext*, JSObject&);
   void PassNullableCallback(JSContext*, JSObject*);
-  void PassOptionalCallback(JSContext*, const Optional<JSObject*>&);
+  void PassOptionalCallback(JSContext*, const Optional<NonNull<JSObject> >&);
   void PassOptionalNullableCallback(JSContext*, const Optional<JSObject*>&);
   void PassOptionalNullableCallbackWithDefaultValue(JSContext*, JSObject*);
   JSObject* ReceiveCallback(JSContext*);
   JSObject* ReceiveNullableCallback(JSContext*);
+  void PassNullableTreatAsNullCallback(JSContext*, JSObject*);
+  void PassOptionalNullableTreatAsNullCallback(JSContext*, const Optional<JSObject*>&);
+  void PassOptionalNullableTreatAsNullCallbackWithDefaultValue(JSContext*, JSObject*);
 
   // Any types
   void PassAny(JSContext*, JS::Value);
   void PassOptionalAny(JSContext*, const Optional<JS::Value>&);
   void PassAnyDefaultNull(JSContext*, JS::Value);
   JS::Value ReceiveAny(JSContext*);
 
   // object types
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -19,16 +19,17 @@ callback interface TestCallbackInterface
 };
 
 enum TestEnum {
   "a",
   "b"
 };
 
 callback TestCallback = void();
+[TreatNonCallableAsNull] callback TestTreatAsNullCallback = void();
 
 TestInterface implements ImplementedInterface;
 
 // This interface is only for use in the constructor below
 interface OnlyForUseInConstructor {
 };
 
 [Constructor,
@@ -260,16 +261,19 @@ interface TestInterface {
   // Callback types
   void passCallback(TestCallback arg);
   void passNullableCallback(TestCallback? arg);
   void passOptionalCallback(optional TestCallback arg);
   void passOptionalNullableCallback(optional TestCallback? arg);
   void passOptionalNullableCallbackWithDefaultValue(optional TestCallback? arg = null);
   TestCallback receiveCallback();
   TestCallback? receiveNullableCallback();
+  void passNullableTreatAsNullCallback(TestTreatAsNullCallback? arg);
+  void passOptionalNullableTreatAsNullCallback(optional TestTreatAsNullCallback? arg);
+  void passOptionalNullableTreatAsNullCallbackWithDefaultValue(optional TestTreatAsNullCallback? arg = null);
 
   // Any types
   void passAny(any arg);
   void passOptionalAny(optional any arg);
   void passAnyDefaultNull(optional any arg = null);
   any receiveAny();
 
   // object types
new file mode 100644
--- /dev/null
+++ b/dom/webidl/EventHandler.webidl
@@ -0,0 +1,15 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * http://www.whatwg.org/specs/web-apps/current-work/#eventhandler
+ *
+ *  Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
+ * Opera Software ASA. You are granted a license to use, reproduce
+ * and create derivative works of this document.
+ */
+[TreatNonCallableAsNull]
+callback EventHandlerNonNull = any (Event event);
+typedef EventHandlerNonNull? EventHandler;
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -17,16 +17,17 @@ webidl_files = \
   AudioSourceNode.webidl \
   Blob.webidl \
   CanvasRenderingContext2D.webidl \
   ClientRectList.webidl \
   CSSStyleDeclaration.webidl \
   DOMTokenList.webidl \
   DOMSettableTokenList.webidl \
   Function.webidl \
+  EventHandler.webidl \
   EventListener.webidl \
   EventTarget.webidl \
   FileList.webidl \
   FileReaderSync.webidl \
   HTMLCollection.webidl \
   HTMLOptionsCollection.webidl \
   HTMLPropertiesCollection.webidl \
   NodeList.webidl \
--- a/dom/webidl/WebSocket.webidl
+++ b/dom/webidl/WebSocket.webidl
@@ -27,36 +27,36 @@ interface WebSocket : EventTarget {
   const unsigned short CLOSED = 3;
 
   readonly attribute unsigned short readyState;
 
   readonly attribute unsigned long bufferedAmount;
 
   // networking
 
-  [TreatNonCallableAsNull, SetterThrows]
-  attribute Function? onopen;
+  [SetterThrows]
+  attribute EventHandler onopen;
 
-  [TreatNonCallableAsNull, SetterThrows]
-  attribute Function? onerror;
+  [SetterThrows]
+  attribute EventHandler onerror;
 
-  [TreatNonCallableAsNull, SetterThrows]
-  attribute Function? onclose;
+  [SetterThrows]
+  attribute EventHandler onclose;
 
   readonly attribute DOMString extensions;
 
   readonly attribute DOMString protocol;
 
   [Throws]
   void close([Clamp] optional unsigned short code, optional DOMString reason);
 
   // messaging
 
-  [TreatNonCallableAsNull, SetterThrows]
-  attribute Function? onmessage;
+  [SetterThrows]
+  attribute EventHandler onmessage;
 
   attribute BinaryType binaryType;
 
   [Throws]
   void send(DOMString data);
 
   [Throws]
   void send(Blob data);
--- a/dom/webidl/XMLHttpRequest.webidl
+++ b/dom/webidl/XMLHttpRequest.webidl
@@ -54,18 +54,18 @@ dictionary MozXMLHttpRequestParameters
 [Constructor(optional MozXMLHttpRequestParameters params),
  // There are apparently callers, specifically CoffeeScript, who do
  // things like this:
  //   c = new(window.ActiveXObject || XMLHttpRequest)("Microsoft.XMLHTTP")
  // To handle that, we need a constructor that takes a string.
  Constructor(DOMString ignored)]
 interface XMLHttpRequest : XMLHttpRequestEventTarget {
   // event handler
-  [TreatNonCallableAsNull, SetterThrows, GetterThrows=Workers]
-  attribute Function? onreadystatechange;
+  [SetterThrows, GetterThrows=Workers]
+  attribute EventHandler onreadystatechange;
 
   // states
   const unsigned short UNSENT = 0;
   const unsigned short OPENED = 1;
   const unsigned short HEADERS_RECEIVED = 2;
   const unsigned short LOADING = 3;
   const unsigned short DONE = 4;
 
--- a/dom/webidl/XMLHttpRequestEventTarget.webidl
+++ b/dom/webidl/XMLHttpRequestEventTarget.webidl
@@ -8,29 +8,29 @@
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 [NoInterfaceObject]
 interface XMLHttpRequestEventTarget : EventTarget {
   // event handlers
-  [TreatNonCallableAsNull, SetterThrows, GetterThrows=Workers]
-  attribute Function? onloadstart;
+  [SetterThrows, GetterThrows=Workers]
+  attribute EventHandler onloadstart;
 
-  [TreatNonCallableAsNull, SetterThrows, GetterThrows=Workers]
-  attribute Function? onprogress;
+  [SetterThrows, GetterThrows=Workers]
+  attribute EventHandler onprogress;
 
-  [TreatNonCallableAsNull, SetterThrows, GetterThrows=Workers]
-  attribute Function? onabort;
+  [SetterThrows, GetterThrows=Workers]
+  attribute EventHandler onabort;
 
-  [TreatNonCallableAsNull, SetterThrows, GetterThrows=Workers]
-  attribute Function? onerror;
+  [SetterThrows, GetterThrows=Workers]
+  attribute EventHandler onerror;
 
-  [TreatNonCallableAsNull, SetterThrows, GetterThrows=Workers]
-  attribute Function? onload;
+  [SetterThrows, GetterThrows=Workers]
+  attribute EventHandler onload;
 
-  [TreatNonCallableAsNull, SetterThrows, GetterThrows=Workers]
-  attribute Function? ontimeout;
+  [SetterThrows, GetterThrows=Workers]
+  attribute EventHandler ontimeout;
 
-  [TreatNonCallableAsNull, SetterThrows, GetterThrows=Workers]
-  attribute Function? onloadend;
+  [SetterThrows, GetterThrows=Workers]
+  attribute EventHandler onloadend;
 };