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("dblclick", this._ondblclick.bind(this), false
); |
| 35 this.element.addEventListener("mousemove", this._onmousemove.bind(this), fal
se); |
| 36 this.element.addEventListener("mouseout", this._onmouseout.bind(this), false
); |
| 37 |
| 38 TreeOutline.call(this, this.element); |
| 39 |
| 40 this.includeRootDOMNode = true; |
| 41 this.selectEnabled = 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 = (Preferences.ignoreWhitespace ? firstChildSkippingWhitesp
ace.call(this.rootDOMNode) : this.rootDOMNode.firstChild); |
| 120 while (node) { |
| 121 treeElement = new WebInspector.ElementsTreeElement(node); |
| 122 treeElement.selectable = this.selectEnabled; |
| 123 this.appendChild(treeElement); |
| 124 node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitesp
ace.call(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 _ondblclick: function(event) |
| 190 { |
| 191 var element = this._treeElementFromEvent(event); |
| 192 |
| 193 if (!element || !element.ondblclick) |
| 194 return; |
| 195 |
| 196 element.ondblclick(element, event); |
| 197 }, |
| 198 |
| 199 _onmousedown: function(event) |
| 200 { |
| 201 var element = this._treeElementFromEvent(event); |
| 202 |
| 203 if (!element || element.isEventWithinDisclosureTriangle(event)) |
| 204 return; |
| 205 |
| 206 element.select(); |
| 207 }, |
| 208 |
| 209 _onmousemove: function(event) |
| 210 { |
| 211 if (this._previousHoveredElement) { |
| 212 this._previousHoveredElement.hovered = false; |
| 213 delete this._previousHoveredElement; |
| 214 } |
| 215 |
| 216 var element = this._treeElementFromEvent(event); |
| 217 if (element && !element.elementCloseTag) { |
| 218 element.hovered = true; |
| 219 this._previousHoveredElement = element; |
| 220 } |
| 221 |
| 222 WebInspector.hoveredDOMNode = (element && !element.elementCloseTag ? ele
ment.representedObject : null); |
| 223 }, |
| 224 |
| 225 _onmouseout: function(event) |
| 226 { |
| 227 var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY)
; |
| 228 if (nodeUnderMouse && nodeUnderMouse.isDescendant(this.element)) |
| 229 return; |
| 230 |
| 231 if (this._previousHoveredElement) { |
| 232 this._previousHoveredElement.hovered = false; |
| 233 delete this._previousHoveredElement; |
| 234 } |
| 235 |
| 236 WebInspector.hoveredDOMNode = null; |
| 237 } |
| 238 } |
| 239 |
| 240 WebInspector.ElementsTreeOutline.prototype.__proto__ = TreeOutline.prototype; |
| 241 |
| 242 WebInspector.ElementsTreeElement = function(node) |
| 243 { |
| 244 var hasChildren = Preferences.ignoreWhitespace ? (firstChildSkippingWhitespa
ce.call(node) ? true : false) : node.hasChildNodes(); |
| 245 var titleInfo = nodeTitleInfo.call(node, hasChildren, WebInspector.linkifyUR
L); |
| 246 |
| 247 if (titleInfo.hasChildren) |
| 248 this.whitespaceIgnored = Preferences.ignoreWhitespace; |
| 249 |
| 250 // The title will be updated in onattach. |
| 251 TreeElement.call(this, "", node, titleInfo.hasChildren); |
| 252 |
| 253 if (this.representedObject.nodeType == Node.ELEMENT_NODE) |
| 254 this._canAddAttributes = true; |
| 255 } |
| 256 |
| 257 WebInspector.ElementsTreeElement.prototype = { |
| 258 get highlighted() |
| 259 { |
| 260 return this._highlighted; |
| 261 }, |
| 262 |
| 263 set highlighted(x) |
| 264 { |
| 265 if (this._highlighted === x) |
| 266 return; |
| 267 |
| 268 this._highlighted = x; |
| 269 |
| 270 if (this.listItemElement) { |
| 271 if (x) |
| 272 this.listItemElement.addStyleClass("highlighted"); |
| 273 else |
| 274 this.listItemElement.removeStyleClass("highlighted"); |
| 275 } |
| 276 }, |
| 277 |
| 278 get hovered() |
| 279 { |
| 280 return this._hovered; |
| 281 }, |
| 282 |
| 283 set hovered(x) |
| 284 { |
| 285 if (this._hovered === x) |
| 286 return; |
| 287 |
| 288 this._hovered = x; |
| 289 |
| 290 if (this.listItemElement) { |
| 291 if (x) { |
| 292 this.updateSelection(); |
| 293 this.listItemElement.addStyleClass("hovered"); |
| 294 } else |
| 295 this.listItemElement.removeStyleClass("hovered"); |
| 296 if (this._canAddAttributes) |
| 297 this.toggleNewAttributeButton(); |
| 298 } |
| 299 }, |
| 300 |
| 301 toggleNewAttributeButton: function() |
| 302 { |
| 303 function removeWhenEditing(event) |
| 304 { |
| 305 if (this._addAttributeElement && this._addAttributeElement.parentNod
e) |
| 306 this._addAttributeElement.parentNode.removeChild(this._addAttrib
uteElement); |
| 307 delete this._addAttributeElement; |
| 308 } |
| 309 |
| 310 if (!this._addAttributeElement && this._hovered && !this._editing) { |
| 311 var span = document.createElement("span"); |
| 312 span.className = "add-attribute"; |
| 313 span.textContent = "\u2026"; |
| 314 span.addEventListener("dblclick", removeWhenEditing.bind(this), fals
e); |
| 315 this._addAttributeElement = span; |
| 316 |
| 317 var tag = this.listItemElement.getElementsByClassName("webkit-html-t
ag")[0]; |
| 318 this._insertInLastAttributePosition(tag, span); |
| 319 } else if (!this._hovered && this._addAttributeElement) { |
| 320 if (this._addAttributeElement.parentNode) |
| 321 this._addAttributeElement.parentNode.removeChild(this._addAttrib
uteElement); |
| 322 delete this._addAttributeElement; |
| 323 } |
| 324 }, |
| 325 |
| 326 updateSelection: function() |
| 327 { |
| 328 var listItemElement = this.listItemElement; |
| 329 if (!listItemElement) |
| 330 return; |
| 331 |
| 332 if (document.body.offsetWidth <= 0) { |
| 333 // The stylesheet hasn't loaded yet or the window is closed, |
| 334 // so we can't calculate what is need. Return early. |
| 335 return; |
| 336 } |
| 337 |
| 338 if (!this.selectionElement) { |
| 339 this.selectionElement = document.createElement("div"); |
| 340 this.selectionElement.className = "selection selected"; |
| 341 listItemElement.insertBefore(this.selectionElement, listItemElement.
firstChild); |
| 342 } |
| 343 |
| 344 this.selectionElement.style.height = listItemElement.offsetHeight + "px"
; |
| 345 }, |
| 346 |
| 347 onattach: function() |
| 348 { |
| 349 this.listItemElement.addEventListener("mousedown", this.onmousedown.bind
(this), false); |
| 350 |
| 351 if (this._highlighted) |
| 352 this.listItemElement.addStyleClass("highlighted"); |
| 353 |
| 354 if (this._hovered) { |
| 355 this.updateSelection(); |
| 356 this.listItemElement.addStyleClass("hovered"); |
| 357 } |
| 358 |
| 359 this._updateTitle(); |
| 360 |
| 361 this._preventFollowingLinksOnDoubleClick(); |
| 362 }, |
| 363 |
| 364 _preventFollowingLinksOnDoubleClick: function() |
| 365 { |
| 366 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"); |
| 367 if (!links) |
| 368 return; |
| 369 |
| 370 for (var i = 0; i < links.length; ++i) |
| 371 links[i].preventFollowOnDoubleClick = true; |
| 372 }, |
| 373 |
| 374 onpopulate: function() |
| 375 { |
| 376 if (this.children.length || this.whitespaceIgnored !== Preferences.ignor
eWhitespace) |
| 377 return; |
| 378 |
| 379 this.whitespaceIgnored = Preferences.ignoreWhitespace; |
| 380 |
| 381 this.updateChildren(); |
| 382 }, |
| 383 |
| 384 updateChildren: function(fullRefresh) |
| 385 { |
| 386 WebInspector.domAgent.getChildNodesAsync(this.representedObject, this._u
pdateChildren.bind(this, fullRefresh)); |
| 387 }, |
| 388 |
| 389 _updateChildren: function(fullRefresh) |
| 390 { |
| 391 if (fullRefresh) { |
| 392 var selectedTreeElement = this.treeOutline.selectedTreeElement; |
| 393 if (selectedTreeElement && selectedTreeElement.hasAncestor(this)) |
| 394 this.select(); |
| 395 this.removeChildren(); |
| 396 } |
| 397 |
| 398 var treeElement = this; |
| 399 var treeChildIndex = 0; |
| 400 |
| 401 function updateChildrenOfNode(node) |
| 402 { |
| 403 var treeOutline = treeElement.treeOutline; |
| 404 var child = (Preferences.ignoreWhitespace ? firstChildSkippingWhites
pace.call(node) : node.firstChild); |
| 405 while (child) { |
| 406 var currentTreeElement = treeElement.children[treeChildIndex]; |
| 407 if (!currentTreeElement || currentTreeElement.representedObject
!== child) { |
| 408 // Find any existing element that is later in the children l
ist. |
| 409 var existingTreeElement = null; |
| 410 for (var i = (treeChildIndex + 1); i < treeElement.children.
length; ++i) { |
| 411 if (treeElement.children[i].representedObject === child)
{ |
| 412 existingTreeElement = treeElement.children[i]; |
| 413 break; |
| 414 } |
| 415 } |
| 416 |
| 417 if (existingTreeElement && existingTreeElement.parent === tr
eeElement) { |
| 418 // If an existing element was found and it has the same
parent, just move it. |
| 419 var wasSelected = existingTreeElement.selected; |
| 420 treeElement.removeChild(existingTreeElement); |
| 421 treeElement.insertChild(existingTreeElement, treeChildIn
dex); |
| 422 if (wasSelected) |
| 423 existingTreeElement.select(); |
| 424 } else { |
| 425 // No existing element found, insert a new element. |
| 426 var newElement = new WebInspector.ElementsTreeElement(ch
ild); |
| 427 newElement.selectable = treeOutline.selectEnabled; |
| 428 treeElement.insertChild(newElement, treeChildIndex); |
| 429 } |
| 430 } |
| 431 |
| 432 child = Preferences.ignoreWhitespace ? nextSiblingSkippingWhites
pace.call(child) : child.nextSibling; |
| 433 ++treeChildIndex; |
| 434 } |
| 435 } |
| 436 |
| 437 // Remove any tree elements that no longer have this node (or this node'
s contentDocument) as their parent. |
| 438 for (var i = (this.children.length - 1); i >= 0; --i) { |
| 439 if ("elementCloseTag" in this.children[i]) |
| 440 continue; |
| 441 |
| 442 var currentChild = this.children[i]; |
| 443 var currentNode = currentChild.representedObject; |
| 444 var currentParentNode = currentNode.parentNode; |
| 445 |
| 446 if (currentParentNode === this.representedObject) |
| 447 continue; |
| 448 |
| 449 var selectedTreeElement = this.treeOutline.selectedTreeElement; |
| 450 if (selectedTreeElement && (selectedTreeElement === currentChild ||
selectedTreeElement.hasAncestor(currentChild))) |
| 451 this.select(); |
| 452 |
| 453 this.removeChildAtIndex(i); |
| 454 } |
| 455 |
| 456 updateChildrenOfNode(this.representedObject); |
| 457 |
| 458 var lastChild = this.children[this.children.length - 1]; |
| 459 if (this.representedObject.nodeType == Node.ELEMENT_NODE && (!lastChild
|| !lastChild.elementCloseTag)) { |
| 460 var title = "<span class=\"webkit-html-tag close\"></" + this.rep
resentedObject.nodeName.toLowerCase().escapeHTML() + "></span>"; |
| 461 var item = new TreeElement(title, null, false); |
| 462 item.selectable = false; |
| 463 item.elementCloseTag = true; |
| 464 this.appendChild(item); |
| 465 } |
| 466 }, |
| 467 |
| 468 onexpand: function() |
| 469 { |
| 470 this.treeOutline.updateSelection(); |
| 471 }, |
| 472 |
| 473 oncollapse: function() |
| 474 { |
| 475 this.treeOutline.updateSelection(); |
| 476 }, |
| 477 |
| 478 onreveal: function() |
| 479 { |
| 480 if (this.listItemElement) |
| 481 this.listItemElement.scrollIntoViewIfNeeded(false); |
| 482 }, |
| 483 |
| 484 onselect: function() |
| 485 { |
| 486 this.treeOutline.focusedDOMNode = this.representedObject; |
| 487 this.updateSelection(); |
| 488 }, |
| 489 |
| 490 onmousedown: function(event) |
| 491 { |
| 492 if (this._editing) |
| 493 return; |
| 494 |
| 495 // Prevent selecting the nearest word on double click. |
| 496 if (event.detail >= 2) |
| 497 event.preventDefault(); |
| 498 }, |
| 499 |
| 500 ondblclick: function(treeElement, event) |
| 501 { |
| 502 if (this._editing) |
| 503 return; |
| 504 |
| 505 if (this._startEditing(event, treeElement)) |
| 506 return; |
| 507 |
| 508 if (this.treeOutline.panel) { |
| 509 this.treeOutline.rootDOMNode = this.representedObject.parentNode; |
| 510 this.treeOutline.focusedDOMNode = this.representedObject; |
| 511 } |
| 512 |
| 513 if (this.hasChildren && !this.expanded) |
| 514 this.expand(); |
| 515 }, |
| 516 |
| 517 _insertInLastAttributePosition: function(tag, node) |
| 518 { |
| 519 if (tag.getElementsByClassName("webkit-html-attribute").length > 0) |
| 520 tag.insertBefore(node, tag.lastChild); |
| 521 else { |
| 522 var nodeName = tag.textContent.match(/^<(.*?)>$/)[1]; |
| 523 tag.textContent = ''; |
| 524 tag.appendChild(document.createTextNode('<'+nodeName)); |
| 525 tag.appendChild(node); |
| 526 tag.appendChild(document.createTextNode('>')); |
| 527 } |
| 528 }, |
| 529 |
| 530 _startEditing: function(event, treeElement) |
| 531 { |
| 532 if (this.treeOutline.focusedDOMNode != this.representedObject) |
| 533 return; |
| 534 |
| 535 if (this.representedObject.nodeType != Node.ELEMENT_NODE && this.represe
ntedObject.nodeType != Node.TEXT_NODE) |
| 536 return false; |
| 537 |
| 538 var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-te
xt-node"); |
| 539 if (textNode) |
| 540 return this._startEditingTextNode(textNode); |
| 541 |
| 542 var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-a
ttribute"); |
| 543 if (attribute) |
| 544 return this._startEditingAttribute(attribute, event.target); |
| 545 |
| 546 var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attrib
ute"); |
| 547 if (newAttribute) |
| 548 return this._addNewAttribute(treeElement.listItemElement); |
| 549 |
| 550 return false; |
| 551 }, |
| 552 |
| 553 _addNewAttribute: function(listItemElement) |
| 554 { |
| 555 var attr = document.createElement("span"); |
| 556 attr.className = "webkit-html-attribute"; |
| 557 attr.style.marginLeft = "2px"; // overrides the .editing margin rule |
| 558 attr.style.marginRight = "2px"; // overrides the .editing margin rule |
| 559 var name = document.createElement("span"); |
| 560 name.className = "webkit-html-attribute-name new-attribute"; |
| 561 name.textContent = " "; |
| 562 var value = document.createElement("span"); |
| 563 value.className = "webkit-html-attribute-value"; |
| 564 attr.appendChild(name); |
| 565 attr.appendChild(value); |
| 566 |
| 567 var tag = listItemElement.getElementsByClassName("webkit-html-tag")[0]; |
| 568 this._insertInLastAttributePosition(tag, attr); |
| 569 return this._startEditingAttribute(attr, attr); |
| 570 }, |
| 571 |
| 572 _triggerEditAttribute: function(attributeName) |
| 573 { |
| 574 var attributeElements = this.listItemElement.getElementsByClassName("web
kit-html-attribute-name"); |
| 575 for (var i = 0, len = attributeElements.length; i < len; ++i) { |
| 576 if (attributeElements[i].textContent === attributeName) { |
| 577 for (var elem = attributeElements[i].nextSibling; elem; elem = e
lem.nextSibling) { |
| 578 if (elem.nodeType !== Node.ELEMENT_NODE) |
| 579 continue; |
| 580 |
| 581 if (elem.hasStyleClass("webkit-html-attribute-value")) |
| 582 return this._startEditingAttribute(attributeElements[i].
parentNode, elem); |
| 583 } |
| 584 } |
| 585 } |
| 586 }, |
| 587 |
| 588 _startEditingAttribute: function(attribute, elementForSelection) |
| 589 { |
| 590 if (WebInspector.isBeingEdited(attribute)) |
| 591 return true; |
| 592 |
| 593 var attributeNameElement = attribute.getElementsByClassName("webkit-html
-attribute-name")[0]; |
| 594 if (!attributeNameElement) |
| 595 return false; |
| 596 |
| 597 var attributeName = attributeNameElement.innerText; |
| 598 |
| 599 function removeZeroWidthSpaceRecursive(node) |
| 600 { |
| 601 if (node.nodeType === Node.TEXT_NODE) { |
| 602 node.nodeValue = node.nodeValue.replace(/\u200B/g, ""); |
| 603 return; |
| 604 } |
| 605 |
| 606 if (node.nodeType !== Node.ELEMENT_NODE) |
| 607 return; |
| 608 |
| 609 for (var child = node.firstChild; child; child = child.nextSibling) |
| 610 removeZeroWidthSpaceRecursive(child); |
| 611 } |
| 612 |
| 613 // Remove zero-width spaces that were added by nodeTitleInfo. |
| 614 removeZeroWidthSpaceRecursive(attribute); |
| 615 |
| 616 this._editing = true; |
| 617 |
| 618 WebInspector.startEditing(attribute, this._attributeEditingCommitted.bin
d(this), this._editingCancelled.bind(this), attributeName); |
| 619 window.getSelection().setBaseAndExtent(elementForSelection, 0, elementFo
rSelection, 1); |
| 620 |
| 621 return true; |
| 622 }, |
| 623 |
| 624 _startEditingTextNode: function(textNode) |
| 625 { |
| 626 if (WebInspector.isBeingEdited(textNode)) |
| 627 return true; |
| 628 |
| 629 this._editing = true; |
| 630 |
| 631 WebInspector.startEditing(textNode, this._textNodeEditingCommitted.bind(
this), this._editingCancelled.bind(this)); |
| 632 window.getSelection().setBaseAndExtent(textNode, 0, textNode, 1); |
| 633 |
| 634 return true; |
| 635 }, |
| 636 |
| 637 _attributeEditingCommitted: function(element, newText, oldText, attributeNam
e, moveDirection) |
| 638 { |
| 639 delete this._editing; |
| 640 |
| 641 // Before we do anything, determine where we should move |
| 642 // next based on the current element's settings |
| 643 var moveToAttribute; |
| 644 var newAttribute; |
| 645 if (moveDirection) { |
| 646 var found = false; |
| 647 var attributes = this.representedObject.attributes; |
| 648 for (var i = 0, len = attributes.length; i < len; ++i) { |
| 649 if (attributes[i].name === attributeName) { |
| 650 found = true; |
| 651 if (moveDirection === "backward" && i > 0) |
| 652 moveToAttribute = attributes[i - 1].name; |
| 653 else if (moveDirection === "forward" && i < attributes.lengt
h - 1) |
| 654 moveToAttribute = attributes[i + 1].name; |
| 655 else if (moveDirection === "forward" && i === attributes.len
gth - 1) |
| 656 newAttribute = true; |
| 657 } |
| 658 } |
| 659 |
| 660 if (!found && moveDirection === "backward") |
| 661 moveToAttribute = attributes[attributes.length - 1].name; |
| 662 else if (!found && moveDirection === "forward" && !/^\s*$/.test(newT
ext)) |
| 663 newAttribute = true; |
| 664 } |
| 665 |
| 666 function moveToNextAttributeIfNeeded() { |
| 667 if (moveToAttribute) |
| 668 this._triggerEditAttribute(moveToAttribute); |
| 669 else if (newAttribute) |
| 670 this._addNewAttribute(this.listItemElement); |
| 671 } |
| 672 |
| 673 var parseContainerElement = document.createElement("span"); |
| 674 parseContainerElement.innerHTML = "<span " + newText + "></span>"; |
| 675 var parseElement = parseContainerElement.firstChild; |
| 676 |
| 677 if (!parseElement) { |
| 678 this._editingCancelled(element, attributeName); |
| 679 moveToNextAttributeIfNeeded.call(this); |
| 680 return; |
| 681 } |
| 682 |
| 683 if (!parseElement.hasAttributes()) { |
| 684 this.representedObject.removeAttribute(attributeName); |
| 685 this._updateTitle(); |
| 686 moveToNextAttributeIfNeeded.call(this); |
| 687 return; |
| 688 } |
| 689 |
| 690 var foundOriginalAttribute = false; |
| 691 for (var i = 0; i < parseElement.attributes.length; ++i) { |
| 692 var attr = parseElement.attributes[i]; |
| 693 foundOriginalAttribute = foundOriginalAttribute || attr.name === att
ributeName; |
| 694 try { |
| 695 this.representedObject.setAttribute(attr.name, attr.value); |
| 696 } catch(e) {} // ignore invalid attribute (innerHTML doesn't throw e
rrors, but this can) |
| 697 } |
| 698 |
| 699 if (!foundOriginalAttribute) |
| 700 this.representedObject.removeAttribute(attributeName); |
| 701 |
| 702 this._updateTitle(); |
| 703 |
| 704 this.treeOutline.focusedNodeChanged(true); |
| 705 |
| 706 moveToNextAttributeIfNeeded.call(this); |
| 707 }, |
| 708 |
| 709 _textNodeEditingCommitted: function(element, newText) |
| 710 { |
| 711 delete this._editing; |
| 712 |
| 713 var textNode; |
| 714 if (this.representedObject.nodeType == Node.ELEMENT_NODE) { |
| 715 // We only show text nodes inline in elements if the element only |
| 716 // has a single child, and that child is a text node. |
| 717 textNode = this.representedObject.firstChild; |
| 718 } else if (this.representedObject.nodeType == Node.TEXT_NODE) |
| 719 textNode = this.representedObject; |
| 720 |
| 721 textNode.nodeValue = newText; |
| 722 this._updateTitle(); |
| 723 }, |
| 724 |
| 725 _editingCancelled: function(element, context) |
| 726 { |
| 727 delete this._editing; |
| 728 |
| 729 this._updateTitle(); |
| 730 }, |
| 731 |
| 732 _updateTitle: function() |
| 733 { |
| 734 var title = nodeTitleInfo.call(this.representedObject, this.hasChildren,
WebInspector.linkifyURL).title; |
| 735 this.title = "<span class=\"highlight\">" + title + "</span>"; |
| 736 delete this.selectionElement; |
| 737 this.updateSelection(); |
| 738 this._preventFollowingLinksOnDoubleClick(); |
| 739 }, |
| 740 } |
| 741 |
| 742 WebInspector.ElementsTreeElement.prototype.__proto__ = TreeElement.prototype; |
OLD | NEW |