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