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