1 /* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * Copyright (C) 2009 Joseph Pecoraro 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 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 /** 28 * @constructor 29 * @extends {WebInspector.PropertiesSection} 30 * @param {!WebInspector.RemoteObject} object 31 * @param {?string|!Element=} title 32 * @param {string=} subtitle 33 * @param {?string=} emptyPlaceholder 34 * @param {boolean=} ignoreHasOwnProperty 35 * @param {!Array.<!WebInspector.RemoteObjectProperty>=} extraProperties 36 * @param {function(new:TreeElement, !WebInspector.RemoteObjectProperty)=} treeElementConstructor 37 */ 38 WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor) 39 { 40 this._emptyPlaceholder = emptyPlaceholder; 41 this.object = object; 42 this.ignoreHasOwnProperty = ignoreHasOwnProperty; 43 this.extraProperties = extraProperties; 44 this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement; 45 this.editable = true; 46 this.skipProto = false; 47 48 WebInspector.PropertiesSection.call(this, title || "", subtitle); 49 } 50 51 WebInspector.ObjectPropertiesSection._arrayLoadThreshold = 100; 52 53 WebInspector.ObjectPropertiesSection.prototype = { 54 enableContextMenu: function() 55 { 56 this.element.addEventListener("contextmenu", this._contextMenuEventFired.bind(this), false); 57 }, 58 59 _contextMenuEventFired: function(event) 60 { 61 var contextMenu = new WebInspector.ContextMenu(event); 62 contextMenu.appendApplicableItems(this.object); 63 contextMenu.show(); 64 }, 65 66 onpopulate: function() 67 { 68 this.update(); 69 }, 70 71 update: function() 72 { 73 if (this.object.arrayLength() > WebInspector.ObjectPropertiesSection._arrayLoadThreshold) { 74 this.propertiesTreeOutline.removeChildren(); 75 WebInspector.ArrayGroupingTreeElement._populateArray(this.propertiesTreeOutline, this.object, 0, this.object.arrayLength() - 1); 76 return; 77 } 78 79 /** 80 * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties 81 * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties 82 * @this {WebInspector.ObjectPropertiesSection} 83 */ 84 function callback(properties, internalProperties) 85 { 86 if (!properties) 87 return; 88 this.updateProperties(properties, internalProperties); 89 } 90 91 WebInspector.RemoteObject.loadFromObject(this.object, !!this.ignoreHasOwnProperty, callback.bind(this)); 92 }, 93 94 updateProperties: function(properties, internalProperties, rootTreeElementConstructor, rootPropertyComparer) 95 { 96 if (!rootTreeElementConstructor) 97 rootTreeElementConstructor = this.treeElementConstructor; 98 99 if (!rootPropertyComparer) 100 rootPropertyComparer = WebInspector.ObjectPropertiesSection.CompareProperties; 101 102 if (this.extraProperties) { 103 for (var i = 0; i < this.extraProperties.length; ++i) 104 properties.push(this.extraProperties[i]); 105 } 106 107 this.propertiesTreeOutline.removeChildren(); 108 109 WebInspector.ObjectPropertyTreeElement.populateWithProperties(this.propertiesTreeOutline, 110 properties, internalProperties, 111 rootTreeElementConstructor, rootPropertyComparer, 112 this.skipProto, this.object, this._emptyPlaceholder); 113 114 this.propertiesForTest = properties; 115 }, 116 117 __proto__: WebInspector.PropertiesSection.prototype 118 } 119 120 /** 121 * @param {!WebInspector.RemoteObjectProperty} propertyA 122 * @param {!WebInspector.RemoteObjectProperty} propertyB 123 * @return {number} 124 */ 125 WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB) 126 { 127 var a = propertyA.name; 128 var b = propertyB.name; 129 if (a === "__proto__") 130 return 1; 131 if (b === "__proto__") 132 return -1; 133 if (propertyA.symbol && !propertyB.symbol) 134 return 1; 135 if (propertyB.symbol && !propertyA.symbol) 136 return -1; 137 return String.naturalOrderComparator(a, b); 138 } 139 140 /** 141 * @constructor 142 * @extends {TreeElement} 143 * @param {!WebInspector.RemoteObjectProperty} property 144 */ 145 WebInspector.ObjectPropertyTreeElement = function(property) 146 { 147 this.property = property; 148 149 // Pass an empty title, the title gets made later in onattach. 150 TreeElement.call(this, "", null, false); 151 this.toggleOnClick = true; 152 this.selectable = false; 153 } 154 155 WebInspector.ObjectPropertyTreeElement.prototype = { 156 onpopulate: function() 157 { 158 var propertyValue = /** @type {!WebInspector.RemoteObject} */ (this.property.value); 159 console.assert(propertyValue); 160 WebInspector.ObjectPropertyTreeElement.populate(this, propertyValue); 161 }, 162 163 /** 164 * @override 165 * @return {boolean} 166 */ 167 ondblclick: function(event) 168 { 169 if (this.property.writable || this.property.setter) 170 this.startEditing(event); 171 return false; 172 }, 173 174 /** 175 * @override 176 */ 177 onattach: function() 178 { 179 this.update(); 180 }, 181 182 update: function() 183 { 184 this.nameElement = document.createElementWithClass("span", "name"); 185 var name = this.property.name; 186 if (/^\s|\s$|^$|\n/.test(name)) 187 this.nameElement.createTextChildren("\"", name.replace(/\n/g, "\u21B5"), "\""); 188 else 189 this.nameElement.textContent = name; 190 if (!this.property.enumerable) 191 this.nameElement.classList.add("dimmed"); 192 if (this.property.isAccessorProperty()) 193 this.nameElement.classList.add("properties-accessor-property-name"); 194 if (this.property.symbol) 195 this.nameElement.addEventListener("contextmenu", this._contextMenuFired.bind(this, this.property.symbol), false); 196 197 var separatorElement = document.createElementWithClass("span", "separator"); 198 separatorElement.textContent = ": "; 199 200 if (this.property.value) { 201 this.valueElement = document.createElementWithClass("span", "value"); 202 var type = this.property.value.type; 203 var subtype = this.property.value.subtype; 204 var description = this.property.value.description; 205 var prefix; 206 var valueText; 207 var suffix; 208 if (this.property.wasThrown) { 209 prefix = "[Exception: "; 210 valueText = description; 211 suffix = "]"; 212 } else if (type === "string" && typeof description === "string") { 213 // Render \n as a nice unicode cr symbol. 214 prefix = "\""; 215 valueText = description.replace(/\n/g, "\u21B5"); 216 suffix = "\""; 217 this.valueElement._originalTextContent = "\"" + description + "\""; 218 } else if (type === "function" && typeof description === "string") { 219 // Render function description until the first \n. 220 valueText = /.*/.exec(description)[0].replace(/\s+$/g, ""); 221 this.valueElement._originalTextContent = description; 222 } else if (type !== "object" || subtype !== "node") { 223 valueText = description; 224 } 225 this.valueElement.setTextContentTruncatedIfNeeded(valueText || ""); 226 if (prefix) 227 this.valueElement.insertBefore(document.createTextNode(prefix), this.valueElement.firstChild); 228 if (suffix) 229 this.valueElement.createTextChild(suffix); 230 231 if (this.property.wasThrown) 232 this.valueElement.classList.add("error"); 233 if (subtype || type) 234 this.valueElement.classList.add("console-formatted-" + (subtype || type)); 235 236 this.valueElement.addEventListener("contextmenu", this._contextMenuFired.bind(this, this.property.value), false); 237 if (type === "object" && subtype === "node" && description) { 238 WebInspector.DOMPresentationUtils.createSpansForNodeTitle(this.valueElement, description); 239 this.valueElement.addEventListener("mousemove", this._mouseMove.bind(this, this.property.value), false); 240 this.valueElement.addEventListener("mouseout", this._mouseOut.bind(this, this.property.value), false); 241 } else { 242 this.valueElement.title = description || ""; 243 } 244 245 this.listItemElement.removeChildren(); 246 247 this.hasChildren = this.property.value.hasChildren && !this.property.wasThrown; 248 } else { 249 if (this.property.getter) { 250 this.valueElement = WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan(this.property.parentObject, [this.property.name], this._onInvokeGetterClick.bind(this)); 251 } else { 252 this.valueElement = document.createElementWithClass("span", "console-formatted-undefined"); 253 this.valueElement.textContent = WebInspector.UIString("<unreadable>"); 254 this.valueElement.title = WebInspector.UIString("No property getter"); 255 } 256 } 257 258 this.listItemElement.appendChildren(this.nameElement, separatorElement, this.valueElement); 259 }, 260 261 _contextMenuFired: function(value, event) 262 { 263 var contextMenu = new WebInspector.ContextMenu(event); 264 this.populateContextMenu(contextMenu); 265 contextMenu.appendApplicableItems(value); 266 contextMenu.show(); 267 }, 268 269 /** 270 * @param {!WebInspector.ContextMenu} contextMenu 271 */ 272 populateContextMenu: function(contextMenu) 273 { 274 }, 275 276 _mouseMove: function(event) 277 { 278 this.property.value.highlightAsDOMNode(); 279 }, 280 281 _mouseOut: function(event) 282 { 283 this.property.value.hideDOMNodeHighlight(); 284 }, 285 286 updateSiblings: function() 287 { 288 if (this.parent.root) 289 this.treeOutline.section.update(); 290 else 291 this.parent.shouldRefreshChildren = true; 292 }, 293 294 /** 295 * @return {boolean} 296 */ 297 renderPromptAsBlock: function() 298 { 299 return false; 300 }, 301 302 /** 303 * @return {{element: !Element, value: (string|undefined)}} 304 */ 305 elementAndValueToEdit: function() 306 { 307 return { 308 element: this.valueElement, 309 value: (typeof this.valueElement._originalTextContent === "string") ? this.valueElement._originalTextContent : undefined 310 }; 311 }, 312 313 /** 314 * @param {!Event=} event 315 */ 316 startEditing: function(event) 317 { 318 var elementAndValueToEdit = this.elementAndValueToEdit(); 319 var elementToEdit = elementAndValueToEdit.element; 320 var valueToEdit = elementAndValueToEdit.value; 321 322 if (WebInspector.isBeingEdited(elementToEdit) || !this.treeOutline.section.editable || this._readOnly) 323 return; 324 325 // Edit original source. 326 if (typeof valueToEdit !== "undefined") 327 elementToEdit.setTextContentTruncatedIfNeeded(valueToEdit, WebInspector.UIString("<string is too large to edit>")); 328 329 var context = { expanded: this.expanded, elementToEdit: elementToEdit, previousContent: elementToEdit.textContent }; 330 331 // Lie about our children to prevent expanding on double click and to collapse subproperties. 332 this.hasChildren = false; 333 334 this.listItemElement.classList.add("editing-sub-part"); 335 336 this._prompt = new WebInspector.ObjectPropertyPrompt(this.renderPromptAsBlock()); 337 338 /** 339 * @this {WebInspector.ObjectPropertyTreeElement} 340 */ 341 function blurListener() 342 { 343 this.editingCommitted(null, elementToEdit.textContent, context.previousContent, context); 344 } 345 346 var proxyElement = this._prompt.attachAndStartEditing(elementToEdit, blurListener.bind(this)); 347 window.getSelection().setBaseAndExtent(elementToEdit, 0, elementToEdit, 1); 348 proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this, context), false); 349 }, 350 351 /** 352 * @return {boolean} 353 */ 354 isEditing: function() 355 { 356 return !!this._prompt; 357 }, 358 359 editingEnded: function(context) 360 { 361 this._prompt.detach(); 362 delete this._prompt; 363 364 this.listItemElement.scrollLeft = 0; 365 this.listItemElement.classList.remove("editing-sub-part"); 366 if (context.expanded) 367 this.expand(); 368 }, 369 370 editingCancelled: function(element, context) 371 { 372 this.editingEnded(context); 373 this.update(); 374 }, 375 376 editingCommitted: function(element, userInput, previousContent, context) 377 { 378 if (userInput === previousContent) { 379 this.editingCancelled(element, context); // nothing changed, so cancel 380 return; 381 } 382 383 this.editingEnded(context); 384 this.applyExpression(userInput); 385 }, 386 387 _promptKeyDown: function(context, event) 388 { 389 if (isEnterKey(event)) { 390 event.consume(true); 391 this.editingCommitted(null, context.elementToEdit.textContent, context.previousContent, context); 392 return; 393 } 394 if (event.keyIdentifier === "U+001B") { // Esc 395 event.consume(); 396 this.editingCancelled(null, context); 397 return; 398 } 399 }, 400 401 /** 402 * @param {string} expression 403 */ 404 applyExpression: function(expression) 405 { 406 var property = WebInspector.RemoteObject.toCallArgument(this.property.symbol || this.property.name); 407 expression = expression.trim(); 408 if (expression) 409 this.property.parentObject.setPropertyValue(property, expression, callback.bind(this)); 410 else 411 this.property.parentObject.deleteProperty(property, callback.bind(this)); 412 413 /** 414 * @param {?Protocol.Error} error 415 * @this {WebInspector.ObjectPropertyTreeElement} 416 */ 417 function callback(error) 418 { 419 if (error) { 420 this.update(); 421 return; 422 } 423 424 if (!expression) { 425 // The property was deleted, so remove this tree element. 426 this.parent.removeChild(this); 427 } else { 428 // Call updateSiblings since their value might be based on the value that just changed. 429 this.updateSiblings(); 430 } 431 }; 432 }, 433 434 /** 435 * @return {string|undefined} 436 */ 437 propertyPath: function() 438 { 439 if ("_cachedPropertyPath" in this) 440 return this._cachedPropertyPath; 441 442 var current = this; 443 var result; 444 445 do { 446 if (current.property) { 447 if (result) 448 result = current.property.name + "." + result; 449 else 450 result = current.property.name; 451 } 452 current = current.parent; 453 } while (current && !current.root); 454 455 this._cachedPropertyPath = result; 456 return result; 457 }, 458 459 /** 460 * @param {?WebInspector.RemoteObject} result 461 * @param {boolean=} wasThrown 462 */ 463 _onInvokeGetterClick: function(result, wasThrown) 464 { 465 if (!result) 466 return; 467 this.property.value = result; 468 this.property.wasThrown = wasThrown; 469 470 this.update(); 471 this.shouldRefreshChildren = true; 472 }, 473 474 __proto__: TreeElement.prototype 475 } 476 477 /** 478 * @param {!TreeElement} treeElement 479 * @param {!WebInspector.RemoteObject} value 480 * @param {string=} emptyPlaceholder 481 */ 482 WebInspector.ObjectPropertyTreeElement.populate = function(treeElement, value, emptyPlaceholder) { 483 if (treeElement.children.length && !treeElement.shouldRefreshChildren) 484 return; 485 486 if (value.arrayLength() > WebInspector.ObjectPropertiesSection._arrayLoadThreshold) { 487 treeElement.removeChildren(); 488 WebInspector.ArrayGroupingTreeElement._populateArray(treeElement, value, 0, value.arrayLength() - 1); 489 return; 490 } 491 492 /** 493 * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties 494 * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties 495 */ 496 function callback(properties, internalProperties) 497 { 498 treeElement.removeChildren(); 499 if (!properties) 500 return; 501 WebInspector.ObjectPropertyTreeElement.populateWithProperties(treeElement, properties, internalProperties, 502 treeElement.treeOutline.section.treeElementConstructor, WebInspector.ObjectPropertiesSection.CompareProperties, 503 treeElement.treeOutline.section.skipProto, value, emptyPlaceholder); 504 } 505 506 WebInspector.RemoteObject.loadFromObjectPerProto(value, callback); 507 } 508 509 /** 510 * @param {!TreeElement|!TreeOutline} treeElement 511 * @param {!Array.<!WebInspector.RemoteObjectProperty>} properties 512 * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties 513 * @param {function(new:TreeElement, !WebInspector.RemoteObjectProperty)} treeElementConstructor 514 * @param {function (!WebInspector.RemoteObjectProperty, !WebInspector.RemoteObjectProperty): number} comparator 515 * @param {boolean} skipProto 516 * @param {?WebInspector.RemoteObject} value 517 * @param {?string=} emptyPlaceholder 518 */ 519 WebInspector.ObjectPropertyTreeElement.populateWithProperties = function(treeElement, properties, internalProperties, treeElementConstructor, comparator, skipProto, value, emptyPlaceholder) { 520 properties.sort(comparator); 521 522 for (var i = 0; i < properties.length; ++i) { 523 var property = properties[i]; 524 if (skipProto && property.name === "__proto__") 525 continue; 526 if (property.isAccessorProperty()) { 527 if (property.name !== "__proto__" && property.getter) { 528 property.parentObject = value; 529 treeElement.appendChild(new treeElementConstructor(property)); 530 } 531 if (property.isOwn) { 532 if (property.getter) { 533 var getterProperty = new WebInspector.RemoteObjectProperty("get " + property.name, property.getter); 534 getterProperty.parentObject = value; 535 treeElement.appendChild(new treeElementConstructor(getterProperty)); 536 } 537 if (property.setter) { 538 var setterProperty = new WebInspector.RemoteObjectProperty("set " + property.name, property.setter); 539 setterProperty.parentObject = value; 540 treeElement.appendChild(new treeElementConstructor(setterProperty)); 541 } 542 } 543 } else { 544 property.parentObject = value; 545 treeElement.appendChild(new treeElementConstructor(property)); 546 } 547 } 548 if (value && value.type === "function") { 549 // Whether function has TargetFunction internal property. 550 // This is a simple way to tell that the function is actually a bound function (we are not told). 551 // Bound function never has inner scope and doesn't need corresponding UI node. 552 var hasTargetFunction = false; 553 554 if (internalProperties) { 555 for (var i = 0; i < internalProperties.length; i++) { 556 if (internalProperties[i].name == "[[TargetFunction]]") { 557 hasTargetFunction = true; 558 break; 559 } 560 } 561 } 562 if (!hasTargetFunction) 563 treeElement.appendChild(new WebInspector.FunctionScopeMainTreeElement(value)); 564 } 565 if (value && value.type === "object" && (value.subtype === "map" || value.subtype === "set")) 566 treeElement.appendChild(new WebInspector.CollectionEntriesMainTreeElement(value)); 567 if (internalProperties) { 568 for (var i = 0; i < internalProperties.length; i++) { 569 internalProperties[i].parentObject = value; 570 treeElement.appendChild(new treeElementConstructor(internalProperties[i])); 571 } 572 } 573 574 WebInspector.ObjectPropertyTreeElement._appendEmptyPlaceholderIfNeeded(treeElement, emptyPlaceholder); 575 } 576 577 /** 578 * @param {!TreeElement|!TreeOutline} treeElement 579 * @param {?string=} emptyPlaceholder 580 */ 581 WebInspector.ObjectPropertyTreeElement._appendEmptyPlaceholderIfNeeded = function(treeElement, emptyPlaceholder) 582 { 583 if (treeElement.children.length) 584 return; 585 var title = document.createElementWithClass("div", "info"); 586 title.textContent = emptyPlaceholder || WebInspector.UIString("No Properties"); 587 var infoElement = new TreeElement(title, null, false); 588 treeElement.appendChild(infoElement); 589 } 590 591 /** 592 * @param {?WebInspector.RemoteObject} object 593 * @param {!Array.<string>} propertyPath 594 * @param {function(?WebInspector.RemoteObject, boolean=)} callback 595 * @return {!Element} 596 */ 597 WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan = function(object, propertyPath, callback) 598 { 599 var rootElement = document.createElement("span"); 600 var element = rootElement.createChild("span"); 601 element.textContent = WebInspector.UIString("(...)"); 602 if (!object) 603 return rootElement; 604 element.classList.add("properties-calculate-value-button"); 605 element.title = WebInspector.UIString("Invoke property getter"); 606 element.addEventListener("click", onInvokeGetterClick, false); 607 608 function onInvokeGetterClick(event) 609 { 610 event.consume(); 611 object.getProperty(propertyPath, callback); 612 } 613 614 return rootElement; 615 } 616 617 /** 618 * @constructor 619 * @extends {TreeElement} 620 * @param {!WebInspector.RemoteObject} remoteObject 621 */ 622 WebInspector.FunctionScopeMainTreeElement = function(remoteObject) 623 { 624 TreeElement.call(this, "<function scope>", null, false); 625 this.toggleOnClick = true; 626 this.selectable = false; 627 this._remoteObject = remoteObject; 628 this.hasChildren = true; 629 } 630 631 WebInspector.FunctionScopeMainTreeElement.prototype = { 632 onpopulate: function() 633 { 634 if (this.children.length && !this.shouldRefreshChildren) 635 return; 636 637 /** 638 * @param {?WebInspector.DebuggerModel.FunctionDetails} response 639 * @this {WebInspector.FunctionScopeMainTreeElement} 640 */ 641 function didGetDetails(response) 642 { 643 if (!response) 644 return; 645 this.removeChildren(); 646 647 var scopeChain = response.scopeChain || []; 648 for (var i = 0; i < scopeChain.length; ++i) { 649 var scope = scopeChain[i]; 650 var title = null; 651 var isTrueObject; 652 653 switch (scope.type) { 654 case DebuggerAgent.ScopeType.Local: 655 // Not really expecting this scope type here. 656 title = WebInspector.UIString("Local"); 657 isTrueObject = false; 658 break; 659 case DebuggerAgent.ScopeType.Closure: 660 title = WebInspector.UIString("Closure"); 661 isTrueObject = false; 662 break; 663 case DebuggerAgent.ScopeType.Catch: 664 title = WebInspector.UIString("Catch"); 665 isTrueObject = false; 666 break; 667 case DebuggerAgent.ScopeType.With: 668 title = WebInspector.UIString("With Block"); 669 isTrueObject = true; 670 break; 671 case DebuggerAgent.ScopeType.Global: 672 title = WebInspector.UIString("Global"); 673 isTrueObject = true; 674 break; 675 default: 676 console.error("Unknown scope type: " + scope.type); 677 continue; 678 } 679 680 var runtimeModel = this._remoteObject.target().runtimeModel; 681 if (isTrueObject) { 682 var remoteObject = runtimeModel.createRemoteObject(scope.object); 683 var property = new WebInspector.RemoteObjectProperty(title, remoteObject); 684 property.writable = false; 685 property.parentObject = null; 686 this.appendChild(new this.treeOutline.section.treeElementConstructor(property)); 687 } else { 688 var scopeRef = new WebInspector.ScopeRef(i, undefined, this._remoteObject.objectId); 689 var remoteObject = runtimeModel.createScopeRemoteObject(scope.object, scopeRef); 690 var scopeTreeElement = new WebInspector.ScopeTreeElement(title, remoteObject); 691 this.appendChild(scopeTreeElement); 692 } 693 } 694 695 WebInspector.ObjectPropertyTreeElement._appendEmptyPlaceholderIfNeeded(this, WebInspector.UIString("No Scopes")); 696 } 697 698 this._remoteObject.functionDetails(didGetDetails.bind(this)); 699 }, 700 701 __proto__: TreeElement.prototype 702 } 703 704 /** 705 * @constructor 706 * @extends {TreeElement} 707 * @param {!WebInspector.RemoteObject} remoteObject 708 */ 709 WebInspector.CollectionEntriesMainTreeElement = function(remoteObject) 710 { 711 TreeElement.call(this, "<entries>", null, false); 712 this.toggleOnClick = true; 713 this.selectable = false; 714 this._remoteObject = remoteObject; 715 this.hasChildren = true; 716 } 717 718 WebInspector.CollectionEntriesMainTreeElement.prototype = { 719 onpopulate: function() 720 { 721 if (this.children.length && !this.shouldRefreshChildren) 722 return; 723 724 /** 725 * @param {?Array.<!DebuggerAgent.CollectionEntry>} entries 726 * @this {WebInspector.CollectionEntriesMainTreeElement} 727 */ 728 function didGetCollectionEntries(entries) 729 { 730 if (!entries) 731 return; 732 this.removeChildren(); 733 734 var entriesLocalObject = []; 735 var runtimeModel = this._remoteObject.target().runtimeModel; 736 for (var i = 0; i < entries.length; ++i) { 737 var entry = entries[i]; 738 if (entry.key) { 739 entriesLocalObject.push(new WebInspector.MapEntryLocalJSONObject({ 740 key: runtimeModel.createRemoteObject(entry.key), 741 value: runtimeModel.createRemoteObject(entry.value) 742 })); 743 } else { 744 entriesLocalObject.push(runtimeModel.createRemoteObject(entry.value)); 745 } 746 } 747 WebInspector.ObjectPropertyTreeElement.populate(this, WebInspector.RemoteObject.fromLocalObject(entriesLocalObject), WebInspector.UIString("No Entries")); 748 this.title = "<entries>[" + entriesLocalObject.length + "]"; 749 } 750 751 this._remoteObject.collectionEntries(didGetCollectionEntries.bind(this)); 752 }, 753 754 __proto__: TreeElement.prototype 755 } 756 757 /** 758 * @constructor 759 * @extends {TreeElement} 760 * @param {string} title 761 * @param {!WebInspector.RemoteObject} remoteObject 762 */ 763 WebInspector.ScopeTreeElement = function(title, remoteObject) 764 { 765 TreeElement.call(this, title, null, false); 766 this.toggleOnClick = true; 767 this.selectable = false; 768 this._remoteObject = remoteObject; 769 this.hasChildren = true; 770 } 771 772 WebInspector.ScopeTreeElement.prototype = { 773 onpopulate: function() 774 { 775 WebInspector.ObjectPropertyTreeElement.populate(this, this._remoteObject); 776 }, 777 778 __proto__: TreeElement.prototype 779 } 780 781 /** 782 * @constructor 783 * @extends {TreeElement} 784 * @param {!WebInspector.RemoteObject} object 785 * @param {number} fromIndex 786 * @param {number} toIndex 787 * @param {number} propertyCount 788 */ 789 WebInspector.ArrayGroupingTreeElement = function(object, fromIndex, toIndex, propertyCount) 790 { 791 TreeElement.call(this, String.sprintf("[%d \u2026 %d]", fromIndex, toIndex), undefined, true); 792 this.toggleOnClick = true; 793 this.selectable = false; 794 this._fromIndex = fromIndex; 795 this._toIndex = toIndex; 796 this._object = object; 797 this._readOnly = true; 798 this._propertyCount = propertyCount; 799 this._populated = false; 800 } 801 802 WebInspector.ArrayGroupingTreeElement._bucketThreshold = 100; 803 WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold = 250000; 804 805 /** 806 * @param {!TreeElement|!TreeOutline} treeElement 807 * @param {!WebInspector.RemoteObject} object 808 * @param {number} fromIndex 809 * @param {number} toIndex 810 */ 811 WebInspector.ArrayGroupingTreeElement._populateArray = function(treeElement, object, fromIndex, toIndex) 812 { 813 WebInspector.ArrayGroupingTreeElement._populateRanges(treeElement, object, fromIndex, toIndex, true); 814 } 815 816 /** 817 * @param {!TreeElement|!TreeOutline} treeElement 818 * @param {!WebInspector.RemoteObject} object 819 * @param {number} fromIndex 820 * @param {number} toIndex 821 * @param {boolean} topLevel 822 * @this {WebInspector.ArrayGroupingTreeElement} 823 */ 824 WebInspector.ArrayGroupingTreeElement._populateRanges = function(treeElement, object, fromIndex, toIndex, topLevel) 825 { 826 object.callFunctionJSON(packRanges, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._bucketThreshold}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], callback); 827 828 /** 829 * @suppressReceiverCheck 830 * @this {Object} 831 * @param {number=} fromIndex // must declare optional 832 * @param {number=} toIndex // must declare optional 833 * @param {number=} bucketThreshold // must declare optional 834 * @param {number=} sparseIterationThreshold // must declare optional 835 */ 836 function packRanges(fromIndex, toIndex, bucketThreshold, sparseIterationThreshold) 837 { 838 var ownPropertyNames = null; 839 840 /** 841 * @this {Object} 842 */ 843 function doLoop(iterationCallback) 844 { 845 if (toIndex - fromIndex < sparseIterationThreshold) { 846 for (var i = fromIndex; i <= toIndex; ++i) { 847 if (i in this) 848 iterationCallback(i); 849 } 850 } else { 851 ownPropertyNames = ownPropertyNames || Object.getOwnPropertyNames(this); 852 for (var i = 0; i < ownPropertyNames.length; ++i) { 853 var name = ownPropertyNames[i]; 854 var index = name >>> 0; 855 if (String(index) === name && fromIndex <= index && index <= toIndex) 856 iterationCallback(index); 857 } 858 } 859 } 860 861 var count = 0; 862 function countIterationCallback() 863 { 864 ++count; 865 } 866 doLoop.call(this, countIterationCallback); 867 868 var bucketSize = count; 869 if (count <= bucketThreshold) 870 bucketSize = count; 871 else 872 bucketSize = Math.pow(bucketThreshold, Math.ceil(Math.log(count) / Math.log(bucketThreshold)) - 1); 873 874 var ranges = []; 875 count = 0; 876 var groupStart = -1; 877 var groupEnd = 0; 878 function loopIterationCallback(i) 879 { 880 if (groupStart === -1) 881 groupStart = i; 882 883 groupEnd = i; 884 if (++count === bucketSize) { 885 ranges.push([groupStart, groupEnd, count]); 886 count = 0; 887 groupStart = -1; 888 } 889 } 890 doLoop.call(this, loopIterationCallback); 891 892 if (count > 0) 893 ranges.push([groupStart, groupEnd, count]); 894 return ranges; 895 } 896 897 function callback(ranges) 898 { 899 if (ranges.length == 1) 900 WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeElement, object, ranges[0][0], ranges[0][1]); 901 else { 902 for (var i = 0; i < ranges.length; ++i) { 903 var fromIndex = ranges[i][0]; 904 var toIndex = ranges[i][1]; 905 var count = ranges[i][2]; 906 if (fromIndex == toIndex) 907 WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeElement, object, fromIndex, toIndex); 908 else 909 treeElement.appendChild(new WebInspector.ArrayGroupingTreeElement(object, fromIndex, toIndex, count)); 910 } 911 } 912 if (topLevel) 913 WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties(treeElement, object); 914 } 915 } 916 917 /** 918 * @param {!TreeElement|!TreeOutline} treeElement 919 * @param {!WebInspector.RemoteObject} object 920 * @param {number} fromIndex 921 * @param {number} toIndex 922 * @this {WebInspector.ArrayGroupingTreeElement} 923 */ 924 WebInspector.ArrayGroupingTreeElement._populateAsFragment = function(treeElement, object, fromIndex, toIndex) 925 { 926 object.callFunction(buildArrayFragment, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], processArrayFragment.bind(this)); 927 928 /** 929 * @suppressReceiverCheck 930 * @this {Object} 931 * @param {number=} fromIndex // must declare optional 932 * @param {number=} toIndex // must declare optional 933 * @param {number=} sparseIterationThreshold // must declare optional 934 */ 935 function buildArrayFragment(fromIndex, toIndex, sparseIterationThreshold) 936 { 937 var result = Object.create(null); 938 if (toIndex - fromIndex < sparseIterationThreshold) { 939 for (var i = fromIndex; i <= toIndex; ++i) { 940 if (i in this) 941 result[i] = this[i]; 942 } 943 } else { 944 var ownPropertyNames = Object.getOwnPropertyNames(this); 945 for (var i = 0; i < ownPropertyNames.length; ++i) { 946 var name = ownPropertyNames[i]; 947 var index = name >>> 0; 948 if (String(index) === name && fromIndex <= index && index <= toIndex) 949 result[index] = this[index]; 950 } 951 } 952 return result; 953 } 954 955 /** 956 * @param {?WebInspector.RemoteObject} arrayFragment 957 * @param {boolean=} wasThrown 958 * @this {WebInspector.ArrayGroupingTreeElement} 959 */ 960 function processArrayFragment(arrayFragment, wasThrown) 961 { 962 if (!arrayFragment || wasThrown) 963 return; 964 arrayFragment.getAllProperties(false, processProperties.bind(this)); 965 } 966 967 /** @this {WebInspector.ArrayGroupingTreeElement} */ 968 function processProperties(properties, internalProperties) 969 { 970 if (!properties) 971 return; 972 973 properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties); 974 for (var i = 0; i < properties.length; ++i) { 975 properties[i].parentObject = this._object; 976 var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]); 977 childTreeElement._readOnly = true; 978 treeElement.appendChild(childTreeElement); 979 } 980 } 981 } 982 983 /** 984 * @param {!TreeElement|!TreeOutline} treeElement 985 * @param {!WebInspector.RemoteObject} object 986 * @this {WebInspector.ArrayGroupingTreeElement} 987 */ 988 WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties = function(treeElement, object) 989 { 990 object.callFunction(buildObjectFragment, undefined, processObjectFragment.bind(this)); 991 992 /** 993 * @suppressReceiverCheck 994 * @this {Object} 995 */ 996 function buildObjectFragment() 997 { 998 var result = Object.create(this.__proto__); 999 var names = Object.getOwnPropertyNames(this); 1000 for (var i = 0; i < names.length; ++i) { 1001 var name = names[i]; 1002 // Array index check according to the ES5-15.4. 1003 if (String(name >>> 0) === name && name >>> 0 !== 0xffffffff) 1004 continue; 1005 var descriptor = Object.getOwnPropertyDescriptor(this, name); 1006 if (descriptor) 1007 Object.defineProperty(result, name, descriptor); 1008 } 1009 return result; 1010 } 1011 1012 /** 1013 * @param {?WebInspector.RemoteObject} arrayFragment 1014 * @param {boolean=} wasThrown 1015 * @this {WebInspector.ArrayGroupingTreeElement} 1016 */ 1017 function processObjectFragment(arrayFragment, wasThrown) 1018 { 1019 if (!arrayFragment || wasThrown) 1020 return; 1021 arrayFragment.getOwnProperties(processProperties.bind(this)); 1022 } 1023 1024 /** 1025 * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties 1026 * @param {?Array.<!WebInspector.RemoteObjectProperty>=} internalProperties 1027 * @this {WebInspector.ArrayGroupingTreeElement} 1028 */ 1029 function processProperties(properties, internalProperties) 1030 { 1031 if (!properties) 1032 return; 1033 properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties); 1034 for (var i = 0; i < properties.length; ++i) { 1035 properties[i].parentObject = this._object; 1036 var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]); 1037 childTreeElement._readOnly = true; 1038 treeElement.appendChild(childTreeElement); 1039 } 1040 } 1041 } 1042 1043 WebInspector.ArrayGroupingTreeElement.prototype = { 1044 onpopulate: function() 1045 { 1046 if (this._populated) 1047 return; 1048 1049 this._populated = true; 1050 1051 if (this._propertyCount >= WebInspector.ArrayGroupingTreeElement._bucketThreshold) { 1052 WebInspector.ArrayGroupingTreeElement._populateRanges(this, this._object, this._fromIndex, this._toIndex, false); 1053 return; 1054 } 1055 WebInspector.ArrayGroupingTreeElement._populateAsFragment(this, this._object, this._fromIndex, this._toIndex); 1056 }, 1057 1058 onattach: function() 1059 { 1060 this.listItemElement.classList.add("name"); 1061 }, 1062 1063 __proto__: TreeElement.prototype 1064 } 1065 1066 /** 1067 * @constructor 1068 * @extends {WebInspector.TextPrompt} 1069 * @param {boolean=} renderAsBlock 1070 */ 1071 WebInspector.ObjectPropertyPrompt = function(renderAsBlock) 1072 { 1073 WebInspector.TextPrompt.call(this, WebInspector.ExecutionContextSelector.completionsForTextPromptInCurrentContext); 1074 this.setSuggestBoxEnabled(true); 1075 if (renderAsBlock) 1076 this.renderAsBlock(); 1077 } 1078 1079 WebInspector.ObjectPropertyPrompt.prototype = { 1080 __proto__: WebInspector.TextPrompt.prototype 1081 } 1082