Web Inspector: ES6: Provide a better view for Classes in the console
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 27 Mar 2015 01:42:37 +0000 (01:42 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 27 Mar 2015 01:42:37 +0000 (01:42 +0000)
https://bugs.webkit.org/show_bug.cgi?id=142999

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

* inspector/protocol/Runtime.json:
Provide a new `subtype` enum "class". This is a subtype of `type`
"function", all other subtypes are subtypes of `object` types.
For a class, the frontend will immediately want to get the prototype
to enumerate its methods, so include the `classPrototype`.

* inspector/JSInjectedScriptHost.cpp:
(Inspector::JSInjectedScriptHost::subtype):
Denote class construction functions as "class" subtypes.

* inspector/InjectedScriptSource.js:
Handling for the new "class" type.

* bytecode/UnlinkedCodeBlock.h:
(JSC::UnlinkedFunctionExecutable::isClassConstructorFunction):
* runtime/Executable.h:
(JSC::FunctionExecutable::isClassConstructorFunction):
* runtime/JSFunction.h:
* runtime/JSFunctionInlines.h:
(JSC::JSFunction::isClassConstructorFunction):
Check if this function is a class constructor function. That information
is on the UnlinkedFunctionExecutable, so plumb it through to JSFunction.

Source/WebInspectorUI:

* UserInterface/Protocol/RemoteObject.js:
(WebInspector.RemoteObject):
(WebInspector.RemoteObject.fromPrimitiveValue):
(WebInspector.RemoteObject.fromPayload):
(WebInspector.RemoteObject.prototype.get classPrototype):
(WebInspector.RemoteObject.prototype.isClass):
Update our RemoteObject model object for the new subtype
and its unique properties.

* UserInterface/Views/FormattedValue.js:
(WebInspector.FormattedValue.createElementForTypesAndValue):
(WebInspector.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject):
Better handle "class", as it is a new function subtype.

* UserInterface/Views/LegacyConsoleMessageImpl.js:
(WebInspector.LegacyConsoleMessageImpl):
(WebInspector.LegacyConsoleMessageImpl.prototype._formatParameterAsObject):
Format a "class" with ObjectTreeView.

* UserInterface/Views/ObjectTreeArrayIndexTreeElement.js:
* UserInterface/Views/ObjectTreeBaseTreeElement.js:
* UserInterface/Views/ObjectTreePropertyTreeElement.css:
(.object-tree-property .getter.disabled):
(.object-tree-property .getter:not(.disabled):hover):
(.object-tree-property .getter:hover): Deleted.
* UserInterface/Views/ObjectTreePropertyTreeElement.js:
In ClassAPI mode, you cannot invoke a getter since we don't have
an instance to invoke it on. So disable interactivity with getters.

* UserInterface/Views/ObjectTreeView.js:
(WebInspector.ObjectTreeView):
Update the modes to include an API mode for instances and classes.

(WebInspector.ObjectTreeView.defaultModeForObject):
* UserInterface/Views/SourceCodeTextEditor.js:
(WebInspector.SourceCodeTextEditor.prototype._showPopoverForObject):
Simplify ObjectTree construction to automatically determine mode based
on the RemoteObject that was provided.

* Localizations/en.lproj/localizedStrings.js:
"Getter" tooltip.

LayoutTests:

* inspector/model/remote-object-expected.txt:
* inspector/model/remote-object.html:
Update the test to include coverage of the new "class" subtype of "function".

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@182047 268f45cc-cd09-0410-ab3c-d52691b4dbfc

22 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/model/remote-object-expected.txt
LayoutTests/inspector/model/remote-object.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h
Source/JavaScriptCore/inspector/InjectedScriptSource.js
Source/JavaScriptCore/inspector/JSInjectedScriptHost.cpp
Source/JavaScriptCore/inspector/protocol/Runtime.json
Source/JavaScriptCore/runtime/Executable.h
Source/JavaScriptCore/runtime/JSFunction.h
Source/JavaScriptCore/runtime/JSFunctionInlines.h
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Protocol/RemoteObject.js
Source/WebInspectorUI/UserInterface/Views/FormattedValue.js
Source/WebInspectorUI/UserInterface/Views/LegacyConsoleMessageImpl.js
Source/WebInspectorUI/UserInterface/Views/ObjectTreeArrayIndexTreeElement.js
Source/WebInspectorUI/UserInterface/Views/ObjectTreeBaseTreeElement.js
Source/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.css
Source/WebInspectorUI/UserInterface/Views/ObjectTreePropertyTreeElement.js
Source/WebInspectorUI/UserInterface/Views/ObjectTreeView.js
Source/WebInspectorUI/UserInterface/Views/SourceCodeTextEditor.js

index 0e99eaa..9bc073e 100644 (file)
@@ -1,3 +1,14 @@
+2015-03-26  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: ES6: Provide a better view for Classes in the console
+        https://bugs.webkit.org/show_bug.cgi?id=142999
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/model/remote-object-expected.txt:
+        * inspector/model/remote-object.html:
+        Update the test to include coverage of the new "class" subtype of "function".
+
 2015-03-26  Tim Horton  <timothy_horton@apple.com>
 
         REGRESSION (r181358 and r181507): Lots of sites think that we support touch events on OS X
index d2f0549..248095f 100644 (file)
@@ -196,7 +196,7 @@ EXPRESSION: (function(){})
 {
   "_type": "function",
   "_objectId": "<filtered>",
-  "_description": "function () {}"
+  "_description": "function (){}"
 }
 
 -----------------------------------------------------
@@ -204,7 +204,7 @@ EXPRESSION: function foo(){}; foo
 {
   "_type": "function",
   "_objectId": "<filtered>",
-  "_description": "function foo() {}"
+  "_description": "function foo(){}"
 }
 
 -----------------------------------------------------
@@ -244,7 +244,7 @@ EXPRESSION: Object.getOwnPropertyDescriptor({ get getter() { return 1 } }, 'gett
 {
   "_type": "function",
   "_objectId": "<filtered>",
-  "_description": "function () { return 1; }"
+  "_description": "function () { return 1 }"
 }
 
 -----------------------------------------------------
@@ -4092,6 +4092,69 @@ EXPRESSION: Promise.resolve({result:1})
 }
 
 -----------------------------------------------------
+EXPRESSION: Person = class Person { constructor(){} get fullName(){} methodName(p1, p2){} }; Person
+{
+  "_type": "function",
+  "_subtype": "class",
+  "_objectId": "<filtered>",
+  "_description": "class Person",
+  "_classPrototype": {
+    "_type": "object",
+    "_objectId": "<filtered>",
+    "_description": "Person"
+  }
+}
+
+-----------------------------------------------------
+EXPRESSION: Person.prototype.methodName
+{
+  "_type": "function",
+  "_objectId": "<filtered>",
+  "_description": "function methodName(p1, p2){}"
+}
+
+-----------------------------------------------------
+EXPRESSION: Alpha = class A { methodA(){} }; Beta = class B extends Alpha { methodB(){} }; Beta
+{
+  "_type": "function",
+  "_subtype": "class",
+  "_objectId": "<filtered>",
+  "_description": "class B",
+  "_classPrototype": {
+    "_type": "object",
+    "_objectId": "<filtered>",
+    "_description": "B"
+  }
+}
+
+-----------------------------------------------------
+EXPRESSION: [Beta]
+{
+  "_type": "object",
+  "_subtype": "array",
+  "_objectId": "<filtered>",
+  "_description": "Array",
+  "_size": 1,
+  "_preview": {
+    "_type": "object",
+    "_subtype": "array",
+    "_description": "Array",
+    "_lossless": false,
+    "_overflow": false,
+    "_size": 1,
+    "_properties": [
+      {
+        "_name": "0",
+        "_type": "function",
+        "_subtype": "class",
+        "_value": "B"
+      }
+    ],
+    "_entries": null
+  }
+}
+
+-----------------------------------------------------
 EXPRESSION: Object.seal({})
 {
   "_type": "object",
index 6ecac87..7fd472c 100644 (file)
@@ -154,6 +154,13 @@ function test()
         {expression: "Promise.resolve()"},
         {expression: "Promise.resolve({result:1})"},
 
+    // Classes
+
+        {expression: "Person = class Person { constructor(){} get fullName(){} methodName(p1, p2){} }; Person"}, // Constructor => class type
+        {expression: "Person.prototype.methodName"}, // Method => just a function type
+        {expression: "Alpha = class A { methodA(){} }; Beta = class B extends Alpha { methodB(){} }; Beta"},
+        {expression: "[Beta]"},
+
     // Improveable:
 
         // Sealed / Frozen objects.
@@ -216,7 +223,11 @@ function runInBrowserTest()
 
     for (var step of steps) {
         console.info("EXPRESSION", step.expression);
-        console.log(eval(step.expression));
+        try {
+            console.log(eval(step.expression));
+        } catch (e) {
+            console.log("EXCEPTION: " + e);
+        }
     }
 }
 </script>
index a8eaa95..4dcb5d8 100644 (file)
@@ -1,3 +1,33 @@
+2015-03-26  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: ES6: Provide a better view for Classes in the console
+        https://bugs.webkit.org/show_bug.cgi?id=142999
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/protocol/Runtime.json:
+        Provide a new `subtype` enum "class". This is a subtype of `type`
+        "function", all other subtypes are subtypes of `object` types.
+        For a class, the frontend will immediately want to get the prototype
+        to enumerate its methods, so include the `classPrototype`.
+
+        * inspector/JSInjectedScriptHost.cpp:
+        (Inspector::JSInjectedScriptHost::subtype):
+        Denote class construction functions as "class" subtypes.
+
+        * inspector/InjectedScriptSource.js:
+        Handling for the new "class" type.
+
+        * bytecode/UnlinkedCodeBlock.h:
+        (JSC::UnlinkedFunctionExecutable::isClassConstructorFunction):
+        * runtime/Executable.h:
+        (JSC::FunctionExecutable::isClassConstructorFunction):
+        * runtime/JSFunction.h:
+        * runtime/JSFunctionInlines.h:
+        (JSC::JSFunction::isClassConstructorFunction):
+        Check if this function is a class constructor function. That information
+        is on the UnlinkedFunctionExecutable, so plumb it through to JSFunction.
+
 2015-03-26  Geoffrey Garen  <ggaren@apple.com>
 
         Function.prototype.toString should not decompile the AST
index 6dec4eb..7972327 100644 (file)
@@ -166,6 +166,7 @@ public:
     static void destroy(JSCell*);
 
     bool isBuiltinFunction() const { return m_isBuiltinFunction; }
+    bool isClassConstructorFunction() const { return constructorKind() != ConstructorKind::None; }
 
 private:
     UnlinkedFunctionExecutable(VM*, Structure*, const SourceCode&, RefPtr<SourceProvider>&& sourceOverride, FunctionBodyNode*, UnlinkedFunctionKind);
index a1c252d..729921c 100644 (file)
@@ -779,6 +779,9 @@ InjectedScript.prototype = {
         if (subtype === "array")
             return className;
 
+        if (subtype === "class")
+            return obj.name;
+
         // NodeList in JSC is a function, check for array prior to this.
         if (typeof obj === "function")
             return toString(obj);
@@ -922,6 +925,8 @@ InjectedScript.RemoteObject = function(object, objectGroupName, forceValueType,
         this.size = object.size;
     else if (subtype === "weakmap")
         this.size = InjectedScriptHost.weakMapSize(object);
+    else if (subtype === "class")
+        this.classPrototype = injectedScript._wrapObject(object.prototype, objectGroupName);
 
     if (generatePreview && this.type === "object")
         this.preview = this._generatePreview(object, undefined, columnNames);
@@ -955,6 +960,8 @@ InjectedScript.RemoteObject.prototype = {
         var remoteObject = new InjectedScript.RemoteObject(value, undefined, false, true, undefined);
         if (remoteObject.objectId)
             injectedScript.releaseObject(remoteObject.objectId);
+        if (remoteObject.classPrototype && remoteObject.classPrototype.objectId)
+            injectedScript.releaseObject(remoteObject.classPrototype.objectId);
 
         return remoteObject.preview || remoteObject._emptyPreview();
     },
@@ -1103,7 +1110,7 @@ InjectedScript.RemoteObject.prototype = {
                     preview.overflow = true;
             } else {
                 var description = "";
-                if (type !== "function")
+                if (type !== "function" || subtype === "class")
                     description = this._abbreviateString(injectedScript._describe(value), maxLength, subtype === "regexp");
                 property.value = description;
                 preview.lossless = false;
index d15af32..b2974ab 100644 (file)
@@ -136,8 +136,15 @@ JSValue JSInjectedScriptHost::subtype(ExecState* exec)
         return exec->vm().smallStrings.symbolString();
 
     JSObject* object = asObject(value);
-    if (object && object->isErrorInstance())
-        return jsNontrivialString(exec, ASCIILiteral("error"));
+    if (object) {
+        if (object->isErrorInstance())
+            return jsNontrivialString(exec, ASCIILiteral("error"));
+
+        // Consider class constructor functions class objects.
+        JSFunction* function = jsDynamicCast<JSFunction*>(value);
+        if (function && function->isClassConstructorFunction())
+            return jsNontrivialString(exec, ASCIILiteral("class"));
+    }
 
     if (value.inherits(JSArray::info()))
         return jsNontrivialString(exec, ASCIILiteral("array"));
index f29adc7..e3bb91a 100644 (file)
             "description": "Mirror object referencing original JavaScript object.",
             "properties": [
                 { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "symbol"], "description": "Object type." },
-                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error", "map", "set", "weakmap", "iterator"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
+                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error", "map", "set", "weakmap", "iterator", "class"], "description": "Object subtype hint. Specified for <code>object</code> <code>function</code> (for class) type values only." },
                 { "name": "className", "type": "string", "optional": true, "description": "Object class (constructor) name. Specified for <code>object</code> type values only." },
                 { "name": "value", "type": "any", "optional": true, "description": "Remote object value (in case of primitive values or JSON values if it was requested)." },
                 { "name": "description", "type": "string", "optional": true, "description": "String representation of the object." },
                 { "name": "objectId", "$ref": "RemoteObjectId", "optional": true, "description": "Unique object identifier (for non-primitive values)." },
                 { "name": "size", "type": "integer", "optional": true, "description": "Size of the array/collection. Specified for array/map/set/weakmap object type values only." },
+                { "name": "classPrototype", "$ref": "RemoteObject", "optional": true, "description": "Remote object for the class prototype. Specified for class object type values only." },
                 { "name": "preview", "$ref": "ObjectPreview", "optional": true, "description": "Preview containing abbreviated property values. Specified for <code>object</code> type values only." }
             ]
         },
@@ -28,7 +29,7 @@
             "description": "Object containing abbreviated remote object value.",
             "properties": [
                 { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "symbol"], "description": "Object type." },
-                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error", "map", "set", "weakmap", "iterator"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
+                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error", "map", "set", "weakmap", "iterator", "class"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
                 { "name": "description", "type": "string", "optional": true, "description": "String representation of the object." },
                 { "name": "lossless", "type": "boolean", "description": "Determines whether preview is lossless (contains all information of the original object)." },
                 { "name": "overflow", "type": "boolean", "optional": true, "description": "True iff some of the properties of the original did not fit." },
@@ -43,7 +44,7 @@
             "properties": [
                 { "name": "name", "type": "string", "description": "Property name." },
                 { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "symbol", "accessor"], "description": "Object type." },
-                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error", "map", "set", "weakmap", "iterator"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
+                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error", "map", "set", "weakmap", "iterator", "class"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
                 { "name": "value", "type": "string", "optional": true, "description": "User-friendly property value string." },
                 { "name": "valuePreview", "$ref": "ObjectPreview", "optional": true, "description": "Nested value preview." },
                 { "name": "internal", "type": "boolean", "optional": true, "description": "True if this is an internal property." }
index d3e02ec..9dcd0a0 100644 (file)
@@ -627,6 +627,7 @@ public:
         
     FunctionMode functionMode() { return m_unlinkedExecutable->functionMode(); }
     bool isBuiltinFunction() const { return m_unlinkedExecutable->isBuiltinFunction(); }
+    bool isClassConstructorFunction() const { return m_unlinkedExecutable->isClassConstructorFunction(); }
     const Identifier& name() { return m_unlinkedExecutable->name(); }
     const Identifier& inferredName() { return m_unlinkedExecutable->inferredName(); }
     JSString* nameValue() const { return m_unlinkedExecutable->nameValue(); }
index be0bdc7..9ab9dc2 100644 (file)
@@ -125,6 +125,7 @@ public:
     bool isHostOrBuiltinFunction() const;
     bool isBuiltinFunction() const;
     JS_EXPORT_PRIVATE bool isHostFunctionNonInline() const;
+    bool isClassConstructorFunction() const;
 
 protected:
     const static unsigned StructureFlags = OverridesGetOwnPropertySlot | ImplementsHasInstance | OverridesGetPropertyNames | JSObject::StructureFlags;
index a5de917..37b281a 100644 (file)
@@ -60,6 +60,11 @@ inline bool JSFunction::isHostOrBuiltinFunction() const
     return isHostFunction() || isBuiltinFunction();
 }
 
+inline bool JSFunction::isClassConstructorFunction() const
+{
+    return !isHostFunction() && jsExecutable()->isClassConstructorFunction();
+}
+
 inline NativeFunction JSFunction::nativeFunction()
 {
     ASSERT(isHostFunctionNonInline());
index 4676551..bc3dbd7 100644 (file)
@@ -1,3 +1,52 @@
+2015-03-26  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: ES6: Provide a better view for Classes in the console
+        https://bugs.webkit.org/show_bug.cgi?id=142999
+
+        Reviewed by Timothy Hatcher.
+
+        * UserInterface/Protocol/RemoteObject.js:
+        (WebInspector.RemoteObject):
+        (WebInspector.RemoteObject.fromPrimitiveValue):
+        (WebInspector.RemoteObject.fromPayload):
+        (WebInspector.RemoteObject.prototype.get classPrototype):
+        (WebInspector.RemoteObject.prototype.isClass):
+        Update our RemoteObject model object for the new subtype
+        and its unique properties.
+
+        * UserInterface/Views/FormattedValue.js:
+        (WebInspector.FormattedValue.createElementForTypesAndValue):
+        (WebInspector.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject):
+        Better handle "class", as it is a new function subtype.
+
+        * UserInterface/Views/LegacyConsoleMessageImpl.js:
+        (WebInspector.LegacyConsoleMessageImpl):
+        (WebInspector.LegacyConsoleMessageImpl.prototype._formatParameterAsObject):
+        Format a "class" with ObjectTreeView.
+
+        * UserInterface/Views/ObjectTreeArrayIndexTreeElement.js:
+        * UserInterface/Views/ObjectTreeBaseTreeElement.js:
+        * UserInterface/Views/ObjectTreePropertyTreeElement.css:
+        (.object-tree-property .getter.disabled):
+        (.object-tree-property .getter:not(.disabled):hover):
+        (.object-tree-property .getter:hover): Deleted.
+        * UserInterface/Views/ObjectTreePropertyTreeElement.js:
+        In ClassAPI mode, you cannot invoke a getter since we don't have
+        an instance to invoke it on. So disable interactivity with getters.
+
+        * UserInterface/Views/ObjectTreeView.js:
+        (WebInspector.ObjectTreeView):
+        Update the modes to include an API mode for instances and classes.
+
+        (WebInspector.ObjectTreeView.defaultModeForObject):
+        * UserInterface/Views/SourceCodeTextEditor.js:
+        (WebInspector.SourceCodeTextEditor.prototype._showPopoverForObject):
+        Simplify ObjectTree construction to automatically determine mode based
+        on the RemoteObject that was provided.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        "Getter" tooltip.
+
 2015-03-26  Timothy Hatcher  <timothy@apple.com>
 
         Web Inspector: Convert TreeElement classes to ES6
index 27082e2..57cffc2 100644 (file)
Binary files a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js and b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js differ
index 511a466..1dd6e23 100644 (file)
@@ -31,7 +31,7 @@
 
 WebInspector.RemoteObject = class RemoteObject
 {
-    constructor(objectId, type, subtype, value, description, size, preview)
+    constructor(objectId, type, subtype, value, description, size, classPrototype, preview)
     {
         console.assert(type);
         console.assert(!preview || preview instanceof WebInspector.ObjectPreview);
@@ -40,7 +40,7 @@ WebInspector.RemoteObject = class RemoteObject
         this._subtype = subtype;
 
         if (objectId) {
-            // Object or Symbol.
+            // Object, Function, or Symbol.
             console.assert(!subtype || typeof subtype === "string");
             console.assert(!description || typeof description === "string");
             console.assert(!value);
@@ -49,7 +49,11 @@ WebInspector.RemoteObject = class RemoteObject
             this._description = description;
             this._hasChildren = type !== "symbol";
             this._size = size;
+            this._classPrototype = classPrototype;
             this._preview = preview;
+
+            if (subtype === "class")
+                this._description = "class " + this._description;
         } else {
             // Primitive or null.
             console.assert(type !== "object" || value === null);
@@ -65,7 +69,7 @@ WebInspector.RemoteObject = class RemoteObject
 
     static fromPrimitiveValue(value)
     {
-        return new WebInspector.RemoteObject(undefined, typeof value, undefined, undefined, value);
+        return new WebInspector.RemoteObject(undefined, typeof value, undefined, value, undefined, undefined, undefined);
     }
 
     static fromPayload(payload)
@@ -82,6 +86,9 @@ WebInspector.RemoteObject = class RemoteObject
             }
         }
 
+        if (payload.classPrototype)
+            payload.classPrototype = WebInspector.RemoteObject.fromPayload(payload.classPrototype);
+
         if (payload.preview) {
             // COMPATIBILITY (iOS 8): iOS 7 and 8 did not have type/subtype/description on
             // Runtime.ObjectPreview. Copy them over from the RemoteObject.
@@ -95,7 +102,7 @@ WebInspector.RemoteObject = class RemoteObject
             payload.preview = WebInspector.ObjectPreview.fromPayload(payload.preview);
         }
 
-        return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.size, payload.preview);
+        return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.size, payload.classPrototype, payload.preview);
     }
 
     static createCallArgument(valueOrObject)
@@ -171,6 +178,11 @@ WebInspector.RemoteObject = class RemoteObject
         return this._size || 0;
     }
 
+    get classPrototype()
+    {
+        return this._classPrototype;
+    }
+
     get preview()
     {
         return this._preview;
@@ -323,6 +335,11 @@ WebInspector.RemoteObject = class RemoteObject
         return this._subtype === "array";
     }
 
+    isClass()
+    {
+        return this._subtype === "class";
+    }
+
     isCollectionType()
     {
         return this._subtype === "map" || this._subtype === "set" || this._subtype === "weakmap";
index f2926e0..2838255 100644 (file)
@@ -86,9 +86,12 @@ WebInspector.FormattedValue.createElementForTypesAndValue = function(type, subty
         return span;
     }
 
-    // Function: ellided in previews.
+    // Function: if class, show the description, otherwise ellide in previews.
     if (type === "function") {
-        span.textContent = isPreview ? "function" : displayString;
+        if (subtype === "class")
+            span.textContent = displayString;
+        else
+            span.textContent = isPreview ? "function" : displayString;
         return span;
     }
 
@@ -125,8 +128,8 @@ WebInspector.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject = fu
     if (object.subtype === "node")
         return WebInspector.FormattedValue.createElementForNode(object);
 
-    if (object.type === "object") {
-        var objectTree = new WebInspector.ObjectTreeView(object, WebInspector.ObjectTreeView.Mode.Properties, propertyPath, forceExpanding);
+    if (object.type === "object" || object.subtype === "class") {
+        var objectTree = new WebInspector.ObjectTreeView(object, null, propertyPath, forceExpanding);
         return objectTree.element;
     }
 
index 34c2eae..1246dc9 100644 (file)
@@ -46,6 +46,7 @@ WebInspector.LegacyConsoleMessageImpl = function(source, level, message, linkifi
         "set": this._formatParameterAsObject,
         "weakmap": this._formatParameterAsObject,
         "iterator": this._formatParameterAsObject,
+        "class": this._formatParameterAsObject,
         "array":  this._formatParameterAsArray,
         "node":   this._formatParameterAsNode,
         "string": this._formatParameterAsString
@@ -308,7 +309,8 @@ WebInspector.LegacyConsoleMessageImpl.prototype = {
 
     _formatParameterAsObject: function(obj, elem, forceExpansion)
     {
-        this._objectTree = new WebInspector.ObjectTreeView(obj, WebInspector.ObjectTreeView.Mode.Properties, this._rootPropertyPathForObject(obj), forceExpansion);
+        // FIXME: Should have a better ObjectTreeView mode for classes (static methods and methods).
+        this._objectTree = new WebInspector.ObjectTreeView(obj, null, this._rootPropertyPathForObject(obj), forceExpansion);
         elem.appendChild(this._objectTree.element);
     },
 
index 6895dc7..080b87f 100644 (file)
@@ -69,7 +69,7 @@ WebInspector.ObjectTreeArrayIndexTreeElement = class ObjectTreeArrayIndexTreeEle
             valueElement.appendChild(WebInspector.FormattedValue.createObjectTreeOrFormattedValueForRemoteObject(resolvedValue, this.resolvedValuePropertyPath()));
         else {
             if (this.property.hasGetter())
-                container.appendChild(this.createInteractiveGetterElement());
+                container.appendChild(this.createInteractiveGetterElement(true));
             if (!this.property.hasSetter())
                 container.appendChild(this.createReadOnlyIconElement());
             // FIXME: What if just a setter?
index 55a4fbe..3d16d67 100644 (file)
@@ -113,12 +113,18 @@ WebInspector.ObjectTreeBaseTreeElement = class ObjectTreeBaseTreeElement extends
         return propertyPath.displayPath(this.propertyPathType());
     }
 
-    createInteractiveGetterElement()
+    createInteractiveGetterElement(enabled)
     {
         var getterElement = document.createElement("img");
         getterElement.className = "getter";
-        getterElement.title = WebInspector.UIString("Invoke getter");
 
+        if (!enabled) {
+            getterElement.classList.add("disabled");
+            getterElement.title = WebInspector.UIString("Getter");
+            return getterElement;
+        }
+
+        getterElement.title = WebInspector.UIString("Invoke getter");
         getterElement.addEventListener("click", function(event) {
             event.stopPropagation();
             var lastNonPrototypeObject = this._propertyPath.lastNonPrototypeObject;
index 6b733dd..ee01482 100644 (file)
     margin-left: 3px;
 }
 
-.object-tree-property .getter:hover {
+.object-tree-property .getter.disabled {
+    opacity: 0.35;
+}
+
+.object-tree-property .getter:not(.disabled):hover {
     opacity: 1;
 }
 
index 8c762bd..b558a7b 100644 (file)
@@ -169,7 +169,7 @@ WebInspector.ObjectTreePropertyTreeElement = class ObjectTreePropertyTreeElement
         } else {
             valueOrGetterElement = document.createElement("span");
             if (this.property.hasGetter())
-                valueOrGetterElement.appendChild(this.createInteractiveGetterElement());
+                valueOrGetterElement.appendChild(this.createInteractiveGetterElement(this._mode !== WebInspector.ObjectTreeView.Mode.ClassAPI));
             if (!this.property.hasSetter())
                 valueOrGetterElement.appendChild(this.createReadOnlyIconElement());
             // FIXME: What if just a setter?
@@ -211,7 +211,7 @@ WebInspector.ObjectTreePropertyTreeElement = class ObjectTreePropertyTreeElement
             container.appendChild(paramElement);
         } else {
             if (this.property.hasGetter())
-                container.appendChild(this.createInteractiveGetterElement());
+                container.appendChild(this.createInteractiveGetterElement(this._mode !== WebInspector.ObjectTreeView.Mode.ClassAPI));
             if (!this.property.hasSetter())
                 container.appendChild(this.createReadOnlyIconElement());
             // FIXME: What if just a setter?
@@ -304,8 +304,10 @@ WebInspector.ObjectTreePropertyTreeElement = class ObjectTreePropertyTreeElement
         var resolvedValue = this.resolvedValue();
         if (resolvedValue.isCollectionType() && this._mode === WebInspector.ObjectTreeView.Mode.Properties)
             resolvedValue.getCollectionEntries(0, 100, this._updateChildrenInternal.bind(this, this._updateEntries, this._mode));
+        else if (this._mode === WebInspector.ObjectTreeView.Mode.ClassAPI)
+            resolvedValue.getOwnPropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, WebInspector.ObjectTreeView.Mode.ClassAPI));
         else if (this.property.name === "__proto__")
-            resolvedValue.getOwnPropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, WebInspector.ObjectTreeView.Mode.API));
+            resolvedValue.getOwnPropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, WebInspector.ObjectTreeView.Mode.PrototypeAPI));
         else
             resolvedValue.getDisplayablePropertyDescriptors(this._updateChildrenInternal.bind(this, this._updateProperties, this._mode));
     }
@@ -353,7 +355,7 @@ WebInspector.ObjectTreePropertyTreeElement = class ObjectTreePropertyTreeElement
         var resolvedValue = this.resolvedValue();
         var isArray = resolvedValue.isArray();
         var isPropertyMode = mode === WebInspector.ObjectTreeView.Mode.Properties || this._getterValue;
-        var isAPI = mode === WebInspector.ObjectTreeView.Mode.API;
+        var isAPI = mode !== WebInspector.ObjectTreeView.Mode.Prototype;
 
         var prototypeName = undefined;
         if (this.property.name === "__proto__") {
@@ -367,7 +369,7 @@ WebInspector.ObjectTreePropertyTreeElement = class ObjectTreePropertyTreeElement
             // already showed them in the Properties section.
             if (isAPI && propertyDescriptor.nativeGetter)
                 continue;
-            
+
             if (isArray && isPropertyMode) {
                 if (propertyDescriptor.isIndexProperty())
                     this.appendChild(new WebInspector.ObjectTreeArrayIndexTreeElement(propertyDescriptor, propertyPath));
index b3b613a..ad14936 100644 (file)
@@ -32,7 +32,7 @@ WebInspector.ObjectTreeView = function(object, mode, propertyPath, forceExpandin
     console.assert(!propertyPath || propertyPath instanceof WebInspector.PropertyPath);
 
     this._object = object;
-    this._mode = mode || WebInspector.ObjectTreeView.Mode.Properties;
+    this._mode = mode || WebInspector.ObjectTreeView.defaultModeForObject(object);
     this._propertyPath = propertyPath || new WebInspector.PropertyPath(this._object, "this");
     this._expanded = false;
     this._hasLosslessPreview = false;
@@ -42,6 +42,10 @@ WebInspector.ObjectTreeView = function(object, mode, propertyPath, forceExpandin
     // listen for console clear events. Currently all ObjectTrees are in the console.
     this._inConsole = true;
 
+    // Always force expanding for classes.
+    if (this._object.isClass())
+        forceExpanding = true;
+
     this._element = document.createElement("div");
     this._element.className = "object-tree";
 
@@ -70,6 +74,14 @@ WebInspector.ObjectTreeView = function(object, mode, propertyPath, forceExpandin
     // FIXME: Support editable ObjectTrees.
 };
 
+WebInspector.ObjectTreeView.defaultModeForObject = function(object)
+{
+    if (object.subtype === "class")
+        return WebInspector.ObjectTreeView.Mode.ClassAPI;
+
+    return WebInspector.ObjectTreeView.Mode.Properties;
+}
+
 WebInspector.ObjectTreeView.emptyMessageElement = function(message)
 {
     var emptyMessageElement = document.createElement("div");
@@ -79,8 +91,9 @@ WebInspector.ObjectTreeView.emptyMessageElement = function(message)
 };
 
 WebInspector.ObjectTreeView.Mode = {
-    Properties: Symbol("object-tree-properties"),
-    API: Symbol("object-tree-api"),
+    Properties: Symbol("object-tree-properties"),      // Properties
+    PrototypeAPI: Symbol("object-tree-prototype-api"), // API view on a live object instance, so getters can be invoked.
+    ClassAPI: Symbol("object-tree-class-api"),         // API view without an object instance, can not invoke getters.
 };
 
 WebInspector.ObjectTreeView.ComparePropertyDescriptors = function(propertyA, propertyB)
@@ -221,6 +234,8 @@ WebInspector.ObjectTreeView.prototype = {
     {
         if (this._object.isCollectionType() && this._mode === WebInspector.ObjectTreeView.Mode.Properties)
             this._object.getCollectionEntries(0, 100, this._updateChildren.bind(this, this._updateEntries));
+        else if (this._object.isClass())
+            this._object.classPrototype.getDisplayablePropertyDescriptors(this._updateChildren.bind(this, this._updateProperties));
         else
             this._object.getDisplayablePropertyDescriptors(this._updateChildren.bind(this, this._updateProperties));
     },
index 5985f17..204b718 100644 (file)
@@ -1525,7 +1525,7 @@ WebInspector.SourceCodeTextEditor.prototype = {
         content.appendChild(titleElement);
 
         // FIXME: If this is a variable, it would be nice to put the variable name in the PropertyPath.
-        var objectTree = new WebInspector.ObjectTreeView(data, WebInspector.ObjectTreeView.Mode.Properties, null);
+        var objectTree = new WebInspector.ObjectTreeView(data);
         objectTree.showOnlyProperties();
         objectTree.expand();