Fix for bug 742156 (Stringifying EventTarget throws): implement a toString method for interface objects. r=bz, a=akeybl.
authorPeter Van der Beken <peterv@propagandism.org>
Wed, 02 May 2012 15:28:17 +0200
changeset 94202 b486a6c98abe24707bb80715cf5807c4908d2f74
parent 94201 7d702f45c71a1875dd3e87e583a7d848057211b5
child 94203 5a750b6f3a3c7ca0a788c0a5f4448d3482040be5
push idunknown
push userunknown
push dateunknown
reviewersbz, akeybl
bugs742156
milestone14.0a2
Fix for bug 742156 (Stringifying EventTarget throws): implement a toString method for interface objects. r=bz, a=akeybl.
dom/bindings/Utils.cpp
dom/bindings/test/Makefile.in
dom/bindings/test/test_interfaceToString.html
--- a/dom/bindings/Utils.cpp
+++ b/dom/bindings/Utils.cpp
@@ -19,16 +19,79 @@ DefineConstants(JSContext* cx, JSObject*
                         JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     if (!ok) {
       return false;
     }
   }
   return true;
 }
 
+// We should use JSFunction objects for interface objects, but we need a custom
+// hasInstance hook because we have new interface objects on prototype chains of
+// old (XPConnect-based) bindings. Because Function.prototype.toString throws if
+// passed a non-Function object we also need to provide our own toString method
+// for interface objects.
+
+enum {
+  TOSTRING_CLASS_RESERVED_SLOT = 0,
+  TOSTRING_NAME_RESERVED_SLOT = 1
+};
+
+JSBool
+InterfaceObjectToString(JSContext* cx, unsigned argc, JS::Value *vp)
+{
+  JSObject* callee = JSVAL_TO_OBJECT(JS_CALLEE(cx, vp));
+
+  JSObject* obj = JS_THIS_OBJECT(cx, vp);
+  if (!obj) {
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CONVERT_TO,
+                         "null", "object");
+    return false;
+  }
+
+  jsval v = js::GetFunctionNativeReserved(callee, TOSTRING_CLASS_RESERVED_SLOT);
+  JSClass* clasp = static_cast<JSClass*>(JSVAL_TO_PRIVATE(v));
+
+  v = js::GetFunctionNativeReserved(callee, TOSTRING_NAME_RESERVED_SLOT);
+  JSString* jsname = static_cast<JSString*>(JSVAL_TO_STRING(v));
+  size_t length;
+  const jschar* name = JS_GetInternedStringCharsAndLength(jsname, &length);
+
+  if (js::GetObjectJSClass(obj) != clasp) {
+    JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_INCOMPATIBLE_PROTO,
+                         NS_ConvertUTF16toUTF8(name).get(), "toString",
+                         "object");
+    return false;
+  }
+
+  JS::Value* argv = JS_ARGV(cx, vp);
+  uint32_t indent = 0;
+  if (argc != 0 && !JS_ValueToECMAUint32(cx, argv[0], &indent))
+      return false;
+
+  nsAutoString spaces;
+  while (indent-- > 0) {
+    spaces.Append(PRUnichar(' '));
+  }
+
+  nsString str;
+  str.Append(spaces);
+  str.AppendLiteral("function ");
+  str.Append(name, length);
+  str.AppendLiteral("() {");
+  str.Append('\n');
+  str.Append(spaces);
+  str.AppendLiteral("    [native code]");
+  str.Append('\n');
+  str.Append(spaces);
+  str.AppendLiteral("}");
+
+  return xpc::NonVoidStringToJsval(cx, str, vp);
+}
+
 static JSObject*
 CreateInterfaceObject(JSContext* cx, JSObject* global, JSObject* receiver,
                       JSClass* constructorClass, JSNative constructorNative,
                       unsigned ctorNargs, JSObject* proto,
                       JSFunctionSpec* staticMethods, ConstantSpec* constants,
                       const char* name)
 {
   JSObject* constructor;
@@ -50,16 +113,37 @@ CreateInterfaceObject(JSContext* cx, JSO
   if (!constructor) {
     return NULL;
   }
 
   if (staticMethods && !JS_DefineFunctions(cx, constructor, staticMethods)) {
     return NULL;
   }
 
+  if (constructorClass) {
+    JSFunction* toString = js::DefineFunctionWithReserved(cx, constructor,
+                                                          "toString",
+                                                          InterfaceObjectToString,
+                                                          0, 0);
+    if (!toString) {
+      return NULL;
+    }
+
+    JSObject* toStringObj = JS_GetFunctionObject(toString);
+    js::SetFunctionNativeReserved(toStringObj, TOSTRING_CLASS_RESERVED_SLOT,
+                                  PRIVATE_TO_JSVAL(constructorClass));
+
+    JSString *str = ::JS_InternString(cx, name);
+    if (!str) {
+      return NULL;
+    }
+    js::SetFunctionNativeReserved(toStringObj, TOSTRING_NAME_RESERVED_SLOT,
+                                  STRING_TO_JSVAL(str));
+  }
+
   if (constants && !DefineConstants(cx, constructor, constants)) {
     return NULL;
   }
 
   if (proto && !JS_LinkConstructorAndPrototype(cx, constructor, proto)) {
     return NULL;
   }
 
--- a/dom/bindings/test/Makefile.in
+++ b/dom/bindings/test/Makefile.in
@@ -7,15 +7,16 @@ topsrcdir = @top_srcdir@
 srcdir = @srcdir@
 VPATH = @srcdir@
 relativesrcdir = dom/bindings/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
+  test_interfaceToString.html \
   test_lookupGetter.html \
   test_InstanceOf.html \
   test_traceProtos.html \
   $(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/test_interfaceToString.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=742156
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 742156</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=742156">Mozilla Bug 742156</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 742156 **/
+
+var nativeToString = ("" + String.replace).replace("replace", "EventTarget");
+try {
+    var eventTargetToString = "" + EventTarget;
+    is(eventTargetToString, nativeToString,
+       "Stringifying a DOM interface object should return the same string" +
+       "as stringifying a native function.");
+}
+catch (e) {
+    ok(false, "Stringifying a DOM interface object shouldn't throw.");
+}
+
+
+</script>
+</pre>
+</body>
+</html>