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