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 = nullifyObjectProto({});
161domAttributesWithObservableSideEffectOnGet["Request"] = nullifyObjectProto({});
162domAttributesWithObservableSideEffectOnGet["Request"]["body"] = true;
163domAttributesWithObservableSideEffectOnGet["Response"] = nullifyObjectProto({});
164domAttributesWithObservableSideEffectOnGet["Response"]["body"] = true;
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}
189
190/**
191 * @type {!Object.<string, boolean>}
192 * @const
193 */
194InjectedScript.primitiveTypes = {
195    "undefined": true,
196    "boolean": true,
197    "number": true,
198    "string": true,
199    __proto__: null
200}
201
202/**
203 * @type {!Object<string, string>}
204 * @const
205 */
206InjectedScript.closureTypes = { __proto__: null };
207InjectedScript.closureTypes["local"] = "Local";
208InjectedScript.closureTypes["closure"] = "Closure";
209InjectedScript.closureTypes["catch"] = "Catch";
210InjectedScript.closureTypes["block"] = "Block";
211InjectedScript.closureTypes["script"] = "Script";
212InjectedScript.closureTypes["with"] = "With Block";
213InjectedScript.closureTypes["global"] = "Global";
214
215InjectedScript.prototype = {
216    /**
217     * @param {*} object
218     * @return {boolean}
219     */
220    isPrimitiveValue: function(object)
221    {
222        // FIXME(33716): typeof document.all is always 'undefined'.
223        return InjectedScript.primitiveTypes[typeof object] && !this._isHTMLAllCollection(object);
224    },
225
226    /**
227     * @param {*} object
228     * @return {boolean}
229     */
230    _shouldPassByValue: function(object)
231    {
232        return typeof object === "object" && InjectedScriptHost.subtype(object) === "internal#location";
233    },
234
235    /**
236     * @param {*} object
237     * @param {string} groupName
238     * @param {boolean} forceValueType
239     * @param {boolean} generatePreview
240     * @return {!RuntimeAgent.RemoteObject}
241     */
242    wrapObject: function(object, groupName, forceValueType, generatePreview)
243    {
244        return this._wrapObject(object, groupName, forceValueType, generatePreview);
245    },
246
247    /**
248     * @param {!Array<!Object>} array
249     * @param {string} property
250     * @param {string} groupName
251     * @param {boolean} forceValueType
252     * @param {boolean} generatePreview
253     */
254    wrapPropertyInArray: function(array, property, groupName, forceValueType, generatePreview)
255    {
256        for (var i = 0; i < array.length; ++i) {
257            if (typeof array[i] === "object" && property in array[i])
258                array[i][property] = this.wrapObject(array[i][property], groupName, forceValueType, generatePreview);
259        }
260    },
261
262    /**
263     * @param {!Object} table
264     * @param {!Array.<string>|string|boolean} columns
265     * @return {!RuntimeAgent.RemoteObject}
266     */
267    wrapTable: function(table, columns)
268    {
269        var columnNames = null;
270        if (typeof columns === "string")
271            columns = [columns];
272        if (InjectedScriptHost.subtype(columns) === "array") {
273            columnNames = [];
274            for (var i = 0; i < columns.length; ++i)
275                columnNames[i] = toString(columns[i]);
276        }
277        return this._wrapObject(table, "console", false, true, columnNames, true);
278    },
279
280    /**
281     * This method cannot throw.
282     * @param {*} object
283     * @param {string=} objectGroupName
284     * @param {boolean=} forceValueType
285     * @param {boolean=} generatePreview
286     * @param {?Array.<string>=} columnNames
287     * @param {boolean=} isTable
288     * @param {boolean=} doNotBind
289     * @param {*=} customObjectConfig
290     * @return {!RuntimeAgent.RemoteObject}
291     * @suppress {checkTypes}
292     */
293    _wrapObject: function(object, objectGroupName, forceValueType, generatePreview, columnNames, isTable, doNotBind, customObjectConfig)
294    {
295        try {
296            return new InjectedScript.RemoteObject(object, objectGroupName, doNotBind, forceValueType, generatePreview, columnNames, isTable, undefined, customObjectConfig);
297        } catch (e) {
298            try {
299                var description = injectedScript._describe(e);
300            } catch (ex) {
301                var description = "<failed to convert exception to string>";
302            }
303            return new InjectedScript.RemoteObject(description);
304        }
305    },
306
307    /**
308     * @param {!Object|symbol} object
309     * @param {string=} objectGroupName
310     * @return {string}
311     */
312    _bind: function(object, objectGroupName)
313    {
314        var id = InjectedScriptHost.bind(object, objectGroupName || "");
315        return "{\"injectedScriptId\":" + injectedScriptId + ",\"id\":" + id + "}";
316    },
317
318    /**
319     * @param {!Object} object
320     * @param {string} objectGroupName
321     * @param {boolean} ownProperties
322     * @param {boolean} accessorPropertiesOnly
323     * @param {boolean} generatePreview
324     * @return {!Array<!RuntimeAgent.PropertyDescriptor>|boolean}
325     */
326    getProperties: function(object, objectGroupName, ownProperties, accessorPropertiesOnly, generatePreview)
327    {
328        var subtype = this._subtype(object);
329        if (subtype === "internal#scope") {
330            // Internally, scope contains object with scope variables and additional information like type,
331            // we use additional information for preview and would like to report variables as scope
332            // properties.
333            object = object.object;
334        }
335
336        var descriptors = [];
337        var iter = this._propertyDescriptors(object, ownProperties, accessorPropertiesOnly, undefined);
338        // Go over properties, wrap object values.
339        for (var descriptor of iter) {
340            if (subtype === "internal#scopeList" && descriptor.name === "length")
341                continue;
342            if ("get" in descriptor)
343                descriptor.get = this._wrapObject(descriptor.get, objectGroupName);
344            if ("set" in descriptor)
345                descriptor.set = this._wrapObject(descriptor.set, objectGroupName);
346            if ("value" in descriptor)
347                descriptor.value = this._wrapObject(descriptor.value, objectGroupName, false, generatePreview);
348            if (!("configurable" in descriptor))
349                descriptor.configurable = false;
350            if (!("enumerable" in descriptor))
351                descriptor.enumerable = false;
352            if ("symbol" in descriptor)
353                descriptor.symbol = this._wrapObject(descriptor.symbol, objectGroupName);
354            push(descriptors, descriptor);
355        }
356        return descriptors;
357    },
358
359    /**
360     * @param {!Object} object
361     * @return {?Object}
362     */
363    _objectPrototype: function(object)
364    {
365        if (InjectedScriptHost.subtype(object) === "proxy")
366            return null;
367        try {
368            return Object.getPrototypeOf(object);
369        } catch (e) {
370            return null;
371        }
372    },
373
374    /**
375     * @param {!Object} object
376     * @param {boolean=} ownProperties
377     * @param {boolean=} accessorPropertiesOnly
378     * @param {?Array.<string>=} propertyNamesOnly
379     */
380    _propertyDescriptors: function*(object, ownProperties, accessorPropertiesOnly, propertyNamesOnly)
381    {
382        var propertyProcessed = { __proto__: null };
383
384        /**
385         * @param {?Object} o
386         * @param {!Iterable<string|symbol|number>|!Array<string|number|symbol>} properties
387         */
388        function* process(o, properties)
389        {
390            for (var property of properties) {
391                var name;
392                if (isSymbol(property))
393                    name = /** @type {string} */ (injectedScript._describe(property));
394                else
395                    name = typeof property === "number" ? ("" + property) : /** @type {string} */(property);
396
397                if (propertyProcessed[property])
398                    continue;
399
400                try {
401                    propertyProcessed[property] = true;
402                    var descriptor = nullifyObjectProto(Object.getOwnPropertyDescriptor(o, property));
403                    if (descriptor) {
404                        if (accessorPropertiesOnly && !("get" in descriptor || "set" in descriptor))
405                            continue;
406                        if ("get" in descriptor && "set" in descriptor && name != "__proto__" && InjectedScriptHost.formatAccessorsAsProperties(object, descriptor.get) && !doesAttributeHaveObservableSideEffectOnGet(object, name)) {
407                            descriptor.value = object[property];
408                            descriptor.isOwn = true;
409                            delete descriptor.get;
410                            delete descriptor.set;
411                        }
412                    } else {
413                        // Not all bindings provide proper descriptors. Fall back to the writable, configurable property.
414                        if (accessorPropertiesOnly)
415                            continue;
416                        try {
417                            descriptor = { name: name, value: o[property], writable: false, configurable: false, enumerable: false, __proto__: null };
418                            if (o === object)
419                                descriptor.isOwn = true;
420                            yield descriptor;
421                        } catch (e) {
422                            // Silent catch.
423                        }
424                        continue;
425                    }
426                } catch (e) {
427                    if (accessorPropertiesOnly)
428                        continue;
429                    var descriptor = { __proto__: null };
430                    descriptor.value = e;
431                    descriptor.wasThrown = true;
432                }
433
434                descriptor.name = name;
435                if (o === object)
436                    descriptor.isOwn = true;
437                if (isSymbol(property))
438                    descriptor.symbol = property;
439                yield descriptor;
440            }
441        }
442
443        if (propertyNamesOnly) {
444            for (var i = 0; i < propertyNamesOnly.length; ++i) {
445                var name = propertyNamesOnly[i];
446                for (var o = object; this._isDefined(o); o = this._objectPrototype(o)) {
447                    if (InjectedScriptHost.objectHasOwnProperty(o, name)) {
448                        for (var descriptor of process(o, [name]))
449                            yield descriptor;
450                        break;
451                    }
452                    if (ownProperties)
453                        break;
454                }
455            }
456            return;
457        }
458
459        /**
460         * @param {number} length
461         */
462        function* arrayIndexNames(length)
463        {
464            for (var i = 0; i < length; ++i)
465                yield "" + i;
466        }
467
468        var skipGetOwnPropertyNames;
469        try {
470            skipGetOwnPropertyNames = InjectedScriptHost.subtype(object) === "typedarray" && object.length > 500000;
471        } catch (e) {
472        }
473
474        for (var o = object; this._isDefined(o); o = this._objectPrototype(o)) {
475            if (InjectedScriptHost.subtype(o) === "proxy")
476                continue;
477            if (skipGetOwnPropertyNames && o === object) {
478                // Avoid OOM crashes from getting all own property names of a large TypedArray.
479                for (var descriptor of process(o, arrayIndexNames(o.length)))
480                    yield descriptor;
481            } else {
482                // First call Object.keys() to enforce ordering of the property descriptors.
483                for (var descriptor of process(o, Object.keys(/** @type {!Object} */ (o))))
484                    yield descriptor;
485                for (var descriptor of process(o, Object.getOwnPropertyNames(/** @type {!Object} */ (o))))
486                    yield descriptor;
487            }
488            if (Object.getOwnPropertySymbols) {
489                for (var descriptor of process(o, Object.getOwnPropertySymbols(/** @type {!Object} */ (o))))
490                    yield descriptor;
491            }
492            if (ownProperties) {
493                var proto = this._objectPrototype(o);
494                if (proto && !accessorPropertiesOnly)
495                    yield { name: "__proto__", value: proto, writable: true, configurable: true, enumerable: false, isOwn: true, __proto__: null };
496                break;
497            }
498        }
499    },
500
501    /**
502     * @param {string|undefined} objectGroupName
503     * @param {*} jsonMLObject
504     * @throws {string} error message
505     */
506    _substituteObjectTagsInCustomPreview: function(objectGroupName, jsonMLObject)
507    {
508        var maxCustomPreviewRecursionDepth = 20;
509        this._customPreviewRecursionDepth = (this._customPreviewRecursionDepth || 0) + 1
510        try {
511            if (this._customPreviewRecursionDepth >= maxCustomPreviewRecursionDepth)
512                throw new Error("Too deep hierarchy of inlined custom previews");
513
514            if (!isArrayLike(jsonMLObject))
515                return;
516
517            if (jsonMLObject[0] === "object") {
518                var attributes = jsonMLObject[1];
519                var originObject = attributes["object"];
520                var config = attributes["config"];
521                if (typeof originObject === "undefined")
522                    throw new Error("Illegal format: obligatory attribute \"object\" isn't specified");
523
524                jsonMLObject[1] = this._wrapObject(originObject, objectGroupName, false, false, null, false, false, config);
525                return;
526            }
527
528            for (var i = 0; i < jsonMLObject.length; ++i)
529                this._substituteObjectTagsInCustomPreview(objectGroupName, jsonMLObject[i]);
530        } finally {
531            this._customPreviewRecursionDepth--;
532        }
533    },
534
535    /**
536     * @param {*} object
537     * @return {boolean}
538     */
539    _isDefined: function(object)
540    {
541        return !!object || this._isHTMLAllCollection(object);
542    },
543
544    /**
545     * @param {*} object
546     * @return {boolean}
547     */
548    _isHTMLAllCollection: function(object)
549    {
550        // document.all is reported as undefined, but we still want to process it.
551        return (typeof object === "undefined") && !!InjectedScriptHost.subtype(object);
552    },
553
554    /**
555     * @param {*} obj
556     * @return {?string}
557     */
558    _subtype: function(obj)
559    {
560        if (obj === null)
561            return "null";
562
563        if (this.isPrimitiveValue(obj))
564            return null;
565
566        var subtype = InjectedScriptHost.subtype(obj);
567        if (subtype)
568            return subtype;
569
570        if (isArrayLike(obj))
571            return "array";
572
573        // If owning frame has navigated to somewhere else window properties will be undefined.
574        return null;
575    },
576
577    /**
578     * @param {*} obj
579     * @return {?string}
580     */
581    _describe: function(obj)
582    {
583        if (this.isPrimitiveValue(obj))
584            return null;
585
586        var subtype = this._subtype(obj);
587
588        if (subtype === "regexp")
589            return toString(obj);
590
591        if (subtype === "date")
592            return toString(obj);
593
594        if (subtype === "node") {
595            var description = "";
596            if (obj.nodeName)
597                description = obj.nodeName.toLowerCase();
598            else if (obj.constructor)
599                description = obj.constructor.name.toLowerCase();
600
601            switch (obj.nodeType) {
602            case 1 /* Node.ELEMENT_NODE */:
603                description += obj.id ? "#" + obj.id : "";
604                var className = obj.className;
605                description += (className && typeof className === "string") ? "." + className.trim().replace(/\s+/g, ".") : "";
606                break;
607            case 10 /*Node.DOCUMENT_TYPE_NODE */:
608                description = "<!DOCTYPE " + description + ">";
609                break;
610            }
611            return description;
612        }
613
614        if (subtype === "proxy")
615            return "Proxy";
616
617        var className = InjectedScriptHost.internalConstructorName(obj);
618        if (subtype === "array" || subtype === "typedarray") {
619            if (typeof obj.length === "number")
620                className += "[" + obj.length + "]";
621            return className;
622        }
623
624        if (typeof obj === "function")
625            return toString(obj);
626
627        if (isSymbol(obj)) {
628            try {
629                // It isn't safe, because Symbol.prototype.toString can be overriden.
630                return /* suppressBlacklist */ obj.toString() || "Symbol";
631            } catch (e) {
632                return "Symbol";
633            }
634        }
635
636        if (InjectedScriptHost.subtype(obj) === "error") {
637            try {
638                var stack = obj.stack;
639                var message = obj.message && obj.message.length ? ": " + obj.message : "";
640                var firstCallFrame = /^\s+at\s/m.exec(stack);
641                var stackMessageEnd = firstCallFrame ? firstCallFrame.index : -1;
642                if (stackMessageEnd !== -1) {
643                    var stackTrace = stack.substr(stackMessageEnd);
644                    return className + message + "\n" + stackTrace;
645                }
646                return className + message;
647            } catch(e) {
648            }
649        }
650
651        if (subtype === "internal#entry") {
652            if ("key" in obj)
653                return "{" + this._describeIncludingPrimitives(obj.key) + " => " + this._describeIncludingPrimitives(obj.value) + "}";
654            return this._describeIncludingPrimitives(obj.value);
655        }
656
657        if (subtype === "internal#scopeList")
658            return "Scopes[" + obj.length + "]";
659
660        if (subtype === "internal#scope")
661            return (InjectedScript.closureTypes[obj.type] || "Unknown") + (obj.name ? " (" + obj.name + ")" : "");
662
663        return className;
664    },
665
666    /**
667     * @param {*} value
668     * @return {string}
669     */
670    _describeIncludingPrimitives: function(value)
671    {
672        if (typeof value === "string")
673            return "\"" + value.replace(/\n/g, "\u21B5") + "\"";
674        if (value === null)
675            return "" + value;
676        return this.isPrimitiveValue(value) ? toStringDescription(value) : (this._describe(value) || "");
677    },
678
679    /**
680     * @param {boolean} enabled
681     */
682    setCustomObjectFormatterEnabled: function(enabled)
683    {
684        this._customObjectFormatterEnabled = enabled;
685    }
686}
687
688/**
689 * @type {!InjectedScript}
690 * @const
691 */
692var injectedScript = new InjectedScript();
693
694/**
695 * @constructor
696 * @param {*} object
697 * @param {string=} objectGroupName
698 * @param {boolean=} doNotBind
699 * @param {boolean=} forceValueType
700 * @param {boolean=} generatePreview
701 * @param {?Array.<string>=} columnNames
702 * @param {boolean=} isTable
703 * @param {boolean=} skipEntriesPreview
704 * @param {*=} customObjectConfig
705 */
706InjectedScript.RemoteObject = function(object, objectGroupName, doNotBind, forceValueType, generatePreview, columnNames, isTable, skipEntriesPreview, customObjectConfig)
707{
708    this.type = typeof object;
709    if (this.type === "undefined" && injectedScript._isHTMLAllCollection(object))
710        this.type = "object";
711
712    if (injectedScript.isPrimitiveValue(object) || object === null || forceValueType) {
713        // We don't send undefined values over JSON.
714        if (this.type !== "undefined")
715            this.value = object;
716
717        // Null object is object with 'null' subtype.
718        if (object === null)
719            this.subtype = "null";
720
721        // Provide user-friendly number values.
722        if (this.type === "number") {
723            this.description = toStringDescription(object);
724            switch (this.description) {
725            case "NaN":
726            case "Infinity":
727            case "-Infinity":
728            case "-0":
729                delete this.value;
730                this.unserializableValue = this.description;
731                break;
732            }
733        }
734
735        return;
736    }
737
738    if (injectedScript._shouldPassByValue(object)) {
739        this.value = object;
740        this.subtype = injectedScript._subtype(object);
741        this.description = injectedScript._describeIncludingPrimitives(object);
742        return;
743    }
744
745    object = /** @type {!Object} */ (object);
746
747    if (!doNotBind)
748        this.objectId = injectedScript._bind(object, objectGroupName);
749    var subtype = injectedScript._subtype(object);
750    if (subtype)
751        this.subtype = subtype;
752    var className = InjectedScriptHost.internalConstructorName(object);
753    if (className)
754        this.className = className;
755    this.description = injectedScript._describe(object);
756
757    if (generatePreview && this.type === "object") {
758        if (this.subtype === "proxy")
759            this.preview = this._generatePreview(InjectedScriptHost.proxyTargetValue(object), undefined, columnNames, isTable, skipEntriesPreview);
760        else if (this.subtype !== "node")
761            this.preview = this._generatePreview(object, undefined, columnNames, isTable, skipEntriesPreview);
762    }
763
764    if (injectedScript._customObjectFormatterEnabled) {
765        var customPreview = this._customPreview(object, objectGroupName, customObjectConfig);
766        if (customPreview)
767            this.customPreview = customPreview;
768    }
769}
770
771InjectedScript.RemoteObject.prototype = {
772
773    /**
774     * @param {*} object
775     * @param {string=} objectGroupName
776     * @param {*=} customObjectConfig
777     * @return {?RuntimeAgent.CustomPreview}
778     */
779    _customPreview: function(object, objectGroupName, customObjectConfig)
780    {
781        /**
782         * @param {!Error} error
783         */
784        function logError(error)
785        {
786            // We use user code to generate custom output for object, we can use user code for reporting error too.
787            Promise.resolve().then(/* suppressBlacklist */ inspectedGlobalObject.console.error.bind(inspectedGlobalObject.console, "Custom Formatter Failed: " + error.message));
788        }
789
790        /**
791         * @param {*} object
792         * @param {*=} customObjectConfig
793         * @return {*}
794         */
795        function wrap(object, customObjectConfig)
796        {
797            return injectedScript._wrapObject(object, objectGroupName, false, false, null, false, false, customObjectConfig);
798        }
799
800        try {
801            var formatters = inspectedGlobalObject["devtoolsFormatters"];
802            if (!formatters || !isArrayLike(formatters))
803                return null;
804
805            for (var i = 0; i < formatters.length; ++i) {
806                try {
807                    var formatted = formatters[i].header(object, customObjectConfig);
808                    if (!formatted)
809                        continue;
810
811                    var hasBody = formatters[i].hasBody(object, customObjectConfig);
812                    injectedScript._substituteObjectTagsInCustomPreview(objectGroupName, formatted);
813                    var formatterObjectId = injectedScript._bind(formatters[i], objectGroupName);
814                    var bindRemoteObjectFunctionId = injectedScript._bind(wrap, objectGroupName);
815                    var result = {header: JSON.stringify(formatted), hasBody: !!hasBody, formatterObjectId: formatterObjectId, bindRemoteObjectFunctionId: bindRemoteObjectFunctionId};
816                    if (customObjectConfig)
817                        result["configObjectId"] = injectedScript._bind(customObjectConfig, objectGroupName);
818                    return result;
819                } catch (e) {
820                    logError(e);
821                }
822            }
823        } catch (e) {
824            logError(e);
825        }
826        return null;
827    },
828
829    /**
830     * @return {!RuntimeAgent.ObjectPreview} preview
831     */
832    _createEmptyPreview: function()
833    {
834        var preview = {
835            type: /** @type {!RuntimeAgent.ObjectPreviewType.<string>} */ (this.type),
836            description: this.description || toStringDescription(this.value),
837            overflow: false,
838            properties: [],
839            __proto__: null
840        };
841        if (this.subtype)
842            preview.subtype = /** @type {!RuntimeAgent.ObjectPreviewSubtype.<string>} */ (this.subtype);
843        return preview;
844    },
845
846    /**
847     * @param {!Object} object
848     * @param {?Array.<string>=} firstLevelKeys
849     * @param {?Array.<string>=} secondLevelKeys
850     * @param {boolean=} isTable
851     * @param {boolean=} skipEntriesPreview
852     * @return {!RuntimeAgent.ObjectPreview} preview
853     */
854    _generatePreview: function(object, firstLevelKeys, secondLevelKeys, isTable, skipEntriesPreview)
855    {
856        var preview = this._createEmptyPreview();
857        var firstLevelKeysCount = firstLevelKeys ? firstLevelKeys.length : 0;
858
859        var propertiesThreshold = {
860            properties: isTable ? 1000 : max(5, firstLevelKeysCount),
861            indexes: isTable ? 1000 : max(100, firstLevelKeysCount),
862            __proto__: null
863        };
864
865        try {
866            var descriptors = injectedScript._propertyDescriptors(object, undefined, undefined, firstLevelKeys);
867
868            this._appendPropertyDescriptors(preview, descriptors, propertiesThreshold, secondLevelKeys, isTable);
869            if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
870                return preview;
871
872            // Add internal properties to preview.
873            var rawInternalProperties = InjectedScriptHost.getInternalProperties(object) || [];
874            var internalProperties = [];
875            var entries = null;
876            for (var i = 0; i < rawInternalProperties.length; i += 2) {
877                if (rawInternalProperties[i] === "[[Entries]]") {
878                    entries = /** @type {!Array<*>} */(rawInternalProperties[i + 1]);
879                    continue;
880                }
881                push(internalProperties, {
882                    name: rawInternalProperties[i],
883                    value: rawInternalProperties[i + 1],
884                    isOwn: true,
885                    enumerable: true,
886                    __proto__: null
887                });
888            }
889            this._appendPropertyDescriptors(preview, internalProperties, propertiesThreshold, secondLevelKeys, isTable);
890
891            if (this.subtype === "map" || this.subtype === "set" || this.subtype === "iterator")
892                this._appendEntriesPreview(entries, preview, skipEntriesPreview);
893
894        } catch (e) {}
895
896        return preview;
897    },
898
899    /**
900     * @param {!RuntimeAgent.ObjectPreview} preview
901     * @param {!Array.<*>|!Iterable.<*>} descriptors
902     * @param {!Object} propertiesThreshold
903     * @param {?Array.<string>=} secondLevelKeys
904     * @param {boolean=} isTable
905     */
906    _appendPropertyDescriptors: function(preview, descriptors, propertiesThreshold, secondLevelKeys, isTable)
907    {
908        for (var descriptor of descriptors) {
909            if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0)
910                break;
911            if (!descriptor || descriptor.wasThrown)
912                continue;
913
914            var name = descriptor.name;
915
916            // Ignore __proto__ property.
917            if (name === "__proto__")
918                continue;
919
920            // Ignore length property of array.
921            if ((this.subtype === "array" || this.subtype === "typedarray") && name === "length")
922                continue;
923
924            // Ignore size property of map, set.
925            if ((this.subtype === "map" || this.subtype === "set") && name === "size")
926                continue;
927
928            // Never preview prototype properties.
929            if (!descriptor.isOwn)
930                continue;
931
932            // Ignore computed properties.
933            if (!("value" in descriptor))
934                continue;
935
936            var value = descriptor.value;
937            var type = typeof value;
938
939            // Never render functions in object preview.
940            if (type === "function" && (this.subtype !== "array" || !isUInt32(name)))
941                continue;
942
943            // Special-case HTMLAll.
944            if (type === "undefined" && injectedScript._isHTMLAllCollection(value))
945                type = "object";
946
947            // Render own properties.
948            if (value === null) {
949                this._appendPropertyPreview(preview, { name: name, type: "object", subtype: "null", value: "null", __proto__: null }, propertiesThreshold);
950                continue;
951            }
952
953            var maxLength = 100;
954            if (InjectedScript.primitiveTypes[type]) {
955                if (type === "string" && value.length > maxLength)
956                    value = this._abbreviateString(value, maxLength, true);
957                this._appendPropertyPreview(preview, { name: name, type: type, value: toStringDescription(value), __proto__: null }, propertiesThreshold);
958                continue;
959            }
960
961            var property = { name: name, type: type, __proto__: null };
962            var subtype = injectedScript._subtype(value);
963            if (subtype)
964                property.subtype = subtype;
965
966            if (secondLevelKeys === null || secondLevelKeys) {
967                var subPreview = this._generatePreview(value, secondLevelKeys || undefined, undefined, isTable);
968                property.valuePreview = subPreview;
969                if (subPreview.overflow)
970                    preview.overflow = true;
971            } else {
972                var description = "";
973                if (type !== "function")
974                    description = this._abbreviateString(/** @type {string} */ (injectedScript._describe(value)), maxLength, subtype === "regexp");
975                property.value = description;
976            }
977            this._appendPropertyPreview(preview, property, propertiesThreshold);
978        }
979    },
980
981    /**
982     * @param {!RuntimeAgent.ObjectPreview} preview
983     * @param {!Object} property
984     * @param {!Object} propertiesThreshold
985     */
986    _appendPropertyPreview: function(preview, property, propertiesThreshold)
987    {
988        if (toString(property.name >>> 0) === property.name)
989            propertiesThreshold.indexes--;
990        else
991            propertiesThreshold.properties--;
992        if (propertiesThreshold.indexes < 0 || propertiesThreshold.properties < 0) {
993            preview.overflow = true;
994        } else {
995            push(preview.properties, property);
996        }
997    },
998
999    /**
1000     * @param {?Array<*>} entries
1001     * @param {!RuntimeAgent.ObjectPreview} preview
1002     * @param {boolean=} skipEntriesPreview
1003     */
1004    _appendEntriesPreview: function(entries, preview, skipEntriesPreview)
1005    {
1006        if (!entries)
1007            return;
1008        if (skipEntriesPreview) {
1009            if (entries.length)
1010                preview.overflow = true;
1011            return;
1012        }
1013        preview.entries = [];
1014        var entriesThreshold = 5;
1015        for (var i = 0; i < entries.length; ++i) {
1016            if (preview.entries.length >= entriesThreshold) {
1017                preview.overflow = true;
1018                break;
1019            }
1020            var entry = nullifyObjectProto(entries[i]);
1021            var previewEntry = {
1022                value: generateValuePreview(entry.value),
1023                __proto__: null
1024            };
1025            if ("key" in entry)
1026                previewEntry.key = generateValuePreview(entry.key);
1027            push(preview.entries, previewEntry);
1028        }
1029
1030        /**
1031         * @param {*} value
1032         * @return {!RuntimeAgent.ObjectPreview}
1033         */
1034        function generateValuePreview(value)
1035        {
1036            var remoteObject = new InjectedScript.RemoteObject(value, undefined, true, undefined, true, undefined, undefined, true);
1037            var valuePreview = remoteObject.preview || remoteObject._createEmptyPreview();
1038            return valuePreview;
1039        }
1040    },
1041
1042    /**
1043     * @param {string} string
1044     * @param {number} maxLength
1045     * @param {boolean=} middle
1046     * @return {string}
1047     */
1048    _abbreviateString: function(string, maxLength, middle)
1049    {
1050        if (string.length <= maxLength)
1051            return string;
1052        if (middle) {
1053            var leftHalf = maxLength >> 1;
1054            var rightHalf = maxLength - leftHalf - 1;
1055            return string.substr(0, leftHalf) + "\u2026" + string.substr(string.length - rightHalf, rightHalf);
1056        }
1057        return string.substr(0, maxLength) + "\u2026";
1058    },
1059
1060    __proto__: null
1061}
1062
1063return injectedScript;
1064})
1065