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 || WebInspector.UIString("No Properties")); 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); 113 114 this.propertiesForTest = properties; 115 116 if (!this.propertiesTreeOutline.children.length) { 117 var title = document.createElement("div"); 118 title.className = "info"; 119 title.textContent = this.emptyPlaceholder; 120 var infoElement = new TreeElement(title, null, false); 121 this.propertiesTreeOutline.appendChild(infoElement); 122 } 123 }, 124 125 __proto__: WebInspector.PropertiesSection.prototype 126 } 127 128 /** 129 * @param {!WebInspector.RemoteObjectProperty} propertyA 130 * @param {!WebInspector.RemoteObjectProperty} propertyB 131 * @return {number} 132 */ 133 WebInspector.ObjectPropertiesSection.CompareProperties = function(propertyA, propertyB) 134 { 135 var a = propertyA.name; 136 var b = propertyB.name; 137 if (a === "__proto__") 138 return 1; 139 if (b === "__proto__") 140 return -1; 141 if (propertyA.symbol && !propertyB.symbol) 142 return 1; 143 if (propertyB.symbol && !propertyA.symbol) 144 return -1; 145 return String.naturalOrderComparator(a, b); 146 } 147 148 /** 149 * @constructor 150 * @extends {TreeElement} 151 * @param {!WebInspector.RemoteObjectProperty} property 152 */ 153 WebInspector.ObjectPropertyTreeElement = function(property) 154 { 155 this.property = property; 156 157 // Pass an empty title, the title gets made later in onattach. 158 TreeElement.call(this, "", null, false); 159 this.toggleOnClick = true; 160 this.selectable = false; 161 } 162 163 WebInspector.ObjectPropertyTreeElement.prototype = { 164 onpopulate: function() 165 { 166 var propertyValue = /** @type {!WebInspector.RemoteObject} */ (this.property.value); 167 console.assert(propertyValue); 168 WebInspector.ObjectPropertyTreeElement.populate(this, propertyValue); 169 }, 170 171 /** 172 * @override 173 * @return {boolean} 174 */ 175 ondblclick: function(event) 176 { 177 if (this.property.writable || this.property.setter) 178 this.startEditing(event); 179 return false; 180 }, 181 182 /** 183 * @override 184 */ 185 onattach: function() 186 { 187 this.update(); 188 }, 189 190 update: function() 191 { 192 this.nameElement = document.createElement("span"); 193 this.nameElement.className = "name"; 194 var name = this.property.name; 195 if (/^\s|\s$|^$|\n/.test(name)) 196 name = "\"" + name.replace(/\n/g, "\u21B5") + "\""; 197 this.nameElement.textContent = name; 198 if (!this.property.enumerable) 199 this.nameElement.classList.add("dimmed"); 200 if (this.property.isAccessorProperty()) 201 this.nameElement.classList.add("properties-accessor-property-name"); 202 if (this.property.symbol) 203 this.nameElement.addEventListener("contextmenu", this._contextMenuFired.bind(this, this.property.symbol), false); 204 205 var separatorElement = document.createElement("span"); 206 separatorElement.className = "separator"; 207 separatorElement.textContent = ": "; 208 209 if (this.property.value) { 210 this.valueElement = document.createElement("span"); 211 this.valueElement.className = "value"; 212 var description = this.property.value.description; 213 var valueText; 214 if (this.property.wasThrown) { 215 valueText = "[Exception: " + description + "]"; 216 } else if (this.property.value.type === "string" && typeof description === "string") { 217 // Render \n as a nice unicode cr symbol. 218 valueText = "\"" + description.replace(/\n/g, "\u21B5") + "\""; 219 this.valueElement._originalTextContent = "\"" + description + "\""; 220 } else if (this.property.value.type === "function" && typeof description === "string") { 221 valueText = /.*/.exec(description)[0].replace(/ +$/g, ""); 222 this.valueElement._originalTextContent = description; 223 } else if (this.property.value.type !== "object" || this.property.value.subtype !== "node") { 224 valueText = description; 225 } 226 this.valueElement.setTextContentTruncatedIfNeeded(valueText || ""); 227 228 if (this.property.wasThrown) 229 this.valueElement.classList.add("error"); 230 if (this.property.value.subtype) 231 this.valueElement.classList.add("console-formatted-" + this.property.value.subtype); 232 else if (this.property.value.type) 233 this.valueElement.classList.add("console-formatted-" + this.property.value.type); 234 235 this.valueElement.addEventListener("contextmenu", this._contextMenuFired.bind(this, this.property.value), false); 236 if (this.property.value.type === "object" && this.property.value.subtype === "node" && this.property.value.description) { 237 WebInspector.DOMPresentationUtils.createSpansForNodeTitle(this.valueElement, this.property.value.description); 238 this.valueElement.addEventListener("mousemove", this._mouseMove.bind(this, this.property.value), false); 239 this.valueElement.addEventListener("mouseout", this._mouseOut.bind(this, this.property.value), false); 240 } else { 241 this.valueElement.title = description || ""; 242 } 243 244 this.listItemElement.removeChildren(); 245 246 this.hasChildren = this.property.value.hasChildren && !this.property.wasThrown; 247 } else { 248 if (this.property.getter) { 249 this.valueElement = WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan(this.property.parentObject, [this.property.name], this._onInvokeGetterClick.bind(this)); 250 } else { 251 this.valueElement = document.createElement("span"); 252 this.valueElement.className = "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.appendChild(this.nameElement); 259 this.listItemElement.appendChild(separatorElement); 260 this.listItemElement.appendChild(this.valueElement); 261 }, 262 263 _contextMenuFired: function(value, event) 264 { 265 var contextMenu = new WebInspector.ContextMenu(event); 266 this.populateContextMenu(contextMenu); 267 contextMenu.appendApplicableItems(value); 268 contextMenu.show(); 269 }, 270 271 /** 272 * @param {!WebInspector.ContextMenu} contextMenu 273 */ 274 populateContextMenu: function(contextMenu) 275 { 276 }, 277 278 _mouseMove: function(event) 279 { 280 this.property.value.highlightAsDOMNode(); 281 }, 282 283 _mouseOut: function(event) 284 { 285 this.property.value.hideDOMNodeHighlight(); 286 }, 287 288 updateSiblings: function() 289 { 290 if (this.parent.root) 291 this.treeOutline.section.update(); 292 else 293 this.parent.shouldRefreshChildren = true; 294 }, 295 296 /** 297 * @return {boolean} 298 */ 299 renderPromptAsBlock: function() 300 { 301 return false; 302 }, 303 304 /** 305 * @return {{element: !Element, value: (string|undefined)}} 306 */ 307 elementAndValueToEdit: function() 308 { 309 return { 310 element: this.valueElement, 311 value: (typeof this.valueElement._originalTextContent === "string") ? this.valueElement._originalTextContent : undefined 312 }; 313 }, 314 315 /** 316 * @param {!Event=} event 317 */ 318 startEditing: function(event) 319 { 320 var elementAndValueToEdit = this.elementAndValueToEdit(); 321 var elementToEdit = elementAndValueToEdit.element; 322 var valueToEdit = elementAndValueToEdit.value; 323 324 if (WebInspector.isBeingEdited(elementToEdit) || !this.treeOutline.section.editable || this._readOnly) 325 return; 326 327 // Edit original source. 328 if (typeof valueToEdit !== "undefined") 329 elementToEdit.setTextContentTruncatedIfNeeded(valueToEdit, WebInspector.UIString("<string is too large to edit>")); 330 331 var context = { expanded: this.expanded, elementToEdit: elementToEdit, previousContent: elementToEdit.textContent }; 332 333 // Lie about our children to prevent expanding on double click and to collapse subproperties. 334 this.hasChildren = false; 335 336 this.listItemElement.classList.add("editing-sub-part"); 337 338 this._prompt = new WebInspector.ObjectPropertyPrompt(this.renderPromptAsBlock()); 339 340 /** 341 * @this {WebInspector.ObjectPropertyTreeElement} 342 */ 343 function blurListener() 344 { 345 this.editingCommitted(null, elementToEdit.textContent, context.previousContent, context); 346 } 347 348 var proxyElement = this._prompt.attachAndStartEditing(elementToEdit, blurListener.bind(this)); 349 window.getSelection().setBaseAndExtent(elementToEdit, 0, elementToEdit, 1); 350 proxyElement.addEventListener("keydown", this._promptKeyDown.bind(this, context), false); 351 }, 352 353 /** 354 * @return {boolean} 355 */ 356 isEditing: function() 357 { 358 return !!this._prompt; 359 }, 360 361 editingEnded: function(context) 362 { 363 this._prompt.detach(); 364 delete this._prompt; 365 366 this.listItemElement.scrollLeft = 0; 367 this.listItemElement.classList.remove("editing-sub-part"); 368 if (context.expanded) 369 this.expand(); 370 }, 371 372 editingCancelled: function(element, context) 373 { 374 this.editingEnded(context); 375 this.update(); 376 }, 377 378 editingCommitted: function(element, userInput, previousContent, context) 379 { 380 if (userInput === previousContent) { 381 this.editingCancelled(element, context); // nothing changed, so cancel 382 return; 383 } 384 385 this.editingEnded(context); 386 this.applyExpression(userInput); 387 }, 388 389 _promptKeyDown: function(context, event) 390 { 391 if (isEnterKey(event)) { 392 event.consume(true); 393 this.editingCommitted(null, context.elementToEdit.textContent, context.previousContent, context); 394 return; 395 } 396 if (event.keyIdentifier === "U+001B") { // Esc 397 event.consume(); 398 this.editingCancelled(null, context); 399 return; 400 } 401 }, 402 403 /** 404 * @param {string} expression 405 */ 406 applyExpression: function(expression) 407 { 408 expression = expression.trim(); 409 if (expression) 410 this.property.parentObject.setPropertyValue(this.property.name, expression, callback.bind(this)); 411 else 412 this.property.parentObject.deleteProperty(this.property.name, callback.bind(this)); 413 414 /** 415 * @param {?Protocol.Error} error 416 * @this {WebInspector.ObjectPropertyTreeElement} 417 */ 418 function callback(error) 419 { 420 if (error) { 421 this.update(); 422 return; 423 } 424 425 if (!expression) { 426 // The property was deleted, so remove this tree element. 427 this.parent.removeChild(this); 428 } else { 429 // Call updateSiblings since their value might be based on the value that just changed. 430 this.updateSiblings(); 431 } 432 }; 433 }, 434 435 /** 436 * @return {string|undefined} 437 */ 438 propertyPath: function() 439 { 440 if ("_cachedPropertyPath" in this) 441 return this._cachedPropertyPath; 442 443 var current = this; 444 var result; 445 446 do { 447 if (current.property) { 448 if (result) 449 result = current.property.name + "." + result; 450 else 451 result = current.property.name; 452 } 453 current = current.parent; 454 } while (current && !current.root); 455 456 this._cachedPropertyPath = result; 457 return result; 458 }, 459 460 /** 461 * @param {?WebInspector.RemoteObject} result 462 * @param {boolean=} wasThrown 463 */ 464 _onInvokeGetterClick: function(result, wasThrown) 465 { 466 if (!result) 467 return; 468 this.property.value = result; 469 this.property.wasThrown = wasThrown; 470 471 this.update(); 472 this.shouldRefreshChildren = true; 473 }, 474 475 __proto__: TreeElement.prototype 476 } 477 478 /** 479 * @param {!TreeElement} treeElement 480 * @param {!WebInspector.RemoteObject} value 481 */ 482 WebInspector.ObjectPropertyTreeElement.populate = function(treeElement, value) { 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 if (!internalProperties) 502 internalProperties = []; 503 504 WebInspector.ObjectPropertyTreeElement.populateWithProperties(treeElement, properties, internalProperties, 505 treeElement.treeOutline.section.treeElementConstructor, WebInspector.ObjectPropertiesSection.CompareProperties, 506 treeElement.treeOutline.section.skipProto, value); 507 } 508 509 WebInspector.RemoteObject.loadFromObjectPerProto(value, callback); 510 } 511 512 /** 513 * @param {!TreeElement|!TreeOutline} treeElement 514 * @param {!Array.<!WebInspector.RemoteObjectProperty>} properties 515 * @param {?Array.<!WebInspector.RemoteObjectProperty>} internalProperties 516 * @param {function(new:TreeElement, !WebInspector.RemoteObjectProperty)} treeElementConstructor 517 * @param {function (!WebInspector.RemoteObjectProperty, !WebInspector.RemoteObjectProperty): number} comparator 518 * @param {boolean} skipProto 519 * @param {?WebInspector.RemoteObject} value 520 */ 521 WebInspector.ObjectPropertyTreeElement.populateWithProperties = function(treeElement, properties, internalProperties, treeElementConstructor, comparator, skipProto, value) { 522 properties.sort(comparator); 523 524 for (var i = 0; i < properties.length; ++i) { 525 var property = properties[i]; 526 if (skipProto && property.name === "__proto__") 527 continue; 528 if (property.isAccessorProperty()) { 529 if (property.name !== "__proto__" && property.getter) { 530 property.parentObject = value; 531 treeElement.appendChild(new treeElementConstructor(property)); 532 } 533 if (property.isOwn) { 534 if (property.getter) { 535 var getterProperty = new WebInspector.RemoteObjectProperty("get " + property.name, property.getter); 536 getterProperty.parentObject = value; 537 treeElement.appendChild(new treeElementConstructor(getterProperty)); 538 } 539 if (property.setter) { 540 var setterProperty = new WebInspector.RemoteObjectProperty("set " + property.name, property.setter); 541 setterProperty.parentObject = value; 542 treeElement.appendChild(new treeElementConstructor(setterProperty)); 543 } 544 } 545 } else { 546 property.parentObject = value; 547 treeElement.appendChild(new treeElementConstructor(property)); 548 } 549 } 550 if (value && value.type === "function") { 551 // Whether function has TargetFunction internal property. 552 // This is a simple way to tell that the function is actually a bound function (we are not told). 553 // Bound function never has inner scope and doesn't need corresponding UI node. 554 var hasTargetFunction = false; 555 556 if (internalProperties) { 557 for (var i = 0; i < internalProperties.length; i++) { 558 if (internalProperties[i].name == "[[TargetFunction]]") { 559 hasTargetFunction = true; 560 break; 561 } 562 } 563 } 564 if (!hasTargetFunction) 565 treeElement.appendChild(new WebInspector.FunctionScopeMainTreeElement(value)); 566 } 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 575 /** 576 * @param {!WebInspector.RemoteObject} object 577 * @param {!Array.<string>} propertyPath 578 * @param {function(?WebInspector.RemoteObject, boolean=)} callback 579 * @return {!Element} 580 */ 581 WebInspector.ObjectPropertyTreeElement.createRemoteObjectAccessorPropertySpan = function(object, propertyPath, callback) 582 { 583 var rootElement = document.createElement("span"); 584 var element = rootElement.createChild("span", "properties-calculate-value-button"); 585 element.textContent = WebInspector.UIString("(...)"); 586 element.title = WebInspector.UIString("Invoke property getter"); 587 element.addEventListener("click", onInvokeGetterClick, false); 588 589 function onInvokeGetterClick(event) 590 { 591 event.consume(); 592 object.getProperty(propertyPath, callback); 593 } 594 595 return rootElement; 596 } 597 598 /** 599 * @constructor 600 * @extends {TreeElement} 601 * @param {!WebInspector.RemoteObject} remoteObject 602 */ 603 WebInspector.FunctionScopeMainTreeElement = function(remoteObject) 604 { 605 TreeElement.call(this, "<function scope>", null, false); 606 this.toggleOnClick = true; 607 this.selectable = false; 608 this._remoteObject = remoteObject; 609 this.hasChildren = true; 610 } 611 612 WebInspector.FunctionScopeMainTreeElement.prototype = { 613 onpopulate: function() 614 { 615 if (this.children.length && !this.shouldRefreshChildren) 616 return; 617 618 /** 619 * @param {?DebuggerAgent.FunctionDetails} response 620 * @this {WebInspector.FunctionScopeMainTreeElement} 621 */ 622 function didGetDetails(response) 623 { 624 if (!response) 625 return; 626 this.removeChildren(); 627 628 var scopeChain = response.scopeChain; 629 if (!scopeChain) 630 return; 631 for (var i = 0; i < scopeChain.length; ++i) { 632 var scope = scopeChain[i]; 633 var title = null; 634 var isTrueObject; 635 636 switch (scope.type) { 637 case DebuggerAgent.ScopeType.Local: 638 // Not really expecting this scope type here. 639 title = WebInspector.UIString("Local"); 640 isTrueObject = false; 641 break; 642 case DebuggerAgent.ScopeType.Closure: 643 title = WebInspector.UIString("Closure"); 644 isTrueObject = false; 645 break; 646 case DebuggerAgent.ScopeType.Catch: 647 title = WebInspector.UIString("Catch"); 648 isTrueObject = false; 649 break; 650 case DebuggerAgent.ScopeType.With: 651 title = WebInspector.UIString("With Block"); 652 isTrueObject = true; 653 break; 654 case DebuggerAgent.ScopeType.Global: 655 title = WebInspector.UIString("Global"); 656 isTrueObject = true; 657 break; 658 default: 659 console.error("Unknown scope type: " + scope.type); 660 continue; 661 } 662 663 var runtimeModel = this._remoteObject.target().runtimeModel; 664 if (isTrueObject) { 665 var remoteObject = runtimeModel.createRemoteObject(scope.object); 666 var property = new WebInspector.RemoteObjectProperty(title, remoteObject); 667 property.writable = false; 668 property.parentObject = null; 669 this.appendChild(new this.treeOutline.section.treeElementConstructor(property)); 670 } else { 671 var scopeRef = new WebInspector.ScopeRef(i, undefined, this._remoteObject.objectId); 672 var remoteObject = runtimeModel.createScopeRemoteObject(scope.object, scopeRef); 673 var scopeTreeElement = new WebInspector.ScopeTreeElement(title, null, remoteObject); 674 this.appendChild(scopeTreeElement); 675 } 676 } 677 } 678 this._remoteObject.functionDetails(didGetDetails.bind(this)); 679 }, 680 681 __proto__: TreeElement.prototype 682 } 683 684 /** 685 * @constructor 686 * @extends {TreeElement} 687 * @param {string} title 688 * @param {?string} subtitle 689 * @param {!WebInspector.RemoteObject} remoteObject 690 */ 691 WebInspector.ScopeTreeElement = function(title, subtitle, remoteObject) 692 { 693 // TODO: use subtitle parameter. 694 TreeElement.call(this, title, null, false); 695 this.toggleOnClick = true; 696 this.selectable = false; 697 this._remoteObject = remoteObject; 698 this.hasChildren = true; 699 } 700 701 WebInspector.ScopeTreeElement.prototype = { 702 onpopulate: function() 703 { 704 WebInspector.ObjectPropertyTreeElement.populate(this, this._remoteObject); 705 }, 706 707 __proto__: TreeElement.prototype 708 } 709 710 /** 711 * @constructor 712 * @extends {TreeElement} 713 * @param {!WebInspector.RemoteObject} object 714 * @param {number} fromIndex 715 * @param {number} toIndex 716 * @param {number} propertyCount 717 */ 718 WebInspector.ArrayGroupingTreeElement = function(object, fromIndex, toIndex, propertyCount) 719 { 720 TreeElement.call(this, String.sprintf("[%d \u2026 %d]", fromIndex, toIndex), undefined, true); 721 this._fromIndex = fromIndex; 722 this._toIndex = toIndex; 723 this._object = object; 724 this._readOnly = true; 725 this._propertyCount = propertyCount; 726 this._populated = false; 727 } 728 729 WebInspector.ArrayGroupingTreeElement._bucketThreshold = 100; 730 WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold = 250000; 731 732 /** 733 * @param {!TreeElement|!TreeOutline} treeElement 734 * @param {!WebInspector.RemoteObject} object 735 * @param {number} fromIndex 736 * @param {number} toIndex 737 */ 738 WebInspector.ArrayGroupingTreeElement._populateArray = function(treeElement, object, fromIndex, toIndex) 739 { 740 WebInspector.ArrayGroupingTreeElement._populateRanges(treeElement, object, fromIndex, toIndex, true); 741 } 742 743 /** 744 * @param {!TreeElement|!TreeOutline} treeElement 745 * @param {!WebInspector.RemoteObject} object 746 * @param {number} fromIndex 747 * @param {number} toIndex 748 * @param {boolean} topLevel 749 * @this {WebInspector.ArrayGroupingTreeElement} 750 */ 751 WebInspector.ArrayGroupingTreeElement._populateRanges = function(treeElement, object, fromIndex, toIndex, topLevel) 752 { 753 object.callFunctionJSON(packRanges, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._bucketThreshold}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], callback); 754 755 /** 756 * @suppressReceiverCheck 757 * @this {Object} 758 * @param {number=} fromIndex // must declare optional 759 * @param {number=} toIndex // must declare optional 760 * @param {number=} bucketThreshold // must declare optional 761 * @param {number=} sparseIterationThreshold // must declare optional 762 */ 763 function packRanges(fromIndex, toIndex, bucketThreshold, sparseIterationThreshold) 764 { 765 var ownPropertyNames = null; 766 767 /** 768 * @this {Object} 769 */ 770 function doLoop(iterationCallback) 771 { 772 if (toIndex - fromIndex < sparseIterationThreshold) { 773 for (var i = fromIndex; i <= toIndex; ++i) { 774 if (i in this) 775 iterationCallback(i); 776 } 777 } else { 778 ownPropertyNames = ownPropertyNames || Object.getOwnPropertyNames(this); 779 for (var i = 0; i < ownPropertyNames.length; ++i) { 780 var name = ownPropertyNames[i]; 781 var index = name >>> 0; 782 if (String(index) === name && fromIndex <= index && index <= toIndex) 783 iterationCallback(index); 784 } 785 } 786 } 787 788 var count = 0; 789 function countIterationCallback() 790 { 791 ++count; 792 } 793 doLoop.call(this, countIterationCallback); 794 795 var bucketSize = count; 796 if (count <= bucketThreshold) 797 bucketSize = count; 798 else 799 bucketSize = Math.pow(bucketThreshold, Math.ceil(Math.log(count) / Math.log(bucketThreshold)) - 1); 800 801 var ranges = []; 802 count = 0; 803 var groupStart = -1; 804 var groupEnd = 0; 805 function loopIterationCallback(i) 806 { 807 if (groupStart === -1) 808 groupStart = i; 809 810 groupEnd = i; 811 if (++count === bucketSize) { 812 ranges.push([groupStart, groupEnd, count]); 813 count = 0; 814 groupStart = -1; 815 } 816 } 817 doLoop.call(this, loopIterationCallback); 818 819 if (count > 0) 820 ranges.push([groupStart, groupEnd, count]); 821 return ranges; 822 } 823 824 function callback(ranges) 825 { 826 if (ranges.length == 1) 827 WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeElement, object, ranges[0][0], ranges[0][1]); 828 else { 829 for (var i = 0; i < ranges.length; ++i) { 830 var fromIndex = ranges[i][0]; 831 var toIndex = ranges[i][1]; 832 var count = ranges[i][2]; 833 if (fromIndex == toIndex) 834 WebInspector.ArrayGroupingTreeElement._populateAsFragment(treeElement, object, fromIndex, toIndex); 835 else 836 treeElement.appendChild(new WebInspector.ArrayGroupingTreeElement(object, fromIndex, toIndex, count)); 837 } 838 } 839 if (topLevel) 840 WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties(treeElement, object); 841 } 842 } 843 844 /** 845 * @param {!TreeElement|!TreeOutline} treeElement 846 * @param {!WebInspector.RemoteObject} object 847 * @param {number} fromIndex 848 * @param {number} toIndex 849 * @this {WebInspector.ArrayGroupingTreeElement} 850 */ 851 WebInspector.ArrayGroupingTreeElement._populateAsFragment = function(treeElement, object, fromIndex, toIndex) 852 { 853 object.callFunction(buildArrayFragment, [{value: fromIndex}, {value: toIndex}, {value: WebInspector.ArrayGroupingTreeElement._sparseIterationThreshold}], processArrayFragment.bind(this)); 854 855 /** 856 * @suppressReceiverCheck 857 * @this {Object} 858 * @param {number=} fromIndex // must declare optional 859 * @param {number=} toIndex // must declare optional 860 * @param {number=} sparseIterationThreshold // must declare optional 861 */ 862 function buildArrayFragment(fromIndex, toIndex, sparseIterationThreshold) 863 { 864 var result = Object.create(null); 865 if (toIndex - fromIndex < sparseIterationThreshold) { 866 for (var i = fromIndex; i <= toIndex; ++i) { 867 if (i in this) 868 result[i] = this[i]; 869 } 870 } else { 871 var ownPropertyNames = Object.getOwnPropertyNames(this); 872 for (var i = 0; i < ownPropertyNames.length; ++i) { 873 var name = ownPropertyNames[i]; 874 var index = name >>> 0; 875 if (String(index) === name && fromIndex <= index && index <= toIndex) 876 result[index] = this[index]; 877 } 878 } 879 return result; 880 } 881 882 /** 883 * @param {?WebInspector.RemoteObject} arrayFragment 884 * @param {boolean=} wasThrown 885 * @this {WebInspector.ArrayGroupingTreeElement} 886 */ 887 function processArrayFragment(arrayFragment, wasThrown) 888 { 889 if (!arrayFragment || wasThrown) 890 return; 891 arrayFragment.getAllProperties(false, processProperties.bind(this)); 892 } 893 894 /** @this {WebInspector.ArrayGroupingTreeElement} */ 895 function processProperties(properties, internalProperties) 896 { 897 if (!properties) 898 return; 899 900 properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties); 901 for (var i = 0; i < properties.length; ++i) { 902 properties[i].parentObject = this._object; 903 var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]); 904 childTreeElement._readOnly = true; 905 treeElement.appendChild(childTreeElement); 906 } 907 } 908 } 909 910 /** 911 * @param {!TreeElement|!TreeOutline} treeElement 912 * @param {!WebInspector.RemoteObject} object 913 * @this {WebInspector.ArrayGroupingTreeElement} 914 */ 915 WebInspector.ArrayGroupingTreeElement._populateNonIndexProperties = function(treeElement, object) 916 { 917 object.callFunction(buildObjectFragment, undefined, processObjectFragment.bind(this)); 918 919 /** 920 * @suppressReceiverCheck 921 * @this {Object} 922 */ 923 function buildObjectFragment() 924 { 925 var result = Object.create(this.__proto__); 926 var names = Object.getOwnPropertyNames(this); 927 for (var i = 0; i < names.length; ++i) { 928 var name = names[i]; 929 // Array index check according to the ES5-15.4. 930 if (String(name >>> 0) === name && name >>> 0 !== 0xffffffff) 931 continue; 932 var descriptor = Object.getOwnPropertyDescriptor(this, name); 933 if (descriptor) 934 Object.defineProperty(result, name, descriptor); 935 } 936 return result; 937 } 938 939 /** 940 * @param {?WebInspector.RemoteObject} arrayFragment 941 * @param {boolean=} wasThrown 942 * @this {WebInspector.ArrayGroupingTreeElement} 943 */ 944 function processObjectFragment(arrayFragment, wasThrown) 945 { 946 if (!arrayFragment || wasThrown) 947 return; 948 arrayFragment.getOwnProperties(processProperties.bind(this)); 949 } 950 951 /** 952 * @param {?Array.<!WebInspector.RemoteObjectProperty>} properties 953 * @param {?Array.<!WebInspector.RemoteObjectProperty>=} internalProperties 954 * @this {WebInspector.ArrayGroupingTreeElement} 955 */ 956 function processProperties(properties, internalProperties) 957 { 958 if (!properties) 959 return; 960 properties.sort(WebInspector.ObjectPropertiesSection.CompareProperties); 961 for (var i = 0; i < properties.length; ++i) { 962 properties[i].parentObject = this._object; 963 var childTreeElement = new treeElement.treeOutline.section.treeElementConstructor(properties[i]); 964 childTreeElement._readOnly = true; 965 treeElement.appendChild(childTreeElement); 966 } 967 } 968 } 969 970 WebInspector.ArrayGroupingTreeElement.prototype = { 971 onpopulate: function() 972 { 973 if (this._populated) 974 return; 975 976 this._populated = true; 977 978 if (this._propertyCount >= WebInspector.ArrayGroupingTreeElement._bucketThreshold) { 979 WebInspector.ArrayGroupingTreeElement._populateRanges(this, this._object, this._fromIndex, this._toIndex, false); 980 return; 981 } 982 WebInspector.ArrayGroupingTreeElement._populateAsFragment(this, this._object, this._fromIndex, this._toIndex); 983 }, 984 985 onattach: function() 986 { 987 this.listItemElement.classList.add("name"); 988 }, 989 990 __proto__: TreeElement.prototype 991 } 992 993 /** 994 * @constructor 995 * @extends {WebInspector.TextPrompt} 996 * @param {boolean=} renderAsBlock 997 */ 998 WebInspector.ObjectPropertyPrompt = function(renderAsBlock) 999 { 1000 WebInspector.TextPrompt.call(this, WebInspector.ExecutionContextSelector.completionsForTextPromptInCurrentContext); 1001 this.setSuggestBoxEnabled(true); 1002 if (renderAsBlock) 1003 this.renderAsBlock(); 1004 } 1005 1006 WebInspector.ObjectPropertyPrompt.prototype = { 1007 __proto__: WebInspector.TextPrompt.prototype 1008 } 1009