blob: f3c8d6b96e1e3745cfce176fb09f0780a5b03b71 [file] [log] [blame]
Ben Murdochf3b273f2017-01-17 12:11:28 +00001/*
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 /**
Ben Murdochf3b273f2017-01-17 12:11:28 +0000263 * @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})