OLD | NEW |
(Empty) | |
| 1 /* |
| 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. |
| 3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> |
| 4 * Copyright (C) 2009 Joseph Pecoraro |
| 5 * |
| 6 * Redistribution and use in source and binary forms, with or without |
| 7 * modification, are permitted provided that the following conditions |
| 8 * are met: |
| 9 * |
| 10 * 1. Redistributions of source code must retain the above copyright |
| 11 * notice, this list of conditions and the following disclaimer. |
| 12 * 2. Redistributions in binary form must reproduce the above copyright |
| 13 * notice, this list of conditions and the following disclaimer in the |
| 14 * documentation and/or other materials provided with the distribution. |
| 15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| 16 * its contributors may be used to endorse or promote products derived |
| 17 * from this software without specific prior written permission. |
| 18 * |
| 19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| 20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 29 */ |
| 30 |
| 31 WebInspector.ElementsTreeOutline = function() { |
| 32 this.element = document.createElement("ol"); |
| 33 this.element.addEventListener("mousedown", this._onmousedown.bind(this), fal
se); |
| 34 this.element.addEventListener("mousemove", this._onmousemove.bind(this), fal
se); |
| 35 this.element.addEventListener("mouseout", this._onmouseout.bind(this), false
); |
| 36 |
| 37 TreeOutline.call(this, this.element); |
| 38 |
| 39 this.includeRootDOMNode = true; |
| 40 this.selectEnabled = false; |
| 41 this.showInElementsPanelEnabled = false; |
| 42 this.rootDOMNode = null; |
| 43 this.focusedDOMNode = null; |
| 44 } |
| 45 |
| 46 WebInspector.ElementsTreeOutline.prototype = { |
| 47 get rootDOMNode() |
| 48 { |
| 49 return this._rootDOMNode; |
| 50 }, |
| 51 |
| 52 set rootDOMNode(x) |
| 53 { |
| 54 if (this._rootDOMNode === x) |
| 55 return; |
| 56 |
| 57 this._rootDOMNode = x; |
| 58 |
| 59 this.update(); |
| 60 }, |
| 61 |
| 62 get focusedDOMNode() |
| 63 { |
| 64 return this._focusedDOMNode; |
| 65 }, |
| 66 |
| 67 set focusedDOMNode(x) |
| 68 { |
| 69 if (this._focusedDOMNode === x) { |
| 70 this.revealAndSelectNode(x); |
| 71 return; |
| 72 } |
| 73 |
| 74 this._focusedDOMNode = x; |
| 75 |
| 76 this.revealAndSelectNode(x); |
| 77 |
| 78 // The revealAndSelectNode() method might find a different element if th
ere is inlined text, |
| 79 // and the select() call would change the focusedDOMNode and reenter thi
s setter. So to |
| 80 // avoid calling focusedNodeChanged() twice, first check if _focusedDOMN
ode is the same |
| 81 // node as the one passed in. |
| 82 if (this._focusedDOMNode === x) { |
| 83 this.focusedNodeChanged(); |
| 84 |
| 85 if (x && !this.suppressSelectHighlight) { |
| 86 InspectorController.highlightDOMNode(x.id); |
| 87 |
| 88 if ("_restorePreviousHighlightNodeTimeout" in this) |
| 89 clearTimeout(this._restorePreviousHighlightNodeTimeout); |
| 90 |
| 91 function restoreHighlightToHoveredNode() |
| 92 { |
| 93 var hoveredNode = WebInspector.hoveredDOMNode; |
| 94 if (hoveredNode) |
| 95 InspectorController.highlightDOMNode(hoveredNode.id); |
| 96 else |
| 97 InspectorController.hideDOMNodeHighlight(); |
| 98 } |
| 99 |
| 100 this._restorePreviousHighlightNodeTimeout = setTimeout(restoreHi
ghlightToHoveredNode, 2000); |
| 101 } |
| 102 } |
| 103 }, |
| 104 |
| 105 update: function() |
| 106 { |
| 107 this.removeChildren(); |
| 108 |
| 109 if (!this.rootDOMNode) |
| 110 return; |
| 111 |
| 112 var treeElement; |
| 113 if (this.includeRootDOMNode) { |
| 114 treeElement = new WebInspector.ElementsTreeElement(this.rootDOMNode)
; |
| 115 treeElement.selectable = this.selectEnabled; |
| 116 this.appendChild(treeElement); |
| 117 } else { |
| 118 // FIXME: this could use findTreeElement to reuse a tree element if
it already exists |
| 119 var node = this.rootDOMNode.firstChild; |
| 120 while (node) { |
| 121 treeElement = new WebInspector.ElementsTreeElement(node); |
| 122 treeElement.selectable = this.selectEnabled; |
| 123 this.appendChild(treeElement); |
| 124 node = node.nextSibling; |
| 125 } |
| 126 } |
| 127 |
| 128 this.updateSelection(); |
| 129 }, |
| 130 |
| 131 updateSelection: function() |
| 132 { |
| 133 if (!this.selectedTreeElement) |
| 134 return; |
| 135 var element = this.treeOutline.selectedTreeElement; |
| 136 element.updateSelection(); |
| 137 }, |
| 138 |
| 139 focusedNodeChanged: function(forceUpdate) {}, |
| 140 |
| 141 findTreeElement: function(node) |
| 142 { |
| 143 var treeElement = TreeOutline.prototype.findTreeElement.call(this, node,
isAncestorNode, parentNode); |
| 144 if (!treeElement && node.nodeType === Node.TEXT_NODE) { |
| 145 // The text node might have been inlined if it was short, so try to
find the parent element. |
| 146 treeElement = TreeOutline.prototype.findTreeElement.call(this, node.
parentNode, isAncestorNode, parentNode); |
| 147 } |
| 148 |
| 149 return treeElement; |
| 150 }, |
| 151 |
| 152 revealAndSelectNode: function(node) |
| 153 { |
| 154 if (!node) |
| 155 return; |
| 156 |
| 157 var treeElement = this.findTreeElement(node); |
| 158 if (!treeElement) |
| 159 return; |
| 160 |
| 161 treeElement.reveal(); |
| 162 treeElement.select(); |
| 163 }, |
| 164 |
| 165 _treeElementFromEvent: function(event) |
| 166 { |
| 167 var root = this.element; |
| 168 |
| 169 // We choose this X coordinate based on the knowledge that our list |
| 170 // items extend nearly to the right edge of the outer <ol>. |
| 171 var x = root.totalOffsetLeft + root.offsetWidth - 20; |
| 172 |
| 173 var y = event.pageY; |
| 174 |
| 175 // Our list items have 1-pixel cracks between them vertically. We avoid |
| 176 // the cracks by checking slightly above and slightly below the mouse |
| 177 // and seeing if we hit the same element each time. |
| 178 var elementUnderMouse = this.treeElementFromPoint(x, y); |
| 179 var elementAboveMouse = this.treeElementFromPoint(x, y - 2); |
| 180 var element; |
| 181 if (elementUnderMouse === elementAboveMouse) |
| 182 element = elementUnderMouse; |
| 183 else |
| 184 element = this.treeElementFromPoint(x, y + 2); |
| 185 |
| 186 return element; |
| 187 }, |
| 188 |
| 189 handleKeyEvent: function(event) |
| 190 { |
| 191 var selectedElement = this.selectedTreeElement; |
| 192 if (!selectedElement) |
| 193 return; |
| 194 |
| 195 // Delete or backspace pressed, delete the node. |
| 196 if (event.keyCode === 8 || event.keyCode === 46) { |
| 197 selectedElement.remove(); |
| 198 return; |
| 199 } |
| 200 |
| 201 // On Enter or Return start editing the first attribute |
| 202 // or create a new attribute on the selected element. |
| 203 if (event.keyIdentifier === "Enter") { |
| 204 if (this._editing) |
| 205 return; |
| 206 |
| 207 selectedElement._startEditing(); |
| 208 |
| 209 // prevent a newline from being immediately inserted |
| 210 event.preventDefault(); |
| 211 return; |
| 212 } |
| 213 |
| 214 TreeOutline.prototype.handleKeyEvent.call(this, event); |
| 215 }, |
| 216 |
| 217 _onmousedown: function(event) |
| 218 { |
| 219 var element = this._treeElementFromEvent(event); |
| 220 |
| 221 if (!element || element.isEventWithinDisclosureTriangle(event)) |
| 222 return; |
| 223 |
| 224 element.select(); |
| 225 }, |
| 226 |
| 227 _onmousemove: function(event) |
| 228 { |
| 229 var element = this._treeElementFromEvent(event); |
| 230 if (element && this._previousHoveredElement === element) |
| 231 return; |
| 232 |
| 233 if (this._previousHoveredElement) { |
| 234 this._previousHoveredElement.hovered = false; |
| 235 delete this._previousHoveredElement; |
| 236 } |
| 237 |
| 238 if (element && !element.elementCloseTag) { |
| 239 element.hovered = true; |
| 240 this._previousHoveredElement = element; |
| 241 } |
| 242 |
| 243 WebInspector.hoveredDOMNode = (element && !element.elementCloseTag ? ele
ment.representedObject : null); |
| 244 }, |
| 245 |
| 246 _onmouseout: function(event) |
| 247 { |
| 248 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY)
; |
| 249 if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element)) |
| 250 return; |
| 251 |
| 252 if (this._previousHoveredElement) { |
| 253 this._previousHoveredElement.hovered = false; |
| 254 delete this._previousHoveredElement; |
| 255 } |
| 256 |
| 257 WebInspector.hoveredDOMNode = null; |
| 258 } |
| 259 } |
| 260 |
| 261 WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype; |
| 262 |
| 263 WebInspector.ElementsTreeElement = function(node) |
| 264 { |
| 265 var hasChildrenOverride = node.hasChildNodes() && !this._showInlineText(node
); |
| 266 |
| 267 // The title will be updated in onattach. |
| 268 TreeElement.call(this, "", node, hasChildrenOverride); |
| 269 |
| 270 if (this.representedObject.nodeType == Node.ELEMENT_NODE) |
| 271 this._canAddAttributes = true; |
| 272 } |
| 273 |
| 274 WebInspector.ElementsTreeElement.prototype = { |
| 275 get highlighted() |
| 276 { |
| 277 return this._highlighted; |
| 278 }, |
| 279 |
| 280 set highlighted(x) |
| 281 { |
| 282 if (this._highlighted === x) |
| 283 return; |
| 284 |
| 285 this._highlighted = x; |
| 286 |
| 287 if (this.listItemElement) { |
| 288 if (x) |
| 289 this.listItemElement.addStyleClass("highlighted"); |
| 290 else |
| 291 this.listItemElement.removeStyleClass("highlighted"); |
| 292 } |
| 293 }, |
| 294 |
| 295 get hovered() |
| 296 { |
| 297 return this._hovered; |
| 298 }, |
| 299 |
| 300 set hovered(x) |
| 301 { |
| 302 if (this._hovered === x) |
| 303 return; |
| 304 |
| 305 this._hovered = x; |
| 306 |
| 307 if (this.listItemElement) { |
| 308 if (x) { |
| 309 this.updateSelection(); |
| 310 this.listItemElement.addStyleClass("hovered"); |
| 311 if (this._canAddAttributes) |
| 312 this._pendingToggleNewAttribute = setTimeout(this.toggleNewA
ttributeButton.bind(this, true), 500); |
| 313 } else { |
| 314 this.listItemElement.removeStyleClass("hovered"); |
| 315 if (this._pendingToggleNewAttribute) { |
| 316 clearTimeout(this._pendingToggleNewAttribute); |
| 317 delete this._pendingToggleNewAttribute; |
| 318 } |
| 319 this.toggleNewAttributeButton(false); |
| 320 } |
| 321 } |
| 322 }, |
| 323 |
| 324 toggleNewAttributeButton: function(visible) |
| 325 { |
| 326 function removeAddAttributeSpan() |
| 327 { |
| 328 if (this._addAttributeElement && this._addAttributeElement.parentNod
e) |
| 329 this._addAttributeElement.parentNode.removeChild(this._addAttrib
uteElement); |
| 330 delete this._addAttributeElement; |
| 331 } |
| 332 |
| 333 if (!this._addAttributeElement && visible && !this._editing) { |
| 334 var span = document.createElement("span"); |
| 335 span.className = "add-attribute webkit-html-attribute-name"; |
| 336 span.textContent = " ?=\"\""; |
| 337 span.addEventListener("dblclick", removeAddAttributeSpan.bind(this),
false); |
| 338 this._addAttributeElement = span; |
| 339 |
| 340 var tag = this.listItemElement.getElementsByClassName("webkit-html-t
ag")[0]; |
| 341 this._insertInLastAttributePosition(tag, span); |
| 342 } else if (!visible && this._addAttributeElement) |
| 343 removeAddAttributeSpan.call(this); |
| 344 }, |
| 345 |
| 346 updateSelection: function() |
| 347 { |
| 348 var listItemElement = this.listItemElement; |
| 349 if (!listItemElement) |
| 350 return; |
| 351 |
| 352 if (document.body.offsetWidth <= 0) { |
| 353 // The stylesheet hasn't loaded yet or the window is closed, |
| 354 // so we can't calculate what is need. Return early. |
| 355 return; |
| 356 } |
| 357 |
| 358 if (!this.selectionElement) { |
| 359 this.selectionElement = document.createElement("div"); |
| 360 this.selectionElement.className = "selection selected"; |
| 361 listItemElement.insertBefore(this.selectionElement, listItemElement.
firstChild); |
| 362 } |
| 363 |
| 364 this.selectionElement.style.height = listItemElement.offsetHeight + "px"
; |
| 365 }, |
| 366 |
| 367 onattach: function() |
| 368 { |
| 369 this.listItemElement.addEventListener("mousedown", this.onmousedown.bind
(this), false); |
| 370 |
| 371 if (this._highlighted) |
| 372 this.listItemElement.addStyleClass("highlighted"); |
| 373 |
| 374 if (this._hovered) { |
| 375 this.updateSelection(); |
| 376 this.listItemElement.addStyleClass("hovered"); |
| 377 } |
| 378 |
| 379 this._updateTitle(); |
| 380 |
| 381 this._preventFollowingLinksOnDoubleClick(); |
| 382 }, |
| 383 |
| 384 _preventFollowingLinksOnDoubleClick: function() |
| 385 { |
| 386 var links = this.listItemElement.querySelectorAll("li > .webkit-html-tag
> .webkit-html-attribute > .webkit-html-external-link, li > .webkit-html-tag >
.webkit-html-attribute > .webkit-html-resource-link"); |
| 387 if (!links) |
| 388 return; |
| 389 |
| 390 for (var i = 0; i < links.length; ++i) |
| 391 links[i].preventFollowOnDoubleClick = true; |
| 392 }, |
| 393 |
| 394 onpopulate: function() |
| 395 { |
| 396 if (this.children.length || this._showInlineText(this.representedObject)
) |
| 397 return; |
| 398 |
| 399 this.updateChildren(); |
| 400 }, |
| 401 |
| 402 updateChildren: function(fullRefresh) |
| 403 { |
| 404 WebInspector.domAgent.getChildNodesAsync(this.representedObject, this._u
pdateChildren.bind(this, fullRefresh)); |
| 405 }, |
| 406 |
| 407 _updateChildren: function(fullRefresh) |
| 408 { |
| 409 if (fullRefresh) { |
| 410 var selectedTreeElement = this.treeOutline.selectedTreeElement; |
| 411 if (selectedTreeElement && selectedTreeElement.hasAncestor(this)) |
| 412 this.select(); |
| 413 this.removeChildren(); |
| 414 } |
| 415 |
| 416 var treeElement = this; |
| 417 var treeChildIndex = 0; |
| 418 |
| 419 function updateChildrenOfNode(node) |
| 420 { |
| 421 var treeOutline = treeElement.treeOutline; |
| 422 var child = node.firstChild; |
| 423 while (child) { |
| 424 var currentTreeElement = treeElement.children[treeChildIndex]; |
| 425 if (!currentTreeElement || currentTreeElement.representedObject
!== child) { |
| 426 // Find any existing element that is later in the children l
ist. |
| 427 var existingTreeElement = null; |
| 428 for (var i = (treeChildIndex + 1); i < treeElement.children.
length; ++i) { |
| 429 if (treeElement.children[i].representedObject === child)
{ |
| 430 existingTreeElement = treeElement.children[i]; |
| 431 break; |
| 432 } |
| 433 } |
| 434 |
| 435 if (existingTreeElement && existingTreeElement.parent === tr
eeElement) { |
| 436 // If an existing element was found and it has the same
parent, just move it. |
| 437 var wasSelected = existingTreeElement.selected; |
| 438 treeElement.removeChild(existingTreeElement); |
| 439 treeElement.insertChild(existingTreeElement, treeChildIn
dex); |
| 440 if (wasSelected) |
| 441 existingTreeElement.select(); |
| 442 } else { |
| 443 // No existing element found, insert a new element. |
| 444 var newElement = new WebInspector.ElementsTreeElement(ch
ild); |
| 445 newElement.selectable = treeOutline.selectEnabled; |
| 446 treeElement.insertChild(newElement, treeChildIndex); |
| 447 } |
| 448 } |
| 449 |
| 450 child = child.nextSibling; |
| 451 ++treeChildIndex; |
| 452 } |
| 453 } |
| 454 |
| 455 // Remove any tree elements that no longer have this node (or this node'
s contentDocument) as their parent. |
| 456 for (var i = (this.children.length - 1); i >= 0; --i) { |
| 457 if ("elementCloseTag" in this.children[i]) |
| 458 continue; |
| 459 |
| 460 var currentChild = this.children[i]; |
| 461 var currentNode = currentChild.representedObject; |
| 462 var currentParentNode = currentNode.parentNode; |
| 463 |
| 464 if (currentParentNode === this.representedObject) |
| 465 continue; |
| 466 |
| 467 var selectedTreeElement = this.treeOutline.selectedTreeElement; |
| 468 if (selectedTreeElement && (selectedTreeElement === currentChild ||
selectedTreeElement.hasAncestor(currentChild))) |
| 469 this.select(); |
| 470 |
| 471 this.removeChildAtIndex(i); |
| 472 } |
| 473 |
| 474 updateChildrenOfNode(this.representedObject); |
| 475 |
| 476 var lastChild = this.children[this.children.length - 1]; |
| 477 if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild
|| !lastChild.elementCloseTag)) { |
| 478 var title = "<span class=\"webkit-html-tag close\"></" + this.rep
resentedObject.nodeName.toLowerCase().escapeHTML() + "></span>"; |
| 479 var item = new TreeElement(title, null, false); |
| 480 item.selectable = false; |
| 481 item.elementCloseTag = true; |
| 482 this.appendChild(item); |
| 483 } |
| 484 }, |
| 485 |
| 486 onexpand: function() |
| 487 { |
| 488 this.treeOutline.updateSelection(); |
| 489 }, |
| 490 |
| 491 oncollapse: function() |
| 492 { |
| 493 this.treeOutline.updateSelection(); |
| 494 }, |
| 495 |
| 496 onreveal: function() |
| 497 { |
| 498 if (this.listItemElement) |
| 499 this.listItemElement.scrollIntoViewIfNeeded(false); |
| 500 }, |
| 501 |
| 502 onselect: function() |
| 503 { |
| 504 this.treeOutline.focusedDOMNode = this.representedObject; |
| 505 this.updateSelection(); |
| 506 }, |
| 507 |
| 508 onmousedown: function(event) |
| 509 { |
| 510 if (this._editing) |
| 511 return; |
| 512 |
| 513 if (this.treeOutline.showInElementsPanelEnabled) { |
| 514 WebInspector.showElementsPanel(); |
| 515 WebInspector.panels.elements.focusedDOMNode = this.representedObject
; |
| 516 } |
| 517 |
| 518 // Prevent selecting the nearest word on double click. |
| 519 if (event.detail >= 2) |
| 520 event.preventDefault(); |
| 521 }, |
| 522 |
| 523 ondblclick: function(treeElement, event) |
| 524 { |
| 525 if (this._editing) |
| 526 return; |
| 527 |
| 528 if (this._startEditingFromEvent(event, treeElement)) |
| 529 return; |
| 530 |
| 531 if (this.treeOutline.panel) { |
| 532 this.treeOutline.rootDOMNode = this.representedObject.parentNode; |
| 533 this.treeOutline.focusedDOMNode = this.representedObject; |
| 534 } |
| 535 |
| 536 if (this.hasChildren && !this.expanded) |
| 537 this.expand(); |
| 538 }, |
| 539 |
| 540 _insertInLastAttributePosition: function(tag, node) |
| 541 { |
| 542 if (tag.getElementsByClassName("webkit-html-attribute").length > 0) |
| 543 tag.insertBefore(node, tag.lastChild); |
| 544 else { |
| 545 var nodeName = tag.textContent.match(/^<(.*?)>$/)[1]; |
| 546 tag.textContent = ''; |
| 547 tag.appendChild(document.createTextNode('<'+nodeName)); |
| 548 tag.appendChild(node); |
| 549 tag.appendChild(document.createTextNode('>')); |
| 550 } |
| 551 }, |
| 552 |
| 553 _startEditingFromEvent: function(event, treeElement) |
| 554 { |
| 555 if (this.treeOutline.focusedDOMNode != this.representedObject) |
| 556 return; |
| 557 |
| 558 if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.represe
ntedObject.nodeType != Node.TEXT_NODE) |
| 559 return false; |
| 560 |
| 561 var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-te
xt-node"); |
| 562 if (textNode) |
| 563 return this._startEditingTextNode(textNode); |
| 564 |
| 565 var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-a
ttribute"); |
| 566 if (attribute) |
| 567 return this._startEditingAttribute(attribute, event.target); |
| 568 |
| 569 var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attrib
ute"); |
| 570 if (newAttribute) |
| 571 return this._addNewAttribute(treeElement.listItemElement); |
| 572 |
| 573 return false; |
| 574 }, |
| 575 |
| 576 _startEditing: function() |
| 577 { |
| 578 if (this.treeOutline.focusedDOMNode !== this.representedObject) |
| 579 return; |
| 580 |
| 581 var listItem = this._listItemNode; |
| 582 |
| 583 if (this._canAddAttributes) { |
| 584 this.toggleNewAttributeButton(false); |
| 585 var attribute = listItem.getElementsByClassName("webkit-html-attribu
te")[0]; |
| 586 if (attribute) |
| 587 return this._startEditingAttribute(attribute, attribute.getEleme
ntsByClassName("webkit-html-attribute-value")[0]); |
| 588 |
| 589 return this._addNewAttribute(listItem); |
| 590 } |
| 591 |
| 592 if (this.representedObject.nodeType === Node.TEXT_NODE) { |
| 593 var textNode = listItem.getElementsByClassName("webkit-html-text-nod
e")[0]; |
| 594 if (textNode) |
| 595 return this._startEditingTextNode(textNode); |
| 596 return; |
| 597 } |
| 598 }, |
| 599 |
| 600 _addNewAttribute: function(listItemElement) |
| 601 { |
| 602 var attr = document.createElement("span"); |
| 603 attr.className = "webkit-html-attribute"; |
| 604 attr.style.marginLeft = "2px"; // overrides the .editing margin rule |
| 605 attr.style.marginRight = "2px"; // overrides the .editing margin rule |
| 606 var name = document.createElement("span"); |
| 607 name.className = "webkit-html-attribute-name new-attribute"; |
| 608 name.textContent = " "; |
| 609 var value = document.createElement("span"); |
| 610 value.className = "webkit-html-attribute-value"; |
| 611 attr.appendChild(name); |
| 612 attr.appendChild(value); |
| 613 |
| 614 var tag = listItemElement.getElementsByClassName("webkit-html-tag")[0]; |
| 615 this._insertInLastAttributePosition(tag, attr); |
| 616 return this._startEditingAttribute(attr, attr); |
| 617 }, |
| 618 |
| 619 _triggerEditAttribute: function(attributeName) |
| 620 { |
| 621 var attributeElements = this.listItemElement.getElementsByClassName("web
kit-html-attribute-name"); |
| 622 for (var i = 0, len = attributeElements.length; i < len; ++i) { |
| 623 if (attributeElements[i].textContent === attributeName) { |
| 624 for (var elem = attributeElements[i].nextSibling; elem; elem = e
lem.nextSibling) { |
| 625 if (elem.nodeType !== Node.ELEMENT_NODE) |
| 626 continue; |
| 627 |
| 628 if (elem.hasStyleClass("webkit-html-attribute-value")) |
| 629 return this._startEditingAttribute(attributeElements[i].
parentNode, elem); |
| 630 } |
| 631 } |
| 632 } |
| 633 }, |
| 634 |
| 635 _startEditingAttribute: function(attribute, elementForSelection) |
| 636 { |
| 637 if (WebInspector.isBeingEdited(attribute)) |
| 638 return true; |
| 639 |
| 640 var attributeNameElement = attribute.getElementsByClassName("webkit-html
-attribute-name")[0]; |
| 641 if (!attributeNameElement) |
| 642 return false; |
| 643 |
| 644 var attributeName = attributeNameElement.innerText; |
| 645 |
| 646 function removeZeroWidthSpaceRecursive(node) |
| 647 { |
| 648 if (node.nodeType === Node.TEXT_NODE) { |
| 649 node.nodeValue = node.nodeValue.replace(/\u200B/g, ""); |
| 650 return; |
| 651 } |
| 652 |
| 653 if (node.nodeType !== Node.ELEMENT_NODE) |
| 654 return; |
| 655 |
| 656 for (var child = node.firstChild; child; child = child.nextSibling) |
| 657 removeZeroWidthSpaceRecursive(child); |
| 658 } |
| 659 |
| 660 // Remove zero-width spaces that were added by nodeTitleInfo. |
| 661 removeZeroWidthSpaceRecursive(attribute); |
| 662 |
| 663 this._editing = true; |
| 664 |
| 665 WebInspector.startEditing(attribute, this._attributeEditingCommitted.bin
d(this), this._editingCancelled.bind(this), attributeName); |
| 666 window.getSelection().setBaseAndExtent(elementForSelection, 0, elementFo
rSelection, 1); |
| 667 |
| 668 return true; |
| 669 }, |
| 670 |
| 671 _startEditingTextNode: function(textNode) |
| 672 { |
| 673 if (WebInspector.isBeingEdited(textNode)) |
| 674 return true; |
| 675 |
| 676 this._editing = true; |
| 677 |
| 678 WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(
this), this._editingCancelled.bind(this)); |
| 679 window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1); |
| 680 |
| 681 return true; |
| 682 }, |
| 683 |
| 684 _attributeEditingCommitted: function(element, newText, oldText, attributeNam
e, moveDirection) |
| 685 { |
| 686 delete this._editing; |
| 687 |
| 688 // Before we do anything, determine where we should move |
| 689 // next based on the current element's settings |
| 690 var moveToAttribute; |
| 691 var newAttribute; |
| 692 if (moveDirection) { |
| 693 var found = false; |
| 694 var attributes = this.representedObject.attributes; |
| 695 for (var i = 0, len = attributes.length; i < len; ++i) { |
| 696 if (attributes[i].name === attributeName) { |
| 697 found = true; |
| 698 if (moveDirection === "backward" && i > 0) |
| 699 moveToAttribute = attributes[i - 1].name; |
| 700 else if (moveDirection === "forward" && i < attributes.lengt
h - 1) |
| 701 moveToAttribute = attributes[i + 1].name; |
| 702 else if (moveDirection === "forward" && i === attributes.len
gth - 1) |
| 703 newAttribute = true; |
| 704 } |
| 705 } |
| 706 |
| 707 if (!found && moveDirection === "backward" && attributes.length > 0) |
| 708 moveToAttribute = attributes[attributes.length - 1].name; |
| 709 else if (!found && moveDirection === "forward" && !/^\s*$/.test(newT
ext)) |
| 710 newAttribute = true; |
| 711 } |
| 712 |
| 713 function moveToNextAttributeIfNeeded() { |
| 714 if (moveToAttribute) |
| 715 this._triggerEditAttribute(moveToAttribute); |
| 716 else if (newAttribute) |
| 717 this._addNewAttribute(this.listItemElement); |
| 718 } |
| 719 |
| 720 var parseContainerElement = document.createElement("span"); |
| 721 parseContainerElement.innerHTML = "<span " + newText + "></span>"; |
| 722 var parseElement = parseContainerElement.firstChild; |
| 723 |
| 724 if (!parseElement) { |
| 725 this._editingCancelled(element, attributeName); |
| 726 moveToNextAttributeIfNeeded.call(this); |
| 727 return; |
| 728 } |
| 729 |
| 730 if (!parseElement.hasAttributes()) { |
| 731 this.representedObject.removeAttribute(attributeName); |
| 732 this._updateTitle(); |
| 733 moveToNextAttributeIfNeeded.call(this); |
| 734 return; |
| 735 } |
| 736 |
| 737 var foundOriginalAttribute = false; |
| 738 for (var i = 0; i < parseElement.attributes.length; ++i) { |
| 739 var attr = parseElement.attributes[i]; |
| 740 foundOriginalAttribute = foundOriginalAttribute || attr.name === att
ributeName; |
| 741 try { |
| 742 this.representedObject.setAttribute(attr.name, attr.value); |
| 743 } catch(e) {} // ignore invalid attribute (innerHTML doesn't throw e
rrors, but this can) |
| 744 } |
| 745 |
| 746 if (!foundOriginalAttribute) |
| 747 this.representedObject.removeAttribute(attributeName); |
| 748 |
| 749 this._updateTitle(); |
| 750 |
| 751 this.treeOutline.focusedNodeChanged(true); |
| 752 |
| 753 moveToNextAttributeIfNeeded.call(this); |
| 754 }, |
| 755 |
| 756 _textNodeEditingCommitted: function(element, newText) |
| 757 { |
| 758 delete this._editing; |
| 759 |
| 760 var textNode; |
| 761 if (this.representedObject.nodeType == Node.ELEMENT_NODE) { |
| 762 // We only show text nodes inline in elements if the element only |
| 763 // has a single child, and that child is a text node. |
| 764 textNode = this.representedObject.firstChild; |
| 765 } else if (this.representedObject.nodeType == Node.TEXT_NODE) |
| 766 textNode = this.representedObject; |
| 767 |
| 768 textNode.nodeValue = newText; |
| 769 this._updateTitle(); |
| 770 }, |
| 771 |
| 772 _editingCancelled: function(element, context) |
| 773 { |
| 774 delete this._editing; |
| 775 |
| 776 this._updateTitle(); |
| 777 }, |
| 778 |
| 779 _updateTitle: function() |
| 780 { |
| 781 var title = this._nodeTitleInfo(this.representedObject, this.hasChildren
, WebInspector.linkifyURL).title; |
| 782 this.title = "<span class=\"highlight\">" + title + "</span>"; |
| 783 delete this.selectionElement; |
| 784 this.updateSelection(); |
| 785 this._preventFollowingLinksOnDoubleClick(); |
| 786 }, |
| 787 |
| 788 _nodeTitleInfo: function(node, hasChildren, linkify) |
| 789 { |
| 790 var info = {title: "", hasChildren: hasChildren}; |
| 791 |
| 792 switch (node.nodeType) { |
| 793 case Node.DOCUMENT_NODE: |
| 794 info.title = "Document"; |
| 795 break; |
| 796 |
| 797 case Node.ELEMENT_NODE: |
| 798 info.title = "<span class=\"webkit-html-tag\"><" + node.nodeN
ame.toLowerCase().escapeHTML(); |
| 799 |
| 800 if (node.hasAttributes()) { |
| 801 for (var i = 0; i < node.attributes.length; ++i) { |
| 802 var attr = node.attributes[i]; |
| 803 info.title += " <span class=\"webkit-html-attribute\"><s
pan class=\"webkit-html-attribute-name\">" + attr.name.escapeHTML() + "</span>=&
#8203;\""; |
| 804 |
| 805 var value = attr.value; |
| 806 if (linkify && (attr.name === "src" || attr.name === "hr
ef")) { |
| 807 var value = value.replace(/([\/;:\)\]\}])/g, "$1\u20
0B"); |
| 808 info.title += linkify(attr.value, value, "webkit-htm
l-attribute-value", node.nodeName.toLowerCase() == "a"); |
| 809 } else { |
| 810 var value = value.escapeHTML(); |
| 811 value = value.replace(/([\/;:\)\]\}])/g, "$1​"
); |
| 812 info.title += "<span class=\"webkit-html-attribute-v
alue\">" + value + "</span>"; |
| 813 } |
| 814 info.title += "\"</span>"; |
| 815 } |
| 816 } |
| 817 info.title += "></span>​"; |
| 818 |
| 819 // If this element only has a single child that is a text node, |
| 820 // just show that text and the closing tag inline rather than |
| 821 // create a subtree for them |
| 822 |
| 823 var textChild = onlyTextChild.call(node); |
| 824 var showInlineText = textChild && textChild.textContent.length <
Preferences.maxInlineTextChildLength; |
| 825 |
| 826 if (showInlineText) { |
| 827 info.title += "<span class=\"webkit-html-text-node\">" + tex
tChild.nodeValue.escapeHTML() + "</span>​<span class=\"webkit-html-tag\">&
lt;/" + node.nodeName.toLowerCase().escapeHTML() + "></span>"; |
| 828 info.hasChildren = false; |
| 829 } |
| 830 break; |
| 831 |
| 832 case Node.TEXT_NODE: |
| 833 if (isNodeWhitespace.call(node)) |
| 834 info.title = "(whitespace)"; |
| 835 else { |
| 836 if (node.parentNode && node.parentNode.nodeName.toLowerCase(
) == "script") { |
| 837 var newNode = document.createElement("span"); |
| 838 newNode.textContent = node.textContent; |
| 839 |
| 840 var javascriptSyntaxHighlighter = new WebInspector.JavaS
criptSourceSyntaxHighlighter(null, null); |
| 841 javascriptSyntaxHighlighter.syntaxHighlightLine(newNode,
null); |
| 842 |
| 843 info.title = "<span class=\"webkit-html-text-node webkit
-html-js-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, ""
) + "</span>"; |
| 844 } else if (node.parentNode && node.parentNode.nodeName.toLow
erCase() == "style") { |
| 845 var newNode = document.createElement("span"); |
| 846 newNode.textContent = node.textContent; |
| 847 |
| 848 var cssSyntaxHighlighter = new WebInspector.CSSSourceSyn
taxHighligher(null, null); |
| 849 cssSyntaxHighlighter.syntaxHighlightLine(newNode, null); |
| 850 |
| 851 info.title = "<span class=\"webkit-html-text-node webkit
-html-css-node\">" + newNode.innerHTML.replace(/^[\n\r]*/, "").replace(/\s*$/, "
") + "</span>"; |
| 852 } else { |
| 853 info.title = "\"<span class=\"webkit-html-text-node\">"
+ node.nodeValue.escapeHTML() + "</span>\""; |
| 854 } |
| 855 } |
| 856 break; |
| 857 |
| 858 case Node.COMMENT_NODE: |
| 859 info.title = "<span class=\"webkit-html-comment\"><!--" + nod
e.nodeValue.escapeHTML() + "--></span>"; |
| 860 break; |
| 861 |
| 862 case Node.DOCUMENT_TYPE_NODE: |
| 863 info.title = "<span class=\"webkit-html-doctype\"><!DOCTYPE "
+ node.nodeName; |
| 864 if (node.publicId) { |
| 865 info.title += " PUBLIC \"" + node.publicId + "\""; |
| 866 if (node.systemId) |
| 867 info.title += " \"" + node.systemId + "\""; |
| 868 } else if (node.systemId) |
| 869 info.title += " SYSTEM \"" + node.systemId + "\""; |
| 870 if (node.internalSubset) |
| 871 info.title += " [" + node.internalSubset + "]"; |
| 872 info.title += "></span>"; |
| 873 break; |
| 874 default: |
| 875 info.title = node.nodeName.toLowerCase().collapseWhitespace().es
capeHTML(); |
| 876 } |
| 877 |
| 878 return info; |
| 879 }, |
| 880 |
| 881 _showInlineText: function(node) |
| 882 { |
| 883 if (node.nodeType === Node.ELEMENT_NODE) { |
| 884 var textChild = onlyTextChild.call(node); |
| 885 if (textChild && textChild.textContent.length < Preferences.maxInlin
eTextChildLength) |
| 886 return true; |
| 887 } |
| 888 return false; |
| 889 }, |
| 890 |
| 891 remove: function() |
| 892 { |
| 893 var parentElement = this.parent; |
| 894 if (!parentElement) |
| 895 return; |
| 896 |
| 897 var self = this; |
| 898 function removeNodeCallback(removedNodeId) |
| 899 { |
| 900 // -1 is an error code, which means removing the node from the DOM f
ailed, |
| 901 // so we shouldn't remove it from the tree. |
| 902 if (removedNodeId === -1) |
| 903 return; |
| 904 |
| 905 parentElement.removeChild(self); |
| 906 } |
| 907 |
| 908 var callId = WebInspector.Callback.wrap(removeNodeCallback); |
| 909 InspectorController.removeNode(callId, this.representedObject.id); |
| 910 } |
| 911 } |
| 912 |
| 913 WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype; |
| 914 |
| 915 WebInspector.didRemoveNode = WebInspector.Callback.processCallback; |
OLD | NEW |