previewers.js (36450B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 /* global Temporal, TrustedHTML, TrustedScript, TrustedScriptURL */ 8 9 const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js"); 10 loader.lazyRequireGetter( 11 this, 12 "ObjectUtils", 13 "resource://devtools/server/actors/object/utils.js" 14 ); 15 loader.lazyRequireGetter( 16 this, 17 "PropertyIterators", 18 "resource://devtools/server/actors/object/property-iterator.js" 19 ); 20 loader.lazyRequireGetter( 21 this, 22 "propertyDescriptor", 23 "resource://devtools/server/actors/object/property-descriptor.js", 24 true 25 ); 26 27 // Number of items to preview in objects, arrays, maps, sets, lists, 28 // collections, etc. 29 const OBJECT_PREVIEW_MAX_ITEMS = 10; 30 31 const ERROR_CLASSNAMES = new Set([ 32 "Error", 33 "EvalError", 34 "RangeError", 35 "ReferenceError", 36 "SyntaxError", 37 "TypeError", 38 "URIError", 39 "InternalError", 40 "AggregateError", 41 "CompileError", 42 "DebuggeeWouldRun", 43 "LinkError", 44 "RuntimeError", 45 "Exception", // This related to Components.Exception() 46 "SuppressedError", 47 ]); 48 const ARRAY_LIKE_CLASSNAMES = new Set([ 49 "DOMStringList", 50 "DOMTokenList", 51 "CSSRuleList", 52 "MediaList", 53 "StyleSheetList", 54 "NamedNodeMap", 55 "FileList", 56 "NodeList", 57 ]); 58 const OBJECT_WITH_URL_CLASSNAMES = new Set([ 59 "CSSImportRule", 60 "CSSStyleSheet", 61 "Location", 62 "TrustedScriptURL" 63 ]); 64 65 /** 66 * Functions for adding information to ObjectActor grips for the purpose of 67 * having customized output. This object holds arrays mapped by 68 * Debugger.Object.prototype.class. 69 * 70 * In each array you can add functions that take three 71 * arguments: 72 * - the ObjectActor instance and its hooks to make a preview for, 73 * - the grip object being prepared for the client, 74 * - the depth of the object compared to the top level object, 75 * when we are inspecting nested attributes. 76 * 77 * Functions must return false if they cannot provide preview 78 * information for the debugger object, or true otherwise. 79 */ 80 const previewers = { 81 String: [ 82 function(objectActor, grip, depth) { 83 return wrappedPrimitivePreviewer( 84 String, 85 objectActor, 86 grip, 87 depth 88 ); 89 }, 90 ], 91 92 Boolean: [ 93 function(objectActor, grip, depth) { 94 return wrappedPrimitivePreviewer( 95 Boolean, 96 objectActor, 97 grip, 98 depth 99 ); 100 }, 101 ], 102 103 Number: [ 104 function(objectActor, grip, depth) { 105 return wrappedPrimitivePreviewer( 106 Number, 107 objectActor, 108 grip, 109 depth 110 ); 111 }, 112 ], 113 114 Symbol: [ 115 function(objectActor, grip, depth) { 116 return wrappedPrimitivePreviewer( 117 Symbol, 118 objectActor, 119 grip, 120 depth 121 ); 122 }, 123 ], 124 125 Function: [ 126 function(objectActor, grip, depth) { 127 const { obj } = objectActor; 128 if (obj.name) { 129 grip.name = obj.name; 130 } 131 132 if (obj.displayName) { 133 grip.displayName = obj.displayName.substr(0, 500); 134 } 135 136 if (obj.parameterNames) { 137 grip.parameterNames = obj.parameterNames; 138 } 139 140 // Check if the developer has added a de-facto standard displayName 141 // property for us to use. 142 let userDisplayName; 143 try { 144 userDisplayName = obj.getOwnPropertyDescriptor("displayName"); 145 } catch (e) { 146 // The above can throw "permission denied" errors when the debuggee 147 // does not subsume the function's compartment. 148 } 149 150 if ( 151 userDisplayName && 152 typeof userDisplayName.value == "string" && 153 userDisplayName.value 154 ) { 155 grip.userDisplayName = objectActor.createValueGrip(userDisplayName.value, depth); 156 } 157 158 grip.isAsync = obj.isAsyncFunction; 159 grip.isGenerator = obj.isGeneratorFunction; 160 161 if (obj.script) { 162 // NOTE: Debugger.Script.prototype.startColumn is 1-based. 163 // Convert to 0-based, while keeping the wasm's column (1) as is. 164 // (bug 1863878) 165 const columnBase = obj.script.format === "wasm" ? 0 : 1; 166 grip.location = { 167 url: obj.script.url, 168 line: obj.script.startLine, 169 column: obj.script.startColumn - columnBase, 170 }; 171 } 172 173 return true; 174 }, 175 ], 176 177 RegExp: [ 178 function(objectActor, grip, depth) { 179 let str; 180 if (isWorker) { 181 // For some reason, the following incantation on the worker thread returns "/undefined/undefined" 182 // str = RegExp.prototype.toString.call(objectActor.obj.unsafeDereference()); 183 // 184 // The following method will throw in case of method being overloaded by the page, 185 // and a more generic previewer will render the object. 186 try { 187 str = DevToolsUtils.callPropertyOnObject(objectActor.obj, "toString"); 188 } catch(e) { 189 // Ensure displaying something in case of error. 190 // Otherwise this would render an object with an empty label 191 grip.displayString = "RegExp with overloaded toString"; 192 } 193 } else { 194 const { RegExp } = objectActor.targetActor.targetGlobal; 195 str = RegExp.prototype.toString.call(objectActor.safeRawObj); 196 } 197 198 if (typeof str != "string") { 199 return false; 200 } 201 202 grip.displayString = objectActor.createValueGrip(str, depth); 203 return true; 204 }, 205 ], 206 207 Date: [ 208 function(objectActor, grip, depth) { 209 let time; 210 if (isWorker) { 211 // Also, targetGlobal is an opaque wrapper, from which we can't access its Date object, 212 // so fallback to the privileged one 213 // 214 // In worker objectActor.safeRawObj is considered unsafe and is null, 215 // so retrieve the objectActor.rawObj object directly from Debugger.Object.unsafeDereference 216 time = Date.prototype.getTime.call(objectActor.rawObj); 217 } else { 218 const { Date } = objectActor.targetActor.targetGlobal; 219 time = Date.prototype.getTime.call(objectActor.safeRawObj); 220 } 221 if (typeof time != "number") { 222 return false; 223 } 224 225 grip.preview = { 226 timestamp: objectActor.createValueGrip(time, depth), 227 }; 228 return true; 229 }, 230 ], 231 232 "Temporal.Instant": [ 233 function(objectActor, grip, _depth) { 234 temporalPreviewer(Temporal.Instant, objectActor, grip); 235 return true; 236 }, 237 ], 238 239 "Temporal.PlainDate": [ 240 function(objectActor, grip, _depth) { 241 temporalPreviewer(Temporal.PlainDate, objectActor, grip); 242 return true; 243 }, 244 ], 245 246 "Temporal.PlainDateTime": [ 247 function(objectActor, grip, _depth) { 248 temporalPreviewer(Temporal.PlainDateTime, objectActor, grip); 249 return true; 250 }, 251 ], 252 253 "Temporal.PlainMonthDay": [ 254 function(objectActor, grip, _depth) { 255 temporalPreviewer(Temporal.PlainMonthDay, objectActor, grip); 256 return true; 257 }, 258 ], 259 260 "Temporal.PlainTime": [ 261 function(objectActor, grip, _depth) { 262 temporalPreviewer(Temporal.PlainTime, objectActor, grip); 263 return true; 264 }, 265 ], 266 267 "Temporal.PlainYearMonth": [ 268 function(objectActor, grip, _depth) { 269 temporalPreviewer(Temporal.PlainYearMonth, objectActor, grip); 270 return true; 271 }, 272 ], 273 274 "Temporal.ZonedDateTime": [ 275 function(objectActor, grip, _depth) { 276 temporalPreviewer(Temporal.ZonedDateTime, objectActor, grip); 277 return true; 278 }, 279 ], 280 281 "Temporal.Duration": [ 282 function(objectActor, grip, _depth) { 283 temporalPreviewer(Temporal.Duration, objectActor, grip); 284 return true; 285 }, 286 ], 287 288 TrustedHTML: [ 289 function(objectActor, grip, depth) { 290 const text = TrustedHTML.prototype.toString.call( 291 // In worker objectActor.safeRawObj is considered unsafe and is null 292 objectActor.safeRawObj || objectActor.rawObj 293 ); 294 295 grip.preview = { 296 kind: "ObjectWithText", 297 text: objectActor.createValueGrip(text, depth) 298 }; 299 return true; 300 }, 301 ], 302 303 TrustedScript: [ 304 function(objectActor, grip, depth) { 305 const text = TrustedScript.prototype.toString.call( 306 // In worker objectActor.safeRawObj is considered unsafe and is null 307 objectActor.safeRawObj || objectActor.rawObj 308 ); 309 310 grip.preview = { 311 kind: "ObjectWithText", 312 text: objectActor.createValueGrip(text, depth) 313 }; 314 return true; 315 }, 316 ], 317 318 TrustedScriptURL: [ 319 function(objectActor, grip, depth) { 320 const url = TrustedScriptURL.prototype.toString.call( 321 // In worker objectActor.safeRawObj is considered unsafe and is null 322 objectActor.safeRawObj || objectActor.rawObj 323 ); 324 325 grip.preview = { 326 kind: "ObjectWithURL", 327 url: objectActor.createValueGrip(url, depth) 328 }; 329 return true; 330 }, 331 ], 332 333 Array: [ 334 function(objectActor, grip, depth) { 335 const length = ObjectUtils.getArrayLength(objectActor.obj); 336 337 grip.preview = { 338 kind: "ArrayLike", 339 length, 340 }; 341 342 if (depth > 1) { 343 return true; 344 } 345 346 const { obj, rawObj } = objectActor; 347 const items = (grip.preview.items = []); 348 349 for (let i = 0; i < length; ++i) { 350 if (rawObj && !isWorker) { 351 // Array Xrays filter out various possibly-unsafe properties (like 352 // functions, and claim that the value is undefined instead. This 353 // is generally the right thing for privileged code accessing untrusted 354 // objects, but quite confusing for Object previews. So we manually 355 // override this protection by waiving Xrays on the array, and re-applying 356 // Xrays on any indexed value props that we pull off of it. 357 const desc = Object.getOwnPropertyDescriptor(Cu.waiveXrays(rawObj), i); 358 if (desc && !desc.get && !desc.set) { 359 let value = Cu.unwaiveXrays(desc.value); 360 value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, value); 361 items.push(objectActor.createValueGrip(value, depth)); 362 } else if (!desc) { 363 items.push(null); 364 } else { 365 const item = {}; 366 if (desc.get) { 367 let getter = Cu.unwaiveXrays(desc.get); 368 getter = ObjectUtils.makeDebuggeeValueIfNeeded(obj, getter); 369 item.get = objectActor.createValueGrip(getter, depth); 370 } 371 if (desc.set) { 372 let setter = Cu.unwaiveXrays(desc.set); 373 setter = ObjectUtils.makeDebuggeeValueIfNeeded(obj, setter); 374 item.set = objectActor.createValueGrip(setter, depth); 375 } 376 items.push(item); 377 } 378 } else if (rawObj && !obj.getOwnPropertyDescriptor(i)) { 379 items.push(null); 380 } else { 381 // Workers do not have access to Cu. 382 const value = DevToolsUtils.getProperty(obj, i); 383 items.push(objectActor.createValueGrip(value, depth)); 384 } 385 386 if (items.length == OBJECT_PREVIEW_MAX_ITEMS) { 387 break; 388 } 389 } 390 391 return true; 392 }, 393 ], 394 395 Set: [ 396 function(objectActor, grip, depth) { 397 const size = DevToolsUtils.getProperty(objectActor.obj, "size"); 398 if (typeof size != "number") { 399 return false; 400 } 401 402 grip.preview = { 403 kind: "ArrayLike", 404 length: size, 405 }; 406 407 // Avoid recursive object grips. 408 if (depth > 1) { 409 return true; 410 } 411 412 const items = (grip.preview.items = []); 413 for (const item of PropertyIterators.enumSetEntries(objectActor, depth)) { 414 items.push(item); 415 if (items.length == OBJECT_PREVIEW_MAX_ITEMS) { 416 break; 417 } 418 } 419 420 return true; 421 }, 422 ], 423 424 WeakSet: [ 425 function(objectActor, grip, depth) { 426 const enumEntries = PropertyIterators.enumWeakSetEntries(objectActor, depth); 427 428 grip.preview = { 429 kind: "ArrayLike", 430 length: enumEntries.size, 431 }; 432 433 // Avoid recursive object grips. 434 if (depth > 1) { 435 return true; 436 } 437 438 const items = (grip.preview.items = []); 439 for (const item of enumEntries) { 440 items.push(item); 441 if (items.length == OBJECT_PREVIEW_MAX_ITEMS) { 442 break; 443 } 444 } 445 446 return true; 447 }, 448 ], 449 450 Map: [ 451 function(objectActor, grip, depth) { 452 const size = DevToolsUtils.getProperty(objectActor.obj, "size"); 453 if (typeof size != "number") { 454 return false; 455 } 456 457 grip.preview = { 458 kind: "MapLike", 459 size, 460 }; 461 462 if (depth > 1) { 463 return true; 464 } 465 466 const entries = (grip.preview.entries = []); 467 for (const entry of PropertyIterators.enumMapEntries(objectActor, depth)) { 468 entries.push(entry); 469 if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 470 break; 471 } 472 } 473 474 return true; 475 }, 476 ], 477 478 WeakMap: [ 479 function(objectActor, grip, depth) { 480 const enumEntries = PropertyIterators.enumWeakMapEntries(objectActor, depth); 481 482 grip.preview = { 483 kind: "MapLike", 484 size: enumEntries.size, 485 }; 486 487 if (depth > 1) { 488 return true; 489 } 490 491 const entries = (grip.preview.entries = []); 492 for (const entry of enumEntries) { 493 entries.push(entry); 494 if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 495 break; 496 } 497 } 498 499 return true; 500 }, 501 ], 502 503 URLSearchParams: [ 504 function(objectActor, grip, depth) { 505 const enumEntries = PropertyIterators.enumURLSearchParamsEntries(objectActor, depth); 506 507 grip.preview = { 508 kind: "MapLike", 509 size: enumEntries.size, 510 }; 511 512 if (depth > 1) { 513 return true; 514 } 515 516 const entries = (grip.preview.entries = []); 517 for (const entry of enumEntries) { 518 entries.push(entry); 519 if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 520 break; 521 } 522 } 523 524 return true; 525 }, 526 ], 527 528 FormData: [ 529 function(objectActor, grip, depth) { 530 const enumEntries = PropertyIterators.enumFormDataEntries(objectActor, depth); 531 532 grip.preview = { 533 kind: "MapLike", 534 size: enumEntries.size, 535 }; 536 537 if (depth > 1) { 538 return true; 539 } 540 541 const entries = (grip.preview.entries = []); 542 for (const entry of enumEntries) { 543 entries.push(entry); 544 if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 545 break; 546 } 547 } 548 549 return true; 550 }, 551 ], 552 553 Headers: [ 554 function(objectActor, grip, depth) { 555 // Bug 1863776: Headers can't be yet previewed from workers 556 if (isWorker) { 557 return false; 558 } 559 const enumEntries = PropertyIterators.enumHeadersEntries(objectActor, depth); 560 561 grip.preview = { 562 kind: "MapLike", 563 size: enumEntries.size, 564 }; 565 566 if (depth > 1) { 567 return true; 568 } 569 570 const entries = (grip.preview.entries = []); 571 for (const entry of enumEntries) { 572 entries.push(entry); 573 if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 574 break; 575 } 576 } 577 578 return true; 579 }, 580 ], 581 582 583 HighlightRegistry: [ 584 function(objectActor, grip, depth) { 585 const enumEntries = PropertyIterators.enumHighlightRegistryEntries(objectActor, depth); 586 587 grip.preview = { 588 kind: "MapLike", 589 size: enumEntries.size, 590 }; 591 592 if (depth > 1) { 593 return true; 594 } 595 596 const entries = (grip.preview.entries = []); 597 for (const entry of enumEntries) { 598 entries.push(entry); 599 if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 600 break; 601 } 602 } 603 604 return true; 605 }, 606 ], 607 608 MIDIInputMap: [ 609 function(objectActor, grip, depth) { 610 const enumEntries = PropertyIterators.enumMidiInputMapEntries( 611 objectActor, 612 depth 613 ); 614 615 grip.preview = { 616 kind: "MapLike", 617 size: enumEntries.size, 618 }; 619 620 if (depth > 1) { 621 return true; 622 } 623 624 const entries = (grip.preview.entries = []); 625 for (const entry of enumEntries) { 626 entries.push(entry); 627 if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 628 break; 629 } 630 } 631 632 return true; 633 }, 634 ], 635 636 MIDIOutputMap: [ 637 function(objectActor, grip, depth) { 638 const enumEntries = PropertyIterators.enumMidiOutputMapEntries( 639 objectActor, 640 depth 641 ); 642 643 grip.preview = { 644 kind: "MapLike", 645 size: enumEntries.size, 646 }; 647 648 if (depth > 1) { 649 return true; 650 } 651 652 const entries = (grip.preview.entries = []); 653 for (const entry of enumEntries) { 654 entries.push(entry); 655 if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 656 break; 657 } 658 } 659 660 return true; 661 }, 662 ], 663 664 DOMStringMap: [ 665 function(objectActor, grip, depth) { 666 const { obj, safeRawObj } = objectActor; 667 if (!safeRawObj) { 668 return false; 669 } 670 671 const keys = obj.getOwnPropertyNames(); 672 grip.preview = { 673 kind: "MapLike", 674 size: keys.length, 675 }; 676 677 if (depth > 1) { 678 return true; 679 } 680 681 const entries = (grip.preview.entries = []); 682 for (const key of keys) { 683 const value = ObjectUtils.makeDebuggeeValueIfNeeded(obj, safeRawObj[key]); 684 entries.push([key, objectActor.createValueGrip(value, depth)]); 685 if (entries.length == OBJECT_PREVIEW_MAX_ITEMS) { 686 break; 687 } 688 } 689 690 return true; 691 }, 692 ], 693 694 Promise: [ 695 function(objectActor, grip, depth) { 696 const { state, value, reason } = ObjectUtils.getPromiseState(objectActor.obj); 697 const ownProperties = Object.create(null); 698 ownProperties["<state>"] = { value: state }; 699 let ownPropertiesLength = 1; 700 701 // Only expose <value> or <reason> in top-level promises, to avoid recursion. 702 // <state> is not problematic because it's a string. 703 if (depth === 1) { 704 if (state == "fulfilled") { 705 ownProperties["<value>"] = { value: objectActor.createValueGrip(value, depth) }; 706 ++ownPropertiesLength; 707 } else if (state == "rejected") { 708 ownProperties["<reason>"] = { value: objectActor.createValueGrip(reason, depth) }; 709 ++ownPropertiesLength; 710 } 711 } 712 713 grip.preview = { 714 kind: "Object", 715 ownProperties, 716 ownPropertiesLength, 717 }; 718 719 return true; 720 }, 721 ], 722 723 Proxy: [ 724 function(objectActor, grip, depth) { 725 // Only preview top-level proxies, avoiding recursion. Otherwise, since both the 726 // target and handler can also be proxies, we could get an exponential behavior. 727 if (depth > 1) { 728 return true; 729 } 730 731 const { obj } = objectActor; 732 733 // The `isProxy` getter of the debuggee object only detects proxies without 734 // security wrappers. If false, the target and handler are not available. 735 const hasTargetAndHandler = obj.isProxy; 736 737 grip.preview = { 738 kind: "Object", 739 ownProperties: Object.create(null), 740 ownPropertiesLength: 2 * hasTargetAndHandler, 741 }; 742 743 if (hasTargetAndHandler) { 744 Object.assign(grip.preview.ownProperties, { 745 "<target>": { value: objectActor.createValueGrip(obj.proxyTarget, depth) }, 746 "<handler>": { value: objectActor.createValueGrip(obj.proxyHandler, depth) }, 747 }); 748 } 749 750 return true; 751 }, 752 ], 753 754 CustomStateSet: [ 755 function(objectActor, grip, depth) { 756 const size = DevToolsUtils.getProperty(objectActor.obj, "size"); 757 if (typeof size != "number") { 758 return false; 759 } 760 761 grip.preview = { 762 kind: "ArrayLike", 763 length: size, 764 }; 765 766 const items = (grip.preview.items = []); 767 for (const item of PropertyIterators.enumCustomStateSetEntries(objectActor, depth)) { 768 items.push(item); 769 if (items.length == OBJECT_PREVIEW_MAX_ITEMS) { 770 break; 771 } 772 } 773 774 return true; 775 }, 776 ], 777 }; 778 779 /** 780 * Generic previewer for classes wrapping primitives, like String, 781 * Number and Boolean. 782 * 783 * @param object classObj 784 * The class to expect, eg. String. The valueOf() method of the class is 785 * invoked on the given object. 786 * @param ObjectActor objectActor 787 * The object actor 788 * @param Object grip 789 * The result grip to fill in 790 * @param Number depth 791 * Depth of the object compared to the top level object, 792 * when we are inspecting nested attributes. 793 * @return Booolean true if the object was handled, false otherwise 794 */ 795 function wrappedPrimitivePreviewer( 796 classObj, 797 objectActor, 798 grip, 799 depth 800 ) { 801 const { safeRawObj } = objectActor; 802 let v = null; 803 try { 804 v = classObj.prototype.valueOf.call(safeRawObj); 805 } catch (ex) { 806 // valueOf() can throw if the raw JS object is "misbehaved". 807 return false; 808 } 809 810 if (v === null) { 811 return false; 812 } 813 814 const canHandle = GenericObject(objectActor, grip, depth); 815 if (!canHandle) { 816 return false; 817 } 818 819 grip.preview.wrappedValue = objectActor.createValueGrip( 820 ObjectUtils.makeDebuggeeValueIfNeeded(objectActor.obj, v), 821 depth 822 ); 823 return true; 824 } 825 826 /** 827 * Previewer for Temporal objects 828 * 829 * @param cls 830 * The class of the object we're previewing (e.g. `Temporal.Instant`) 831 * @param ObjectActor objectActor 832 * The object actor 833 * @param Object grip 834 * The result grip to fill in 835 */ 836 function temporalPreviewer(cls, objectActor, grip) { 837 grip.preview = { 838 kind: "ObjectWithText", 839 text: cls.prototype.toString.call( 840 // In worker objectActor.safeRawObj is considered unsafe and is null 841 objectActor.safeRawObj || objectActor.rawObj 842 ) 843 } 844 } 845 846 /** 847 * @param {ObjectActor} objectActor 848 * @param {object} grip: The grip built by the objectActor, for which we need to populate 849 * the `preview` property. 850 * @param {number} depth 851 * Depth of the object compared to the top level object, 852 * when we are inspecting nested attributes. 853 * @returns 854 */ 855 // eslint-disable-next-line complexity 856 function GenericObject(objectActor, grip, depth) { 857 const { obj, safeRawObj } = objectActor; 858 if (grip.preview || grip.displayString || depth > 1) { 859 return false; 860 } 861 862 const preview = (grip.preview = { 863 kind: "Object", 864 ownProperties: Object.create(null), 865 }); 866 867 const names = ObjectUtils.getPropNamesFromObject(obj, safeRawObj); 868 preview.ownPropertiesLength = names.length; 869 870 let length, 871 i = 0; 872 let specialStringBehavior = objectActor.className === "String"; 873 if (specialStringBehavior) { 874 length = DevToolsUtils.getProperty(obj, "length"); 875 if (typeof length != "number") { 876 specialStringBehavior = false; 877 } 878 } 879 880 for (const name of names) { 881 if (specialStringBehavior && /^[0-9]+$/.test(name)) { 882 const num = parseInt(name, 10); 883 if (num.toString() === name && num >= 0 && num < length) { 884 continue; 885 } 886 } 887 888 const desc = propertyDescriptor(objectActor, name, depth, true); 889 if (!desc) { 890 continue; 891 } 892 893 preview.ownProperties[name] = desc; 894 if (++i == OBJECT_PREVIEW_MAX_ITEMS) { 895 break; 896 } 897 } 898 899 if (i === OBJECT_PREVIEW_MAX_ITEMS) { 900 return true; 901 } 902 903 const privatePropertiesSymbols = ObjectUtils.getSafePrivatePropertiesSymbols( 904 obj 905 ); 906 if (privatePropertiesSymbols.length) { 907 preview.privatePropertiesLength = privatePropertiesSymbols.length; 908 preview.privateProperties = []; 909 910 // Retrieve private properties, which are represented as non-enumerable Symbols 911 for (const privateProperty of privatePropertiesSymbols) { 912 if ( 913 !privateProperty.description || 914 !privateProperty.description.startsWith("#") 915 ) { 916 continue; 917 } 918 const descriptor = propertyDescriptor(objectActor, privateProperty, depth); 919 if (!descriptor) { 920 continue; 921 } 922 923 preview.privateProperties.push( 924 Object.assign( 925 { 926 descriptor, 927 }, 928 objectActor.createValueGrip(privateProperty, depth) 929 ) 930 ); 931 932 if (++i == OBJECT_PREVIEW_MAX_ITEMS) { 933 break; 934 } 935 } 936 } 937 938 if (i === OBJECT_PREVIEW_MAX_ITEMS) { 939 return true; 940 } 941 942 const symbols = ObjectUtils.getSafeOwnPropertySymbols(obj); 943 if (symbols.length) { 944 preview.ownSymbolsLength = symbols.length; 945 preview.ownSymbols = []; 946 947 for (const symbol of symbols) { 948 const descriptor = propertyDescriptor(objectActor, symbol, depth, true); 949 if (!descriptor) { 950 continue; 951 } 952 953 preview.ownSymbols.push( 954 Object.assign( 955 { 956 descriptor, 957 }, 958 objectActor.createValueGrip(symbol, depth) 959 ) 960 ); 961 962 if (++i == OBJECT_PREVIEW_MAX_ITEMS) { 963 break; 964 } 965 } 966 } 967 968 if (i === OBJECT_PREVIEW_MAX_ITEMS) { 969 return true; 970 } 971 972 const safeGetterValues = objectActor._findSafeGetterValues( 973 Object.keys(preview.ownProperties), 974 depth, 975 OBJECT_PREVIEW_MAX_ITEMS - i 976 ); 977 if (Object.keys(safeGetterValues).length) { 978 preview.safeGetterValues = safeGetterValues; 979 } 980 981 return true; 982 } 983 984 // Preview functions that do not rely on the object class. 985 previewers.Object = [ 986 function TypedArray(objectActor, grip, depth) { 987 const { obj, className } = objectActor; 988 if (!ObjectUtils.isTypedArray(obj)) { 989 return false; 990 } 991 992 grip.preview = { 993 kind: "ArrayLike", 994 length: ObjectUtils.getArrayLength(obj), 995 }; 996 997 if (depth > 1) { 998 return true; 999 } 1000 1001 const previewLength = Math.min( 1002 OBJECT_PREVIEW_MAX_ITEMS, 1003 grip.preview.length 1004 ); 1005 grip.preview.items = []; 1006 const isBigIntArray = className.startsWith("BigInt") || className.startsWith("BigUint"); 1007 1008 for (let i = 0; i < previewLength; i++) { 1009 const desc = obj.getOwnPropertyDescriptor(i); 1010 if (!desc) { 1011 break; 1012 } 1013 1014 // We need to create grips for items of BigInt arrays. Other typed arrays are fine 1015 // as they hold serializable primitives (Numbers) 1016 const item = isBigIntArray 1017 ? ObjectUtils.createBigIntValueGrip(desc.value) 1018 : desc.value; 1019 grip.preview.items.push(item); 1020 } 1021 1022 return true; 1023 }, 1024 1025 function Error(objectActor, grip, depth) { 1026 if (!ERROR_CLASSNAMES.has(objectActor.className)) { 1027 return false; 1028 } 1029 1030 const { obj, allowSideEffect = false } = objectActor; 1031 1032 // The name and/or message could be getters, and even if it's unsafe, 1033 // we do want to show it to the user, unless the error is muted 1034 // (See Bug 1710694). 1035 const invokeUnsafeGetters = allowSideEffect && !obj.isMutedError; 1036 1037 const name = DevToolsUtils.getProperty(obj, "name", invokeUnsafeGetters); 1038 const msg = DevToolsUtils.getProperty(obj, "message", invokeUnsafeGetters); 1039 const stack = DevToolsUtils.getProperty(obj, "stack"); 1040 const fileName = DevToolsUtils.getProperty(obj, "fileName"); 1041 const lineNumber = DevToolsUtils.getProperty(obj, "lineNumber"); 1042 const columnNumber = DevToolsUtils.getProperty(obj, "columnNumber"); 1043 1044 grip.preview = { 1045 kind: "Error", 1046 name: objectActor.createValueGrip(name, depth), 1047 message: objectActor.createValueGrip(msg, depth), 1048 stack: objectActor.createValueGrip(stack, depth), 1049 fileName: objectActor.createValueGrip(fileName, depth), 1050 lineNumber: objectActor.createValueGrip(lineNumber, depth), 1051 columnNumber: objectActor.createValueGrip(columnNumber, depth), 1052 }; 1053 1054 const errorHasCause = obj.getOwnPropertyNames().includes("cause"); 1055 if (errorHasCause) { 1056 grip.preview.cause = objectActor.createValueGrip( 1057 DevToolsUtils.getProperty(obj, "cause", true), 1058 depth 1059 ); 1060 } 1061 1062 return true; 1063 }, 1064 1065 function CSSMediaRule(objectActor, grip, depth) { 1066 const { safeRawObj } = objectActor; 1067 if (!safeRawObj || objectActor.className != "CSSMediaRule" || isWorker) { 1068 return false; 1069 } 1070 grip.preview = { 1071 kind: "ObjectWithText", 1072 text: objectActor.createValueGrip(safeRawObj.conditionText, depth), 1073 }; 1074 return true; 1075 }, 1076 1077 function CSSStyleRule(objectActor, grip, depth) { 1078 const { safeRawObj } = objectActor; 1079 if (!safeRawObj || objectActor.className != "CSSStyleRule" || isWorker) { 1080 return false; 1081 } 1082 grip.preview = { 1083 kind: "ObjectWithText", 1084 text: objectActor.createValueGrip(safeRawObj.selectorText, depth), 1085 }; 1086 return true; 1087 }, 1088 1089 function ObjectWithURL(objectActor, grip, depth) { 1090 const { safeRawObj } = objectActor; 1091 if (isWorker || !safeRawObj) { 1092 return false; 1093 } 1094 1095 const isWindow = Window.isInstance(safeRawObj); 1096 if (!OBJECT_WITH_URL_CLASSNAMES.has(objectActor.className) && !isWindow) { 1097 return false; 1098 } 1099 1100 let url; 1101 if (isWindow && safeRawObj.location) { 1102 try { 1103 url = safeRawObj.location.href; 1104 } catch(e) { 1105 // This can happen when we have a cross-process window. 1106 // In such case, let's retrieve the url from the iframe. 1107 // For window.top from a remote iframe, there's no way we can't retrieve the URL, 1108 // so return a label that help user know what's going on. 1109 url = safeRawObj.browsingContext?.embedderElement?.src || "Restricted"; 1110 } 1111 } else if (safeRawObj.href) { 1112 url = safeRawObj.href; 1113 } else { 1114 return false; 1115 } 1116 1117 grip.preview = { 1118 kind: "ObjectWithURL", 1119 url: objectActor.createValueGrip(url, depth), 1120 }; 1121 1122 return true; 1123 }, 1124 1125 function ArrayLike(objectActor, grip, depth) { 1126 const { safeRawObj } = objectActor; 1127 if ( 1128 !safeRawObj || 1129 !ARRAY_LIKE_CLASSNAMES.has(objectActor.className) || 1130 typeof safeRawObj.length != "number" || 1131 isWorker 1132 ) { 1133 return false; 1134 } 1135 1136 grip.preview = { 1137 kind: "ArrayLike", 1138 length: safeRawObj.length, 1139 }; 1140 1141 if (depth > 1) { 1142 return true; 1143 } 1144 1145 const items = (grip.preview.items = []); 1146 1147 for ( 1148 let i = 0; 1149 i < safeRawObj.length && items.length < OBJECT_PREVIEW_MAX_ITEMS; 1150 i++ 1151 ) { 1152 const value = ObjectUtils.makeDebuggeeValueIfNeeded(objectActor.obj, safeRawObj[i]); 1153 items.push(objectActor.createValueGrip(value, depth)); 1154 } 1155 1156 return true; 1157 }, 1158 1159 function CSSStyleDeclaration(objectActor, grip, depth) { 1160 const { safeRawObj, className } = objectActor; 1161 if ( 1162 !safeRawObj || 1163 (className != "CSSStyleDeclaration" && className != "CSSStyleProperties") || 1164 isWorker 1165 ) { 1166 return false; 1167 } 1168 1169 grip.preview = { 1170 kind: "MapLike", 1171 size: safeRawObj.length, 1172 }; 1173 1174 const entries = (grip.preview.entries = []); 1175 1176 for (let i = 0; i < OBJECT_PREVIEW_MAX_ITEMS && i < safeRawObj.length; i++) { 1177 const prop = safeRawObj[i]; 1178 const value = safeRawObj.getPropertyValue(prop); 1179 entries.push([prop, objectActor.createValueGrip(value, depth)]); 1180 } 1181 1182 return true; 1183 }, 1184 1185 function DOMNode(objectActor, grip, depth) { 1186 const { safeRawObj } = objectActor; 1187 if ( 1188 objectActor.className == "Object" || 1189 !safeRawObj || 1190 !Node.isInstance(safeRawObj) || 1191 isWorker 1192 ) { 1193 return false; 1194 } 1195 1196 const { obj, className } = objectActor; 1197 1198 const preview = (grip.preview = { 1199 kind: "DOMNode", 1200 nodeType: safeRawObj.nodeType, 1201 nodeName: safeRawObj.nodeName, 1202 isConnected: safeRawObj.isConnected === true, 1203 }); 1204 1205 if (safeRawObj.nodeType == safeRawObj.DOCUMENT_NODE && safeRawObj.location) { 1206 preview.location = objectActor.createValueGrip(safeRawObj.location.href, depth); 1207 } else if (className == "DocumentFragment") { 1208 preview.childNodesLength = safeRawObj.childNodes.length; 1209 1210 if (depth < 2) { 1211 preview.childNodes = []; 1212 for (const node of safeRawObj.childNodes) { 1213 const actor = objectActor.createValueGrip(obj.makeDebuggeeValue(node), depth); 1214 preview.childNodes.push(actor); 1215 if (preview.childNodes.length == OBJECT_PREVIEW_MAX_ITEMS) { 1216 break; 1217 } 1218 } 1219 } 1220 } else if (Element.isInstance(safeRawObj)) { 1221 // For HTML elements (in an HTML document, at least), the nodeName is an 1222 // uppercased version of the actual element name. Check for HTML 1223 // elements, that is elements in the HTML namespace, and lowercase the 1224 // nodeName in that case. 1225 if (safeRawObj.namespaceURI == "http://www.w3.org/1999/xhtml") { 1226 preview.nodeName = preview.nodeName.toLowerCase(); 1227 } 1228 1229 // Add preview for DOM element attributes. 1230 preview.attributes = {}; 1231 preview.attributesLength = safeRawObj.attributes.length; 1232 for (const attr of safeRawObj.attributes) { 1233 preview.attributes[attr.nodeName] = objectActor.createValueGrip(attr.value, depth); 1234 } 1235 1236 // Custom elements may have private properties. Ensure that we provide 1237 // enough information for ObjectInspector to know it should check for 1238 // them. 1239 const privatePropertiesSymbols = ObjectUtils.getSafePrivatePropertiesSymbols( 1240 obj 1241 ); 1242 if (privatePropertiesSymbols.length) { 1243 preview.privatePropertiesLength = privatePropertiesSymbols.length; 1244 } 1245 } else if (className == "Attr") { 1246 preview.value = objectActor.createValueGrip(safeRawObj.value, depth); 1247 } else if ( 1248 className == "Text" || 1249 className == "CDATASection" || 1250 className == "Comment" 1251 ) { 1252 preview.textContent = objectActor.createValueGrip(safeRawObj.textContent, depth); 1253 } 1254 1255 return true; 1256 }, 1257 1258 function DOMEvent(objectActor, grip, depth) { 1259 const { safeRawObj } = objectActor; 1260 if (!safeRawObj || !Event.isInstance(safeRawObj) || isWorker) { 1261 return false; 1262 } 1263 1264 const { obj, className } = objectActor; 1265 const preview = (grip.preview = { 1266 kind: "DOMEvent", 1267 type: safeRawObj.type, 1268 properties: Object.create(null), 1269 }); 1270 1271 if (depth < 2) { 1272 const target = obj.makeDebuggeeValue(safeRawObj.target); 1273 preview.target = objectActor.createValueGrip(target, depth); 1274 } 1275 1276 if (className == "KeyboardEvent") { 1277 preview.eventKind = "key"; 1278 preview.modifiers = ObjectUtils.getModifiersForEvent(safeRawObj); 1279 } 1280 1281 const props = ObjectUtils.getPropsForEvent(className); 1282 1283 // Add event-specific properties. 1284 for (const prop of props) { 1285 let value = safeRawObj[prop]; 1286 if (ObjectUtils.isObjectOrFunction(value)) { 1287 // Skip properties pointing to objects. 1288 if (depth > 1) { 1289 continue; 1290 } 1291 value = obj.makeDebuggeeValue(value); 1292 } 1293 preview.properties[prop] = objectActor.createValueGrip(value, depth); 1294 } 1295 1296 // Add any properties we find on the event object. 1297 if (!props.length) { 1298 let i = 0; 1299 for (const prop in safeRawObj) { 1300 let value = safeRawObj[prop]; 1301 if ( 1302 prop == "target" || 1303 prop == "type" || 1304 value === null || 1305 typeof value == "function" 1306 ) { 1307 continue; 1308 } 1309 if (value && typeof value == "object") { 1310 if (depth > 1) { 1311 continue; 1312 } 1313 value = obj.makeDebuggeeValue(value); 1314 } 1315 preview.properties[prop] = objectActor.createValueGrip(value, depth); 1316 if (++i == OBJECT_PREVIEW_MAX_ITEMS) { 1317 break; 1318 } 1319 } 1320 } 1321 1322 return true; 1323 }, 1324 1325 function DOMException(objectActor, grip, depth) { 1326 const { safeRawObj } = objectActor; 1327 if (!safeRawObj || objectActor.className !== "DOMException" || isWorker) { 1328 return false; 1329 } 1330 1331 grip.preview = { 1332 kind: "DOMException", 1333 name: objectActor.createValueGrip(safeRawObj.name, depth), 1334 message: objectActor.createValueGrip(safeRawObj.message, depth), 1335 code: objectActor.createValueGrip(safeRawObj.code, depth), 1336 result: objectActor.createValueGrip(safeRawObj.result, depth), 1337 filename: objectActor.createValueGrip(safeRawObj.filename, depth), 1338 lineNumber: objectActor.createValueGrip(safeRawObj.lineNumber, depth), 1339 columnNumber: objectActor.createValueGrip(safeRawObj.columnNumber, depth), 1340 stack: objectActor.createValueGrip(safeRawObj.stack, depth), 1341 }; 1342 1343 return true; 1344 }, 1345 1346 function Object(objectActor, grip, depth) { 1347 return GenericObject(objectActor, grip, depth); 1348 }, 1349 ]; 1350 1351 module.exports = previewers;