Bug 862531: Replace BaseProxyHandler::obj_toString with className. r=jorendorff
authorJim Blandy <jimb@mozilla.com>
Tue, 30 Apr 2013 14:44:50 -0700
changeset 130404 e9d66cb5f791794c27c90ce4ab33eb4cca8f72e6
parent 130403 a4a17137352610a0725c63f15bf04685c88f9643
child 130405 3730657cc0dadedb7ff11b9a9949292c63b16077
push id24615
push userryanvm@gmail.com
push dateWed, 01 May 2013 01:58:18 +0000
treeherdermozilla-central@02aa81c59df6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs862531
milestone23.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 862531: Replace BaseProxyHandler::obj_toString with className. r=jorendorff
browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-02.js
browser/devtools/debugger/test/browser_dbg_propertyview-07.js
browser/devtools/debugger/test/browser_dbg_propertyview-08.js
browser/devtools/debugger/test/browser_dbg_propertyview-09.js
browser/devtools/debugger/test/browser_dbg_propertyview-10.js
browser/devtools/debugger/test/browser_dbg_propertyview-edit-watch.js
dom/base/nsGlobalWindow.cpp
dom/bindings/BindingUtils.cpp
dom/bindings/Codegen.py
dom/bindings/DOMJSProxyHandler.cpp
dom/bindings/DOMJSProxyHandler.h
js/src/builtin/Object.cpp
js/src/builtin/Object.h
js/src/jit-test/tests/debug/Object-class.js
js/src/jsarray.cpp
js/src/jsfriendapi.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/jsproxy.cpp
js/src/jsproxy.h
js/src/jswrapper.cpp
js/src/jswrapper.h
js/src/vm/Debugger.cpp
--- a/browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_bug727429_watch-expressions-02.js
@@ -112,111 +112,111 @@ function test()
       gDebuggee.test(); // ermahgerd!!
     });
   }
 
   function test2(callback) {
     waitForWatchExpressions(function() {
       info("Performing test2");
       checkWatchExpressions(undefined,
-                            { type: "object", class: "Proxy" },
+                            { type: "object", class: "Window" },
                             undefined,
                             "sensational",
                             26);
       callback();
     });
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gDebugger.document.getElementById("resume"),
       gDebugger);
   }
 
   function test3(callback) {
     waitForWatchExpressions(function() {
       info("Performing test3");
       checkWatchExpressions({ type: "object", class: "Object" },
-                            { type: "object", class: "Proxy" },
+                            { type: "object", class: "Window" },
                             undefined,
                             "sensational",
                             26);
       callback();
     });
     EventUtils.sendMouseEvent({ type: "mousedown" },
       gDebugger.document.getElementById("resume"),
       gDebugger);
   }
 
   function test4(callback) {
     waitForWatchExpressions(function() {
       info("Performing test4");
       checkWatchExpressions(5,
-                            { type: "object", class: "Proxy" },
+                            { type: "object", class: "Window" },
                             undefined,
                             "sensational",
                             27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("a = 5");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test5(callback) {
     waitForWatchExpressions(function() {
       info("Performing test5");
       checkWatchExpressions(5,
-                            { type: "object", class: "Proxy" },
+                            { type: "object", class: "Window" },
                             undefined,
                             "sensational",
                             27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("encodeURI(\"\\\")");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test6(callback) {
     waitForWatchExpressions(function() {
       info("Performing test6");
       checkWatchExpressions(5,
-                            { type: "object", class: "Proxy" },
+                            { type: "object", class: "Window" },
                             undefined,
                             "sensational",
                             27);
       callback();
     })
     executeSoon(function() {
       gWatch.addExpression("decodeURI(\"\\\")");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test7(callback) {
     waitForWatchExpressions(function() {
       info("Performing test7");
       checkWatchExpressions(5,
-                            { type: "object", class: "Proxy" },
+                            { type: "object", class: "Window" },
                             undefined,
                             "sensational",
                             27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("?");
       EventUtils.sendKey("RETURN", gDebugger);
     });
   }
 
   function test8(callback) {
     waitForWatchExpressions(function() {
       info("Performing test8");
       checkWatchExpressions(5,
-                            { type: "object", class: "Proxy" },
+                            { type: "object", class: "Window" },
                             undefined,
                             "sensational",
                             27);
       callback();
     });
     executeSoon(function() {
       gWatch.addExpression("a");
       EventUtils.sendKey("RETURN", gDebugger);
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-07.js
@@ -37,17 +37,17 @@ function testFrameParameters()
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
       is(localNodes.length, 12,
         "The localScope should contain all the created variable elements.");
 
-      is(localNodes[0].querySelector(".value").getAttribute("value"), "[object Proxy]",
+      is(localNodes[0].querySelector(".value").getAttribute("value"), "[object Window]",
         "Should have the right property value for 'this'.");
 
       is(localNodes[1].querySelector(".value").getAttribute("value"), "[object Object]",
         "Should have the right property value for 'aArg'.");
 
       is(localNodes[2].querySelector(".value").getAttribute("value"), '"beta"',
         "Should have the right property value for 'bArg'.");
 
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-08.js
@@ -38,17 +38,17 @@ function testFrameParameters()
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
       is(localNodes.length + localNonEnums.length, 12,
         "The localScope and localNonEnums should contain all the created variable elements.");
 
-      is(localNodes[0].querySelector(".value").getAttribute("value"), "[object Proxy]",
+      is(localNodes[0].querySelector(".value").getAttribute("value"), "[object Window]",
         "Should have the right property value for 'this'.");
       is(localNodes[8].querySelector(".value").getAttribute("value"), "[object Arguments]",
         "Should have the right property value for 'arguments'.");
       is(localNodes[10].querySelector(".value").getAttribute("value"), "[object Object]",
         "Should have the right property value for 'c'.");
 
 
       let gVars = gDebugger.DebuggerView.Variables;
@@ -112,17 +112,17 @@ function testFrameParameters()
         if (!thisNode._retrieved ||
             !argumentsNode._retrieved ||
             !cNode._retrieved) {
           return;
         }
         window.clearInterval(intervalID);
 
         is(thisNode.target.querySelector(".value")
-           .getAttribute("value"), "[object Proxy]",
+           .getAttribute("value"), "[object Window]",
           "Should have the right property value for 'this'.");
 
         is(thisNode.get("window").target.querySelector(".name")
            .getAttribute("value"), "window",
           "Should have the right property name for 'window'.");
         ok(thisNode.get("window").target.querySelector(".value")
            .getAttribute("value").search(/object/) != -1,
           "'window' should be an object.");
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-09.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-09.js
@@ -45,33 +45,33 @@ function testFrameParameters()
           globalNodes = globalScope.querySelector(".variables-view-element-details").childNodes;
 
       is(gDebugger.DebuggerController.activeThread.state, "paused",
         "Should only be getting stack frames while paused.");
 
       is(frames.querySelectorAll(".dbg-stackframe").length, 3,
         "Should have three frames.");
 
-      is(globalNodes[0].querySelector(".name").getAttribute("value"), "InstallTrigger",
-        "Should have the right property name for |InstallTrigger|.");
-
-      is(globalNodes[0].querySelector(".value").getAttribute("value"), "",
-        "Should have the right property value for |InstallTrigger|.");
-
       is(globalNodes[1].querySelector(".name").getAttribute("value"), "SpecialPowers",
         "Should have the right property name for |SpecialPowers|.");
 
-      is(globalNodes[1].querySelector(".value").getAttribute("value"), "[object Proxy]",
+      is(globalNodes[1].querySelector(".value").getAttribute("value"), "[object Object]",
         "Should have the right property value for |SpecialPowers|.");
 
+      is(globalNodes[3].querySelector(".name").getAttribute("value"), "document",
+        "Should have the right property name for |document|.");
+
+      is(globalNodes[3].querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
+        "Should have the right property value for |document|.");
+
       let len = globalNodes.length - 1;
       is(globalNodes[len].querySelector(".name").getAttribute("value"), "window",
         "Should have the right property name for |window|.");
 
-      is(globalNodes[len].querySelector(".value").getAttribute("value"), "[object Proxy]",
+      is(globalNodes[len].querySelector(".value").getAttribute("value"), "[object Window]",
         "Should have the right property value for |window|.");
 
       resumeAndFinish();
     }}, 0);
   }, false);
 
   EventUtils.sendMouseEvent({ type: "click" },
     content.document.querySelector("button"),
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-10.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-10.js
@@ -56,27 +56,27 @@ function testWithFrame()
       is(scopes.childNodes.length, 5, "Should have 5 variable scopes.");
 
       is(innerNodes[1].querySelector(".name").getAttribute("value"), "one",
         "Should have the right property name for |one|.");
 
       is(innerNodes[1].querySelector(".value").getAttribute("value"), "1",
         "Should have the right property value for |one|.");
 
-      is(globalNodes[0].querySelector(".name").getAttribute("value"), "InstallTrigger",
-        "Should have the right property name for |InstallTrigger|.");
+      is(globalNodes[3].querySelector(".name").getAttribute("value"), "document",
+        "Should have the right property name for |document|.");
 
-      is(globalNodes[0].querySelector(".value").getAttribute("value"), "",
-        "Should have the right property value for |InstallTrigger|.");
+      is(globalNodes[3].querySelector(".value").getAttribute("value"), "[object HTMLDocument]",
+        "Should have the right property value for |document|.");
 
       let len = globalNodes.length - 1;
       is(globalNodes[len].querySelector(".name").getAttribute("value"), "window",
         "Should have the right property name for |window|.");
 
-      is(globalNodes[len].querySelector(".value").getAttribute("value"), "[object Proxy]",
+      is(globalNodes[len].querySelector(".value").getAttribute("value"), "[object Window]",
         "Should have the right property value for |window|.");
 
       resumeAndFinish();
     }}, 0);
   }, false);
 
   EventUtils.sendMouseEvent({ type: "click" },
     content.document.querySelector("button"),
--- a/browser/devtools/debugger/test/browser_dbg_propertyview-edit-watch.js
+++ b/browser/devtools/debugger/test/browser_dbg_propertyview-edit-watch.js
@@ -71,17 +71,17 @@ function testFrameEval() {
       is(scope.get("this")._isContentVisible, true,
         "Should have the right visibility state for 'this'.");
       is(scope.get("this").target.querySelectorAll(".variables-view-delete").length, 1,
         "Should have the one close button visible for 'this'.");
       is(scope.get("this").name, "this",
         "Should have the right name for 'this'.");
       is(scope.get("this").value.type, "object",
         "Should have the right value type for 'this'.");
-      is(scope.get("this").value.class, "Proxy",
+      is(scope.get("this").value.class, "Window",
         "Should have the right value type for 'this'.");
 
       is(scope.get("ermahgerd")._isContentVisible, true,
         "Should have the right visibility state for 'ermahgerd'.");
       is(scope.get("ermahgerd").target.querySelectorAll(".variables-view-delete").length, 1,
         "Should have the one close button visible for 'ermahgerd'.");
       is(scope.get("ermahgerd").name, "ermahgerd",
         "Should have the right name for 'ermahgerd'.");
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -547,18 +547,18 @@ public:
   virtual bool isOuterWindow() {
     return true;
   }
 
   virtual bool finalizeInBackground(JS::Value priv) {
     return false;
   }
 
-  virtual JSString *obj_toString(JSContext *cx,
-                                 JS::Handle<JSObject*> wrapper) MOZ_OVERRIDE;
+  virtual const char *className(JSContext *cx,
+                                JS::Handle<JSObject*> wrapper) MOZ_OVERRIDE;
   virtual void finalize(JSFreeOp *fop, JSObject *proxy) MOZ_OVERRIDE;
 
   // Fundamental traps
   virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
   virtual bool preventExtensions(JSContext *cx,
                                  JS::Handle<JSObject*> proxy) MOZ_OVERRIDE;
   virtual bool getPropertyDescriptor(JSContext* cx,
                                      JS::Handle<JSObject*> proxy,
@@ -641,22 +641,22 @@ nsOuterWindowProxy::preventExtensions(JS
                                       JS::Handle<JSObject*> proxy)
 {
   // See above.
   JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
                        JSMSG_CANT_CHANGE_EXTENSIBILITY);
   return false;
 }
 
-JSString *
-nsOuterWindowProxy::obj_toString(JSContext *cx, JS::Handle<JSObject*> proxy)
+const char *
+nsOuterWindowProxy::className(JSContext *cx, JS::Handle<JSObject*> proxy)
 {
     MOZ_ASSERT(js::IsProxy(proxy));
 
-    return JS_NewStringCopyZ(cx, "[object Window]");
+    return "Window";
 }
 
 void
 nsOuterWindowProxy::finalize(JSFreeOp *fop, JSObject *proxy)
 {
   nsGlobalWindow* global = GetWindow(proxy);
   if (global) {
     global->ClearWrapper();
@@ -926,28 +926,28 @@ nsOuterWindowProxy::AppendIndexedPropert
 nsOuterWindowProxy
 nsOuterWindowProxy::singleton;
 
 class nsChromeOuterWindowProxy : public nsOuterWindowProxy
 {
 public:
   nsChromeOuterWindowProxy() : nsOuterWindowProxy() {}
 
-  virtual JSString *obj_toString(JSContext *cx, JS::Handle<JSObject*> wrapper);
+  virtual const char *className(JSContext *cx, JS::Handle<JSObject*> wrapper) MOZ_OVERRIDE;
 
   static nsChromeOuterWindowProxy singleton;
 };
 
-JSString *
-nsChromeOuterWindowProxy::obj_toString(JSContext *cx,
-                                       JS::Handle<JSObject*> proxy)
+const char *
+nsChromeOuterWindowProxy::className(JSContext *cx,
+                                    JS::Handle<JSObject*> proxy)
 {
     MOZ_ASSERT(js::IsProxy(proxy));
 
-    return JS_NewStringCopyZ(cx, "[object ChromeWindow]");
+    return "ChromeWindow";
 }
 
 nsChromeOuterWindowProxy
 nsChromeOuterWindowProxy::singleton;
 
 static JSObject*
 NewOuterWindowProxy(JSContext *cx, JSObject *parent, bool isChrome)
 {
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1368,17 +1368,17 @@ NativeToString(JSContext* cx, JSObject* 
       if (JS_CallFunctionValue(cx, obj, toString, 0, nullptr,
                                &toStringResult)) {
         str = toStringResult.toString();
       } else {
         str = nullptr;
       }
     } else {
       if (IsDOMProxy(obj)) {
-        str = js::GetProxyHandler(obj)->obj_toString(cx, obj);
+        str = JS_BasicObjectToString(cx, obj);
       } else {
         js::Class* clasp = js::GetObjectClass(obj);
         if (IsDOMClass(clasp)) {
           str = ConcatJSString(cx, "[object ",
                                JS_NewStringCopyZ(cx, clasp->name), "]");
         } else if (IsDOMIfaceAndProtoClass(clasp)) {
           const DOMIfaceAndProtoJSClass* ifaceAndProtoJSClass =
             DOMIfaceAndProtoJSClass::FromJSClass(clasp);
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -6863,23 +6863,24 @@ if (expando) {
   if (found) {
     return true;
   }
 }
 %s
 vp.setUndefined();
 return true;""" % (getIndexedOrExpando, getNamed)
 
-class CGDOMJSProxyHandler_obj_toString(ClassMethod):
+class CGDOMJSProxyHandler_className(ClassMethod):
     def __init__(self, descriptor):
         args = [Argument('JSContext*', 'cx'), Argument('JS::Handle<JSObject*>', 'proxy')]
-        ClassMethod.__init__(self, "obj_toString", "JSString*", args)
+        ClassMethod.__init__(self, "className", "const char*", args,
+                             virtual=True, override=True)
         self.descriptor = descriptor
     def getBody(self):
-        return "return mozilla::dom::DOMProxyHandler::obj_toString(cx, \"%s\");" % self.descriptor.name
+        return 'return "%s";' % self.descriptor.name
 
 class CGDOMJSProxyHandler_finalizeInBackground(ClassMethod):
     def __init__(self, descriptor):
         args = [Argument('JS::Value', 'priv')]
         ClassMethod.__init__(self, "finalizeInBackground", "bool", args)
         self.descriptor = descriptor
     def getBody(self):
         return ("return false;")
@@ -6969,17 +6970,17 @@ class CGDOMJSProxyHandler(CGClass):
         # because we don't know whether we're in strict mode.
         if (descriptor.operations['IndexedSetter'] or
             descriptor.operations['NamedSetter'] or
             UseHolderForUnforgeable(descriptor)):
             methods.append(CGDOMJSProxyHandler_defineProperty(descriptor))
         methods.extend([CGDOMJSProxyHandler_getOwnPropertyNames(descriptor),
                         CGDOMJSProxyHandler_hasOwn(descriptor),
                         CGDOMJSProxyHandler_get(descriptor),
-                        CGDOMJSProxyHandler_obj_toString(descriptor),
+                        CGDOMJSProxyHandler_className(descriptor),
                         CGDOMJSProxyHandler_finalizeInBackground(descriptor),
                         CGDOMJSProxyHandler_finalize(descriptor),
                         CGDOMJSProxyHandler_getElementIfPresent(descriptor),
                         CGDOMJSProxyHandler_getInstance(),
                         CGDOMJSProxyHandler_delete(descriptor)])
         CGClass.__init__(self, 'DOMProxyHandler',
                          bases=[ClassBase('mozilla::dom::DOMProxyHandler')],
                          constructors=constructors,
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -200,44 +200,16 @@ DOMProxyHandler::has(JSContext* cx, JS::
   JSBool protoHasProp;
   bool ok = JS_HasPropertyById(cx, proto, id, &protoHasProp);
   if (ok) {
     *bp = protoHasProp;
   }
   return ok;
 }
 
-// static
-JSString*
-DOMProxyHandler::obj_toString(JSContext* cx, const char* className)
-{
-  size_t nchars = sizeof("[object ]") - 1 + strlen(className);
-  jschar* chars = static_cast<jschar*>(JS_malloc(cx, (nchars + 1) * sizeof(jschar)));
-  if (!chars) {
-    return NULL;
-  }
-
-  const char* prefix = "[object ";
-  nchars = 0;
-  while ((chars[nchars] = (jschar)*prefix) != 0) {
-    nchars++, prefix++;
-  }
-  while ((chars[nchars] = (jschar)*className) != 0) {
-    nchars++, className++;
-  }
-  chars[nchars++] = ']';
-  chars[nchars] = 0;
-
-  JSString* str = JS_NewUCString(cx, chars, nchars);
-  if (!str) {
-    JS_free(cx, chars);
-  }
-  return str;
-}
-
 bool
 DOMProxyHandler::AppendNamedPropertyIds(JSContext* cx, JSObject* proxy,
                                         nsTArray<nsString>& names,
                                         JS::AutoIdVector& props)
 {
   for (uint32_t i = 0; i < names.Length(); ++i) {
     JS::Value v;
     if (!xpc::NonVoidStringToJsval(cx, names[i], &v)) {
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -41,32 +41,29 @@ public:
                             JSPropertyDescriptor* desc, unsigned flags) MOZ_OVERRIDE;
   bool defineProperty(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
                       JSPropertyDescriptor* desc) MOZ_OVERRIDE;
   bool delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
   bool enumerate(JSContext* cx, JS::Handle<JSObject*> proxy, JS::AutoIdVector& props) MOZ_OVERRIDE;
   bool has(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id, bool* bp) MOZ_OVERRIDE;
   bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
-  using js::BaseProxyHandler::obj_toString;
 
   static JSObject* GetExpandoObject(JSObject* obj)
   {
     MOZ_ASSERT(IsDOMProxy(obj), "expected a DOM proxy object");
     JS::Value v = js::GetProxyExtra(obj, JSPROXYSLOT_EXPANDO);
     return v.isUndefined() ? NULL : v.toObjectOrNull();
   }
   static JSObject* GetAndClearExpandoObject(JSObject* obj);
   static JSObject* EnsureExpandoObject(JSContext* cx, JSObject* obj);
 
   const DOMClass& mClass;
 
 protected:
-  static JSString* obj_toString(JSContext* cx, const char* className);
-
   // Append the property names in "names" that don't live on our proto
   // chain to "props"
   bool AppendNamedPropertyIds(JSContext* cx, JSObject* proxy,
                               nsTArray<nsString>& names,
                               JS::AutoIdVector& props);
 };
 
 extern jsid s_length_id;
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -282,23 +282,21 @@ obj_toSource(JSContext *cx, unsigned arg
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 #endif /* JS_HAS_TOSOURCE */
 
 JSString *
-js::obj_toStringHelper(JSContext *cx, HandleObject obj)
+JS_BasicObjectToString(JSContext *cx, HandleObject obj)
 {
-    if (obj->isProxy())
-        return Proxy::obj_toString(cx, obj);
+    const char *className = JSObject::className(cx, obj);
 
     StringBuffer sb(cx);
-    const char *className = obj->getClass()->name;
     if (!sb.append("[object ") || !sb.appendInflated(className, strlen(className)) ||
         !sb.append("]"))
     {
         return NULL;
     }
     return sb.finishString();
 }
 
@@ -321,17 +319,17 @@ obj_toString(JSContext *cx, unsigned arg
     }
 
     /* Step 3. */
     RootedObject obj(cx, ToObject(cx, args.thisv()));
     if (!obj)
         return false;
 
     /* Steps 4-5. */
-    RawString str = js::obj_toStringHelper(cx, obj);
+    RawString str = JS_BasicObjectToString(cx, obj);
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 
 /* ES5 15.2.4.3. */
 static JSBool
--- a/js/src/builtin/Object.h
+++ b/js/src/builtin/Object.h
@@ -13,14 +13,11 @@ namespace js {
 
 extern const JSFunctionSpec object_methods[];
 extern const JSFunctionSpec object_static_methods[];
 
 /* Object constructor native. Exposed only so the JIT can know its address. */
 extern JSBool
 obj_construct(JSContext *cx, unsigned argc, js::Value *vp);
 
-extern JSString *
-obj_toStringHelper(JSContext *cx, HandleObject obj);
-
 } /* namespace js */
 
 #endif
--- a/js/src/jit-test/tests/debug/Object-class.js
+++ b/js/src/jit-test/tests/debug/Object-class.js
@@ -1,15 +1,26 @@
 // Basic tests for Debugger.Object.prototype.class.
-var g = newGlobal('new-compartment');
+var g = newGlobal();
 var dbg = new Debugger(g);
 var hits = 0;
+g.eval('function f() { debugger; }');
+
 dbg.onDebuggerStatement = function (frame) {
     var arr = frame.arguments;
     assertEq(arr[0].class, "Object");
     assertEq(arr[1].class, "Array");
     assertEq(arr[2].class, "Function");
     assertEq(arr[3].class, "Date");
-    assertEq(arr[4].class, "Proxy");
+    assertEq(arr[4].class, "Object");
+    assertEq(arr[5].class, "Function");
+    assertEq(arr[6].class, "Date");
     hits++;
 };
-g.eval("(function () { debugger; })(Object.prototype, [], eval, new Date, Proxy.create({}));");
+g.f(Object.prototype, [], eval, new Date,
+    Proxy.create({}), Proxy.createFunction({}, eval), new Proxy(new Date, {}));
 assertEq(hits, 1);
+
+// Debugger.Object.prototype.class should see through cross-compartment
+// wrappers.
+g.eval('f(Object.prototype, [], eval, new Date,\
+          Proxy.create({}), Proxy.createFunction({}, f), new Proxy(new Date, {}));');
+assertEq(hits, 2);
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -1010,17 +1010,17 @@ array_toString(JSContext *cx, unsigned a
     if (!obj)
         return false;
 
     RootedValue join(cx, args.calleev());
     if (!JSObject::getProperty(cx, obj, obj, cx->names().join, &join))
         return false;
 
     if (!js_IsCallable(join)) {
-        JSString *str = obj_toStringHelper(cx, obj);
+        JSString *str = JS_BasicObjectToString(cx, obj);
         if (!str)
             return false;
         args.rval().setString(str);
         return true;
     }
 
     InvokeArgsGuard ag;
     if (!cx->stack.pushInvokeArgs(cx, 0, &ag))
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -112,16 +112,19 @@ JS_ObjectToInnerObject(JSContext *cx, JS
 
 /* Requires obj != NULL. */
 extern JS_FRIEND_API(JSObject *)
 JS_ObjectToOuterObject(JSContext *cx, JSObject *obj);
 
 extern JS_FRIEND_API(JSObject *)
 JS_CloneObject(JSContext *cx, JSObject *obj, JSObject *proto, JSObject *parent);
 
+extern JS_FRIEND_API(JSString *)
+JS_BasicObjectToString(JSContext *cx, JSHandleObject obj);
+
 extern JS_FRIEND_API(JSBool)
 js_GetterOnlyPropertyStub(JSContext *cx, JSHandleObject obj, JSHandleId id, JSBool strict, JSMutableHandleValue vp);
 
 JS_FRIEND_API(void)
 js_ReportOverRecursed(JSContext *maybecx);
 
 #ifdef DEBUG
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -1205,16 +1205,26 @@ JSObject::isSealedOrFrozen(JSContext *cx
         }
     }
 
     /* All properties checked out. This object is sealed/frozen. */
     *resultp = true;
     return true;
 }
 
+/* static */
+const char *
+JSObject::className(JSContext *cx, HandleObject obj)
+{
+    if (obj->isProxy())
+        return Proxy::className(cx, obj);
+
+    return obj->getClass()->name;
+}
+
 /*
  * Get the GC kind to use for scripted 'new' on the given class.
  * FIXME bug 547327: estimate the size from the allocation site.
  */
 static inline gc::AllocKind
 NewObjectGCKind(js::Class *clasp)
 {
     if (clasp == &ArrayClass)
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -556,16 +556,19 @@ class JSObject : public js::ObjectImpl
 
     static inline bool isSealed(JSContext *cx, js::HandleObject obj, bool *resultp) {
         return isSealedOrFrozen(cx, obj, SEAL, resultp);
     }
     static inline bool isFrozen(JSContext *cx, js::HandleObject obj, bool *resultp) {
         return isSealedOrFrozen(cx, obj, FREEZE, resultp);
     }
 
+    /* toString support. */
+    static const char *className(JSContext *cx, js::HandleObject obj);
+
     /* Accessors for elements. */
 
     struct MaybeContext {
         js::Allocator *allocator;
         JSContext *context;
 
         MaybeContext(JSContext *cx) : allocator(NULL), context(cx) {}
         MaybeContext(js::Allocator *alloc) : allocator(alloc), context(NULL) {}
--- a/js/src/jsproxy.cpp
+++ b/js/src/jsproxy.cpp
@@ -315,22 +315,20 @@ BaseProxyHandler::call(JSContext *cx, Ha
 
 bool
 BaseProxyHandler::construct(JSContext *cx, HandleObject proxy, const CallArgs &args)
 {
     JS_NOT_REACHED("callable proxies should implement construct trap");
     return false;
 }
 
-JSString *
-BaseProxyHandler::obj_toString(JSContext *cx, HandleObject proxy)
+const char *
+BaseProxyHandler::className(JSContext *cx, HandleObject proxy)
 {
-    return JS_NewStringCopyZ(cx, IsFunctionProxy(proxy)
-                                 ? "[object Function]"
-                                 : "[object Object]");
+    return IsFunctionProxy(proxy) ? "Function" : "Object";
 }
 
 JSString *
 BaseProxyHandler::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent)
 {
     JS_NOT_REACHED("callable proxies should implement fun_toString trap");
     return NULL;
 }
@@ -519,22 +517,22 @@ DirectProxyHandler::hasInstance(JSContex
 bool
 DirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
                                   JSContext *cx)
 {
     RootedObject obj(cx, GetProxyTargetObject(proxy));
     return ObjectClassIs(obj, classValue, cx);
 }
 
-JSString *
-DirectProxyHandler::obj_toString(JSContext *cx, HandleObject proxy)
+const char *
+DirectProxyHandler::className(JSContext *cx, HandleObject proxy)
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID);
     RootedObject target(cx, GetProxyTargetObject(proxy));
-    return obj_toStringHelper(cx, target);
+    return JSObject::className(cx, target);
 }
 
 JSString *
 DirectProxyHandler::fun_toString(JSContext *cx, HandleObject proxy,
                                  unsigned indent)
 {
     assertEnteredPolicy(cx, proxy, JSID_VOID);
     RootedObject target(cx, GetProxyTargetObject(proxy));
@@ -2657,28 +2655,28 @@ Proxy::hasInstance(JSContext *cx, Handle
 
 bool
 Proxy::objectClassIs(HandleObject proxy, ESClassValue classValue, JSContext *cx)
 {
     JS_CHECK_RECURSION(cx, return false);
     return GetProxyHandler(proxy)->objectClassIs(proxy, classValue, cx);
 }
 
-JSString *
-Proxy::obj_toString(JSContext *cx, HandleObject proxy)
+const char *
+Proxy::className(JSContext *cx, HandleObject proxy)
 {
     JS_CHECK_RECURSION(cx, return NULL);
     BaseProxyHandler *handler = GetProxyHandler(proxy);
     AutoEnterPolicy policy(cx, handler, proxy, JS::JSID_VOIDHANDLE,
                            BaseProxyHandler::GET, /* mayThrow = */ false);
     // Do the safe thing if the policy rejects.
     if (!policy.allowed()) {
-        return handler->BaseProxyHandler::obj_toString(cx, proxy);
+        return handler->BaseProxyHandler::className(cx, proxy);
     }
-    return handler->obj_toString(cx, proxy);
+    return handler->className(cx, proxy);
 }
 
 JSString *
 Proxy::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent)
 {
     JS_CHECK_RECURSION(cx, return NULL);
     BaseProxyHandler *handler = GetProxyHandler(proxy);
     AutoEnterPolicy policy(cx, handler, proxy, JS::JSID_VOIDHANDLE,
--- a/js/src/jsproxy.h
+++ b/js/src/jsproxy.h
@@ -130,17 +130,17 @@ class JS_FRIEND_API(BaseProxyHandler)
 
     /* Spidermonkey extensions. */
     virtual bool isExtensible(JSObject *proxy) = 0;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args);
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args);
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args);
     virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp);
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx);
-    virtual JSString *obj_toString(JSContext *cx, HandleObject proxy);
+    virtual const char *className(JSContext *cx, HandleObject proxy);
     virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent);
     virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g);
     virtual bool defaultValue(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp);
     virtual void finalize(JSFreeOp *fop, JSObject *proxy);
     virtual bool getElementIfPresent(JSContext *cx, HandleObject obj, HandleObject receiver,
                                      uint32_t index, MutableHandleValue vp, bool *present);
     virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
 
@@ -194,17 +194,17 @@ class JS_PUBLIC_API(DirectProxyHandler) 
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v,
                              bool *bp) MOZ_OVERRIDE;
     virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
                                JSContext *cx) MOZ_OVERRIDE;
-    virtual JSString *obj_toString(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
+    virtual const char *className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual JSString *fun_toString(JSContext *cx, HandleObject proxy,
                                    unsigned indent) MOZ_OVERRIDE;
     virtual bool regexp_toShared(JSContext *cx, HandleObject proxy,
                                  RegExpGuard *g) MOZ_OVERRIDE;
     virtual bool defaultValue(JSContext *cx, HandleObject obj, JSType hint,
                               MutableHandleValue vp) MOZ_OVERRIDE;
     virtual JSObject *weakmapKeyDelegate(JSObject *proxy);
 };
@@ -243,17 +243,17 @@ class Proxy
 
     /* Spidermonkey extensions. */
     static bool isExtensible(JSObject *proxy);
     static bool call(JSContext *cx, HandleObject proxy, const CallArgs &args);
     static bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args);
     static bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, CallArgs args);
     static bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp);
     static bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx);
-    static JSString *obj_toString(JSContext *cx, HandleObject proxy);
+    static const char *className(JSContext *cx, HandleObject proxy);
     static JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent);
     static bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g);
     static bool defaultValue(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp);
     static bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
 
     static JSObject * const LazyProto;
 };
 
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -521,29 +521,21 @@ CrossCompartmentWrapper::hasInstance(JSC
                                      bool *bp)
 {
     AutoCompartment call(cx, wrappedObject(wrapper));
     if (!cx->compartment->wrap(cx, v))
         return false;
     return Wrapper::hasInstance(cx, wrapper, v, bp);
 }
 
-JSString *
-CrossCompartmentWrapper::obj_toString(JSContext *cx, HandleObject wrapper)
+const char *
+CrossCompartmentWrapper::className(JSContext *cx, HandleObject wrapper)
 {
-    RootedString str(cx);
-    {
-        AutoCompartment call(cx, wrappedObject(wrapper));
-        str = Wrapper::obj_toString(cx, wrapper);
-        if (!str)
-            return NULL;
-    }
-    if (!cx->compartment->wrap(cx, str.address()))
-        return NULL;
-    return str;
+    AutoCompartment call(cx, wrappedObject(wrapper));
+    return Wrapper::className(cx, wrapper);
 }
 
 JSString *
 CrossCompartmentWrapper::fun_toString(JSContext *cx, HandleObject wrapper, unsigned indent)
 {
     RootedString str(cx);
     {
         AutoCompartment call(cx, wrappedObject(wrapper));
@@ -784,20 +776,20 @@ DeadObjectProxy::hasInstance(JSContext *
 
 bool
 DeadObjectProxy::objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEAD_OBJECT);
     return false;
 }
 
-JSString *
-DeadObjectProxy::obj_toString(JSContext *cx, HandleObject wrapper)
+const char *
+DeadObjectProxy::className(JSContext *cx, HandleObject wrapper)
 {
-    return JS_NewStringCopyZ(cx, "[object DeadObject]");
+    return "DeadObject";
 }
 
 JSString *
 DeadObjectProxy::fun_toString(JSContext *cx, HandleObject proxy, unsigned indent)
 {
     return NULL;
 }
 
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -107,17 +107,17 @@ class JS_FRIEND_API(CrossCompartmentWrap
     /* Spidermonkey extensions. */
     virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject wrapper, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject wrapper, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
     virtual bool hasInstance(JSContext *cx, HandleObject wrapper, MutableHandleValue v,
                              bool *bp) MOZ_OVERRIDE;
-    virtual JSString *obj_toString(JSContext *cx, HandleObject wrapper) MOZ_OVERRIDE;
+    virtual const char *className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
     virtual JSString *fun_toString(JSContext *cx, HandleObject wrapper,
                                    unsigned indent) MOZ_OVERRIDE;
     virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) MOZ_OVERRIDE;
     virtual bool defaultValue(JSContext *cx, HandleObject wrapper, JSType hint,
                               MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
 
     static CrossCompartmentWrapper singleton;
@@ -185,25 +185,30 @@ class JS_FRIEND_API(DeadObjectProxy) : p
     virtual bool enumerate(JSContext *cx, HandleObject wrapper, AutoIdVector &props) MOZ_OVERRIDE;
 
     /* Spidermonkey extensions. */
     virtual bool isExtensible(JSObject *proxy) MOZ_OVERRIDE;
     virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE;
     virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
                             CallArgs args) MOZ_OVERRIDE;
-    virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v, bool *bp);
-    virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx);
-    virtual JSString *obj_toString(JSContext *cx, HandleObject proxy);
-    virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent);
-    virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g);
-    virtual bool defaultValue(JSContext *cx, HandleObject obj, JSType hint, MutableHandleValue vp);
+    virtual bool hasInstance(JSContext *cx, HandleObject proxy, MutableHandleValue v,
+                             bool *bp) MOZ_OVERRIDE;
+    virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
+                               JSContext *cx) MOZ_OVERRIDE;
+    virtual const char *className(JSContext *cx, HandleObject proxy) MOZ_OVERRIDE;
+    virtual JSString *fun_toString(JSContext *cx, HandleObject proxy, unsigned indent) MOZ_OVERRIDE;
+    virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, RegExpGuard *g) MOZ_OVERRIDE;
+    virtual bool defaultValue(JSContext *cx, HandleObject obj, JSType hint,
+                              MutableHandleValue vp) MOZ_OVERRIDE;
     virtual bool getElementIfPresent(JSContext *cx, HandleObject obj, HandleObject receiver,
-                                     uint32_t index, MutableHandleValue vp, bool *present);
-    virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy, MutableHandleObject protop);
+                                     uint32_t index, MutableHandleValue vp,
+                                     bool *present) MOZ_OVERRIDE;
+    virtual bool getPrototypeOf(JSContext *cx, HandleObject proxy,
+                                MutableHandleObject protop) MOZ_OVERRIDE;
 
     static DeadObjectProxy singleton;
 };
 
 extern JSObject *
 TransparentObjectWrapper(JSContext *cx, HandleObject existing, HandleObject obj,
                          HandleObject wrappedProto, HandleObject parent,
                          unsigned flags);
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4168,18 +4168,18 @@ DebuggerObject_getProto(JSContext *cx, u
     args.rval().set(protov);
     return true;
 }
 
 static JSBool
 DebuggerObject_getClass(JSContext *cx, unsigned argc, Value *vp)
 {
     THIS_DEBUGOBJECT_REFERENT(cx, argc, vp, "get class", args, refobj);
-    const char *s = refobj->getClass()->name;
-    JSAtom *str = Atomize(cx, s, strlen(s));
+    const char *className = JSObject::className(cx, refobj);
+    JSAtom *str = Atomize(cx, className, strlen(className));
     if (!str)
         return false;
     args.rval().setString(str);
     return true;
 }
 
 static JSBool
 DebuggerObject_getCallable(JSContext *cx, unsigned argc, Value *vp)