Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2009 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 /**
     32  * @constructor
     33  * @param {string|undefined} objectId
     34  * @param {string} type
     35  * @param {string|undefined} subtype
     36  * @param {*} value
     37  * @param {string=} description
     38  * @param {RuntimeAgent.ObjectPreview=} preview
     39  */
     40 WebInspector.RemoteObject = function(objectId, type, subtype, value, description, preview)
     41 {
     42     this._type = type;
     43     this._subtype = subtype;
     44     if (objectId) {
     45         // handle
     46         this._objectId = objectId;
     47         this._description = description;
     48         this._hasChildren = true;
     49         this._preview = preview;
     50     } else {
     51         // Primitive or null object.
     52         console.assert(type !== "object" || value === null);
     53         this._description = description || (value + "");
     54         this._hasChildren = false;
     55         this.value = value;
     56     }
     57 }
     58 
     59 /**
     60  * @param {number|string|boolean} value
     61  * @return {WebInspector.RemoteObject}
     62  */
     63 WebInspector.RemoteObject.fromPrimitiveValue = function(value)
     64 {
     65     return new WebInspector.RemoteObject(undefined, typeof value, undefined, value);
     66 }
     67 
     68 /**
     69  * @param {*} value
     70  * @return {WebInspector.RemoteObject}
     71  */
     72 WebInspector.RemoteObject.fromLocalObject = function(value)
     73 {
     74     return new WebInspector.LocalJSONObject(value);
     75 }
     76 
     77 /**
     78  * @param {WebInspector.DOMNode} node
     79  * @param {string} objectGroup
     80  * @param {function(?WebInspector.RemoteObject)} callback
     81  */
     82 WebInspector.RemoteObject.resolveNode = function(node, objectGroup, callback)
     83 {
     84     /**
     85      * @param {?Protocol.Error} error
     86      * @param {RuntimeAgent.RemoteObject} object
     87      */
     88     function mycallback(error, object)
     89     {
     90         if (!callback)
     91             return;
     92 
     93         if (error || !object)
     94             callback(null);
     95         else
     96             callback(WebInspector.RemoteObject.fromPayload(object));
     97     }
     98     DOMAgent.resolveNode(node.id, objectGroup, mycallback);
     99 }
    100 
    101 /**
    102  * @param {RuntimeAgent.RemoteObject=} payload
    103  * @return {WebInspector.RemoteObject}
    104  */
    105 WebInspector.RemoteObject.fromPayload = function(payload)
    106 {
    107     console.assert(typeof payload === "object", "Remote object payload should only be an object");
    108 
    109     return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
    110 }
    111 
    112 /**
    113  * @param {WebInspector.RemoteObject} remoteObject
    114  * @return {string}
    115  */
    116 WebInspector.RemoteObject.type = function(remoteObject)
    117 {
    118     if (remoteObject === null)
    119         return "null";
    120 
    121     var type = typeof remoteObject;
    122     if (type !== "object" && type !== "function")
    123         return type;
    124 
    125     return remoteObject.type;
    126 }
    127 
    128 WebInspector.RemoteObject.prototype = {
    129     /** @return {RuntimeAgent.RemoteObjectId} */
    130     get objectId()
    131     {
    132         return this._objectId;
    133     },
    134 
    135     /** @return {string} */
    136     get type()
    137     {
    138         return this._type;
    139     },
    140 
    141     /** @return {string|undefined} */
    142     get subtype()
    143     {
    144         return this._subtype;
    145     },
    146 
    147     /** @return {string|undefined} */
    148     get description()
    149     {
    150         return this._description;
    151     },
    152 
    153     /** @return {boolean} */
    154     get hasChildren()
    155     {
    156         return this._hasChildren;
    157     },
    158 
    159     /** @return {RuntimeAgent.ObjectPreview|undefined} */
    160     get preview()
    161     {
    162         return this._preview;
    163     },
    164 
    165     /**
    166      * @param {function(Array.<WebInspector.RemoteObjectProperty>, Array.<WebInspector.RemoteObjectProperty>=)} callback
    167      */
    168     getOwnProperties: function(callback)
    169     {
    170         this.doGetProperties(true, false, callback);
    171     },
    172 
    173     /**
    174      * @param {boolean} accessorPropertiesOnly
    175      * @param {function(?Array.<WebInspector.RemoteObjectProperty>, ?Array.<WebInspector.RemoteObjectProperty>)} callback
    176      */
    177     getAllProperties: function(accessorPropertiesOnly, callback)
    178     {
    179         this.doGetProperties(false, accessorPropertiesOnly, callback);
    180     },
    181 
    182     /**
    183      * @param {boolean} ownProperties
    184      * @param {boolean} accessorPropertiesOnly
    185      * @param {?function(Array.<WebInspector.RemoteObjectProperty>, ?Array.<WebInspector.RemoteObjectProperty>)} callback
    186      */
    187     doGetProperties: function(ownProperties, accessorPropertiesOnly, callback)
    188     {
    189         if (!this._objectId) {
    190             callback([], null);
    191             return;
    192         }
    193 
    194         /**
    195          * @param {?Protocol.Error} error
    196          * @param {Array.<RuntimeAgent.PropertyDescriptor>=} properties
    197          * @param {Array.<RuntimeAgent.InternalPropertyDescriptor>=} internalProperties
    198          */
    199         function remoteObjectBinder(error, properties, internalProperties)
    200         {
    201             if (error) {
    202                 callback(null, null);
    203                 return;
    204             }
    205             var result = [];
    206             for (var i = 0; properties && i < properties.length; ++i) {
    207                 var property = properties[i];
    208                 result.push(new WebInspector.RemoteObjectProperty(property.name, null, property));
    209             }
    210             var internalPropertiesResult = null;
    211             if (internalProperties) {
    212                 internalPropertiesResult = [];
    213                 for (var i = 0; i < internalProperties.length; i++) {
    214                     var property = internalProperties[i];
    215                     internalPropertiesResult.push(new WebInspector.RemoteObjectProperty(property.name, WebInspector.RemoteObject.fromPayload(property.value)));
    216                 }
    217             }
    218             callback(result, internalPropertiesResult);
    219         }
    220         RuntimeAgent.getProperties(this._objectId, ownProperties, accessorPropertiesOnly, remoteObjectBinder);
    221     },
    222 
    223     /**
    224      * @param {string} name
    225      * @param {string} value
    226      * @param {function(string=)} callback
    227      */
    228     setPropertyValue: function(name, value, callback)
    229     {
    230         if (!this._objectId) {
    231             callback("Can't set a property of non-object.");
    232             return;
    233         }
    234 
    235         RuntimeAgent.evaluate.invoke({expression:value, doNotPauseOnExceptionsAndMuteConsole:true}, evaluatedCallback.bind(this));
    236 
    237         /**
    238          * @param {?Protocol.Error} error
    239          * @param {RuntimeAgent.RemoteObject} result
    240          * @param {boolean=} wasThrown
    241          */
    242         function evaluatedCallback(error, result, wasThrown)
    243         {
    244             if (error || wasThrown) {
    245                 callback(error || result.description);
    246                 return;
    247             }
    248 
    249             this.doSetObjectPropertyValue(result, name, callback);
    250 
    251             if (result.objectId)
    252                 RuntimeAgent.releaseObject(result.objectId);
    253         }
    254     },
    255 
    256     /**
    257      * @param {RuntimeAgent.RemoteObject} result
    258      * @param {string} name
    259      * @param {function(string=)} callback
    260      */
    261     doSetObjectPropertyValue: function(result, name, callback)
    262     {
    263         // This assignment may be for a regular (data) property, and for an acccessor property (with getter/setter).
    264         // Note the sensitive matter about accessor property: the property may be physically defined in some proto object,
    265         // but logically it is bound to the object in question. JavaScript passes this object to getters/setters, not the object
    266         // where property was defined; so do we.
    267         var setPropertyValueFunction = "function(a, b) { this[a] = b; }";
    268 
    269         // Special case for NaN, Infinity and -Infinity
    270         if (result.type === "number" && typeof result.value !== "number")
    271             setPropertyValueFunction = "function(a) { this[a] = " + result.description + "; }";
    272 
    273         delete result.description; // Optimize on traffic.
    274         RuntimeAgent.callFunctionOn(this._objectId, setPropertyValueFunction, [{ value:name }, result], true, undefined, undefined, propertySetCallback.bind(this));
    275 
    276         /**
    277          * @param {?Protocol.Error} error
    278          * @param {RuntimeAgent.RemoteObject} result
    279          * @param {boolean=} wasThrown
    280          */
    281         function propertySetCallback(error, result, wasThrown)
    282         {
    283             if (error || wasThrown) {
    284                 callback(error || result.description);
    285                 return;
    286             }
    287             callback();
    288         }
    289     },
    290 
    291     /**
    292      * @param {function(?DOMAgent.NodeId)} callback
    293      */
    294     pushNodeToFrontend: function(callback)
    295     {
    296         if (this._objectId)
    297             WebInspector.domAgent.pushNodeToFrontend(this._objectId, callback);
    298         else
    299             callback(0);
    300     },
    301 
    302     highlightAsDOMNode: function()
    303     {
    304         WebInspector.domAgent.highlightDOMNode(undefined, undefined, this._objectId);
    305     },
    306 
    307     hideDOMNodeHighlight: function()
    308     {
    309         WebInspector.domAgent.hideDOMNodeHighlight();
    310     },
    311 
    312     /**
    313      * @param {function(this:Object)} functionDeclaration
    314      * @param {Array.<RuntimeAgent.CallArgument>=} args
    315      * @param {function(?WebInspector.RemoteObject)=} callback
    316      */
    317     callFunction: function(functionDeclaration, args, callback)
    318     {
    319         /**
    320          * @param {?Protocol.Error} error
    321          * @param {RuntimeAgent.RemoteObject} result
    322          * @param {boolean=} wasThrown
    323          */
    324         function mycallback(error, result, wasThrown)
    325         {
    326             if (!callback)
    327                 return;
    328 
    329             callback((error || wasThrown) ? null : WebInspector.RemoteObject.fromPayload(result));
    330         }
    331 
    332         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, undefined, undefined, mycallback);
    333     },
    334 
    335     /**
    336      * @param {function(this:Object)} functionDeclaration
    337      * @param {Array.<RuntimeAgent.CallArgument>|undefined} args
    338      * @param {function(*)} callback
    339      */
    340     callFunctionJSON: function(functionDeclaration, args, callback)
    341     {
    342         /**
    343          * @param {?Protocol.Error} error
    344          * @param {RuntimeAgent.RemoteObject} result
    345          * @param {boolean=} wasThrown
    346          */
    347         function mycallback(error, result, wasThrown)
    348         {
    349             callback((error || wasThrown) ? null : result.value);
    350         }
    351 
    352         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, true, false, mycallback);
    353     },
    354 
    355     release: function()
    356     {
    357         if (!this._objectId)
    358             return;
    359         RuntimeAgent.releaseObject(this._objectId);
    360     },
    361 
    362     /**
    363      * @return {number}
    364      */
    365     arrayLength: function()
    366     {
    367         if (this.subtype !== "array")
    368             return 0;
    369 
    370         var matches = this._description.match(/\[([0-9]+)\]/);
    371         if (!matches)
    372             return 0;
    373         return parseInt(matches[1], 10);
    374     }
    375 };
    376 
    377 
    378 /**
    379  * @param {WebInspector.RemoteObject} object
    380  * @param {boolean} flattenProtoChain
    381  * @param {function(?Array.<WebInspector.RemoteObjectProperty>, ?Array.<WebInspector.RemoteObjectProperty>)} callback
    382  */
    383 WebInspector.RemoteObject.loadFromObject = function(object, flattenProtoChain, callback)
    384 {
    385     if (flattenProtoChain)
    386        object.getAllProperties(false, callback);
    387     else
    388         WebInspector.RemoteObject.loadFromObjectPerProto(object, callback);
    389 };
    390 
    391 /**
    392  * @param {WebInspector.RemoteObject} object
    393  * @param {function(?Array.<WebInspector.RemoteObjectProperty>, ?Array.<WebInspector.RemoteObjectProperty>)} callback
    394  */
    395 WebInspector.RemoteObject.loadFromObjectPerProto = function(object, callback)
    396 {
    397     // Combines 2 asynch calls. Doesn't rely on call-back orders (some calls may be loop-back).
    398     var savedOwnProperties;
    399     var savedAccessorProperties;
    400     var savedInternalProperties;
    401     var resultCounter = 2;
    402 
    403     function processCallback()
    404     {
    405         if (--resultCounter)
    406             return;
    407         if (savedOwnProperties && savedAccessorProperties) {
    408             var combinedList = savedAccessorProperties.slice(0);
    409             for (var i = 0; i < savedOwnProperties.length; i++) {
    410                 var property = savedOwnProperties[i];
    411                 if (!property.isAccessorProperty())
    412                     combinedList.push(property);
    413             }
    414             return callback(combinedList, savedInternalProperties ? savedInternalProperties : null);
    415         } else {
    416             callback(null, null);
    417         }
    418     }
    419 
    420     /**
    421      * @param {Array.<WebInspector.RemoteObjectProperty>} properties
    422      * @param {Array.<WebInspector.RemoteObjectProperty>=} internalProperties
    423      */
    424     function allAccessorPropertiesCallback(properties, internalProperties)
    425     {
    426         savedAccessorProperties = properties;
    427         processCallback();
    428     }
    429 
    430     /**
    431      * @param {Array.<WebInspector.RemoteObjectProperty>} properties
    432      * @param {Array.<WebInspector.RemoteObjectProperty>=} internalProperties
    433      */
    434     function ownPropertiesCallback(properties, internalProperties)
    435     {
    436         savedOwnProperties = properties;
    437         savedInternalProperties = internalProperties;
    438         processCallback();
    439     }
    440 
    441     object.getAllProperties(true, allAccessorPropertiesCallback);
    442     object.getOwnProperties(ownPropertiesCallback);
    443 };
    444 
    445 
    446 /**
    447  * @constructor
    448  * @extends {WebInspector.RemoteObject}
    449  * @param {string|undefined} objectId
    450  * @param {WebInspector.ScopeRef} scopeRef
    451  * @param {string} type
    452  * @param {string|undefined} subtype
    453  * @param {*} value
    454  * @param {string=} description
    455  * @param {RuntimeAgent.ObjectPreview=} preview
    456  */
    457 WebInspector.ScopeRemoteObject = function(objectId, scopeRef, type, subtype, value, description, preview)
    458 {
    459     WebInspector.RemoteObject.call(this, objectId, type, subtype, value, description, preview);
    460     this._scopeRef = scopeRef;
    461     this._savedScopeProperties = undefined;
    462 };
    463 
    464 /**
    465  * @param {RuntimeAgent.RemoteObject} payload
    466  * @param {WebInspector.ScopeRef=} scopeRef
    467  * @return {WebInspector.RemoteObject}
    468  */
    469 WebInspector.ScopeRemoteObject.fromPayload = function(payload, scopeRef)
    470 {
    471     if (scopeRef)
    472         return new WebInspector.ScopeRemoteObject(payload.objectId, scopeRef, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
    473     else
    474         return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
    475 }
    476 
    477 WebInspector.ScopeRemoteObject.prototype = {
    478     /**
    479      * @param {boolean} ownProperties
    480      * @param {boolean} accessorPropertiesOnly
    481      * @param {function(Array.<WebInspector.RemoteObjectProperty>, Array.<WebInspector.RemoteObjectProperty>=)} callback
    482      * @override
    483      */
    484     doGetProperties: function(ownProperties, accessorPropertiesOnly, callback)
    485     {
    486         if (accessorPropertiesOnly) {
    487             callback([], []);
    488             return;
    489         }
    490         if (this._savedScopeProperties) {
    491             // No need to reload scope variables, as the remote object never
    492             // changes its properties. If variable is updated, the properties
    493             // array is patched locally.
    494             callback(this._savedScopeProperties.slice(), []);
    495             return;
    496         }
    497 
    498         /**
    499          * @param {Array.<WebInspector.RemoteObjectProperty>} properties
    500          * @param {Array.<WebInspector.RemoteObjectProperty>=} internalProperties
    501          */
    502         function wrappedCallback(properties, internalProperties)
    503         {
    504             if (this._scopeRef && properties instanceof Array)
    505                 this._savedScopeProperties = properties.slice();
    506             callback(properties, internalProperties);
    507         }
    508 
    509         WebInspector.RemoteObject.prototype.doGetProperties.call(this, ownProperties, accessorPropertiesOnly, wrappedCallback.bind(this));
    510     },
    511 
    512     /**
    513      * @override
    514      * @param {RuntimeAgent.RemoteObject} result
    515      * @param {string} name
    516      * @param {function(string=)} callback
    517      */
    518     doSetObjectPropertyValue: function(result, name, callback)
    519     {
    520         var newValue;
    521 
    522         switch (result.type) {
    523             case "undefined":
    524                 newValue = {};
    525                 break;
    526             case "object":
    527             case "function":
    528                 newValue = { objectId: result.objectId };
    529                 break;
    530             default:
    531                 newValue = { value: result.value };
    532         }
    533 
    534         DebuggerAgent.setVariableValue(this._scopeRef.number, name, newValue, this._scopeRef.callFrameId, this._scopeRef.functionId, setVariableValueCallback.bind(this));
    535 
    536         /**
    537          * @param {?Protocol.Error} error
    538          */
    539         function setVariableValueCallback(error)
    540         {
    541             if (error) {
    542                 callback(error);
    543                 return;
    544             }
    545             if (this._savedScopeProperties) {
    546                 for (var i = 0; i < this._savedScopeProperties.length; i++) {
    547                     if (this._savedScopeProperties[i].name === name)
    548                         this._savedScopeProperties[i].value = WebInspector.RemoteObject.fromPayload(result);
    549                 }
    550             }
    551             callback();
    552         }
    553     },
    554 
    555     __proto__: WebInspector.RemoteObject.prototype
    556 };
    557 
    558 /**
    559  * Either callFrameId or functionId (exactly one) must be defined.
    560  * @constructor
    561  * @param {number} number
    562  * @param {string=} callFrameId
    563  * @param {string=} functionId
    564  */
    565 WebInspector.ScopeRef = function(number, callFrameId, functionId)
    566 {
    567     this.number = number;
    568     this.callFrameId = callFrameId;
    569     this.functionId = functionId;
    570 }
    571 
    572 /**
    573  * @constructor
    574  * @param {string} name
    575  * @param {?WebInspector.RemoteObject} value
    576  * @param {RuntimeAgent.PropertyDescriptor=} descriptor
    577  */
    578 WebInspector.RemoteObjectProperty = function(name, value, descriptor)
    579 {
    580     this.name = name;
    581     this.enumerable = descriptor ? !!descriptor.enumerable : true;
    582     this.writable = descriptor ? !!descriptor.writable : true;
    583 
    584     if (value === null && descriptor) {
    585         if (descriptor.value)
    586             this.value = WebInspector.RemoteObject.fromPayload(descriptor.value)
    587         if (descriptor.get && descriptor.get.type !== "undefined")
    588             this.getter = WebInspector.RemoteObject.fromPayload(descriptor.get);
    589         if (descriptor.set && descriptor.set.type !== "undefined")
    590             this.setter = WebInspector.RemoteObject.fromPayload(descriptor.set);
    591     } else {
    592          this.value = value;
    593     }
    594 
    595     if (descriptor) {
    596         this.isOwn = descriptor.isOwn;
    597         this.wasThrown = !!descriptor.wasThrown;
    598     }
    599 }
    600 
    601 WebInspector.RemoteObjectProperty.prototype = {
    602     isAccessorProperty: function()
    603     {
    604         return this.getter || this.setter;
    605     }
    606 };
    607 
    608 /**
    609  * @param {string} name
    610  * @param {string} value
    611  * @return {WebInspector.RemoteObjectProperty}
    612  */
    613 WebInspector.RemoteObjectProperty.fromPrimitiveValue = function(name, value)
    614 {
    615     return new WebInspector.RemoteObjectProperty(name, WebInspector.RemoteObject.fromPrimitiveValue(value));
    616 }
    617 
    618 /**
    619  * @param {string} name
    620  * @param {WebInspector.RemoteObject} value
    621  * @return {WebInspector.RemoteObjectProperty}
    622  */
    623 WebInspector.RemoteObjectProperty.fromScopeValue = function(name, value)
    624 {
    625     var result = new WebInspector.RemoteObjectProperty(name, value);
    626     result.writable = false;
    627     return result;
    628 }
    629 
    630 // The below is a wrapper around a local object that provides an interface comaptible
    631 // with RemoteObject, to be used by the UI code (primarily ObjectPropertiesSection).
    632 // Note that only JSON-compliant objects are currently supported, as there's no provision
    633 // for traversing prototypes, extracting class names via constuctor, handling properties
    634 // or functions.
    635 
    636 /**
    637  * @constructor
    638  * @extends {WebInspector.RemoteObject}
    639  * @param {*} value
    640  */
    641 WebInspector.LocalJSONObject = function(value)
    642 {
    643     this._value = value;
    644 }
    645 
    646 WebInspector.LocalJSONObject.prototype = {
    647     /**
    648      * @return {string}
    649      */
    650     get description()
    651     {
    652         if (this._cachedDescription)
    653             return this._cachedDescription;
    654 
    655         if (this.type === "object") {
    656             switch (this.subtype) {
    657             case "array":
    658                 function formatArrayItem(property)
    659                 {
    660                     return property.value.description;
    661                 }
    662                 this._cachedDescription = this._concatenate("[", "]", formatArrayItem);
    663                 break;
    664             case "date":
    665                 this._cachedDescription = "" + this._value;
    666                 break;
    667             case "null":
    668                 this._cachedDescription = "null";
    669                 break;
    670             default:
    671                 function formatObjectItem(property)
    672                 {
    673                     return property.name + ":" + property.value.description;
    674                 }
    675                 this._cachedDescription = this._concatenate("{", "}", formatObjectItem);
    676             }
    677         } else
    678             this._cachedDescription = String(this._value);
    679 
    680         return this._cachedDescription;
    681     },
    682 
    683     /**
    684      * @param {string} prefix
    685      * @param {string} suffix
    686      * @return {string}
    687      */
    688     _concatenate: function(prefix, suffix, formatProperty)
    689     {
    690         const previewChars = 100;
    691 
    692         var buffer = prefix;
    693         var children = this._children();
    694         for (var i = 0; i < children.length; ++i) {
    695             var itemDescription = formatProperty(children[i]);
    696             if (buffer.length + itemDescription.length > previewChars) {
    697                 buffer += ",\u2026";
    698                 break;
    699             }
    700             if (i)
    701                 buffer += ", ";
    702             buffer += itemDescription;
    703         }
    704         buffer += suffix;
    705         return buffer;
    706     },
    707 
    708     /**
    709      * @return {string}
    710      */
    711     get type()
    712     {
    713         return typeof this._value;
    714     },
    715 
    716     /**
    717      * @return {string|undefined}
    718      */
    719     get subtype()
    720     {
    721         if (this._value === null)
    722             return "null";
    723 
    724         if (this._value instanceof Array)
    725             return "array";
    726 
    727         if (this._value instanceof Date)
    728             return "date";
    729 
    730         return undefined;
    731     },
    732 
    733     /**
    734      * @return {boolean}
    735      */
    736     get hasChildren()
    737     {
    738         if ((typeof this._value !== "object") || (this._value === null))
    739             return false;
    740         return !!Object.keys(/** @type {!Object} */ (this._value)).length;
    741     },
    742 
    743     /**
    744      * @param {function(Array.<WebInspector.RemoteObjectProperty>)} callback
    745      */
    746     getOwnProperties: function(callback)
    747     {
    748         callback(this._children());
    749     },
    750 
    751     /**
    752      * @param {boolean} accessorPropertiesOnly
    753      * @param {function(Array.<WebInspector.RemoteObjectProperty>)} callback
    754      */
    755     getAllProperties: function(accessorPropertiesOnly, callback)
    756     {
    757         if (accessorPropertiesOnly)
    758             callback([]);
    759         else
    760             callback(this._children());
    761     },
    762 
    763     /**
    764      * @return {Array.<WebInspector.RemoteObjectProperty>}
    765      */
    766     _children: function()
    767     {
    768         if (!this.hasChildren)
    769             return [];
    770         var value = /** @type {!Object} */ (this._value);
    771 
    772         function buildProperty(propName)
    773         {
    774             return new WebInspector.RemoteObjectProperty(propName, new WebInspector.LocalJSONObject(this._value[propName]));
    775         }
    776         if (!this._cachedChildren)
    777             this._cachedChildren = Object.keys(value).map(buildProperty.bind(this));
    778         return this._cachedChildren;
    779     },
    780 
    781     /**
    782      * @return {boolean}
    783      */
    784     isError: function()
    785     {
    786         return false;
    787     },
    788 
    789     /**
    790      * @return {number}
    791      */
    792     arrayLength: function()
    793     {
    794         return this._value instanceof Array ? this._value.length : 0;
    795     }
    796 }
    797