• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
44var Object = /** @type {function(new:Object, *=)} */ ({}.constructor);
45
46/**
47 * @param {!Array.<T>} array
48 * @param {...} var_args
49 * @template T
50 */
51function 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 */
62function 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 */
78function 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 */
90function 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 */
101function 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 */
113function 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 */
134function 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 */
144function 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 */
160var 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 */
171function 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 */
186var InjectedScript = function()
187{
188}
189InjectedScriptHost.nullifyPrototype(InjectedScript);
190
191/**
192 * @type {!Object.<string, boolean>}
193 * @const
194 */
195InjectedScript.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 */
207InjectedScript.closureTypes = { __proto__: null };
208InjectedScript.closureTypes["local"] = "Local";
209InjectedScript.closureTypes["closure"] = "Closure";
210InjectedScript.closureTypes["catch"] = "Catch";
211InjectedScript.closureTypes["block"] = "Block";
212InjectedScript.closureTypes["script"] = "Script";
213InjectedScript.closureTypes["with"] = "With Block";
214InjectedScript.closureTypes["global"] = "Global";
215InjectedScript.closureTypes["eval"] = "Eval";
216InjectedScript.closureTypes["module"] = "Module";
217
218InjectedScript.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 */
701var 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 */
715InjectedScript.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
780InjectedScript.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
1071return injectedScript;
1072})
1073