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