Changeset 179019 in webkit for trunk/Source/JavaScriptCore


Ignore:
Timestamp:
Jan 23, 2015, 12:05:44 PM (10 years ago)
Author:
Joseph Pecoraro
Message:

Web Inspector: Object Previews in the Console
https://bugs.webkit.org/show_bug.cgi?id=129204

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

Update the very old, unused object preview code. Part of this comes from
the earlier WebKit legacy implementation, and the Blink implementation.

A RemoteObject may include a preview, if it is asked for, and if the
RemoteObject is an object. Previews are a shallow (single level) list
of a limited number of properties on the object. The previewed
properties are always stringified (even if primatives). Previews are
limited to just 5 properties or 100 indices. Previews are marked
as lossless if they are a complete snapshot of the object.

There is a path to make previews two levels deep, that is currently
unused but should soon be used for tables (e.g. IndexedDB).

  • inspector/InjectedScriptSource.js:
  • Move some code off of InjectedScript to be generic functions

usable by RemoteObject as well.

  • Update preview generation to use
  • inspector/protocol/Runtime.json:
  • Add a new type, "accessor" for preview objects. This represents

a getter / setter. We currently don't get the value.

Source/WebInspectorUI:

  • UserInterface/Controllers/JavaScriptLogViewController.js:

(WebInspector.JavaScriptLogViewController.prototype.consolePromptTextCommitted):

  • UserInterface/Controllers/JavaScriptRuntimeCompletionProvider.js:

(get WebInspector.JavaScriptRuntimeCompletionProvider.prototype.):
Update RuntimeManager callsites that do not need object previews.

  • UserInterface/Controllers/RuntimeManager.js:

(WebInspector.RuntimeManager.prototype.evalCallback):
(WebInspector.RuntimeManager.prototype.evaluateInInspectedWindow):
Update the main evaluate method to include a boolean parameter for
object previews. Most callers do not need them. Also, since previews
were not available on iOS 6, switch to invoke, to conditionally
include the command parameter.

  • UserInterface/Protocol/RemoteObject.js:

(WebInspector.RemoteObject):
(WebInspector.RemoteObject.fromPayload):
(WebInspector.RemoteObject.prototype.get preview):
Store the preview from the payload.

  • UserInterface/Views/ConsoleMessageImpl.js:

(WebInspector.ConsoleMessageImpl.prototype._format):
(WebInspector.ConsoleMessageImpl.prototype._formatParameter):
(WebInspector.ConsoleMessageImpl.prototype._formatParameterAsNode):
(WebInspector.ConsoleMessageImpl.prototype._formatParameterAsString):
(WebInspector.ConsoleMessageImpl.prototype._formatAsArrayEntry):
Pass an explicit false for most formatters to not use a preview if available.

(WebInspector.ConsoleMessageImpl.prototype._formatParameterAsArray):
(WebInspector.ConsoleMessageImpl.prototype._formatParameterAsObject):
Currently only object types are previewed. Though we request previews
for arrays, we don't use the preview because we show a better preview
by just immediately requesting for a full non-preview property list.

(WebInspector.ConsoleMessageImpl.prototype._appendObjectPreview):
Quickly output an object preview into the title element. The format
is "ClassName {prop: value...}". Elide the class name if it is "Object".
Also skip over certain preview properties that may not be useful
at a glance (like constructor, or accessors without values).

  • UserInterface/Views/LogContentView.css:

(.console-object-preview):
(.console-formatted-array .console-object-preview):
(.console-object-preview-lossless):
(.expanded .console-object-preview):
Show lossy previews in italics.
Show lossless previews and array previews without italics.
Do not show the class name in the preview in italics when expanded.

(.console-object-preview .name):
Give preview property names the same color as ObjectPropertiesSection property names.

(.expanded .console-object-preview > .console-object-preview-body):
When expanding an object, hide the preview.

(.console-object-preview > .console-object-preview-name.console-object-preview-name-Object):
(.expanded .console-object-preview > .console-object-preview-name.console-object-preview-name-Object):
For "Object" previews, hide the name "Object" when not expanded, and show it when expanded.

LayoutTests:

  • inspector/debugger/command-line-api-exception-nested-catch.html:
  • inspector/debugger/command-line-api-exception.html:
  • inspector/model/remote-object-get-properties.html:

Update RuntimeManager callsites to not ask for previews when evaluating.

  • inspector/model/remote-object-expected.txt: Added.
  • inspector/model/remote-object.html: Added.

Add a test specifically for Remote Object. This test can also be
opened in a browser. It attempts to run the gamut of all different
types of objects and shows the RemoteObject constructed for it.

Location:
trunk/Source/JavaScriptCore
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/ChangeLog

    r179015 r179019  
     12015-01-23  Joseph Pecoraro  <[email protected]>
     2
     3        Web Inspector: Object Previews in the Console
     4        https://bugs.webkit.org/show_bug.cgi?id=129204
     5
     6        Reviewed by Timothy Hatcher.
     7
     8        Update the very old, unused object preview code. Part of this comes from
     9        the earlier WebKit legacy implementation, and the Blink implementation.
     10
     11        A RemoteObject may include a preview, if it is asked for, and if the
     12        RemoteObject is an object. Previews are a shallow (single level) list
     13        of a limited number of properties on the object. The previewed
     14        properties are always stringified (even if primatives). Previews are
     15        limited to just 5 properties or 100 indices. Previews are marked
     16        as lossless if they are a complete snapshot of the object.
     17
     18        There is a path to make previews two levels deep, that is currently
     19        unused but should soon be used for tables (e.g. IndexedDB).
     20
     21        * inspector/InjectedScriptSource.js:
     22        - Move some code off of InjectedScript to be generic functions
     23        usable by RemoteObject as well.
     24        - Update preview generation to use
     25
     26        * inspector/protocol/Runtime.json:
     27        - Add a new type, "accessor" for preview objects. This represents
     28        a getter / setter. We currently don't get the value.
     29
    1302015-01-23  Michael Saboff  <[email protected]>
    231
  • trunk/Source/JavaScriptCore/inspector/InjectedScriptSource.js

    r178875 r179019  
    11/*
    22 * Copyright (C) 2007, 2014 Apple Inc.  All rights reserved.
     3 * Copyright (C) 2013 Google Inc. All rights reserved.
    34 *
    45 * Redistribution and use in source and binary forms, with or without
     
    3435var Object = {}.constructor;
    3536
     37function toString(obj)
     38{
     39    return "" + obj;
     40}
     41   
     42function isUInt32(obj)
     43{
     44    if (typeof obj === "number")
     45        return obj >>> 0 === obj && (obj > 0 || 1 / obj > 0);
     46    return "" + (obj >>> 0) === obj;
     47}
     48
    3649var InjectedScript = function()
    3750{
     
    8194            result.value = object;
    8295        else
    83             result.description = this._toString(object);
     96            result.description = toString(object);
    8497        return result;
    8598    },
     
    324337        var remoteObject = this._wrapObject(value, objectGroup);
    325338        try {
    326             remoteObject.description = this._toString(value);
     339            remoteObject.description = toString(value);
    327340        } catch (e) {}
    328341        return {
     
    480493        //  - ownProperties - only own properties and __proto__
    481494        //  - ownAndGetterProperties - own properties, __proto__, and getters in the prototype chain
    482         //  - neither - get all properties in the prototype chain, exclude __proto__
     495        //  - neither - get all properties in the prototype chain and __proto__
    483496
    484497        var descriptors = [];
     
    612625        }
    613626
    614         // If owning frame has navigated to somewhere else window properties will be undefined.
    615627        return null;
    616628    },
     
    624636
    625637        if (subtype === "regexp")
    626             return this._toString(obj);
     638            return toString(obj);
    627639
    628640        if (subtype === "date")
    629             return this._toString(obj);
     641            return toString(obj);
    630642
    631643        if (subtype === "error")
    632             return this._toString(obj);
     644            return toString(obj);
    633645
    634646        if (subtype === "node") {
     
    656668        // NodeList in JSC is a function, check for array prior to this.
    657669        if (typeof obj === "function")
    658             return this._toString(obj);
    659 
    660         // FIXME: Can we remove this?
     670            return toString(obj);
     671
     672        // If Object, try for a better name from the constructor.
    661673        if (className === "Object") {
    662             // In Chromium DOM wrapper prototypes will have Object as their constructor name,
    663             // get the real DOM wrapper name from the constructor property.
    664674            var constructorName = obj.constructor && obj.constructor.name;
    665675            if (constructorName)
    666676                return constructorName;
    667677        }
     678
    668679        return className;
    669     },
    670 
    671     _toString: function(obj)
    672     {
    673         // We don't use String(obj) because inspectedGlobalObject.String is undefined if owning frame navigated to another page.
    674         return "" + obj;
    675680    }
    676681}
     
    709714    this.description = injectedScript._describe(object);
    710715
    711     if (generatePreview && (this.type === "object" || injectedScript._isHTMLAllCollection(object)))
     716    if (generatePreview && this.type === "object")
    712717        this.preview = this._generatePreview(object, undefined, columnNames);
    713718}
     
    728733            indexes: isTableRowsRequest ? 1000 : Math.max(100, firstLevelKeysCount)
    729734        };
    730         for (var o = object; injectedScript._isDefined(o); o = o.__proto__)
    731             this._generateProtoPreview(o, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys);
     735
     736        try {
     737            // All properties.
     738            var descriptors = injectedScript._propertyDescriptors(object);
     739            this._appendPropertyDescriptors(preview, descriptors, propertiesThreshold, secondLevelKeys);
     740            if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
     741                return preview;
     742
     743            // FIXME: Internal properties.
     744            // FIXME: Map/Set/Iterator entries.
     745        } catch (e) {
     746            preview.lossless = false;
     747        }
     748
    732749        return preview;
    733750    },
    734751
    735     _generateProtoPreview: function(object, preview, propertiesThreshold, firstLevelKeys, secondLevelKeys)
    736     {
    737         var propertyNames = firstLevelKeys ? firstLevelKeys : Object.keys(object);
    738         try {
    739             for (var i = 0; i < propertyNames.length; ++i) {
    740                 if (!propertiesThreshold.properties || !propertiesThreshold.indexes) {
     752    _appendPropertyDescriptors: function(preview, descriptors, propertiesThreshold, secondLevelKeys)
     753    {
     754        for (var descriptor of descriptors) {
     755            // Seen enough.
     756            if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
     757                break;
     758
     759            // Error in descriptor.
     760            if (descriptor.wasThrown) {
     761                preview.lossless = false;
     762                continue;
     763            }
     764
     765            // Do not show "__proto__" in preview.
     766            var name = descriptor.name;
     767            if (name === "__proto__")
     768                continue;
     769
     770            // Do not show "length" on array like objects in preview.
     771            if (this.subtype === "array" && name === "length")
     772                continue;
     773
     774            // Do not show non-enumerable non-own properties. Special case to allow array indexes that may be on the prototype.
     775            if (!descriptor.enumerable && !descriptor.isOwn && !(this.subtype === "array" && isUInt32(name)))
     776                continue;
     777
     778            // Getter/setter.
     779            if (!("value" in descriptor)) {
     780                preview.lossless = false;
     781                this._appendPropertyPreview(preview, {name: name, type: "accessor"}, propertiesThreshold);
     782                continue;
     783            }
     784
     785            // Null value.
     786            var value = descriptor.value;
     787            if (value === null) {
     788                this._appendPropertyPreview(preview, {name: name, type: "object", subtype: "null", value: "null"}, propertiesThreshold);
     789                continue;
     790            }
     791
     792            // Ignore non-enumerable functions.
     793            var type = typeof value;
     794            if (!descriptor.enumerable && type === "function")
     795                continue;
     796
     797            // Fix type of document.all.
     798            if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
     799                type = "object";
     800
     801            // Primitive.
     802            const maxLength = 100;
     803            if (InjectedScript.primitiveTypes[type]) {
     804                if (type === "string" && value.length > maxLength) {
     805                    value = this._abbreviateString(value, maxLength, true);
     806                    preview.lossless = false;
     807                }
     808                this._appendPropertyPreview(preview, {name: name, type: type, value: toString(value)}, propertiesThreshold);
     809                continue;
     810            }
     811
     812            // Object.
     813            var property = {name: name, type: type};
     814            var subtype = injectedScript._subtype(value);
     815            if (subtype)
     816                property.subtype = subtype;
     817
     818            // Second level.
     819            if (secondLevelKeys === null || secondLevelKeys) {
     820                var subPreview = this._generatePreview(value, secondLevelKeys || undefined, undefined);
     821                property.valuePreview = subPreview;
     822                if (!subPreview.lossless)
     823                    preview.lossless = false;
     824                if (subPreview.overflow)
    741825                    preview.overflow = true;
    742                     preview.lossless = false;
    743                     break;
    744                 }
    745                 var name = propertyNames[i];
    746                 if (this.subtype === "array" && name === "length")
    747                     continue;
    748 
    749                 var descriptor = Object.getOwnPropertyDescriptor(object, name);
    750                 if (!("value" in descriptor) || !descriptor.enumerable) {
    751                     preview.lossless = false;
    752                     continue;
    753                 }
    754 
    755                 var value = descriptor.value;
    756                 if (value === null) {
    757                     this._appendPropertyPreview(preview, { name: name, type: "object", value: "null" }, propertiesThreshold);
    758                     continue;
    759                 }
    760 
    761                 const maxLength = 100;
    762                 var type = typeof value;
    763 
    764                 if (InjectedScript.primitiveTypes[type]) {
    765                     if (type === "string") {
    766                         if (value.length > maxLength) {
    767                             value = this._abbreviateString(value, maxLength, true);
    768                             preview.lossless = false;
    769                         }
    770                         value = value.replace(/\n/g, "\u21B5");
    771                     }
    772                     this._appendPropertyPreview(preview, { name: name, type: type, value: value + "" }, propertiesThreshold);
    773                     continue;
    774                 }
    775 
    776                 if (secondLevelKeys === null || secondLevelKeys) {
    777                     var subPreview = this._generatePreview(value, secondLevelKeys || undefined);
    778                     var property = { name: name, type: type, valuePreview: subPreview };
    779                     this._appendPropertyPreview(preview, property, propertiesThreshold);
    780                     if (!subPreview.lossless)
    781                         preview.lossless = false;
    782                     if (subPreview.overflow)
    783                         preview.overflow = true;
    784                     continue;
    785                 }
    786 
    787                 preview.lossless = false;
    788 
    789                 var subtype = injectedScript._subtype(value);
     826            } else {
    790827                var description = "";
    791828                if (type !== "function")
    792829                    description = this._abbreviateString(injectedScript._describe(value), maxLength, subtype === "regexp");
    793 
    794                 var property = { name: name, type: type, value: description };
    795                 if (subtype)
    796                     property.subtype = subtype;
    797                 this._appendPropertyPreview(preview, property, propertiesThreshold);
    798             }
    799         } catch (e) {
     830                property.value = description;
     831                preview.lossless = false;
     832            }
     833
     834            this._appendPropertyPreview(preview, property, propertiesThreshold);
    800835        }
    801836    },
     
    803838    _appendPropertyPreview: function(preview, property, propertiesThreshold)
    804839    {
    805         if (isNaN(property.name))
     840        if (toString(property.name >>> 0) === property.name)
     841            propertiesThreshold.indexes--;
     842        else
    806843            propertiesThreshold.properties--;
    807         else
    808             propertiesThreshold.indexes--;
     844
     845        if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) {
     846            preview.overflow = true;
     847            preview.lossless = false;
     848            return;
     849        }
     850
    809851        preview.properties.push(property);
    810852    },
  • trunk/Source/JavaScriptCore/inspector/protocol/Runtime.json

    r178768 r179019  
    1919                { "name": "description", "type": "string", "optional": true, "description": "String representation of the object." },
    2020                { "name": "objectId", "$ref": "RemoteObjectId", "optional": true, "description": "Unique object identifier (for non-primitive values)." },
    21                 { "name": "preview", "$ref": "ObjectPreview", "optional": true, "description": "Preview containsing abbreviated property values." }
     21                { "name": "preview", "$ref": "ObjectPreview", "optional": true, "description": "Preview containing abbreviated property values. Specified for <code>object</code> type values only." }
    2222            ]
    2323        },
     
    3737            "properties": [
    3838                { "name": "name", "type": "string", "description": "Property name." },
    39                 { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean"], "description": "Object type." },
     39                { "name": "type", "type": "string", "enum": ["object", "function", "undefined", "string", "number", "boolean", "accessor"], "description": "Object type." },
     40                { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error"], "description": "Object subtype hint. Specified for <code>object</code> type values only." },
    4041                { "name": "value", "type": "string", "optional": true, "description": "User-friendly property value string." },
    41                 { "name": "valuePreview", "$ref": "ObjectPreview", "optional": true, "description": "Nested value preview." },
    42                 { "name": "subtype", "type": "string", "optional": true, "enum": ["array", "null", "node", "regexp", "date", "error"], "description": "Object subtype hint. Specified for <code>object</code> type values only." }
     42                { "name": "valuePreview", "$ref": "ObjectPreview", "optional": true, "description": "Nested value preview." }
    4343            ]
    4444        },
Note: See TracChangeset for help on using the changeset viewer.