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 {!Array.<string>} propertyPath
    184      * @param {function(?WebInspector.RemoteObject, boolean=)} callback
    185      */
    186     getProperty: function(propertyPath, callback)
    187     {
    188         /**
    189          * @param {string} arrayStr
    190          * @this {Object}
    191          */
    192         function remoteFunction(arrayStr)
    193         {
    194             var result = this;
    195             var properties = JSON.parse(arrayStr);
    196             for (var i = 0, n = properties.length; i < n; ++i)
    197                 result = result[properties[i]];
    198             return result;
    199         }
    200 
    201         var args = [{ value: JSON.stringify(propertyPath) }];
    202         this.callFunction(remoteFunction, args, callback);
    203     },
    204 
    205     /**
    206      * @param {boolean} ownProperties
    207      * @param {boolean} accessorPropertiesOnly
    208      * @param {?function(?Array.<!WebInspector.RemoteObjectProperty>, ?Array.<!WebInspector.RemoteObjectProperty>)} callback
    209      */
    210     doGetProperties: function(ownProperties, accessorPropertiesOnly, callback)
    211     {
    212         if (!this._objectId) {
    213             callback(null, null);
    214             return;
    215         }
    216 
    217         /**
    218          * @param {?Protocol.Error} error
    219          * @param {!Array.<!RuntimeAgent.PropertyDescriptor>} properties
    220          * @param {!Array.<!RuntimeAgent.InternalPropertyDescriptor>=} internalProperties
    221          */
    222         function remoteObjectBinder(error, properties, internalProperties)
    223         {
    224             if (error) {
    225                 callback(null, null);
    226                 return;
    227             }
    228             var result = [];
    229             for (var i = 0; properties && i < properties.length; ++i) {
    230                 var property = properties[i];
    231                 result.push(new WebInspector.RemoteObjectProperty(property.name, null, property));
    232             }
    233             var internalPropertiesResult = null;
    234             if (internalProperties) {
    235                 internalPropertiesResult = [];
    236                 for (var i = 0; i < internalProperties.length; i++) {
    237                     var property = internalProperties[i];
    238                     if (!property.value)
    239                         continue;
    240                     internalPropertiesResult.push(new WebInspector.RemoteObjectProperty(property.name, WebInspector.RemoteObject.fromPayload(property.value)));
    241                 }
    242             }
    243             callback(result, internalPropertiesResult);
    244         }
    245         RuntimeAgent.getProperties(this._objectId, ownProperties, accessorPropertiesOnly, remoteObjectBinder);
    246     },
    247 
    248     /**
    249      * @param {string} name
    250      * @param {string} value
    251      * @param {function(string=)} callback
    252      */
    253     setPropertyValue: function(name, value, callback)
    254     {
    255         if (!this._objectId) {
    256             callback("Can't set a property of non-object.");
    257             return;
    258         }
    259 
    260         RuntimeAgent.evaluate.invoke({expression:value, doNotPauseOnExceptionsAndMuteConsole:true}, evaluatedCallback.bind(this));
    261 
    262         /**
    263          * @param {?Protocol.Error} error
    264          * @param {!RuntimeAgent.RemoteObject} result
    265          * @param {boolean=} wasThrown
    266          * @this {WebInspector.RemoteObject}
    267          */
    268         function evaluatedCallback(error, result, wasThrown)
    269         {
    270             if (error || wasThrown) {
    271                 callback(error || result.description);
    272                 return;
    273             }
    274 
    275             this.doSetObjectPropertyValue(result, name, callback);
    276 
    277             if (result.objectId)
    278                 RuntimeAgent.releaseObject(result.objectId);
    279         }
    280     },
    281 
    282     /**
    283      * @param {!RuntimeAgent.RemoteObject} result
    284      * @param {string} name
    285      * @param {function(string=)} callback
    286      */
    287     doSetObjectPropertyValue: function(result, name, callback)
    288     {
    289         // This assignment may be for a regular (data) property, and for an acccessor property (with getter/setter).
    290         // Note the sensitive matter about accessor property: the property may be physically defined in some proto object,
    291         // but logically it is bound to the object in question. JavaScript passes this object to getters/setters, not the object
    292         // where property was defined; so do we.
    293         var setPropertyValueFunction = "function(a, b) { this[a] = b; }";
    294 
    295         // Special case for NaN, Infinity, -Infinity, -0.
    296         if (result.type === "number" && String(result.value) !== result.description)
    297             setPropertyValueFunction = "function(a) { this[a] = " + result.description + "; }";
    298 
    299         delete result.description; // Optimize on traffic.
    300         RuntimeAgent.callFunctionOn(this._objectId, setPropertyValueFunction, [{ value:name }, result], true, undefined, undefined, propertySetCallback.bind(this));
    301 
    302         /**
    303          * @param {?Protocol.Error} error
    304          * @param {!RuntimeAgent.RemoteObject} result
    305          * @param {boolean=} wasThrown
    306          */
    307         function propertySetCallback(error, result, wasThrown)
    308         {
    309             if (error || wasThrown) {
    310                 callback(error || result.description);
    311                 return;
    312             }
    313             callback();
    314         }
    315     },
    316 
    317     /**
    318      * @param {function(?DOMAgent.NodeId)} callback
    319      */
    320     pushNodeToFrontend: function(callback)
    321     {
    322         if (this._objectId)
    323             WebInspector.domAgent.pushNodeToFrontend(this._objectId, callback);
    324         else
    325             callback(0);
    326     },
    327 
    328     highlightAsDOMNode: function()
    329     {
    330         WebInspector.domAgent.highlightDOMNode(undefined, undefined, this._objectId);
    331     },
    332 
    333     hideDOMNodeHighlight: function()
    334     {
    335         WebInspector.domAgent.hideDOMNodeHighlight();
    336     },
    337 
    338     /**
    339      * @param {function(this:Object, ...)} functionDeclaration
    340      * @param {!Array.<!RuntimeAgent.CallArgument>=} args
    341      * @param {function(?WebInspector.RemoteObject, boolean=)=} callback
    342      */
    343     callFunction: function(functionDeclaration, args, callback)
    344     {
    345         /**
    346          * @param {?Protocol.Error} error
    347          * @param {!RuntimeAgent.RemoteObject} result
    348          * @param {boolean=} wasThrown
    349          */
    350         function mycallback(error, result, wasThrown)
    351         {
    352             if (!callback)
    353                 return;
    354             if (error)
    355                 callback(null, false);
    356             else
    357                 callback(WebInspector.RemoteObject.fromPayload(result), wasThrown);
    358         }
    359 
    360         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, undefined, undefined, mycallback);
    361     },
    362 
    363     /**
    364      * @param {function(this:Object)} functionDeclaration
    365      * @param {!Array.<!RuntimeAgent.CallArgument>|undefined} args
    366      * @param {function(*)} callback
    367      */
    368     callFunctionJSON: function(functionDeclaration, args, callback)
    369     {
    370         /**
    371          * @param {?Protocol.Error} error
    372          * @param {!RuntimeAgent.RemoteObject} result
    373          * @param {boolean=} wasThrown
    374          */
    375         function mycallback(error, result, wasThrown)
    376         {
    377             callback((error || wasThrown) ? null : result.value);
    378         }
    379 
    380         RuntimeAgent.callFunctionOn(this._objectId, functionDeclaration.toString(), args, true, true, false, mycallback);
    381     },
    382 
    383     release: function()
    384     {
    385         if (!this._objectId)
    386             return;
    387         RuntimeAgent.releaseObject(this._objectId);
    388     },
    389 
    390     /**
    391      * @return {number}
    392      */
    393     arrayLength: function()
    394     {
    395         if (this.subtype !== "array")
    396             return 0;
    397 
    398         var matches = this._description.match(/\[([0-9]+)\]/);
    399         if (!matches)
    400             return 0;
    401         return parseInt(matches[1], 10);
    402     }
    403 };
    404 
    405 
    406 /**
    407  * @param {!WebInspector.RemoteObject} object
    408  * @param {boolean} flattenProtoChain
    409  * @param {function(?Array.<!WebInspector.RemoteObjectProperty>, ?Array.<!WebInspector.RemoteObjectProperty>)} callback
    410  */
    411 WebInspector.RemoteObject.loadFromObject = function(object, flattenProtoChain, callback)
    412 {
    413     if (flattenProtoChain)
    414        object.getAllProperties(false, callback);
    415     else
    416         WebInspector.RemoteObject.loadFromObjectPerProto(object, callback);
    417 };
    418 
    419 /**
    420  * @param {!WebInspector.RemoteObject} object
    421  * @param {function(?Array.<!WebInspector.RemoteObjectProperty>, ?Array.<!WebInspector.RemoteObjectProperty>)} callback
    422  */
    423 WebInspector.RemoteObject.loadFromObjectPerProto = function(object, callback)
    424 {
    425     // Combines 2 asynch calls. Doesn't rely on call-back orders (some calls may be loop-back).
    426     var savedOwnProperties;
    427     var savedAccessorProperties;
    428     var savedInternalProperties;
    429     var resultCounter = 2;
    430 
    431     function processCallback()
    432     {
    433         if (--resultCounter)
    434             return;
    435         if (savedOwnProperties && savedAccessorProperties) {
    436             var combinedList = savedAccessorProperties.slice(0);
    437             for (var i = 0; i < savedOwnProperties.length; i++) {
    438                 var property = savedOwnProperties[i];
    439                 if (!property.isAccessorProperty())
    440                     combinedList.push(property);
    441             }
    442             return callback(combinedList, savedInternalProperties ? savedInternalProperties : null);
    443         } else {
    444             callback(null, null);
    445         }
    446     }
    447 
    448     /**
    449      * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
    450      * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
    451      */
    452     function allAccessorPropertiesCallback(properties, internalProperties)
    453     {
    454         savedAccessorProperties = properties;
    455         processCallback();
    456     }
    457 
    458     /**
    459      * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
    460      * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
    461      */
    462     function ownPropertiesCallback(properties, internalProperties)
    463     {
    464         savedOwnProperties = properties;
    465         savedInternalProperties = internalProperties;
    466         processCallback();
    467     }
    468 
    469     object.getAllProperties(true, allAccessorPropertiesCallback);
    470     object.getOwnProperties(ownPropertiesCallback);
    471 };
    472 
    473 
    474 /**
    475  * @constructor
    476  * @extends {WebInspector.RemoteObject}
    477  * @param {string|undefined} objectId
    478  * @param {!WebInspector.ScopeRef} scopeRef
    479  * @param {string} type
    480  * @param {string|undefined} subtype
    481  * @param {*} value
    482  * @param {string=} description
    483  * @param {!RuntimeAgent.ObjectPreview=} preview
    484  */
    485 WebInspector.ScopeRemoteObject = function(objectId, scopeRef, type, subtype, value, description, preview)
    486 {
    487     WebInspector.RemoteObject.call(this, objectId, type, subtype, value, description, preview);
    488     this._scopeRef = scopeRef;
    489     this._savedScopeProperties = undefined;
    490 };
    491 
    492 /**
    493  * @param {!RuntimeAgent.RemoteObject} payload
    494  * @param {!WebInspector.ScopeRef=} scopeRef
    495  * @return {!WebInspector.RemoteObject}
    496  */
    497 WebInspector.ScopeRemoteObject.fromPayload = function(payload, scopeRef)
    498 {
    499     if (scopeRef)
    500         return new WebInspector.ScopeRemoteObject(payload.objectId, scopeRef, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
    501     else
    502         return new WebInspector.RemoteObject(payload.objectId, payload.type, payload.subtype, payload.value, payload.description, payload.preview);
    503 }
    504 
    505 WebInspector.ScopeRemoteObject.prototype = {
    506     /**
    507      * @param {boolean} ownProperties
    508      * @param {boolean} accessorPropertiesOnly
    509      * @param {function(?Array.<!WebInspector.RemoteObjectProperty>, ?Array.<!WebInspector.RemoteObjectProperty>)} callback
    510      * @override
    511      */
    512     doGetProperties: function(ownProperties, accessorPropertiesOnly, callback)
    513     {
    514         if (accessorPropertiesOnly) {
    515             callback([], []);
    516             return;
    517         }
    518         if (this._savedScopeProperties) {
    519             // No need to reload scope variables, as the remote object never
    520             // changes its properties. If variable is updated, the properties
    521             // array is patched locally.
    522             callback(this._savedScopeProperties.slice(), []);
    523             return;
    524         }
    525 
    526         /**
    527          * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties
    528          * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties
    529          * @this {WebInspector.ScopeRemoteObject}
    530          */
    531         function wrappedCallback(properties, internalProperties)
    532         {
    533             if (this._scopeRef && properties instanceof Array)
    534                 this._savedScopeProperties = properties.slice();
    535             callback(properties, internalProperties);
    536         }
    537 
    538         WebInspector.RemoteObject.prototype.doGetProperties.call(this, ownProperties, accessorPropertiesOnly, wrappedCallback.bind(this));
    539     },
    540 
    541     /**
    542      * @override
    543      * @param {!RuntimeAgent.RemoteObject} result
    544      * @param {string} name
    545      * @param {function(string=)} callback
    546      */
    547     doSetObjectPropertyValue: function(result, name, callback)
    548     {
    549         var newValue;
    550 
    551         switch (result.type) {
    552             case "undefined":
    553                 newValue = {};
    554                 break;
    555             case "object":
    556             case "function":
    557                 newValue = { objectId: result.objectId };
    558                 break;
    559             default:
    560                 newValue = { value: result.value };
    561         }
    562 
    563         DebuggerAgent.setVariableValue(this._scopeRef.number, name, newValue, this._scopeRef.callFrameId, this._scopeRef.functionId, setVariableValueCallback.bind(this));
    564 
    565         /**
    566          * @param {?Protocol.Error} error
    567          * @this {WebInspector.ScopeRemoteObject}
    568          */
    569         function setVariableValueCallback(error)
    570         {
    571             if (error) {
    572                 callback(error);
    573                 return;
    574             }
    575             if (this._savedScopeProperties) {
    576                 for (var i = 0; i < this._savedScopeProperties.length; i++) {
    577                     if (this._savedScopeProperties[i].name === name)
    578                         this._savedScopeProperties[i].value = WebInspector.RemoteObject.fromPayload(result);
    579                 }
    580             }
    581             callback();
    582         }
    583     },
    584 
    585     __proto__: WebInspector.RemoteObject.prototype
    586 };
    587 
    588 /**
    589  * Either callFrameId or functionId (exactly one) must be defined.
    590  * @constructor
    591  * @param {number} number
    592  * @param {string=} callFrameId
    593  * @param {string=} functionId
    594  */
    595 WebInspector.ScopeRef = function(number, callFrameId, functionId)
    596 {
    597     this.number = number;
    598     this.callFrameId = callFrameId;
    599     this.functionId = functionId;
    600 }
    601 
    602 /**
    603  * @constructor
    604  * @param {string} name
    605  * @param {?WebInspector.RemoteObject} value
    606  * @param {!RuntimeAgent.PropertyDescriptor=} descriptor
    607  */
    608 WebInspector.RemoteObjectProperty = function(name, value, descriptor)
    609 {
    610     this.name = name;
    611     this.enumerable = descriptor ? !!descriptor.enumerable : true;
    612     this.writable = descriptor ? !!descriptor.writable : true;
    613 
    614     if (value === null && descriptor) {
    615         if (descriptor.value)
    616             this.value = WebInspector.RemoteObject.fromPayload(descriptor.value)
    617         if (descriptor.get && descriptor.get.type !== "undefined")
    618             this.getter = WebInspector.RemoteObject.fromPayload(descriptor.get);
    619         if (descriptor.set && descriptor.set.type !== "undefined")
    620             this.setter = WebInspector.RemoteObject.fromPayload(descriptor.set);
    621     } else {
    622          this.value = value;
    623     }
    624 
    625     if (descriptor) {
    626         this.isOwn = descriptor.isOwn;
    627         this.wasThrown = !!descriptor.wasThrown;
    628     }
    629 }
    630 
    631 WebInspector.RemoteObjectProperty.prototype = {
    632     isAccessorProperty: function()
    633     {
    634         return this.getter || this.setter;
    635     }
    636 };
    637 
    638 /**
    639  * @param {string} name
    640  * @param {string} value
    641  * @return {!WebInspector.RemoteObjectProperty}
    642  */
    643 WebInspector.RemoteObjectProperty.fromPrimitiveValue = function(name, value)
    644 {
    645     return new WebInspector.RemoteObjectProperty(name, WebInspector.RemoteObject.fromPrimitiveValue(value));
    646 }
    647 
    648 /**
    649  * @param {string} name
    650  * @param {!WebInspector.RemoteObject} value
    651  * @return {!WebInspector.RemoteObjectProperty}
    652  */
    653 WebInspector.RemoteObjectProperty.fromScopeValue = function(name, value)
    654 {
    655     var result = new WebInspector.RemoteObjectProperty(name, value);
    656     result.writable = false;
    657     return result;
    658 }
    659 
    660 // The below is a wrapper around a local object that provides an interface comaptible
    661 // with RemoteObject, to be used by the UI code (primarily ObjectPropertiesSection).
    662 // Note that only JSON-compliant objects are currently supported, as there's no provision
    663 // for traversing prototypes, extracting class names via constuctor, handling properties
    664 // or functions.
    665 
    666 /**
    667  * @constructor
    668  * @extends {WebInspector.RemoteObject}
    669  * @param {*} value
    670  */
    671 WebInspector.LocalJSONObject = function(value)
    672 {
    673     this._value = value;
    674 }
    675 
    676 WebInspector.LocalJSONObject.prototype = {
    677     /**
    678      * @return {string}
    679      */
    680     get description()
    681     {
    682         if (this._cachedDescription)
    683             return this._cachedDescription;
    684 
    685         /**
    686          * @param {!WebInspector.RemoteObjectProperty} property
    687          */
    688         function formatArrayItem(property)
    689         {
    690             return property.value.description;
    691         }
    692 
    693         /**
    694          * @param {!WebInspector.RemoteObjectProperty} property
    695          */
    696         function formatObjectItem(property)
    697         {
    698             return property.name + ":" + property.value.description;
    699         }
    700 
    701         if (this.type === "object") {
    702             switch (this.subtype) {
    703             case "array":
    704                 this._cachedDescription = this._concatenate("[", "]", formatArrayItem);
    705                 break;
    706             case "date":
    707                 this._cachedDescription = "" + this._value;
    708                 break;
    709             case "null":
    710                 this._cachedDescription = "null";
    711                 break;
    712             default:
    713                 this._cachedDescription = this._concatenate("{", "}", formatObjectItem);
    714             }
    715         } else
    716             this._cachedDescription = String(this._value);
    717 
    718         return this._cachedDescription;
    719     },
    720 
    721     /**
    722      * @param {string} prefix
    723      * @param {string} suffix
    724      * @param {function (!WebInspector.RemoteObjectProperty)} formatProperty
    725      * @return {string}
    726      */
    727     _concatenate: function(prefix, suffix, formatProperty)
    728     {
    729         const previewChars = 100;
    730 
    731         var buffer = prefix;
    732         var children = this._children();
    733         for (var i = 0; i < children.length; ++i) {
    734             var itemDescription = formatProperty(children[i]);
    735             if (buffer.length + itemDescription.length > previewChars) {
    736                 buffer += ",\u2026";
    737                 break;
    738             }
    739             if (i)
    740                 buffer += ", ";
    741             buffer += itemDescription;
    742         }
    743         buffer += suffix;
    744         return buffer;
    745     },
    746 
    747     /**
    748      * @return {string}
    749      */
    750     get type()
    751     {
    752         return typeof this._value;
    753     },
    754 
    755     /**
    756      * @return {string|undefined}
    757      */
    758     get subtype()
    759     {
    760         if (this._value === null)
    761             return "null";
    762 
    763         if (this._value instanceof Array)
    764             return "array";
    765 
    766         if (this._value instanceof Date)
    767             return "date";
    768 
    769         return undefined;
    770     },
    771 
    772     /**
    773      * @return {boolean}
    774      */
    775     get hasChildren()
    776     {
    777         if ((typeof this._value !== "object") || (this._value === null))
    778             return false;
    779         return !!Object.keys(/** @type {!Object} */ (this._value)).length;
    780     },
    781 
    782     /**
    783      * @param {function(!Array.<!WebInspector.RemoteObjectProperty>)} callback
    784      */
    785     getOwnProperties: function(callback)
    786     {
    787         callback(this._children());
    788     },
    789 
    790     /**
    791      * @param {boolean} accessorPropertiesOnly
    792      * @param {function(!Array.<!WebInspector.RemoteObjectProperty>)} callback
    793      */
    794     getAllProperties: function(accessorPropertiesOnly, callback)
    795     {
    796         if (accessorPropertiesOnly)
    797             callback([]);
    798         else
    799             callback(this._children());
    800     },
    801 
    802     /**
    803      * @return {!Array.<!WebInspector.RemoteObjectProperty>}
    804      */
    805     _children: function()
    806     {
    807         if (!this.hasChildren)
    808             return [];
    809         var value = /** @type {!Object} */ (this._value);
    810 
    811         /**
    812          * @param {string} propName
    813          * @this {WebInspector.LocalJSONObject}
    814          */
    815         function buildProperty(propName)
    816         {
    817             return new WebInspector.RemoteObjectProperty(propName, new WebInspector.LocalJSONObject(this._value[propName]));
    818         }
    819         if (!this._cachedChildren)
    820             this._cachedChildren = Object.keys(value).map(buildProperty.bind(this));
    821         return this._cachedChildren;
    822     },
    823 
    824     /**
    825      * @return {boolean}
    826      */
    827     isError: function()
    828     {
    829         return false;
    830     },
    831 
    832     /**
    833      * @return {number}
    834      */
    835     arrayLength: function()
    836     {
    837         return this._value instanceof Array ? this._value.length : 0;
    838     },
    839 
    840     /**
    841      * @param {function(this:Object, ...)} functionDeclaration
    842      * @param {!Array.<!RuntimeAgent.CallArgument>=} args
    843      * @param {function(?WebInspector.RemoteObject, boolean=)=} callback
    844      */
    845     callFunction: function(functionDeclaration, args, callback)
    846     {
    847         var target = /** @type {?Object} */ (this._value);
    848         var rawArgs = args ? args.map(function(arg) {return arg.value;}) : [];
    849 
    850         var result;
    851         var wasThrown = false;
    852         try {
    853             result = functionDeclaration.apply(target, rawArgs);
    854         } catch (e) {
    855             wasThrown = true;
    856         }
    857 
    858         if (!callback)
    859             return;
    860         callback(WebInspector.RemoteObject.fromLocalObject(result), wasThrown);
    861     },
    862 
    863     /**
    864      * @param {function(this:Object)} functionDeclaration
    865      * @param {!Array.<!RuntimeAgent.CallArgument>|undefined} args
    866      * @param {function(*)} callback
    867      */
    868     callFunctionJSON: function(functionDeclaration, args, callback)
    869     {
    870         var target = /** @type {?Object} */ (this._value);
    871         var rawArgs = args ? args.map(function(arg) {return arg.value;}) : [];
    872 
    873         var result;
    874         try {
    875             result = functionDeclaration.apply(target, rawArgs);
    876         } catch (e) {
    877             result = null;
    878         }
    879 
    880         callback(result);
    881     }
    882 }
    883