Home | History | Annotate | Download | only in inspector
      1 /*
      2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
      3  * Copyright (C) 2013 Google Inc. All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  *
      9  * 1.  Redistributions of source code must retain the above copyright
     10  *     notice, this list of conditions and the following disclaimer.
     11  * 2.  Redistributions in binary form must reproduce the above copyright
     12  *     notice, this list of conditions and the following disclaimer in the
     13  *     documentation and/or other materials provided with the distribution.
     14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     15  *     its contributors may be used to endorse or promote products derived
     16  *     from this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 "use strict";
     31 
     32 /**
     33  * @param {!InjectedScriptHostClass} InjectedScriptHost
     34  * @param {!Window|!WorkerGlobalScope} inspectedGlobalObject
     35  * @param {number} injectedScriptId
     36  * @suppress {uselessCode}
     37  */
     38 (function (InjectedScriptHost, inspectedGlobalObject, injectedScriptId) {
     39 
     40 /**
     41  * Protect against Object overwritten by the user code.
     42  * @suppress {duplicate}
     43  */
     44 var Object = /** @type {function(new:Object, *=)} */ ({}.constructor);
     45 
     46 /**
     47  * @param {!Array.<T>} array
     48  * @param {...} var_args
     49  * @template T
     50  */
     51 function push(array, var_args)
     52 {
     53     for (var i = 1; i < arguments.length; ++i)
     54         array[array.length] = arguments[i];
     55 }
     56 
     57 /**
     58  * @param {*} obj
     59  * @return {string}
     60  * @suppress {uselessCode}
     61  */
     62 function toString(obj)
     63 {
     64     // We don't use String(obj) because String could be overridden.
     65     // Also the ("" + obj) expression may throw.
     66     try {
     67         return "" + obj;
     68     } catch (e) {
     69         var name = InjectedScriptHost.internalConstructorName(obj) || InjectedScriptHost.subtype(obj) || (typeof obj);
     70         return "#<" + name + ">";
     71     }
     72 }
     73 
     74 /**
     75  * @param {*} obj
     76  * @return {string}
     77  */
     78 function toStringDescription(obj)
     79 {
     80     if (typeof obj === "number" && obj === 0 && 1 / obj < 0)
     81         return "-0"; // Negative zero.
     82     return toString(obj);
     83 }
     84 
     85 /**
     86  * @param {T} obj
     87  * @return {T}
     88  * @template T
     89  */
     90 function nullifyObjectProto(obj)
     91 {
     92     if (obj && typeof obj === "object")
     93         obj.__proto__ = null;
     94     return obj;
     95 }
     96 
     97 /**
     98  * @param {number|string} obj
     99  * @return {boolean}
    100  */
    101 function isUInt32(obj)
    102 {
    103     if (typeof obj === "number")
    104         return obj >>> 0 === obj && (obj > 0 || 1 / obj > 0);
    105     return "" + (obj >>> 0) === obj;
    106 }
    107 
    108 /**
    109  * FireBug's array detection.
    110  * @param {*} obj
    111  * @return {boolean}
    112  */
    113 function isArrayLike(obj)
    114 {
    115     if (typeof obj !== "object")
    116         return false;
    117     try {
    118         if (typeof obj.splice === "function") {
    119             if (!InjectedScriptHost.objectHasOwnProperty(/** @type {!Object} */ (obj), "length"))
    120                 return false;
    121             var len = obj.length;
    122             return typeof len === "number" && isUInt32(len);
    123         }
    124     } catch (e) {
    125     }
    126     return false;
    127 }
    128 
    129 /**
    130  * @param {number} a
    131  * @param {number} b
    132  * @return {number}
    133  */
    134 function max(a, b)
    135 {
    136     return a > b ? a : b;
    137 }
    138 
    139 /**
    140  * FIXME: Remove once ES6 is supported natively by JS compiler.
    141  * @param {*} obj
    142  * @return {boolean}
    143  */
    144 function isSymbol(obj)
    145 {
    146     var type = typeof obj;
    147     return (type === "symbol");
    148 }
    149 
    150 /**
    151  * DOM Attributes which have observable side effect on getter, in the form of
    152  *   {interfaceName1: {attributeName1: true,
    153  *                     attributeName2: true,
    154  *                     ...},
    155  *    interfaceName2: {...},
    156  *    ...}
    157  * @type {!Object<string, !Object<string, boolean>>}
    158  * @const
    159  */
    160 var domAttributesWithObservableSideEffectOnGet = {
    161     Request: { body: true, __proto__: null },
    162     Response: { body: true, __proto__: null },
    163     __proto__: null
    164 }
    165 
    166 /**
    167  * @param {!Object} object
    168  * @param {string} attribute
    169  * @return {boolean}
    170  */
    171 function doesAttributeHaveObservableSideEffectOnGet(object, attribute)
    172 {
    173     for (var interfaceName in domAttributesWithObservableSideEffectOnGet) {
    174         var interfaceFunction = inspectedGlobalObject[interfaceName];
    175         // Call to instanceOf looks safe after typeof check.
    176         var isInstance = typeof interfaceFunction === "function" && /* suppressBlacklist */ object instanceof interfaceFunction;
    177         if (isInstance)
    178             return attribute in domAttributesWithObservableSideEffectOnGet[interfaceName];
    179     }
    180     return false;
    181 }
    182 
    183 /**
    184  * @constructor
    185  */
    186 var InjectedScript = function()
    187 {
    188 }
    189 InjectedScriptHost.nullifyPrototype(InjectedScript);
    190 
    191 /**
    192  * @type {!Object.<string, boolean>}
    193  * @const
    194  */
    195 InjectedScript.primitiveTypes = {
    196     "undefined": true,
    197     "boolean": true,
    198     "number": true,
    199     "string": true,
    200     __proto__: null
    201 }
    202 
    203 /**
    204  * @type {!Object<string, string>}
    205  * @const
    206  */
    207 InjectedScript.closureTypes = { __proto__: null };
    208 InjectedScript.closureTypes["local"] = "Local";
    209 InjectedScript.closureTypes["closure"] = "Closure";
    210 InjectedScript.closureTypes["catch"] = "Catch";
    211 InjectedScript.closureTypes["block"] = "Block";
    212 InjectedScript.closureTypes["script"] = "Script";
    213 InjectedScript.closureTypes["with"] = "With Block";
    214 InjectedScript.closureTypes["global"] = "Global";
    215 InjectedScript.closureTypes["eval"] = "Eval";
    216 InjectedScript.closureTypes["module"] = "Module";
    217 
    218 InjectedScript.prototype = {
    219     /**
    220      * @param {*} object
    221      * @return {boolean}
    222      */
    223     isPrimitiveValue: function(object)
    224     {
    225         // FIXME(33716): typeof document.all is always 'undefined'.
    226         return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
    227     },
    228 
    229     /**
    230      * @param {*} object
    231      * @return {boolean}
    232      */
    233     _shouldPassByValue: function(object)
    234     {
    235         return typeof object === "object" && InjectedScriptHost.subtype(object) === "internal#location";
    236     },
    237 
    238     /**
    239      * @param {*} object
    240      * @param {string} groupName
    241      * @param {boolean} forceValueType
    242      * @param {boolean} generatePreview
    243      * @return {!RuntimeAgent.RemoteObject}
    244      */
    245     wrapObject: function(object, groupName, forceValueType, generatePreview)
    246     {
    247         return this._wrapObject(object, groupName, forceValueType, generatePreview);
    248     },
    249 
    250     /**
    251      * @param {!Array<!Object>} array
    252      * @param {string} property
    253      * @param {string} groupName
    254      * @param {boolean} forceValueType
    255      * @param {boolean} generatePreview
    256      */
    257     wrapPropertyInArray: function(array, property, groupName, forceValueType, generatePreview)
    258     {
    259         for (var i = 0; i < array.length; ++i) {
    260             if (typeof array[i] === "object" && property in array[i])
    261                 array[i][property] = this.wrapObject(array[i][property], groupName, forceValueType, generatePreview);
    262         }
    263     },
    264 
    265     /**
    266      * @param {!Object} table
    267      * @param {!Array.<string>|string|boolean} columns
    268      * @return {!RuntimeAgent.RemoteObject}
    269      */
    270     wrapTable: function(table, columns)
    271     {
    272         var columnNames = null;
    273         if (typeof columns === "string")
    274             columns = [columns];
    275         if (InjectedScriptHost.subtype(columns) === "array") {
    276             columnNames = [];
    277             for (var i = 0; i < columns.length; ++i)
    278                 columnNames[i] = toString(columns[i]);
    279         }
    280         return this._wrapObject(table, "console", false, true, columnNames, true);
    281     },
    282 
    283     /**
    284      * This method cannot throw.
    285      * @param {*} object
    286      * @param {string=} objectGroupName
    287      * @param {boolean=} forceValueType
    288      * @param {boolean=} generatePreview
    289      * @param {?Array.<string>=} columnNames
    290      * @param {boolean=} isTable
    291      * @param {boolean=} doNotBind
    292      * @param {*=} customObjectConfig
    293      * @return {!RuntimeAgent.RemoteObject}
    294      * @suppress {checkTypes}
    295      */
    296     _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames, isTable, doNotBind, customObjectConfig)
    297     {
    298         try {
    299             return new InjectedScript.RemoteObject(object, objectGroupName, doNotBind, forceValueType, generatePreview, columnNames, isTable, undefined, customObjectConfig);
    300         } catch (e) {
    301             try {
    302                 var description = injectedScript._describe(e);
    303             } catch (ex) {
    304                 var description = "<failed to convert exception to string>";
    305             }
    306             return new InjectedScript.RemoteObject(description);
    307         }
    308     },
    309 
    310     /**
    311      * @param {!Object|symbol} object
    312      * @param {string=} objectGroupName
    313      * @return {string}
    314      */
    315     _bind: function(object, objectGroupName)
    316     {
    317         var id = InjectedScriptHost.bind(object, objectGroupName || "");
    318         return "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
    319     },
    320 
    321     /**
    322      * @param {!Object} object
    323      * @param {string} objectGroupName
    324      * @param {boolean} ownProperties
    325      * @param {boolean} accessorPropertiesOnly
    326      * @param {boolean} generatePreview
    327      * @return {!Array<!RuntimeAgent.PropertyDescriptor>|boolean}
    328      */
    329     getProperties: function(object, objectGroupName, ownProperties, accessorPropertiesOnly, generatePreview)
    330     {
    331         var subtype = this._subtype(object);
    332         if (subtype === "internal#scope") {
    333             // Internally, scope contains object with scope variables and additional information like type,
    334             // we use additional information for preview and would like to report variables as scope
    335             // properties.
    336             object = object.object;
    337         }
    338 
    339         var descriptors = [];
    340         var iter = this._propertyDescriptors(object, ownProperties, accessorPropertiesOnly, undefined);
    341         // Go over properties, wrap object values.
    342         for (var descriptor of iter) {
    343             if (subtype === "internal#scopeList" && descriptor.name === "length")
    344                 continue;
    345             if ("get" in descriptor)
    346                 descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
    347             if ("set" in descriptor)
    348                 descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
    349             if ("value" in descriptor)
    350                 descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
    351             if (!("configurable" in descriptor))
    352                 descriptor.configurable = false;
    353             if (!("enumerable" in descriptor))
    354                 descriptor.enumerable = false;
    355             if ("symbol" in descriptor)
    356                 descriptor.symbol = this._wrapObject(descriptor.symbol, objectGroupName);
    357             push(descriptors, descriptor);
    358         }
    359         return descriptors;
    360     },
    361 
    362     /**
    363      * @param {!Object} object
    364      * @return {?Object}
    365      */
    366     _objectPrototype: function(object)
    367     {
    368         if (InjectedScriptHost.subtype(object) === "proxy")
    369             return null;
    370         try {
    371             return Object.getPrototypeOf(object);
    372         } catch (e) {
    373             return null;
    374         }
    375     },
    376 
    377     /**
    378      * @param {!Object} object
    379      * @param {boolean=} ownProperties
    380      * @param {boolean=} accessorPropertiesOnly
    381      * @param {?Array.<string>=} propertyNamesOnly
    382      */
    383     _propertyDescriptors: function*(object, ownProperties, accessorPropertiesOnly, propertyNamesOnly)
    384     {
    385         var propertyProcessed = { __proto__: null };
    386 
    387         /**
    388          * @param {?Object} o
    389          * @param {!Iterable<string|symbol|number>|!Array<string|number|symbol>} properties
    390          */
    391         function* process(o, properties)
    392         {
    393             for (var property of properties) {
    394                 var name;
    395                 if (isSymbol(property))
    396                     name = /** @type {string} */ (injectedScript._describe(property));
    397                 else
    398                     name = typeof property === "number" ? ("" + property) : /** @type {string} */(property);
    399 
    400                 if (propertyProcessed[property])
    401                     continue;
    402 
    403                 try {
    404                     propertyProcessed[property] = true;
    405                     var descriptor = nullifyObjectProto(Object.getOwnPropertyDescriptor(o, property));
    406                     if (descriptor) {
    407                         if (accessorPropertiesOnly && !("get" in descriptor || "set" in descriptor))
    408                             continue;
    409                         if ("get" in descriptor && "set" in descriptor && name != "__proto__" && InjectedScriptHost.formatAccessorsAsProperties(object, descriptor.get) && !doesAttributeHaveObservableSideEffectOnGet(object, name)) {
    410                             descriptor.value = object[property];
    411                             descriptor.isOwn = true;
    412                             delete descriptor.get;
    413                             delete descriptor.set;
    414                         }
    415                     } else {
    416                         // Not all bindings provide proper descriptors. Fall back to the writable, configurable property.
    417                         if (accessorPropertiesOnly)
    418                             continue;
    419                         try {
    420                             descriptor = { name: name, value: o[property], writable: false, configurable: false, enumerable: false, __proto__: null };
    421                             if (o === object)
    422                                 descriptor.isOwn = true;
    423                             yield descriptor;
    424                         } catch (e) {
    425                             // Silent catch.
    426                         }
    427                         continue;
    428                     }
    429                 } catch (e) {
    430                     if (accessorPropertiesOnly)
    431                         continue;
    432                     var descriptor = { __proto__: null };
    433                     descriptor.value = e;
    434                     descriptor.wasThrown = true;
    435                 }
    436 
    437                 descriptor.name = name;
    438                 if (o === object)
    439                     descriptor.isOwn = true;
    440                 if (isSymbol(property))
    441                     descriptor.symbol = property;
    442                 yield descriptor;
    443             }
    444         }
    445 
    446         if (propertyNamesOnly) {
    447             for (var i = 0; i < propertyNamesOnly.length; ++i) {
    448                 var name = propertyNamesOnly[i];
    449                 for (var o = object; this._isDefined(o); o = this._objectPrototype(o)) {
    450                     if (InjectedScriptHost.objectHasOwnProperty(o, name)) {
    451                         for (var descriptor of process(o, [name]))
    452                             yield descriptor;
    453                         break;
    454                     }
    455                     if (ownProperties)
    456                         break;
    457                 }
    458             }
    459             return;
    460         }
    461 
    462         /**
    463          * @param {number} length
    464          */
    465         function* arrayIndexNames(length)
    466         {
    467             for (var i = 0; i < length; ++i)
    468                 yield "" + i;
    469         }
    470 
    471         var skipGetOwnPropertyNames;
    472         try {
    473             skipGetOwnPropertyNames = InjectedScriptHost.subtype(object) === "typedarray" && object.length > 500000;
    474         } catch (e) {
    475         }
    476 
    477         for (var o = object; this._isDefined(o); o = this._objectPrototype(o)) {
    478             if (InjectedScriptHost.subtype(o) === "proxy")
    479                 continue;
    480             if (skipGetOwnPropertyNames && o === object) {
    481                 // Avoid OOM crashes from getting all own property names of a large TypedArray.
    482                 for (var descriptor of process(o, arrayIndexNames(o.length)))
    483                     yield descriptor;
    484             } else {
    485                 // First call Object.keys() to enforce ordering of the property descriptors.
    486                 for (var descriptor of process(o, Object.keys(/** @type {!Object} */ (o))))
    487                     yield descriptor;
    488                 for (var descriptor of process(o, Object.getOwnPropertyNames(/** @type {!Object} */ (o))))
    489                     yield descriptor;
    490             }
    491             if (Object.getOwnPropertySymbols) {
    492                 for (var descriptor of process(o, Object.getOwnPropertySymbols(/** @type {!Object} */ (o))))
    493                     yield descriptor;
    494             }
    495             if (ownProperties) {
    496                 var proto = this._objectPrototype(o);
    497                 if (proto && !accessorPropertiesOnly)
    498                     yield { name: "__proto__", value: proto, writable: true, configurable: true, enumerable: false, isOwn: true, __proto__: null };
    499                 break;
    500             }
    501         }
    502     },
    503 
    504     /**
    505      * @param {string|undefined} objectGroupName
    506      * @param {*} jsonMLObject
    507      * @throws {string} error message
    508      */
    509     _substituteObjectTagsInCustomPreview: function(objectGroupName, jsonMLObject)
    510     {
    511         var maxCustomPreviewRecursionDepth = 20;
    512         this._customPreviewRecursionDepth = (this._customPreviewRecursionDepth || 0) + 1
    513         try {
    514             if (this._customPreviewRecursionDepth >= maxCustomPreviewRecursionDepth)
    515                 throw new Error("Too deep hierarchy of inlined custom previews");
    516 
    517             if (!isArrayLike(jsonMLObject))
    518                 return;
    519 
    520             if (jsonMLObject[0] === "object") {
    521                 var attributes = jsonMLObject[1];
    522                 var originObject = attributes["object"];
    523                 var config = attributes["config"];
    524                 if (typeof originObject === "undefined")
    525                     throw new Error("Illegal format: obligatory attribute \"object\" isn't specified");
    526 
    527                 jsonMLObject[1] = this._wrapObject(originObject, objectGroupName, false, false, null, false, false, config);
    528                 return;
    529             }
    530 
    531             for (var i = 0; i < jsonMLObject.length; ++i)
    532                 this._substituteObjectTagsInCustomPreview(objectGroupName, jsonMLObject[i]);
    533         } finally {
    534             this._customPreviewRecursionDepth--;
    535         }
    536     },
    537 
    538     /**
    539      * @param {*} object
    540      * @return {boolean}
    541      */
    542     _isDefined: function(object)
    543     {
    544         return !!object || this._isHTMLAllCollection(object);
    545     },
    546 
    547     /**
    548      * @param {*} object
    549      * @return {boolean}
    550      */
    551     _isHTMLAllCollection: function(object)
    552     {
    553         // document.all is reported as undefined, but we still want to process it.
    554         return (typeof object === "undefined") && !!InjectedScriptHost.subtype(object);
    555     },
    556 
    557     /**
    558      * @param {*} obj
    559      * @return {?string}
    560      */
    561     _subtype: function(obj)
    562     {
    563         if (obj === null)
    564             return "null";
    565 
    566         if (this.isPrimitiveValue(obj))
    567             return null;
    568 
    569         var subtype = InjectedScriptHost.subtype(obj);
    570         if (subtype)
    571             return subtype;
    572 
    573         if (isArrayLike(obj))
    574             return "array";
    575 
    576         // If owning frame has navigated to somewhere else window properties will be undefined.
    577         return null;
    578     },
    579 
    580     /**
    581      * @param {*} obj
    582      * @return {?string}
    583      */
    584     _describe: function(obj)
    585     {
    586         if (this.isPrimitiveValue(obj))
    587             return null;
    588 
    589         var subtype = this._subtype(obj);
    590 
    591         if (subtype === "regexp")
    592             return toString(obj);
    593 
    594         if (subtype === "date")
    595             return toString(obj);
    596 
    597         if (subtype === "node") {
    598             var description = "";
    599             if (obj.nodeName)
    600                 description = obj.nodeName.toLowerCase();
    601             else if (obj.constructor)
    602                 description = obj.constructor.name.toLowerCase();
    603 
    604             switch (obj.nodeType) {
    605             case 1 /* Node.ELEMENT_NODE */:
    606                 description += obj.id ? "#" + obj.id : "";
    607                 var className = obj.className;
    608                 description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : "";
    609                 break;
    610             case 10 /*Node.DOCUMENT_TYPE_NODE */:
    611                 description = "<!DOCTYPE " + description + ">";
    612                 break;
    613             }
    614             return description;
    615         }
    616 
    617         if (subtype === "proxy")
    618             return "Proxy";
    619 
    620         var className = InjectedScriptHost.internalConstructorName(obj);
    621         if (subtype === "array" || subtype === "typedarray") {
    622             if (typeof obj.length === "number")
    623                 return className + "(" + obj.length + ")";
    624             return className;
    625         }
    626 
    627         if (subtype === "map" || subtype === "set") {
    628             if (typeof obj.size === "number")
    629                 return className + "(" + obj.size + ")";
    630             return className;
    631         }
    632 
    633         if (typeof obj === "function")
    634             return toString(obj);
    635 
    636         if (isSymbol(obj)) {
    637             try {
    638                 // It isn't safe, because Symbol.prototype.toString can be overriden.
    639                 return /* suppressBlacklist */ obj.toString() || "Symbol";
    640             } catch (e) {
    641                 return "Symbol";
    642             }
    643         }
    644 
    645         if (InjectedScriptHost.subtype(obj) === "error") {
    646             try {
    647                 var stack = obj.stack;
    648                 var message = obj.message && obj.message.length ? ": " + obj.message : "";
    649                 var firstCallFrame = /^\s+at\s/m.exec(stack);
    650                 var stackMessageEnd = firstCallFrame ? firstCallFrame.index : -1;
    651                 if (stackMessageEnd !== -1) {
    652                     var stackTrace = stack.substr(stackMessageEnd);
    653                     return className + message + "\n" + stackTrace;
    654                 }
    655                 return className + message;
    656             } catch(e) {
    657             }
    658         }
    659 
    660         if (subtype === "internal#entry") {
    661             if ("key" in obj)
    662                 return "{" + this._describeIncludingPrimitives(obj.key) + " => " + this._describeIncludingPrimitives(obj.value) + "}";
    663             return this._describeIncludingPrimitives(obj.value);
    664         }
    665 
    666         if (subtype === "internal#scopeList")
    667             return "Scopes[" + obj.length + "]";
    668 
    669         if (subtype === "internal#scope")
    670             return (InjectedScript.closureTypes[obj.type] || "Unknown") + (obj.name ? " (" + obj.name + ")" : "");
    671 
    672         return className;
    673     },
    674 
    675     /**
    676      * @param {*} value
    677      * @return {string}
    678      */
    679     _describeIncludingPrimitives: function(value)
    680     {
    681         if (typeof value === "string")
    682             return "\"" + value.replace(/\n/g, "\u21B5") + "\"";
    683         if (value === null)
    684             return "" + value;
    685         return this.isPrimitiveValue(value) ? toStringDescription(value) : (this._describe(value) || "");
    686     },
    687 
    688     /**
    689      * @param {boolean} enabled
    690      */
    691     setCustomObjectFormatterEnabled: function(enabled)
    692     {
    693         this._customObjectFormatterEnabled = enabled;
    694     }
    695 }
    696 
    697 /**
    698  * @type {!InjectedScript}
    699  * @const
    700  */
    701 var injectedScript = new InjectedScript();
    702 
    703 /**
    704  * @constructor
    705  * @param {*} object
    706  * @param {string=} objectGroupName
    707  * @param {boolean=} doNotBind
    708  * @param {boolean=} forceValueType
    709  * @param {boolean=} generatePreview
    710  * @param {?Array.<string>=} columnNames
    711  * @param {boolean=} isTable
    712  * @param {boolean=} skipEntriesPreview
    713  * @param {*=} customObjectConfig
    714  */
    715 InjectedScript.RemoteObject = function(object, objectGroupName, doNotBind, forceValueType, generatePreview, columnNames, isTable, skipEntriesPreview, customObjectConfig)
    716 {
    717     this.type = typeof object;
    718     if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object))
    719         this.type = "object";
    720 
    721     if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
    722         // We don't send undefined values over JSON.
    723         if (this.type !== "undefined")
    724             this.value = object;
    725 
    726         // Null object is object with 'null' subtype.
    727         if (object === null)
    728             this.subtype = "null";
    729 
    730         // Provide user-friendly number values.
    731         if (this.type === "number") {
    732             this.description = toStringDescription(object);
    733             switch (this.description) {
    734             case "NaN":
    735             case "Infinity":
    736             case "-Infinity":
    737             case "-0":
    738                 delete this.value;
    739                 this.unserializableValue = this.description;
    740                 break;
    741             }
    742         }
    743 
    744         return;
    745     }
    746 
    747     if (injectedScript._shouldPassByValue(object)) {
    748         this.value = object;
    749         this.subtype = injectedScript._subtype(object);
    750         this.description = injectedScript._describeIncludingPrimitives(object);
    751         return;
    752     }
    753 
    754     object = /** @type {!Object} */ (object);
    755 
    756     if (!doNotBind)
    757         this.objectId = injectedScript._bind(object, objectGroupName);
    758     var subtype = injectedScript._subtype(object);
    759     if (subtype)
    760         this.subtype = subtype;
    761     var className = InjectedScriptHost.internalConstructorName(object);
    762     if (className)
    763         this.className = className;
    764     this.description = injectedScript._describe(object);
    765 
    766     if (generatePreview && this.type === "object") {
    767         if (this.subtype === "proxy")
    768             this.preview = this._generatePreview(InjectedScriptHost.proxyTargetValue(object), undefined, columnNames, isTable, skipEntriesPreview);
    769         else if (this.subtype !== "node")
    770             this.preview = this._generatePreview(object, undefined, columnNames, isTable, skipEntriesPreview);
    771     }
    772 
    773     if (injectedScript._customObjectFormatterEnabled) {
    774         var customPreview = this._customPreview(object, objectGroupName, customObjectConfig);
    775         if (customPreview)
    776             this.customPreview = customPreview;
    777     }
    778 }
    779 
    780 InjectedScript.RemoteObject.prototype = {
    781 
    782     /**
    783      * @param {*} object
    784      * @param {string=} objectGroupName
    785      * @param {*=} customObjectConfig
    786      * @return {?RuntimeAgent.CustomPreview}
    787      */
    788     _customPreview: function(object, objectGroupName, customObjectConfig)
    789     {
    790         /**
    791          * @param {!Error} error
    792          */
    793         function logError(error)
    794         {
    795             // We use user code to generate custom output for object, we can use user code for reporting error too.
    796             Promise.resolve().then(/* suppressBlacklist */ inspectedGlobalObject.console.error.bind(inspectedGlobalObject.console, "Custom Formatter Failed: " + error.message));
    797         }
    798 
    799         /**
    800          * @param {*} object
    801          * @param {*=} customObjectConfig
    802          * @return {*}
    803          */
    804         function wrap(object, customObjectConfig)
    805         {
    806             return injectedScript._wrapObject(object, objectGroupName, false, false, null, false, false, customObjectConfig);
    807         }
    808 
    809         try {
    810             var formatters = inspectedGlobalObject["devtoolsFormatters"];
    811             if (!formatters || !isArrayLike(formatters))
    812                 return null;
    813 
    814             for (var i = 0; i < formatters.length; ++i) {
    815                 try {
    816                     var formatted = formatters[i].header(object, customObjectConfig);
    817                     if (!formatted)
    818                         continue;
    819 
    820                     var hasBody = formatters[i].hasBody(object, customObjectConfig);
    821                     injectedScript._substituteObjectTagsInCustomPreview(objectGroupName, formatted);
    822                     var formatterObjectId = injectedScript._bind(formatters[i], objectGroupName);
    823                     var bindRemoteObjectFunctionId = injectedScript._bind(wrap, objectGroupName);
    824                     var result = {header: JSON.stringify(formatted), hasBody: !!hasBody, formatterObjectId: formatterObjectId, bindRemoteObjectFunctionId: bindRemoteObjectFunctionId};
    825                     if (customObjectConfig)
    826                         result["configObjectId"] = injectedScript._bind(customObjectConfig, objectGroupName);
    827                     return result;
    828                 } catch (e) {
    829                     logError(e);
    830                 }
    831             }
    832         } catch (e) {
    833             logError(e);
    834         }
    835         return null;
    836     },
    837 
    838     /**
    839      * @return {!RuntimeAgent.ObjectPreview} preview
    840      */
    841     _createEmptyPreview: function()
    842     {
    843         var preview = {
    844             type: /** @type {!RuntimeAgent.ObjectPreviewType.<string>} */ (this.type),
    845             description: this.description || toStringDescription(this.value),
    846             overflow: false,
    847             properties: [],
    848             __proto__: null
    849         };
    850         if (this.subtype)
    851             preview.subtype = /** @type {!RuntimeAgent.ObjectPreviewSubtype.<string>} */ (this.subtype);
    852         return preview;
    853     },
    854 
    855     /**
    856      * @param {!Object} object
    857      * @param {?Array.<string>=} firstLevelKeys
    858      * @param {?Array.<string>=} secondLevelKeys
    859      * @param {boolean=} isTable
    860      * @param {boolean=} skipEntriesPreview
    861      * @return {!RuntimeAgent.ObjectPreview} preview
    862      */
    863     _generatePreview: function(object, firstLevelKeys, secondLevelKeys, isTable, skipEntriesPreview)
    864     {
    865         var preview = this._createEmptyPreview();
    866         var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
    867 
    868         var propertiesThreshold = {
    869             properties: isTable ? 1000 : max(5, firstLevelKeysCount),
    870             indexes: isTable ? 1000 : max(100, firstLevelKeysCount),
    871             __proto__: null
    872         };
    873 
    874         try {
    875             var descriptors = injectedScript._propertyDescriptors(object, undefined, undefined, firstLevelKeys);
    876 
    877             this._appendPropertyDescriptors(preview, descriptors, propertiesThreshold, secondLevelKeys, isTable);
    878             if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
    879                 return preview;
    880 
    881             // Add internal properties to preview.
    882             var rawInternalProperties = InjectedScriptHost.getInternalProperties(object) || [];
    883             var internalProperties = [];
    884             var entries = null;
    885             for (var i = 0; i < rawInternalProperties.length; i += 2) {
    886                 if (rawInternalProperties[i] === "[[Entries]]") {
    887                     entries = /** @type {!Array<*>} */(rawInternalProperties[i + 1]);
    888                     continue;
    889                 }
    890                 push(internalProperties, {
    891                     name: rawInternalProperties[i],
    892                     value: rawInternalProperties[i + 1],
    893                     isOwn: true,
    894                     enumerable: true,
    895                     __proto__: null
    896                 });
    897             }
    898             this._appendPropertyDescriptors(preview, internalProperties, propertiesThreshold, secondLevelKeys, isTable);
    899 
    900             if (this.subtype === "map" || this.subtype === "set" || this.subtype === "iterator")
    901                 this._appendEntriesPreview(entries, preview, skipEntriesPreview);
    902 
    903         } catch (e) {}
    904 
    905         return preview;
    906     },
    907 
    908     /**
    909      * @param {!RuntimeAgent.ObjectPreview} preview
    910      * @param {!Array.<*>|!Iterable.<*>} descriptors
    911      * @param {!Object} propertiesThreshold
    912      * @param {?Array.<string>=} secondLevelKeys
    913      * @param {boolean=} isTable
    914      */
    915     _appendPropertyDescriptors: function(preview, descriptors, propertiesThreshold, secondLevelKeys, isTable)
    916     {
    917         for (var descriptor of descriptors) {
    918             if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
    919                 break;
    920             if (!descriptor || descriptor.wasThrown)
    921                 continue;
    922 
    923             var name = descriptor.name;
    924 
    925             // Ignore __proto__ property.
    926             if (name === "__proto__")
    927                 continue;
    928 
    929             // Ignore length property of array.
    930             if ((this.subtype === "array" || this.subtype === "typedarray") && name === "length")
    931                 continue;
    932 
    933             // Ignore size property of map, set.
    934             if ((this.subtype === "map" || this.subtype === "set") && name === "size")
    935                 continue;
    936 
    937             // Never preview prototype properties.
    938             if (!descriptor.isOwn)
    939                 continue;
    940 
    941             // Ignore computed properties unless they have getters.
    942             if (!("value" in descriptor)) {
    943                 if (descriptor.get)
    944                     this._appendPropertyPreview(preview, { name: name, type: "accessor", __proto__: null }, propertiesThreshold);
    945                 continue;
    946             }
    947 
    948             var value = descriptor.value;
    949             var type = typeof value;
    950 
    951             // Special-case HTMLAll.
    952             if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
    953                 type = "object";
    954 
    955             // Render own properties.
    956             if (value === null) {
    957                 this._appendPropertyPreview(preview, { name: name, type: "object", subtype: "null", value: "null", __proto__: null }, propertiesThreshold);
    958                 continue;
    959             }
    960 
    961             var maxLength = 100;
    962             if (InjectedScript.primitiveTypes[type]) {
    963                 if (type === "string" && value.length > maxLength)
    964                     value = this._abbreviateString(value, maxLength, true);
    965                 this._appendPropertyPreview(preview, { name: name, type: type, value: toStringDescription(value), __proto__: null }, propertiesThreshold);
    966                 continue;
    967             }
    968 
    969             var property = { name: name, type: type, __proto__: null };
    970             var subtype = injectedScript._subtype(value);
    971             if (subtype)
    972                 property.subtype = subtype;
    973 
    974             if (secondLevelKeys === null || secondLevelKeys) {
    975                 var subPreview = this._generatePreview(value, secondLevelKeys || undefined, undefined, isTable);
    976                 property.valuePreview = subPreview;
    977                 if (subPreview.overflow)
    978                     preview.overflow = true;
    979             } else {
    980                 var description = "";
    981                 if (type !== "function")
    982                     description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp");
    983                 property.value = description;
    984             }
    985             this._appendPropertyPreview(preview, property, propertiesThreshold);
    986         }
    987     },
    988 
    989     /**
    990      * @param {!RuntimeAgent.ObjectPreview} preview
    991      * @param {!Object} property
    992      * @param {!Object} propertiesThreshold
    993      */
    994     _appendPropertyPreview: function(preview, property, propertiesThreshold)
    995     {
    996         if (toString(property.name >>> 0) === property.name)
    997             propertiesThreshold.indexes--;
    998         else
    999             propertiesThreshold.properties--;
   1000         if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) {
   1001             preview.overflow = true;
   1002         } else {
   1003             push(preview.properties, property);
   1004         }
   1005     },
   1006 
   1007     /**
   1008      * @param {?Array<*>} entries
   1009      * @param {!RuntimeAgent.ObjectPreview} preview
   1010      * @param {boolean=} skipEntriesPreview
   1011      */
   1012     _appendEntriesPreview: function(entries, preview, skipEntriesPreview)
   1013     {
   1014         if (!entries)
   1015             return;
   1016         if (skipEntriesPreview) {
   1017             if (entries.length)
   1018                 preview.overflow = true;
   1019             return;
   1020         }
   1021         preview.entries = [];
   1022         var entriesThreshold = 5;
   1023         for (var i = 0; i < entries.length; ++i) {
   1024             if (preview.entries.length >= entriesThreshold) {
   1025                 preview.overflow = true;
   1026                 break;
   1027             }
   1028             var entry = nullifyObjectProto(entries[i]);
   1029             var previewEntry = {
   1030                 value: generateValuePreview(entry.value),
   1031                 __proto__: null
   1032             };
   1033             if ("key" in entry)
   1034                 previewEntry.key = generateValuePreview(entry.key);
   1035             push(preview.entries, previewEntry);
   1036         }
   1037 
   1038         /**
   1039          * @param {*} value
   1040          * @return {!RuntimeAgent.ObjectPreview}
   1041          */
   1042         function generateValuePreview(value)
   1043         {
   1044             var remoteObject = new InjectedScript.RemoteObject(value, undefined, true, undefined, true, undefined, undefined, true);
   1045             var valuePreview = remoteObject.preview || remoteObject._createEmptyPreview();
   1046             return valuePreview;
   1047         }
   1048     },
   1049 
   1050     /**
   1051      * @param {string} string
   1052      * @param {number} maxLength
   1053      * @param {boolean=} middle
   1054      * @return {string}
   1055      */
   1056     _abbreviateString: function(string, maxLength, middle)
   1057     {
   1058         if (string.length <= maxLength)
   1059             return string;
   1060         if (middle) {
   1061             var leftHalf = maxLength >> 1;
   1062             var rightHalf = maxLength - leftHalf - 1;
   1063             return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
   1064         }
   1065         return string.substr(0, maxLength) + "\u2026";
   1066     },
   1067 
   1068     __proto__: null
   1069 }
   1070 
   1071 return injectedScript;
   1072 })
   1073