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