1/* 2 * Copyright (C) 2007, 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 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 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29function TreeOutline(listNode) 30{ 31 WebInspector.Object.call(this); 32 33 this.element = listNode; 34 35 this.children = []; 36 this.selectedTreeElement = null; 37 this._childrenListNode = listNode; 38 this._childrenListNode.removeChildren(); 39 this._knownTreeElements = []; 40 this._treeElementsExpandedState = []; 41 this.expandTreeElementsWhenArrowing = false; 42 this.allowsRepeatSelection = false; 43 this.root = true; 44 this.hasChildren = false; 45 this.expanded = true; 46 this.selected = false; 47 this.treeOutline = this; 48 49 this._childrenListNode.tabIndex = 0; 50 this._childrenListNode.addEventListener("keydown", this._treeKeyDown.bind(this), true); 51} 52 53TreeOutline._knownTreeElementNextIdentifier = 1; 54TreeOutline.prototype.constructor = TreeOutline; 55 56TreeOutline.prototype.appendChild = function(child) 57{ 58 if (!child) 59 throw("child can't be undefined or null"); 60 61 var lastChild = this.children[this.children.length - 1]; 62 if (lastChild) { 63 lastChild.nextSibling = child; 64 child.previousSibling = lastChild; 65 } else { 66 child.previousSibling = null; 67 child.nextSibling = null; 68 } 69 70 var isFirstChild = !this.children.length; 71 72 this.children.push(child); 73 this.hasChildren = true; 74 child.parent = this; 75 child.treeOutline = this.treeOutline; 76 child.treeOutline._rememberTreeElement(child); 77 78 var current = child.children[0]; 79 while (current) { 80 current.treeOutline = this.treeOutline; 81 current.treeOutline._rememberTreeElement(current); 82 current = current.traverseNextTreeElement(false, child, true); 83 } 84 85 if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined) 86 child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier]; 87 88 if (this._childrenListNode) 89 child._attach(); 90 91 if (this.treeOutline.onadd) 92 this.treeOutline.onadd(child); 93 94 if (isFirstChild && this.expanded) 95 this.expand(); 96} 97 98TreeOutline.prototype.insertChild = function(child, index) 99{ 100 if (!child) 101 throw("child can't be undefined or null"); 102 103 var previousChild = (index > 0 ? this.children[index - 1] : null); 104 if (previousChild) { 105 previousChild.nextSibling = child; 106 child.previousSibling = previousChild; 107 } else { 108 child.previousSibling = null; 109 } 110 111 var nextChild = this.children[index]; 112 if (nextChild) { 113 nextChild.previousSibling = child; 114 child.nextSibling = nextChild; 115 } else { 116 child.nextSibling = null; 117 } 118 119 var isFirstChild = !this.children.length; 120 121 this.children.splice(index, 0, child); 122 this.hasChildren = true; 123 child.parent = this; 124 child.treeOutline = this.treeOutline; 125 child.treeOutline._rememberTreeElement(child); 126 127 var current = child.children[0]; 128 while (current) { 129 current.treeOutline = this.treeOutline; 130 current.treeOutline._rememberTreeElement(current); 131 current = current.traverseNextTreeElement(false, child, true); 132 } 133 134 if (child.hasChildren && child.treeOutline._treeElementsExpandedState[child.identifier] !== undefined) 135 child.expanded = child.treeOutline._treeElementsExpandedState[child.identifier]; 136 137 if (this._childrenListNode) 138 child._attach(); 139 140 if (this.treeOutline.onadd) 141 this.treeOutline.onadd(child); 142 143 if (isFirstChild && this.expanded) 144 this.expand(); 145} 146 147TreeOutline.prototype.removeChildAtIndex = function(childIndex, suppressOnDeselect, suppressSelectSibling) 148{ 149 if (childIndex < 0 || childIndex >= this.children.length) 150 throw("childIndex out of range"); 151 152 var child = this.children[childIndex]; 153 this.children.splice(childIndex, 1); 154 155 var parent = child.parent; 156 if (child.deselect(suppressOnDeselect)) { 157 if (child.previousSibling && !suppressSelectSibling) 158 child.previousSibling.select(true, false); 159 else if (child.nextSibling && !suppressSelectSibling) 160 child.nextSibling.select(true, false); 161 else if (!suppressSelectSibling) 162 parent.select(true, false); 163 } 164 165 if (child.previousSibling) 166 child.previousSibling.nextSibling = child.nextSibling; 167 if (child.nextSibling) 168 child.nextSibling.previousSibling = child.previousSibling; 169 170 if (child.treeOutline) { 171 child.treeOutline._forgetTreeElement(child); 172 child.treeOutline._forgetChildrenRecursive(child); 173 } 174 175 child._detach(); 176 child.treeOutline = null; 177 child.parent = null; 178 child.nextSibling = null; 179 child.previousSibling = null; 180 181 if (this.treeOutline && this.treeOutline.onremove) 182 this.treeOutline.onremove(child); 183} 184 185TreeOutline.prototype.removeChild = function(child, suppressOnDeselect, suppressSelectSibling) 186{ 187 if (!child) 188 throw("child can't be undefined or null"); 189 190 var childIndex = this.children.indexOf(child); 191 if (childIndex === -1) 192 throw("child not found in this node's children"); 193 194 this.removeChildAtIndex(childIndex, suppressOnDeselect, suppressSelectSibling); 195} 196 197TreeOutline.prototype.removeChildren = function(suppressOnDeselect) 198{ 199 var treeOutline = this.treeOutline; 200 201 for (var i = 0; i < this.children.length; ++i) { 202 var child = this.children[i]; 203 child.deselect(suppressOnDeselect); 204 205 if (child.treeOutline) { 206 child.treeOutline._forgetTreeElement(child); 207 child.treeOutline._forgetChildrenRecursive(child); 208 } 209 210 child._detach(); 211 child.treeOutline = null; 212 child.parent = null; 213 child.nextSibling = null; 214 child.previousSibling = null; 215 216 if (treeOutline && treeOutline.onremove) 217 treeOutline.onremove(child); 218 } 219 220 this.children = []; 221} 222 223TreeOutline.prototype.removeChildrenRecursive = function(suppressOnDeselect) 224{ 225 var childrenToRemove = this.children; 226 227 var treeOutline = this.treeOutline; 228 229 var child = this.children[0]; 230 while (child) { 231 if (child.children.length) 232 childrenToRemove = childrenToRemove.concat(child.children); 233 child = child.traverseNextTreeElement(false, this, true); 234 } 235 236 for (var i = 0; i < childrenToRemove.length; ++i) { 237 child = childrenToRemove[i]; 238 child.deselect(suppressOnDeselect); 239 240 if (child.treeOutline) 241 child.treeOutline._forgetTreeElement(child); 242 243 child._detach(); 244 child.children = []; 245 child.treeOutline = null; 246 child.parent = null; 247 child.nextSibling = null; 248 child.previousSibling = null; 249 250 if (treeOutline && treeOutline.onremove) 251 treeOutline.onremove(child); 252 } 253 254 this.children = []; 255} 256 257TreeOutline.prototype._rememberTreeElement = function(element) 258{ 259 if (!this._knownTreeElements[element.identifier]) 260 this._knownTreeElements[element.identifier] = []; 261 262 // check if the element is already known 263 var elements = this._knownTreeElements[element.identifier]; 264 if (elements.indexOf(element) !== -1) 265 return; 266 267 // add the element 268 elements.push(element); 269} 270 271TreeOutline.prototype._forgetTreeElement = function(element) 272{ 273 if (this.selectedTreeElement === element) 274 this.selectedTreeElement = null; 275 if (this._knownTreeElements[element.identifier]) 276 this._knownTreeElements[element.identifier].remove(element, true); 277} 278 279TreeOutline.prototype._forgetChildrenRecursive = function(parentElement) 280{ 281 var child = parentElement.children[0]; 282 while (child) { 283 this._forgetTreeElement(child); 284 child = child.traverseNextTreeElement(false, parentElement, true); 285 } 286} 287 288TreeOutline.prototype.getCachedTreeElement = function(representedObject) 289{ 290 if (!representedObject) 291 return null; 292 293 if (representedObject.__treeElementIdentifier) { 294 // If this representedObject has a tree element identifier, and it is a known TreeElement 295 // in our tree we can just return that tree element. 296 var elements = this._knownTreeElements[representedObject.__treeElementIdentifier]; 297 if (elements) { 298 for (var i = 0; i < elements.length; ++i) 299 if (elements[i].representedObject === representedObject) 300 return elements[i]; 301 } 302 } 303 return null; 304} 305 306TreeOutline.prototype.findTreeElement = function(representedObject, isAncestor, getParent) 307{ 308 if (!representedObject) 309 return null; 310 311 var cachedElement = this.getCachedTreeElement(representedObject); 312 if (cachedElement) 313 return cachedElement; 314 315 // The representedObject isn't known, so we start at the top of the tree and work down to find the first 316 // tree element that represents representedObject or one of its ancestors. 317 var item; 318 var found = false; 319 for (var i = 0; i < this.children.length; ++i) { 320 item = this.children[i]; 321 if (item.representedObject === representedObject || (isAncestor && isAncestor(item.representedObject, representedObject))) { 322 found = true; 323 break; 324 } 325 } 326 327 if (!found) 328 return null; 329 330 // Make sure the item that we found is connected to the root of the tree. 331 // Build up a list of representedObject's ancestors that aren't already in our tree. 332 var ancestors = []; 333 var currentObject = representedObject; 334 while (currentObject) { 335 ancestors.unshift(currentObject); 336 if (currentObject === item.representedObject) 337 break; 338 currentObject = getParent(currentObject); 339 } 340 341 // For each of those ancestors we populate them to fill in the tree. 342 for (var i = 0; i < ancestors.length; ++i) { 343 // Make sure we don't call findTreeElement with the same representedObject 344 // again, to prevent infinite recursion. 345 if (ancestors[i] === representedObject) 346 continue; 347 348 // FIXME: we could do something faster than findTreeElement since we will know the next 349 // ancestor exists in the tree. 350 item = this.findTreeElement(ancestors[i], isAncestor, getParent); 351 if (item) 352 item.onpopulate(); 353 } 354 355 return this.getCachedTreeElement(representedObject); 356} 357 358TreeOutline.prototype._treeElementDidChange = function(treeElement) 359{ 360 if (treeElement.treeOutline !== this) 361 return; 362 363 if (this.onchange) 364 this.onchange(treeElement); 365} 366 367TreeOutline.prototype.treeElementFromPoint = function(x, y) 368{ 369 var node = this._childrenListNode.ownerDocument.elementFromPoint(x, y); 370 if (!node) 371 return null; 372 373 var listNode = node.enclosingNodeOrSelfWithNodeNameInArray(["ol", "li"]); 374 if (listNode) 375 return listNode.parentTreeElement || listNode.treeElement; 376 return null; 377} 378 379TreeOutline.prototype._treeKeyDown = function(event) 380{ 381 if (event.target !== this._childrenListNode) 382 return; 383 384 if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey) 385 return; 386 387 var handled = false; 388 var nextSelectedElement; 389 if (event.keyIdentifier === "Up" && !event.altKey) { 390 nextSelectedElement = this.selectedTreeElement.traversePreviousTreeElement(true); 391 while (nextSelectedElement && !nextSelectedElement.selectable) 392 nextSelectedElement = nextSelectedElement.traversePreviousTreeElement(!this.expandTreeElementsWhenArrowing); 393 handled = nextSelectedElement ? true : false; 394 } else if (event.keyIdentifier === "Down" && !event.altKey) { 395 nextSelectedElement = this.selectedTreeElement.traverseNextTreeElement(true); 396 while (nextSelectedElement && !nextSelectedElement.selectable) 397 nextSelectedElement = nextSelectedElement.traverseNextTreeElement(!this.expandTreeElementsWhenArrowing); 398 handled = nextSelectedElement ? true : false; 399 } else if (event.keyIdentifier === "Left") { 400 if (this.selectedTreeElement.expanded) { 401 if (event.altKey) 402 this.selectedTreeElement.collapseRecursively(); 403 else 404 this.selectedTreeElement.collapse(); 405 handled = true; 406 } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) { 407 handled = true; 408 if (this.selectedTreeElement.parent.selectable) { 409 nextSelectedElement = this.selectedTreeElement.parent; 410 while (nextSelectedElement && !nextSelectedElement.selectable) 411 nextSelectedElement = nextSelectedElement.parent; 412 handled = nextSelectedElement ? true : false; 413 } else if (this.selectedTreeElement.parent) 414 this.selectedTreeElement.parent.collapse(); 415 } 416 } else if (event.keyIdentifier === "Right") { 417 if (!this.selectedTreeElement.revealed()) { 418 this.selectedTreeElement.reveal(); 419 handled = true; 420 } else if (this.selectedTreeElement.hasChildren) { 421 handled = true; 422 if (this.selectedTreeElement.expanded) { 423 nextSelectedElement = this.selectedTreeElement.children[0]; 424 while (nextSelectedElement && !nextSelectedElement.selectable) 425 nextSelectedElement = nextSelectedElement.nextSibling; 426 handled = nextSelectedElement ? true : false; 427 } else { 428 if (event.altKey) 429 this.selectedTreeElement.expandRecursively(); 430 else 431 this.selectedTreeElement.expand(); 432 } 433 } 434 } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */) { 435 if (this.selectedTreeElement.ondelete) 436 handled = this.selectedTreeElement.ondelete(); 437 if (!handled && this.treeOutline.ondelete) 438 handled = this.treeOutline.ondelete(this.selectedTreeElement); 439 } else if (isEnterKey(event)) { 440 if (this.selectedTreeElement.onenter) 441 handled = this.selectedTreeElement.onenter(); 442 if (!handled && this.treeOutline.onenter) 443 handled = this.treeOutline.onenter(this.selectedTreeElement); 444 } else if (event.keyIdentifier === "U+0020" /* Space */) { 445 if (this.selectedTreeElement.onspace) 446 handled = this.selectedTreeElement.onspace(); 447 if (!handled && this.treeOutline.onspace) 448 handled = this.treeOutline.onspace(this.selectedTreeElement); 449 } 450 451 if (nextSelectedElement) { 452 nextSelectedElement.reveal(); 453 nextSelectedElement.select(false, true); 454 } 455 456 if (handled) { 457 event.preventDefault(); 458 event.stopPropagation(); 459 } 460} 461 462TreeOutline.prototype.expand = function() 463{ 464 // this is the root, do nothing 465} 466 467TreeOutline.prototype.collapse = function() 468{ 469 // this is the root, do nothing 470} 471 472TreeOutline.prototype.revealed = function() 473{ 474 return true; 475} 476 477TreeOutline.prototype.reveal = function() 478{ 479 // this is the root, do nothing 480} 481 482TreeOutline.prototype.select = function() 483{ 484 // this is the root, do nothing 485} 486 487TreeOutline.prototype.revealAndSelect = function(omitFocus) 488{ 489 // this is the root, do nothing 490} 491 492TreeOutline.prototype.__proto__ = WebInspector.Object.prototype; 493 494function TreeElement(title, representedObject, hasChildren) 495{ 496 WebInspector.Object.call(this); 497 498 this._title = title; 499 this.representedObject = (representedObject || {}); 500 501 if (this.representedObject.__treeElementIdentifier) 502 this.identifier = this.representedObject.__treeElementIdentifier; 503 else { 504 this.identifier = TreeOutline._knownTreeElementNextIdentifier++; 505 this.representedObject.__treeElementIdentifier = this.identifier; 506 } 507 508 this._hidden = false; 509 this._selectable = true; 510 this.expanded = false; 511 this.selected = false; 512 this.hasChildren = hasChildren; 513 this.children = []; 514 this.treeOutline = null; 515 this.parent = null; 516 this.previousSibling = null; 517 this.nextSibling = null; 518 this._listItemNode = null; 519} 520 521TreeElement.prototype = { 522 constructor: TreeElement, 523 524 arrowToggleWidth: 10, 525 526 get selectable() { 527 if (this._hidden) 528 return false; 529 return this._selectable; 530 }, 531 532 set selectable(x) { 533 this._selectable = x; 534 }, 535 536 get listItemElement() { 537 return this._listItemNode; 538 }, 539 540 get childrenListElement() { 541 return this._childrenListNode; 542 }, 543 544 get title() { 545 return this._title; 546 }, 547 548 set title(x) { 549 this._title = x; 550 this._setListItemNodeContent(); 551 this.didChange(); 552 }, 553 554 get titleHTML() { 555 return this._titleHTML; 556 }, 557 558 set titleHTML(x) { 559 this._titleHTML = x; 560 this._setListItemNodeContent(); 561 this.didChange(); 562 }, 563 564 get tooltip() { 565 return this._tooltip; 566 }, 567 568 set tooltip(x) { 569 this._tooltip = x; 570 if (this._listItemNode) 571 this._listItemNode.title = x ? x : ""; 572 this.didChange(); 573 }, 574 575 get hasChildren() { 576 return this._hasChildren; 577 }, 578 579 set hasChildren(x) { 580 if (this._hasChildren === x) 581 return; 582 583 this._hasChildren = x; 584 585 if (!this._listItemNode) 586 return; 587 588 if (x) 589 this._listItemNode.classList.add("parent"); 590 else { 591 this._listItemNode.classList.remove("parent"); 592 this.collapse(); 593 } 594 595 this.didChange(); 596 }, 597 598 get hidden() { 599 return this._hidden; 600 }, 601 602 set hidden(x) { 603 if (this._hidden === x) 604 return; 605 606 this._hidden = x; 607 608 if (x) { 609 if (this._listItemNode) 610 this._listItemNode.classList.add("hidden"); 611 if (this._childrenListNode) 612 this._childrenListNode.classList.add("hidden"); 613 } else { 614 if (this._listItemNode) 615 this._listItemNode.classList.remove("hidden"); 616 if (this._childrenListNode) 617 this._childrenListNode.classList.remove("hidden"); 618 } 619 620 if (this.treeOutline && this.treeOutline.onhidden) 621 this.treeOutline.onhidden(this, x); 622 }, 623 624 get shouldRefreshChildren() { 625 return this._shouldRefreshChildren; 626 }, 627 628 set shouldRefreshChildren(x) { 629 this._shouldRefreshChildren = x; 630 if (x && this.expanded) 631 this.expand(); 632 }, 633 634 _fireDidChange: function() 635 { 636 delete this._didChangeTimeoutIdentifier; 637 638 if (this.treeOutline) 639 this.treeOutline._treeElementDidChange(this); 640 }, 641 642 didChange: function() 643 { 644 if (!this.treeOutline) 645 return; 646 647 // Prevent telling the TreeOutline multiple times in a row by delaying it with a timeout. 648 if (!this._didChangeTimeoutIdentifier) 649 this._didChangeTimeoutIdentifier = setTimeout(this._fireDidChange.bind(this), 0); 650 }, 651 652 _setListItemNodeContent: function() 653 { 654 if (!this._listItemNode) 655 return; 656 657 if (!this._titleHTML && !this._title) 658 this._listItemNode.removeChildren(); 659 else if (typeof this._titleHTML === "string") 660 this._listItemNode.innerHTML = this._titleHTML; 661 else if (typeof this._title === "string") 662 this._listItemNode.textContent = this._title; 663 else { 664 this._listItemNode.removeChildren(); 665 if (this._title.parentNode) 666 this._title.parentNode.removeChild(this._title); 667 this._listItemNode.appendChild(this._title); 668 } 669 } 670} 671 672TreeElement.prototype.appendChild = TreeOutline.prototype.appendChild; 673TreeElement.prototype.insertChild = TreeOutline.prototype.insertChild; 674TreeElement.prototype.removeChild = TreeOutline.prototype.removeChild; 675TreeElement.prototype.removeChildAtIndex = TreeOutline.prototype.removeChildAtIndex; 676TreeElement.prototype.removeChildren = TreeOutline.prototype.removeChildren; 677TreeElement.prototype.removeChildrenRecursive = TreeOutline.prototype.removeChildrenRecursive; 678 679TreeElement.prototype._attach = function() 680{ 681 if (!this._listItemNode || this.parent._shouldRefreshChildren) { 682 if (this._listItemNode && this._listItemNode.parentNode) 683 this._listItemNode.parentNode.removeChild(this._listItemNode); 684 685 this._listItemNode = this.treeOutline._childrenListNode.ownerDocument.createElement("li"); 686 this._listItemNode.treeElement = this; 687 this._setListItemNodeContent(); 688 this._listItemNode.title = this._tooltip ? this._tooltip : ""; 689 690 if (this.hidden) 691 this._listItemNode.classList.add("hidden"); 692 if (this.hasChildren) 693 this._listItemNode.classList.add("parent"); 694 if (this.expanded) 695 this._listItemNode.classList.add("expanded"); 696 if (this.selected) 697 this._listItemNode.classList.add("selected"); 698 699 this._listItemNode.addEventListener("mousedown", TreeElement.treeElementMouseDown, false); 700 this._listItemNode.addEventListener("click", TreeElement.treeElementToggled, false); 701 this._listItemNode.addEventListener("dblclick", TreeElement.treeElementDoubleClicked, false); 702 703 if (this.onattach) 704 this.onattach(this); 705 } 706 707 var nextSibling = null; 708 if (this.nextSibling && this.nextSibling._listItemNode && this.nextSibling._listItemNode.parentNode === this.parent._childrenListNode) 709 nextSibling = this.nextSibling._listItemNode; 710 this.parent._childrenListNode.insertBefore(this._listItemNode, nextSibling); 711 if (this._childrenListNode) 712 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); 713 if (this.selected) 714 this.select(); 715 if (this.expanded) 716 this.expand(); 717} 718 719TreeElement.prototype._detach = function() 720{ 721 if (this.ondetach) 722 this.ondetach(this); 723 if (this._listItemNode && this._listItemNode.parentNode) 724 this._listItemNode.parentNode.removeChild(this._listItemNode); 725 if (this._childrenListNode && this._childrenListNode.parentNode) 726 this._childrenListNode.parentNode.removeChild(this._childrenListNode); 727} 728 729TreeElement.treeElementMouseDown = function(event) 730{ 731 var element = event.currentTarget; 732 if (!element || !element.treeElement || !element.treeElement.selectable) 733 return; 734 735 if (element.treeElement.isEventWithinDisclosureTriangle(event)) { 736 event.preventDefault(); 737 return; 738 } 739 740 element.treeElement.selectOnMouseDown(event); 741} 742 743TreeElement.treeElementToggled = function(event) 744{ 745 var element = event.currentTarget; 746 if (!element || !element.treeElement) 747 return; 748 749 var toggleOnClick = element.treeElement.toggleOnClick && !element.treeElement.selectable; 750 var isInTriangle = element.treeElement.isEventWithinDisclosureTriangle(event); 751 if (!toggleOnClick && !isInTriangle) 752 return; 753 754 if (element.treeElement.expanded) { 755 if (event.altKey) 756 element.treeElement.collapseRecursively(); 757 else 758 element.treeElement.collapse(); 759 } else { 760 if (event.altKey) 761 element.treeElement.expandRecursively(); 762 else 763 element.treeElement.expand(); 764 } 765 event.stopPropagation(); 766} 767 768TreeElement.treeElementDoubleClicked = function(event) 769{ 770 var element = event.currentTarget; 771 if (!element || !element.treeElement) 772 return; 773 774 if (element.treeElement.isEventWithinDisclosureTriangle(event)) 775 return; 776 777 if (element.treeElement.ondblclick) 778 element.treeElement.ondblclick.call(element.treeElement, event); 779 else if (element.treeElement.hasChildren && !element.treeElement.expanded) 780 element.treeElement.expand(); 781} 782 783TreeElement.prototype.collapse = function() 784{ 785 if (this._listItemNode) 786 this._listItemNode.classList.remove("expanded"); 787 if (this._childrenListNode) 788 this._childrenListNode.classList.remove("expanded"); 789 790 this.expanded = false; 791 if (this.treeOutline) 792 this.treeOutline._treeElementsExpandedState[this.identifier] = false; 793 794 if (this.oncollapse) 795 this.oncollapse(this); 796 797 if (this.treeOutline && this.treeOutline.oncollapse) 798 this.treeOutline.oncollapse(this); 799} 800 801TreeElement.prototype.collapseRecursively = function() 802{ 803 var item = this; 804 while (item) { 805 if (item.expanded) 806 item.collapse(); 807 item = item.traverseNextTreeElement(false, this, true); 808 } 809} 810 811TreeElement.prototype.expand = function() 812{ 813 if (this.expanded && !this._shouldRefreshChildren && this._childrenListNode) 814 return; 815 816 // Set this before onpopulate. Since onpopulate can add elements and call onadd, this makes 817 // sure the expanded flag is true before calling those functions. This prevents the possibility 818 // of an infinite loop if onpopulate or onadd were to call expand. 819 820 this.expanded = true; 821 if (this.treeOutline) 822 this.treeOutline._treeElementsExpandedState[this.identifier] = true; 823 824 // If there are no children, return. We will be expanded once we have children. 825 if (!this.hasChildren) 826 return; 827 828 if (this.treeOutline && (!this._childrenListNode || this._shouldRefreshChildren)) { 829 if (this._childrenListNode && this._childrenListNode.parentNode) 830 this._childrenListNode.parentNode.removeChild(this._childrenListNode); 831 832 this._childrenListNode = this.treeOutline._childrenListNode.ownerDocument.createElement("ol"); 833 this._childrenListNode.parentTreeElement = this; 834 this._childrenListNode.classList.add("children"); 835 836 if (this.hidden) 837 this._childrenListNode.classList.add("hidden"); 838 839 this.onpopulate(); 840 841 for (var i = 0; i < this.children.length; ++i) 842 this.children[i]._attach(); 843 844 delete this._shouldRefreshChildren; 845 } 846 847 if (this._listItemNode) { 848 this._listItemNode.classList.add("expanded"); 849 if (this._childrenListNode && this._childrenListNode.parentNode != this._listItemNode.parentNode) 850 this.parent._childrenListNode.insertBefore(this._childrenListNode, this._listItemNode.nextSibling); 851 } 852 853 if (this._childrenListNode) 854 this._childrenListNode.classList.add("expanded"); 855 856 if (this.onexpand) 857 this.onexpand(this); 858 859 if (this.treeOutline && this.treeOutline.onexpand) 860 this.treeOutline.onexpand(this); 861} 862 863TreeElement.prototype.expandRecursively = function(maxDepth) 864{ 865 var item = this; 866 var info = {}; 867 var depth = 0; 868 869 // The Inspector uses TreeOutlines to represents object properties, so recursive expansion 870 // in some case can be infinite, since JavaScript objects can hold circular references. 871 // So default to a recursion cap of 3 levels, since that gives fairly good results. 872 if (typeof maxDepth === "undefined" || typeof maxDepth === "null") 873 maxDepth = 3; 874 875 while (item) { 876 if (depth < maxDepth) 877 item.expand(); 878 item = item.traverseNextTreeElement(false, this, (depth >= maxDepth), info); 879 depth += info.depthChange; 880 } 881} 882 883TreeElement.prototype.hasAncestor = function(ancestor) { 884 if (!ancestor) 885 return false; 886 887 var currentNode = this.parent; 888 while (currentNode) { 889 if (ancestor === currentNode) 890 return true; 891 currentNode = currentNode.parent; 892 } 893 894 return false; 895} 896 897TreeElement.prototype.reveal = function() 898{ 899 var currentAncestor = this.parent; 900 while (currentAncestor && !currentAncestor.root) { 901 if (!currentAncestor.expanded) 902 currentAncestor.expand(); 903 currentAncestor = currentAncestor.parent; 904 } 905 906 if (this.onreveal) 907 this.onreveal(this); 908} 909 910TreeElement.prototype.revealed = function() 911{ 912 if (this.hidden) 913 return false; 914 915 var currentAncestor = this.parent; 916 while (currentAncestor && !currentAncestor.root) { 917 if (!currentAncestor.expanded) 918 return false; 919 if (currentAncestor.hidden) 920 return false; 921 currentAncestor = currentAncestor.parent; 922 } 923 924 return true; 925} 926 927TreeElement.prototype.selectOnMouseDown = function(event) 928{ 929 this.select(false, true); 930} 931 932TreeElement.prototype.select = function(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect) 933{ 934 if (!this.treeOutline || !this.selectable) 935 return; 936 937 if (this.selected && !this.treeOutline.allowsRepeatSelection) 938 return; 939 940 if (!omitFocus) 941 this.treeOutline._childrenListNode.focus(); 942 943 // Focusing on another node may detach "this" from tree. 944 if (!this.treeOutline) 945 return; 946 947 this.treeOutline.processingSelectionChange = true; 948 949 if (!this.selected) { 950 if (this.treeOutline.selectedTreeElement) 951 this.treeOutline.selectedTreeElement.deselect(suppressOnDeselect); 952 953 this.selected = true; 954 this.treeOutline.selectedTreeElement = this; 955 956 if (this._listItemNode) 957 this._listItemNode.classList.add("selected"); 958 } 959 960 if (this.onselect && !suppressOnSelect) 961 this.onselect(this, selectedByUser); 962 963 if (this.treeOutline.onselect && !suppressOnSelect) 964 this.treeOutline.onselect(this, selectedByUser); 965 966 delete this.treeOutline.processingSelectionChange; 967} 968 969TreeElement.prototype.revealAndSelect = function(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect) 970{ 971 this.reveal(); 972 this.select(omitFocus, selectedByUser, suppressOnSelect, suppressOnDeselect); 973} 974 975TreeElement.prototype.deselect = function(suppressOnDeselect) 976{ 977 if (!this.treeOutline || this.treeOutline.selectedTreeElement !== this || !this.selected) 978 return false; 979 980 this.selected = false; 981 this.treeOutline.selectedTreeElement = null; 982 983 if (this._listItemNode) 984 this._listItemNode.classList.remove("selected"); 985 986 if (this.ondeselect && !suppressOnDeselect) 987 this.ondeselect(this); 988 989 if (this.treeOutline.ondeselect && !suppressOnDeselect) 990 this.treeOutline.ondeselect(this); 991 992 return true; 993} 994 995TreeElement.prototype.onpopulate = function() 996{ 997 // Overriden by subclasses. 998} 999 1000TreeElement.prototype.traverseNextTreeElement = function(skipUnrevealed, stayWithin, dontPopulate, info) 1001{ 1002 if (!dontPopulate && this.hasChildren) 1003 this.onpopulate.call(this); // FIXME: This shouldn't need to use call, but this is working around a JSC bug. https://webkit.org/b/74811 1004 1005 if (info) 1006 info.depthChange = 0; 1007 1008 var element = skipUnrevealed ? (this.revealed() ? this.children[0] : null) : this.children[0]; 1009 if (element && (!skipUnrevealed || (skipUnrevealed && this.expanded))) { 1010 if (info) 1011 info.depthChange = 1; 1012 return element; 1013 } 1014 1015 if (this === stayWithin) 1016 return null; 1017 1018 element = skipUnrevealed ? (this.revealed() ? this.nextSibling : null) : this.nextSibling; 1019 if (element) 1020 return element; 1021 1022 element = this; 1023 while (element && !element.root && !(skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling) && element.parent !== stayWithin) { 1024 if (info) 1025 info.depthChange -= 1; 1026 element = element.parent; 1027 } 1028 1029 if (!element) 1030 return null; 1031 1032 return (skipUnrevealed ? (element.revealed() ? element.nextSibling : null) : element.nextSibling); 1033} 1034 1035TreeElement.prototype.traversePreviousTreeElement = function(skipUnrevealed, dontPopulate) 1036{ 1037 var element = skipUnrevealed ? (this.revealed() ? this.previousSibling : null) : this.previousSibling; 1038 if (!dontPopulate && element && element.hasChildren) 1039 element.onpopulate(); 1040 1041 while (element && (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1])) { 1042 if (!dontPopulate && element.hasChildren) 1043 element.onpopulate(); 1044 element = (skipUnrevealed ? (element.revealed() && element.expanded ? element.children[element.children.length - 1] : null) : element.children[element.children.length - 1]); 1045 } 1046 1047 if (element) 1048 return element; 1049 1050 if (!this.parent || this.parent.root) 1051 return null; 1052 1053 return this.parent; 1054} 1055 1056TreeElement.prototype.isEventWithinDisclosureTriangle = function(event) 1057{ 1058 if (!document.contains(this._listItemNode)) 1059 return false; 1060 1061 // FIXME: We should not use getComputedStyle(). For that we need to get rid of using ::before for disclosure triangle. (http://webk.it/74446) 1062 var computedLeftPadding = window.getComputedStyle(this._listItemNode).getPropertyCSSValue("padding-left").getFloatValue(CSSPrimitiveValue.CSS_PX); 1063 var left = this._listItemNode.totalOffsetLeft + computedLeftPadding; 1064 return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren; 1065} 1066 1067TreeElement.prototype.__proto__ = WebInspector.Object.prototype; 1068