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