OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. | 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. |
3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> | 3 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> |
4 * Copyright (C) 2009 Joseph Pecoraro | 4 * Copyright (C) 2009 Joseph Pecoraro |
5 * | 5 * |
6 * Redistribution and use in source and binary forms, with or without | 6 * Redistribution and use in source and binary forms, with or without |
7 * modification, are permitted provided that the following conditions | 7 * modification, are permitted provided that the following conditions |
8 * are met: | 8 * are met: |
9 * | 9 * |
10 * 1. Redistributions of source code must retain the above copyright | 10 * 1. Redistributions of source code must retain the above copyright |
11 * notice, this list of conditions and the following disclaimer. | 11 * notice, this list of conditions and the following disclaimer. |
12 * 2. Redistributions in binary form must reproduce the above copyright | 12 * 2. Redistributions in binary form must reproduce the above copyright |
13 * notice, this list of conditions and the following disclaimer in the | 13 * notice, this list of conditions and the following disclaimer in the |
14 * documentation and/or other materials provided with the distribution. | 14 * documentation and/or other materials provided with the distribution. |
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of | 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 | 16 * its contributors may be used to endorse or promote products derived |
17 * from this software without specific prior written permission. | 17 * from this software without specific prior written permission. |
18 * | 18 * |
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | 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 | 20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 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 | 22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 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 | 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 | 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 | 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. | 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 */ | 29 */ |
30 | |
31 /** | 30 /** |
32 * @constructor | 31 * @unrestricted |
33 * @extends {TreeElement} | |
34 * @param {!WebInspector.DOMNode} node | |
35 * @param {boolean=} elementCloseTag | |
36 */ | 32 */ |
37 WebInspector.ElementsTreeElement = function(node, elementCloseTag) | 33 WebInspector.ElementsTreeElement = class extends TreeElement { |
38 { | 34 /** |
| 35 * @param {!WebInspector.DOMNode} node |
| 36 * @param {boolean=} elementCloseTag |
| 37 */ |
| 38 constructor(node, elementCloseTag) { |
39 // The title will be updated in onattach. | 39 // The title will be updated in onattach. |
40 TreeElement.call(this); | 40 super(); |
41 this._node = node; | 41 this._node = node; |
42 | 42 |
43 this._gutterContainer = this.listItemElement.createChild("div", "gutter-cont
ainer"); | 43 this._gutterContainer = this.listItemElement.createChild('div', 'gutter-cont
ainer'); |
44 this._gutterContainer.addEventListener("click", this._showContextMenu.bind(t
his)); | 44 this._gutterContainer.addEventListener('click', this._showContextMenu.bind(t
his)); |
45 this._decorationsElement = this._gutterContainer.createChild("div", "hidden"
); | 45 this._decorationsElement = this._gutterContainer.createChild('div', 'hidden'
); |
46 | 46 |
47 this._elementCloseTag = elementCloseTag; | 47 this._elementCloseTag = elementCloseTag; |
48 | 48 |
49 if (this._node.nodeType() === Node.ELEMENT_NODE && !elementCloseTag) | 49 if (this._node.nodeType() === Node.ELEMENT_NODE && !elementCloseTag) |
50 this._canAddAttributes = true; | 50 this._canAddAttributes = true; |
51 this._searchQuery = null; | 51 this._searchQuery = null; |
52 this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildr
enLimit; | 52 this._expandedChildrenLimit = WebInspector.ElementsTreeElement.InitialChildr
enLimit; |
| 53 } |
| 54 |
| 55 /** |
| 56 * @param {!WebInspector.ElementsTreeElement} treeElement |
| 57 */ |
| 58 static animateOnDOMUpdate(treeElement) { |
| 59 var tagName = treeElement.listItemElement.querySelector('.webkit-html-tag-na
me'); |
| 60 WebInspector.runCSSAnimationOnce(tagName || treeElement.listItemElement, 'do
m-update-highlight'); |
| 61 } |
| 62 |
| 63 /** |
| 64 * @param {!WebInspector.DOMNode} node |
| 65 * @return {!Array<!WebInspector.DOMNode>} |
| 66 */ |
| 67 static visibleShadowRoots(node) { |
| 68 var roots = node.shadowRoots(); |
| 69 if (roots.length && !WebInspector.moduleSetting('showUAShadowDOM').get()) |
| 70 roots = roots.filter(filter); |
| 71 |
| 72 /** |
| 73 * @param {!WebInspector.DOMNode} root |
| 74 */ |
| 75 function filter(root) { |
| 76 return root.shadowRootType() !== WebInspector.DOMNode.ShadowRootTypes.User
Agent; |
| 77 } |
| 78 return roots; |
| 79 } |
| 80 |
| 81 /** |
| 82 * @param {!WebInspector.DOMNode} node |
| 83 * @return {boolean} |
| 84 */ |
| 85 static canShowInlineText(node) { |
| 86 if (node.importedDocument() || node.templateContent() || |
| 87 WebInspector.ElementsTreeElement.visibleShadowRoots(node).length || node
.hasPseudoElements()) |
| 88 return false; |
| 89 if (node.nodeType() !== Node.ELEMENT_NODE) |
| 90 return false; |
| 91 if (!node.firstChild || node.firstChild !== node.lastChild || node.firstChil
d.nodeType() !== Node.TEXT_NODE) |
| 92 return false; |
| 93 var textChild = node.firstChild; |
| 94 var maxInlineTextChildLength = 80; |
| 95 if (textChild.nodeValue().length < maxInlineTextChildLength) |
| 96 return true; |
| 97 return false; |
| 98 } |
| 99 |
| 100 /** |
| 101 * @param {!WebInspector.ContextSubMenuItem} subMenu |
| 102 * @param {!WebInspector.DOMNode} node |
| 103 */ |
| 104 static populateForcedPseudoStateItems(subMenu, node) { |
| 105 const pseudoClasses = ['active', 'hover', 'focus', 'visited']; |
| 106 var forcedPseudoState = WebInspector.CSSModel.fromNode(node).pseudoState(nod
e); |
| 107 for (var i = 0; i < pseudoClasses.length; ++i) { |
| 108 var pseudoClassForced = forcedPseudoState.indexOf(pseudoClasses[i]) >= 0; |
| 109 subMenu.appendCheckboxItem( |
| 110 ':' + pseudoClasses[i], setPseudoStateCallback.bind(null, pseudoClasse
s[i], !pseudoClassForced), |
| 111 pseudoClassForced, false); |
| 112 } |
| 113 |
| 114 /** |
| 115 * @param {string} pseudoState |
| 116 * @param {boolean} enabled |
| 117 */ |
| 118 function setPseudoStateCallback(pseudoState, enabled) { |
| 119 WebInspector.CSSModel.fromNode(node).forcePseudoState(node, pseudoState, e
nabled); |
| 120 } |
| 121 } |
| 122 |
| 123 /** |
| 124 * @return {boolean} |
| 125 */ |
| 126 isClosingTag() { |
| 127 return !!this._elementCloseTag; |
| 128 } |
| 129 |
| 130 /** |
| 131 * @return {!WebInspector.DOMNode} |
| 132 */ |
| 133 node() { |
| 134 return this._node; |
| 135 } |
| 136 |
| 137 /** |
| 138 * @return {boolean} |
| 139 */ |
| 140 isEditing() { |
| 141 return !!this._editing; |
| 142 } |
| 143 |
| 144 /** |
| 145 * @param {string} searchQuery |
| 146 */ |
| 147 highlightSearchResults(searchQuery) { |
| 148 if (this._searchQuery !== searchQuery) |
| 149 this._hideSearchHighlight(); |
| 150 |
| 151 this._searchQuery = searchQuery; |
| 152 this._searchHighlightsVisible = true; |
| 153 this.updateTitle(null, true); |
| 154 } |
| 155 |
| 156 hideSearchHighlights() { |
| 157 delete this._searchHighlightsVisible; |
| 158 this._hideSearchHighlight(); |
| 159 } |
| 160 |
| 161 _hideSearchHighlight() { |
| 162 if (!this._highlightResult) |
| 163 return; |
| 164 |
| 165 function updateEntryHide(entry) { |
| 166 switch (entry.type) { |
| 167 case 'added': |
| 168 entry.node.remove(); |
| 169 break; |
| 170 case 'changed': |
| 171 entry.node.textContent = entry.oldText; |
| 172 break; |
| 173 } |
| 174 } |
| 175 |
| 176 for (var i = (this._highlightResult.length - 1); i >= 0; --i) |
| 177 updateEntryHide(this._highlightResult[i]); |
| 178 |
| 179 delete this._highlightResult; |
| 180 } |
| 181 |
| 182 /** |
| 183 * @param {boolean} inClipboard |
| 184 */ |
| 185 setInClipboard(inClipboard) { |
| 186 if (this._inClipboard === inClipboard) |
| 187 return; |
| 188 this._inClipboard = inClipboard; |
| 189 this.listItemElement.classList.toggle('in-clipboard', inClipboard); |
| 190 } |
| 191 |
| 192 get hovered() { |
| 193 return this._hovered; |
| 194 } |
| 195 |
| 196 set hovered(x) { |
| 197 if (this._hovered === x) |
| 198 return; |
| 199 |
| 200 this._hovered = x; |
| 201 |
| 202 if (this.listItemElement) { |
| 203 if (x) { |
| 204 this._createSelection(); |
| 205 this.listItemElement.classList.add('hovered'); |
| 206 } else { |
| 207 this.listItemElement.classList.remove('hovered'); |
| 208 } |
| 209 } |
| 210 } |
| 211 |
| 212 /** |
| 213 * @return {number} |
| 214 */ |
| 215 expandedChildrenLimit() { |
| 216 return this._expandedChildrenLimit; |
| 217 } |
| 218 |
| 219 /** |
| 220 * @param {number} expandedChildrenLimit |
| 221 */ |
| 222 setExpandedChildrenLimit(expandedChildrenLimit) { |
| 223 this._expandedChildrenLimit = expandedChildrenLimit; |
| 224 } |
| 225 |
| 226 _createSelection() { |
| 227 var listItemElement = this.listItemElement; |
| 228 if (!listItemElement) |
| 229 return; |
| 230 |
| 231 if (!this.selectionElement) { |
| 232 this.selectionElement = createElement('div'); |
| 233 this.selectionElement.className = 'selection fill'; |
| 234 this.selectionElement.style.setProperty('margin-left', (-this._computeLeft
Indent()) + 'px'); |
| 235 listItemElement.insertBefore(this.selectionElement, listItemElement.firstC
hild); |
| 236 } |
| 237 } |
| 238 |
| 239 /** |
| 240 * @override |
| 241 */ |
| 242 onbind() { |
| 243 if (!this._elementCloseTag) |
| 244 this._node[this.treeOutline.treeElementSymbol()] = this; |
| 245 } |
| 246 |
| 247 /** |
| 248 * @override |
| 249 */ |
| 250 onunbind() { |
| 251 if (this._node[this.treeOutline.treeElementSymbol()] === this) |
| 252 this._node[this.treeOutline.treeElementSymbol()] = null; |
| 253 } |
| 254 |
| 255 /** |
| 256 * @override |
| 257 */ |
| 258 onattach() { |
| 259 if (this._hovered) { |
| 260 this._createSelection(); |
| 261 this.listItemElement.classList.add('hovered'); |
| 262 } |
| 263 |
| 264 this.updateTitle(); |
| 265 this._preventFollowingLinksOnDoubleClick(); |
| 266 this.listItemElement.draggable = true; |
| 267 } |
| 268 |
| 269 _preventFollowingLinksOnDoubleClick() { |
| 270 var links = this.listItemElement.querySelectorAll( |
| 271 'li .webkit-html-tag > .webkit-html-attribute > .webkit-html-external-li
nk, li .webkit-html-tag > .webkit-html-attribute > .webkit-html-resource-link'); |
| 272 if (!links) |
| 273 return; |
| 274 |
| 275 for (var i = 0; i < links.length; ++i) |
| 276 links[i].preventFollowOnDoubleClick = true; |
| 277 } |
| 278 |
| 279 /** |
| 280 * @override |
| 281 */ |
| 282 onpopulate() { |
| 283 this.populated = true; |
| 284 this.treeOutline.populateTreeElement(this); |
| 285 } |
| 286 |
| 287 /** |
| 288 * @override |
| 289 */ |
| 290 expandRecursively() { |
| 291 this._node.getSubtree(-1, TreeElement.prototype.expandRecursively.bind(this,
Number.MAX_VALUE)); |
| 292 } |
| 293 |
| 294 /** |
| 295 * @override |
| 296 */ |
| 297 onexpand() { |
| 298 if (this._elementCloseTag) |
| 299 return; |
| 300 |
| 301 this.updateTitle(); |
| 302 } |
| 303 |
| 304 /** |
| 305 * @override |
| 306 */ |
| 307 oncollapse() { |
| 308 if (this._elementCloseTag) |
| 309 return; |
| 310 |
| 311 this.updateTitle(); |
| 312 } |
| 313 |
| 314 /** |
| 315 * @override |
| 316 * @param {boolean=} omitFocus |
| 317 * @param {boolean=} selectedByUser |
| 318 * @return {boolean} |
| 319 */ |
| 320 select(omitFocus, selectedByUser) { |
| 321 if (this._editing) |
| 322 return false; |
| 323 return super.select(omitFocus, selectedByUser); |
| 324 } |
| 325 |
| 326 /** |
| 327 * @override |
| 328 * @param {boolean=} selectedByUser |
| 329 * @return {boolean} |
| 330 */ |
| 331 onselect(selectedByUser) { |
| 332 this.treeOutline.suppressRevealAndSelect = true; |
| 333 this.treeOutline.selectDOMNode(this._node, selectedByUser); |
| 334 if (selectedByUser) |
| 335 this._node.highlight(); |
| 336 this._createSelection(); |
| 337 this.treeOutline.suppressRevealAndSelect = false; |
| 338 return true; |
| 339 } |
| 340 |
| 341 /** |
| 342 * @override |
| 343 * @return {boolean} |
| 344 */ |
| 345 ondelete() { |
| 346 var startTagTreeElement = this.treeOutline.findTreeElement(this._node); |
| 347 startTagTreeElement ? startTagTreeElement.remove() : this.remove(); |
| 348 return true; |
| 349 } |
| 350 |
| 351 /** |
| 352 * @override |
| 353 * @return {boolean} |
| 354 */ |
| 355 onenter() { |
| 356 // On Enter or Return start editing the first attribute |
| 357 // or create a new attribute on the selected element. |
| 358 if (this._editing) |
| 359 return false; |
| 360 |
| 361 this._startEditing(); |
| 362 |
| 363 // prevent a newline from being immediately inserted |
| 364 return true; |
| 365 } |
| 366 |
| 367 /** |
| 368 * @override |
| 369 */ |
| 370 selectOnMouseDown(event) { |
| 371 super.selectOnMouseDown(event); |
| 372 |
| 373 if (this._editing) |
| 374 return; |
| 375 |
| 376 // Prevent selecting the nearest word on double click. |
| 377 if (event.detail >= 2) |
| 378 event.preventDefault(); |
| 379 } |
| 380 |
| 381 /** |
| 382 * @override |
| 383 * @return {boolean} |
| 384 */ |
| 385 ondblclick(event) { |
| 386 if (this._editing || this._elementCloseTag) |
| 387 return false; |
| 388 |
| 389 if (this._startEditingTarget(/** @type {!Element} */ (event.target))) |
| 390 return false; |
| 391 |
| 392 if (this.isExpandable() && !this.expanded) |
| 393 this.expand(); |
| 394 return false; |
| 395 } |
| 396 |
| 397 /** |
| 398 * @return {boolean} |
| 399 */ |
| 400 hasEditableNode() { |
| 401 return !this._node.isShadowRoot() && !this._node.ancestorUserAgentShadowRoot
(); |
| 402 } |
| 403 |
| 404 _insertInLastAttributePosition(tag, node) { |
| 405 if (tag.getElementsByClassName('webkit-html-attribute').length > 0) |
| 406 tag.insertBefore(node, tag.lastChild); |
| 407 else { |
| 408 var nodeName = tag.textContent.match(/^<(.*?)>$/)[1]; |
| 409 tag.textContent = ''; |
| 410 tag.createTextChild('<' + nodeName); |
| 411 tag.appendChild(node); |
| 412 tag.createTextChild('>'); |
| 413 } |
| 414 } |
| 415 |
| 416 /** |
| 417 * @param {!Element} eventTarget |
| 418 * @return {boolean} |
| 419 */ |
| 420 _startEditingTarget(eventTarget) { |
| 421 if (this.treeOutline.selectedDOMNode() !== this._node) |
| 422 return false; |
| 423 |
| 424 if (this._node.nodeType() !== Node.ELEMENT_NODE && this._node.nodeType() !==
Node.TEXT_NODE) |
| 425 return false; |
| 426 |
| 427 var textNode = eventTarget.enclosingNodeOrSelfWithClass('webkit-html-text-no
de'); |
| 428 if (textNode) |
| 429 return this._startEditingTextNode(textNode); |
| 430 |
| 431 var attribute = eventTarget.enclosingNodeOrSelfWithClass('webkit-html-attrib
ute'); |
| 432 if (attribute) |
| 433 return this._startEditingAttribute(attribute, eventTarget); |
| 434 |
| 435 var tagName = eventTarget.enclosingNodeOrSelfWithClass('webkit-html-tag-name
'); |
| 436 if (tagName) |
| 437 return this._startEditingTagName(tagName); |
| 438 |
| 439 var newAttribute = eventTarget.enclosingNodeOrSelfWithClass('add-attribute')
; |
| 440 if (newAttribute) |
| 441 return this._addNewAttribute(); |
| 442 |
| 443 return false; |
| 444 } |
| 445 |
| 446 /** |
| 447 * @param {!Event} event |
| 448 */ |
| 449 _showContextMenu(event) { |
| 450 this.treeOutline.showContextMenu(this, event); |
| 451 } |
| 452 |
| 453 /** |
| 454 * @param {!WebInspector.ContextMenu} contextMenu |
| 455 * @param {!Event} event |
| 456 */ |
| 457 populateTagContextMenu(contextMenu, event) { |
| 458 // Add attribute-related actions. |
| 459 var treeElement = this._elementCloseTag ? this.treeOutline.findTreeElement(t
his._node) : this; |
| 460 contextMenu.appendItem( |
| 461 WebInspector.UIString.capitalize('Add ^attribute'), treeElement._addNewA
ttribute.bind(treeElement)); |
| 462 |
| 463 var attribute = event.target.enclosingNodeOrSelfWithClass('webkit-html-attri
bute'); |
| 464 var newAttribute = event.target.enclosingNodeOrSelfWithClass('add-attribute'
); |
| 465 if (attribute && !newAttribute) |
| 466 contextMenu.appendItem( |
| 467 WebInspector.UIString.capitalize('Edit ^attribute'), |
| 468 this._startEditingAttribute.bind(this, attribute, event.target)); |
| 469 this.populateNodeContextMenu(contextMenu); |
| 470 WebInspector.ElementsTreeElement.populateForcedPseudoStateItems(contextMenu,
treeElement.node()); |
| 471 contextMenu.appendSeparator(); |
| 472 this.populateScrollIntoView(contextMenu); |
| 473 } |
| 474 |
| 475 /** |
| 476 * @param {!WebInspector.ContextMenu} contextMenu |
| 477 */ |
| 478 populateScrollIntoView(contextMenu) { |
| 479 contextMenu.appendItem(WebInspector.UIString.capitalize('Scroll into ^view')
, this._scrollIntoView.bind(this)); |
| 480 } |
| 481 |
| 482 populateTextContextMenu(contextMenu, textNode) { |
| 483 if (!this._editing) |
| 484 contextMenu.appendItem( |
| 485 WebInspector.UIString.capitalize('Edit ^text'), this._startEditingText
Node.bind(this, textNode)); |
| 486 this.populateNodeContextMenu(contextMenu); |
| 487 } |
| 488 |
| 489 populateNodeContextMenu(contextMenu) { |
| 490 // Add free-form node-related actions. |
| 491 var openTagElement = this._node[this.treeOutline.treeElementSymbol()] || thi
s; |
| 492 var isEditable = this.hasEditableNode(); |
| 493 if (isEditable && !this._editing) |
| 494 contextMenu.appendItem(WebInspector.UIString('Edit as HTML'), this._editAs
HTML.bind(this)); |
| 495 var isShadowRoot = this._node.isShadowRoot(); |
| 496 |
| 497 // Place it here so that all "Copy"-ing items stick together. |
| 498 var copyMenu = contextMenu.appendSubMenuItem(WebInspector.UIString('Copy')); |
| 499 var createShortcut = WebInspector.KeyboardShortcut.shortcutToString; |
| 500 var modifier = WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta; |
| 501 var treeOutline = this.treeOutline; |
| 502 var menuItem; |
| 503 if (!isShadowRoot) { |
| 504 menuItem = copyMenu.appendItem( |
| 505 WebInspector.UIString('Copy outerHTML'), treeOutline.performCopyOrCut.
bind(treeOutline, false, this._node)); |
| 506 menuItem.setShortcut(createShortcut('V', modifier)); |
| 507 } |
| 508 if (this._node.nodeType() === Node.ELEMENT_NODE) |
| 509 copyMenu.appendItem(WebInspector.UIString.capitalize('Copy selector'), thi
s._copyCSSPath.bind(this)); |
| 510 if (!isShadowRoot) |
| 511 copyMenu.appendItem(WebInspector.UIString('Copy XPath'), this._copyXPath.b
ind(this)); |
| 512 if (!isShadowRoot) { |
| 513 menuItem = copyMenu.appendItem( |
| 514 WebInspector.UIString('Cut element'), treeOutline.performCopyOrCut.bin
d(treeOutline, true, this._node), |
| 515 !this.hasEditableNode()); |
| 516 menuItem.setShortcut(createShortcut('X', modifier)); |
| 517 menuItem = copyMenu.appendItem( |
| 518 WebInspector.UIString('Copy element'), treeOutline.performCopyOrCut.bi
nd(treeOutline, false, this._node)); |
| 519 menuItem.setShortcut(createShortcut('C', modifier)); |
| 520 menuItem = copyMenu.appendItem( |
| 521 WebInspector.UIString('Paste element'), treeOutline.pasteNode.bind(tre
eOutline, this._node), |
| 522 !treeOutline.canPaste(this._node)); |
| 523 menuItem.setShortcut(createShortcut('V', modifier)); |
| 524 } |
| 525 |
| 526 contextMenu.appendSeparator(); |
| 527 menuItem = contextMenu.appendCheckboxItem( |
| 528 WebInspector.UIString('Hide element'), treeOutline.toggleHideElement.bin
d(treeOutline, this._node), |
| 529 treeOutline.isToggledToHidden(this._node)); |
| 530 menuItem.setShortcut(WebInspector.shortcutRegistry.shortcutTitleForAction('e
lements.hide-element')); |
| 531 |
| 532 if (isEditable) |
| 533 contextMenu.appendItem(WebInspector.UIString('Delete element'), this.remov
e.bind(this)); |
| 534 contextMenu.appendSeparator(); |
| 535 |
| 536 contextMenu.appendItem(WebInspector.UIString('Expand all'), this.expandRecur
sively.bind(this)); |
| 537 contextMenu.appendItem(WebInspector.UIString('Collapse all'), this.collapseR
ecursively.bind(this)); |
| 538 contextMenu.appendSeparator(); |
| 539 } |
| 540 |
| 541 _startEditing() { |
| 542 if (this.treeOutline.selectedDOMNode() !== this._node) |
| 543 return; |
| 544 |
| 545 var listItem = this._listItemNode; |
| 546 |
| 547 if (this._canAddAttributes) { |
| 548 var attribute = listItem.getElementsByClassName('webkit-html-attribute')[0
]; |
| 549 if (attribute) |
| 550 return this._startEditingAttribute( |
| 551 attribute, attribute.getElementsByClassName('webkit-html-attribute-v
alue')[0]); |
| 552 |
| 553 return this._addNewAttribute(); |
| 554 } |
| 555 |
| 556 if (this._node.nodeType() === Node.TEXT_NODE) { |
| 557 var textNode = listItem.getElementsByClassName('webkit-html-text-node')[0]
; |
| 558 if (textNode) |
| 559 return this._startEditingTextNode(textNode); |
| 560 return; |
| 561 } |
| 562 } |
| 563 |
| 564 _addNewAttribute() { |
| 565 // Cannot just convert the textual html into an element without |
| 566 // a parent node. Use a temporary span container for the HTML. |
| 567 var container = createElement('span'); |
| 568 this._buildAttributeDOM(container, ' ', '', null); |
| 569 var attr = container.firstElementChild; |
| 570 attr.style.marginLeft = '2px'; // overrides the .editing margin rule |
| 571 attr.style.marginRight = '2px'; // overrides the .editing margin rule |
| 572 |
| 573 var tag = this.listItemElement.getElementsByClassName('webkit-html-tag')[0]; |
| 574 this._insertInLastAttributePosition(tag, attr); |
| 575 attr.scrollIntoViewIfNeeded(true); |
| 576 return this._startEditingAttribute(attr, attr); |
| 577 } |
| 578 |
| 579 _triggerEditAttribute(attributeName) { |
| 580 var attributeElements = this.listItemElement.getElementsByClassName('webkit-
html-attribute-name'); |
| 581 for (var i = 0, len = attributeElements.length; i < len; ++i) { |
| 582 if (attributeElements[i].textContent === attributeName) { |
| 583 for (var elem = attributeElements[i].nextSibling; elem; elem = elem.next
Sibling) { |
| 584 if (elem.nodeType !== Node.ELEMENT_NODE) |
| 585 continue; |
| 586 |
| 587 if (elem.classList.contains('webkit-html-attribute-value')) |
| 588 return this._startEditingAttribute(elem.parentNode, elem); |
| 589 } |
| 590 } |
| 591 } |
| 592 } |
| 593 |
| 594 _startEditingAttribute(attribute, elementForSelection) { |
| 595 console.assert(this.listItemElement.isAncestor(attribute)); |
| 596 |
| 597 if (WebInspector.isBeingEdited(attribute)) |
| 598 return true; |
| 599 |
| 600 var attributeNameElement = attribute.getElementsByClassName('webkit-html-att
ribute-name')[0]; |
| 601 if (!attributeNameElement) |
| 602 return false; |
| 603 |
| 604 var attributeName = attributeNameElement.textContent; |
| 605 var attributeValueElement = attribute.getElementsByClassName('webkit-html-at
tribute-value')[0]; |
| 606 |
| 607 // Make sure elementForSelection is not a child of attributeValueElement. |
| 608 elementForSelection = |
| 609 attributeValueElement.isAncestor(elementForSelection) ? attributeValueEl
ement : elementForSelection; |
| 610 |
| 611 function removeZeroWidthSpaceRecursive(node) { |
| 612 if (node.nodeType === Node.TEXT_NODE) { |
| 613 node.nodeValue = node.nodeValue.replace(/\u200B/g, ''); |
| 614 return; |
| 615 } |
| 616 |
| 617 if (node.nodeType !== Node.ELEMENT_NODE) |
| 618 return; |
| 619 |
| 620 for (var child = node.firstChild; child; child = child.nextSibling) |
| 621 removeZeroWidthSpaceRecursive(child); |
| 622 } |
| 623 |
| 624 var attributeValue = attributeName && attributeValueElement ? this._node.get
Attribute(attributeName) : undefined; |
| 625 if (attributeValue !== undefined) |
| 626 attributeValueElement.setTextContentTruncatedIfNeeded( |
| 627 attributeValue, WebInspector.UIString('<value is too large to edit>'))
; |
| 628 |
| 629 // Remove zero-width spaces that were added by nodeTitleInfo. |
| 630 removeZeroWidthSpaceRecursive(attribute); |
| 631 |
| 632 var config = new WebInspector.InplaceEditor.Config( |
| 633 this._attributeEditingCommitted.bind(this), this._editingCancelled.bind(
this), attributeName); |
| 634 |
| 635 /** |
| 636 * @param {!Event} event |
| 637 * @return {string} |
| 638 */ |
| 639 function postKeyDownFinishHandler(event) { |
| 640 WebInspector.handleElementValueModifications(event, attribute); |
| 641 return ''; |
| 642 } |
| 643 |
| 644 if (!attributeValueElement.textContent.asParsedURL()) |
| 645 config.setPostKeydownFinishHandler(postKeyDownFinishHandler); |
| 646 |
| 647 this._editing = WebInspector.InplaceEditor.startEditing(attribute, config); |
| 648 |
| 649 this.listItemElement.getComponentSelection().setBaseAndExtent(elementForSele
ction, 0, elementForSelection, 1); |
| 650 |
| 651 return true; |
| 652 } |
| 653 |
| 654 /** |
| 655 * @param {!Element} textNodeElement |
| 656 */ |
| 657 _startEditingTextNode(textNodeElement) { |
| 658 if (WebInspector.isBeingEdited(textNodeElement)) |
| 659 return true; |
| 660 |
| 661 var textNode = this._node; |
| 662 // We only show text nodes inline in elements if the element only |
| 663 // has a single child, and that child is a text node. |
| 664 if (textNode.nodeType() === Node.ELEMENT_NODE && textNode.firstChild) |
| 665 textNode = textNode.firstChild; |
| 666 |
| 667 var container = textNodeElement.enclosingNodeOrSelfWithClass('webkit-html-te
xt-node'); |
| 668 if (container) |
| 669 container.textContent = textNode.nodeValue(); // Strip the CSS or JS high
lighting if present. |
| 670 var config = new WebInspector.InplaceEditor.Config( |
| 671 this._textNodeEditingCommitted.bind(this, textNode), this._editingCancel
led.bind(this)); |
| 672 this._editing = WebInspector.InplaceEditor.startEditing(textNodeElement, con
fig); |
| 673 this.listItemElement.getComponentSelection().setBaseAndExtent(textNodeElemen
t, 0, textNodeElement, 1); |
| 674 |
| 675 return true; |
| 676 } |
| 677 |
| 678 /** |
| 679 * @param {!Element=} tagNameElement |
| 680 */ |
| 681 _startEditingTagName(tagNameElement) { |
| 682 if (!tagNameElement) { |
| 683 tagNameElement = this.listItemElement.getElementsByClassName('webkit-html-
tag-name')[0]; |
| 684 if (!tagNameElement) |
| 685 return false; |
| 686 } |
| 687 |
| 688 var tagName = tagNameElement.textContent; |
| 689 if (WebInspector.ElementsTreeElement.EditTagBlacklist.has(tagName.toLowerCas
e())) |
| 690 return false; |
| 691 |
| 692 if (WebInspector.isBeingEdited(tagNameElement)) |
| 693 return true; |
| 694 |
| 695 var closingTagElement = this._distinctClosingTagElement(); |
| 696 |
| 697 /** |
| 698 * @param {!Event} event |
| 699 */ |
| 700 function keyupListener(event) { |
| 701 if (closingTagElement) |
| 702 closingTagElement.textContent = '</' + tagNameElement.textContent + '>'; |
| 703 } |
| 704 |
| 705 /** |
| 706 * @param {!Element} element |
| 707 * @param {string} newTagName |
| 708 * @this {WebInspector.ElementsTreeElement} |
| 709 */ |
| 710 function editingComitted(element, newTagName) { |
| 711 tagNameElement.removeEventListener('keyup', keyupListener, false); |
| 712 this._tagNameEditingCommitted.apply(this, arguments); |
| 713 } |
| 714 |
| 715 /** |
| 716 * @this {WebInspector.ElementsTreeElement} |
| 717 */ |
| 718 function editingCancelled() { |
| 719 tagNameElement.removeEventListener('keyup', keyupListener, false); |
| 720 this._editingCancelled.apply(this, arguments); |
| 721 } |
| 722 |
| 723 tagNameElement.addEventListener('keyup', keyupListener, false); |
| 724 |
| 725 var config = |
| 726 new WebInspector.InplaceEditor.Config(editingComitted.bind(this), editin
gCancelled.bind(this), tagName); |
| 727 this._editing = WebInspector.InplaceEditor.startEditing(tagNameElement, conf
ig); |
| 728 this.listItemElement.getComponentSelection().setBaseAndExtent(tagNameElement
, 0, tagNameElement, 1); |
| 729 return true; |
| 730 } |
| 731 |
| 732 /** |
| 733 * @param {function(string, string)} commitCallback |
| 734 * @param {function()} disposeCallback |
| 735 * @param {?Protocol.Error} error |
| 736 * @param {string} initialValue |
| 737 */ |
| 738 _startEditingAsHTML(commitCallback, disposeCallback, error, initialValue) { |
| 739 if (error) |
| 740 return; |
| 741 if (this._editing) |
| 742 return; |
| 743 |
| 744 function consume(event) { |
| 745 if (event.eventPhase === Event.AT_TARGET) |
| 746 event.consume(true); |
| 747 } |
| 748 |
| 749 initialValue = this._convertWhitespaceToEntities(initialValue).text; |
| 750 |
| 751 this._htmlEditElement = createElement('div'); |
| 752 this._htmlEditElement.className = 'source-code elements-tree-editor'; |
| 753 |
| 754 // Hide header items. |
| 755 var child = this.listItemElement.firstChild; |
| 756 while (child) { |
| 757 child.style.display = 'none'; |
| 758 child = child.nextSibling; |
| 759 } |
| 760 // Hide children item. |
| 761 if (this._childrenListNode) |
| 762 this._childrenListNode.style.display = 'none'; |
| 763 // Append editor. |
| 764 this.listItemElement.appendChild(this._htmlEditElement); |
| 765 this.listItemElement.classList.add('editing-as-html'); |
| 766 this.treeOutline.element.addEventListener('mousedown', consume, false); |
| 767 |
| 768 /** |
| 769 * @param {!Element} element |
| 770 * @param {string} newValue |
| 771 * @this {WebInspector.ElementsTreeElement} |
| 772 */ |
| 773 function commit(element, newValue) { |
| 774 commitCallback(initialValue, newValue); |
| 775 dispose.call(this); |
| 776 } |
| 777 |
| 778 /** |
| 779 * @this {WebInspector.ElementsTreeElement} |
| 780 */ |
| 781 function dispose() { |
| 782 disposeCallback(); |
| 783 delete this._editing; |
| 784 this.treeOutline.setMultilineEditing(null); |
| 785 |
| 786 this.listItemElement.classList.remove('editing-as-html'); |
| 787 // Remove editor. |
| 788 this.listItemElement.removeChild(this._htmlEditElement); |
| 789 delete this._htmlEditElement; |
| 790 // Unhide children item. |
| 791 if (this._childrenListNode) |
| 792 this._childrenListNode.style.removeProperty('display'); |
| 793 // Unhide header items. |
| 794 var child = this.listItemElement.firstChild; |
| 795 while (child) { |
| 796 child.style.removeProperty('display'); |
| 797 child = child.nextSibling; |
| 798 } |
| 799 |
| 800 this.treeOutline.element.removeEventListener('mousedown', consume, false); |
| 801 this.treeOutline.focus(); |
| 802 } |
| 803 |
| 804 var config = new WebInspector.InplaceEditor.Config(commit.bind(this), dispos
e.bind(this)); |
| 805 config.setMultilineOptions( |
| 806 initialValue, {name: 'xml', htmlMode: true}, 'web-inspector-html', |
| 807 WebInspector.moduleSetting('domWordWrap').get(), true); |
| 808 WebInspector.InplaceEditor.startMultilineEditing(this._htmlEditElement, conf
ig).then(markAsBeingEdited.bind(this)); |
| 809 |
| 810 /** |
| 811 * @param {!Object} controller |
| 812 * @this {WebInspector.ElementsTreeElement} |
| 813 */ |
| 814 function markAsBeingEdited(controller) { |
| 815 this._editing = /** @type {!WebInspector.InplaceEditor.Controller} */ (con
troller); |
| 816 this._editing.setWidth(this.treeOutline.visibleWidth() - this._computeLeft
Indent()); |
| 817 this.treeOutline.setMultilineEditing(this._editing); |
| 818 } |
| 819 } |
| 820 |
| 821 _attributeEditingCommitted(element, newText, oldText, attributeName, moveDirec
tion) { |
| 822 delete this._editing; |
| 823 |
| 824 var treeOutline = this.treeOutline; |
| 825 |
| 826 /** |
| 827 * @param {?Protocol.Error=} error |
| 828 * @this {WebInspector.ElementsTreeElement} |
| 829 */ |
| 830 function moveToNextAttributeIfNeeded(error) { |
| 831 if (error) |
| 832 this._editingCancelled(element, attributeName); |
| 833 |
| 834 if (!moveDirection) |
| 835 return; |
| 836 |
| 837 treeOutline.runPendingUpdates(); |
| 838 |
| 839 // Search for the attribute's position, and then decide where to move to. |
| 840 var attributes = this._node.attributes(); |
| 841 for (var i = 0; i < attributes.length; ++i) { |
| 842 if (attributes[i].name !== attributeName) |
| 843 continue; |
| 844 |
| 845 if (moveDirection === 'backward') { |
| 846 if (i === 0) |
| 847 this._startEditingTagName(); |
| 848 else |
| 849 this._triggerEditAttribute(attributes[i - 1].name); |
| 850 } else { |
| 851 if (i === attributes.length - 1) |
| 852 this._addNewAttribute(); |
| 853 else |
| 854 this._triggerEditAttribute(attributes[i + 1].name); |
| 855 } |
| 856 return; |
| 857 } |
| 858 |
| 859 // Moving From the "New Attribute" position. |
| 860 if (moveDirection === 'backward') { |
| 861 if (newText === ' ') { |
| 862 // Moving from "New Attribute" that was not edited |
| 863 if (attributes.length > 0) |
| 864 this._triggerEditAttribute(attributes[attributes.length - 1].name); |
| 865 } else { |
| 866 // Moving from "New Attribute" that holds new value |
| 867 if (attributes.length > 1) |
| 868 this._triggerEditAttribute(attributes[attributes.length - 2].name); |
| 869 } |
| 870 } else if (moveDirection === 'forward') { |
| 871 if (!newText.isWhitespace()) |
| 872 this._addNewAttribute(); |
| 873 else |
| 874 this._startEditingTagName(); |
| 875 } |
| 876 } |
| 877 |
| 878 if ((attributeName.trim() || newText.trim()) && oldText !== newText) { |
| 879 this._node.setAttribute(attributeName, newText, moveToNextAttributeIfNeede
d.bind(this)); |
| 880 return; |
| 881 } |
| 882 |
| 883 this.updateTitle(); |
| 884 moveToNextAttributeIfNeeded.call(this); |
| 885 } |
| 886 |
| 887 _tagNameEditingCommitted(element, newText, oldText, tagName, moveDirection) { |
| 888 delete this._editing; |
| 889 var self = this; |
| 890 |
| 891 function cancel() { |
| 892 var closingTagElement = self._distinctClosingTagElement(); |
| 893 if (closingTagElement) |
| 894 closingTagElement.textContent = '</' + tagName + '>'; |
| 895 |
| 896 self._editingCancelled(element, tagName); |
| 897 moveToNextAttributeIfNeeded.call(self); |
| 898 } |
| 899 |
| 900 /** |
| 901 * @this {WebInspector.ElementsTreeElement} |
| 902 */ |
| 903 function moveToNextAttributeIfNeeded() { |
| 904 if (moveDirection !== 'forward') { |
| 905 this._addNewAttribute(); |
| 906 return; |
| 907 } |
| 908 |
| 909 var attributes = this._node.attributes(); |
| 910 if (attributes.length > 0) |
| 911 this._triggerEditAttribute(attributes[0].name); |
| 912 else |
| 913 this._addNewAttribute(); |
| 914 } |
| 915 |
| 916 newText = newText.trim(); |
| 917 if (newText === oldText) { |
| 918 cancel(); |
| 919 return; |
| 920 } |
| 921 |
| 922 var treeOutline = this.treeOutline; |
| 923 var wasExpanded = this.expanded; |
| 924 |
| 925 function changeTagNameCallback(error, nodeId) { |
| 926 if (error || !nodeId) { |
| 927 cancel(); |
| 928 return; |
| 929 } |
| 930 var newTreeItem = treeOutline.selectNodeAfterEdit(wasExpanded, error, node
Id); |
| 931 moveToNextAttributeIfNeeded.call(newTreeItem); |
| 932 } |
| 933 this._node.setNodeName(newText, changeTagNameCallback); |
| 934 } |
| 935 |
| 936 /** |
| 937 * @param {!WebInspector.DOMNode} textNode |
| 938 * @param {!Element} element |
| 939 * @param {string} newText |
| 940 */ |
| 941 _textNodeEditingCommitted(textNode, element, newText) { |
| 942 delete this._editing; |
| 943 |
| 944 /** |
| 945 * @this {WebInspector.ElementsTreeElement} |
| 946 */ |
| 947 function callback() { |
| 948 this.updateTitle(); |
| 949 } |
| 950 textNode.setNodeValue(newText, callback.bind(this)); |
| 951 } |
| 952 |
| 953 /** |
| 954 * @param {!Element} element |
| 955 * @param {*} context |
| 956 */ |
| 957 _editingCancelled(element, context) { |
| 958 delete this._editing; |
| 959 |
| 960 // Need to restore attributes structure. |
| 961 this.updateTitle(); |
| 962 } |
| 963 |
| 964 /** |
| 965 * @return {!Element} |
| 966 */ |
| 967 _distinctClosingTagElement() { |
| 968 // FIXME: Improve the Tree Element / Outline Abstraction to prevent crawling
the DOM |
| 969 |
| 970 // For an expanded element, it will be the last element with class "close" |
| 971 // in the child element list. |
| 972 if (this.expanded) { |
| 973 var closers = this._childrenListNode.querySelectorAll('.close'); |
| 974 return closers[closers.length - 1]; |
| 975 } |
| 976 |
| 977 // Remaining cases are single line non-expanded elements with a closing |
| 978 // tag, or HTML elements without a closing tag (such as <br>). Return |
| 979 // null in the case where there isn't a closing tag. |
| 980 var tags = this.listItemElement.getElementsByClassName('webkit-html-tag'); |
| 981 return (tags.length === 1 ? null : tags[tags.length - 1]); |
| 982 } |
| 983 |
| 984 /** |
| 985 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord=} updateRecord |
| 986 * @param {boolean=} onlySearchQueryChanged |
| 987 */ |
| 988 updateTitle(updateRecord, onlySearchQueryChanged) { |
| 989 // If we are editing, return early to prevent canceling the edit. |
| 990 // After editing is committed updateTitle will be called. |
| 991 if (this._editing) |
| 992 return; |
| 993 |
| 994 if (onlySearchQueryChanged) { |
| 995 this._hideSearchHighlight(); |
| 996 } else { |
| 997 var nodeInfo = this._nodeTitleInfo(updateRecord || null); |
| 998 if (this._node.nodeType() === Node.DOCUMENT_FRAGMENT_NODE && this._node.is
InShadowTree() && |
| 999 this._node.shadowRootType()) { |
| 1000 this.childrenListElement.classList.add('shadow-root'); |
| 1001 var depth = 4; |
| 1002 for (var node = this._node; depth && node; node = node.parentNode) { |
| 1003 if (node.nodeType() === Node.DOCUMENT_FRAGMENT_NODE) |
| 1004 depth--; |
| 1005 } |
| 1006 if (!depth) |
| 1007 this.childrenListElement.classList.add('shadow-root-deep'); |
| 1008 else |
| 1009 this.childrenListElement.classList.add('shadow-root-depth-' + depth); |
| 1010 } |
| 1011 var highlightElement = createElement('span'); |
| 1012 highlightElement.className = 'highlight'; |
| 1013 highlightElement.appendChild(nodeInfo); |
| 1014 this.title = highlightElement; |
| 1015 this.updateDecorations(); |
| 1016 this.listItemElement.insertBefore(this._gutterContainer, this.listItemElem
ent.firstChild); |
| 1017 delete this._highlightResult; |
| 1018 } |
| 1019 |
| 1020 delete this.selectionElement; |
| 1021 if (this.selected) |
| 1022 this._createSelection(); |
| 1023 this._preventFollowingLinksOnDoubleClick(); |
| 1024 this._highlightSearchResults(); |
| 1025 } |
| 1026 |
| 1027 /** |
| 1028 * @return {number} |
| 1029 */ |
| 1030 _computeLeftIndent() { |
| 1031 var treeElement = this.parent; |
| 1032 var depth = 0; |
| 1033 while (treeElement !== null) { |
| 1034 depth++; |
| 1035 treeElement = treeElement.parent; |
| 1036 } |
| 1037 |
| 1038 /** Keep it in sync with elementsTreeOutline.css **/ |
| 1039 return 12 * (depth - 2) + (this.isExpandable() ? 1 : 12); |
| 1040 } |
| 1041 |
| 1042 updateDecorations() { |
| 1043 this._gutterContainer.style.left = (-this._computeLeftIndent()) + 'px'; |
| 1044 |
| 1045 if (this.isClosingTag()) |
| 1046 return; |
| 1047 |
| 1048 var node = this._node; |
| 1049 if (node.nodeType() !== Node.ELEMENT_NODE) |
| 1050 return; |
| 1051 |
| 1052 if (!this.treeOutline._decoratorExtensions) |
| 1053 /** @type {!Array.<!Runtime.Extension>} */ |
| 1054 this.treeOutline._decoratorExtensions = runtime.extensions(WebInspector.DO
MPresentationUtils.MarkerDecorator); |
| 1055 |
| 1056 var markerToExtension = new Map(); |
| 1057 for (var i = 0; i < this.treeOutline._decoratorExtensions.length; ++i) |
| 1058 markerToExtension.set( |
| 1059 this.treeOutline._decoratorExtensions[i].descriptor()['marker'], this.
treeOutline._decoratorExtensions[i]); |
| 1060 |
| 1061 var promises = []; |
| 1062 var decorations = []; |
| 1063 var descendantDecorations = []; |
| 1064 node.traverseMarkers(visitor); |
| 1065 |
| 1066 /** |
| 1067 * @param {!WebInspector.DOMNode} n |
| 1068 * @param {string} marker |
| 1069 */ |
| 1070 function visitor(n, marker) { |
| 1071 var extension = markerToExtension.get(marker); |
| 1072 if (!extension) |
| 1073 return; |
| 1074 promises.push(extension.instance().then(collectDecoration.bind(null, n))); |
| 1075 } |
| 1076 |
| 1077 /** |
| 1078 * @param {!WebInspector.DOMNode} n |
| 1079 * @param {!WebInspector.DOMPresentationUtils.MarkerDecorator} decorator |
| 1080 */ |
| 1081 function collectDecoration(n, decorator) { |
| 1082 var decoration = decorator.decorate(n); |
| 1083 if (!decoration) |
| 1084 return; |
| 1085 (n === node ? decorations : descendantDecorations).push(decoration); |
| 1086 } |
| 1087 |
| 1088 Promise.all(promises).then(updateDecorationsUI.bind(this)); |
| 1089 |
| 1090 /** |
| 1091 * @this {WebInspector.ElementsTreeElement} |
| 1092 */ |
| 1093 function updateDecorationsUI() { |
| 1094 this._decorationsElement.removeChildren(); |
| 1095 this._decorationsElement.classList.add('hidden'); |
| 1096 this._gutterContainer.classList.toggle('has-decorations', decorations.leng
th || descendantDecorations.length); |
| 1097 |
| 1098 if (!decorations.length && !descendantDecorations.length) |
| 1099 return; |
| 1100 |
| 1101 var colors = new Set(); |
| 1102 var titles = createElement('div'); |
| 1103 |
| 1104 for (var decoration of decorations) { |
| 1105 var titleElement = titles.createChild('div'); |
| 1106 titleElement.textContent = decoration.title; |
| 1107 colors.add(decoration.color); |
| 1108 } |
| 1109 if (this.expanded && !decorations.length) |
| 1110 return; |
| 1111 |
| 1112 var descendantColors = new Set(); |
| 1113 if (descendantDecorations.length) { |
| 1114 var element = titles.createChild('div'); |
| 1115 element.textContent = WebInspector.UIString('Children:'); |
| 1116 for (var decoration of descendantDecorations) { |
| 1117 element = titles.createChild('div'); |
| 1118 element.style.marginLeft = '15px'; |
| 1119 element.textContent = decoration.title; |
| 1120 descendantColors.add(decoration.color); |
| 1121 } |
| 1122 } |
| 1123 |
| 1124 var offset = 0; |
| 1125 processColors.call(this, colors, 'elements-gutter-decoration'); |
| 1126 if (!this.expanded) |
| 1127 processColors.call(this, descendantColors, 'elements-gutter-decoration e
lements-has-decorated-children'); |
| 1128 WebInspector.Tooltip.install(this._decorationsElement, titles); |
| 1129 |
| 1130 /** |
| 1131 * @param {!Set<string>} colors |
| 1132 * @param {string} className |
| 1133 * @this {WebInspector.ElementsTreeElement} |
| 1134 */ |
| 1135 function processColors(colors, className) { |
| 1136 for (var color of colors) { |
| 1137 var child = this._decorationsElement.createChild('div', className); |
| 1138 this._decorationsElement.classList.remove('hidden'); |
| 1139 child.style.backgroundColor = color; |
| 1140 child.style.borderColor = color; |
| 1141 if (offset) |
| 1142 child.style.marginLeft = offset + 'px'; |
| 1143 offset += 3; |
| 1144 } |
| 1145 } |
| 1146 } |
| 1147 } |
| 1148 |
| 1149 /** |
| 1150 * @param {!Node} parentElement |
| 1151 * @param {string} name |
| 1152 * @param {string} value |
| 1153 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord} updateRecord |
| 1154 * @param {boolean=} forceValue |
| 1155 * @param {!WebInspector.DOMNode=} node |
| 1156 */ |
| 1157 _buildAttributeDOM(parentElement, name, value, updateRecord, forceValue, node)
{ |
| 1158 var closingPunctuationRegex = /[\/;:\)\]\}]/g; |
| 1159 var highlightIndex = 0; |
| 1160 var highlightCount; |
| 1161 var additionalHighlightOffset = 0; |
| 1162 var result; |
| 1163 |
| 1164 /** |
| 1165 * @param {string} match |
| 1166 * @param {number} replaceOffset |
| 1167 * @return {string} |
| 1168 */ |
| 1169 function replacer(match, replaceOffset) { |
| 1170 while (highlightIndex < highlightCount && result.entityRanges[highlightInd
ex].offset < replaceOffset) { |
| 1171 result.entityRanges[highlightIndex].offset += additionalHighlightOffset; |
| 1172 ++highlightIndex; |
| 1173 } |
| 1174 additionalHighlightOffset += 1; |
| 1175 return match + '\u200B'; |
| 1176 } |
| 1177 |
| 1178 /** |
| 1179 * @param {!Element} element |
| 1180 * @param {string} value |
| 1181 * @this {WebInspector.ElementsTreeElement} |
| 1182 */ |
| 1183 function setValueWithEntities(element, value) { |
| 1184 result = this._convertWhitespaceToEntities(value); |
| 1185 highlightCount = result.entityRanges.length; |
| 1186 value = result.text.replace(closingPunctuationRegex, replacer); |
| 1187 while (highlightIndex < highlightCount) { |
| 1188 result.entityRanges[highlightIndex].offset += additionalHighlightOffset; |
| 1189 ++highlightIndex; |
| 1190 } |
| 1191 element.setTextContentTruncatedIfNeeded(value); |
| 1192 WebInspector.highlightRangesWithStyleClass(element, result.entityRanges, '
webkit-html-entity-value'); |
| 1193 } |
| 1194 |
| 1195 var hasText = (forceValue || value.length > 0); |
| 1196 var attrSpanElement = parentElement.createChild('span', 'webkit-html-attribu
te'); |
| 1197 var attrNameElement = attrSpanElement.createChild('span', 'webkit-html-attri
bute-name'); |
| 1198 attrNameElement.textContent = name; |
| 1199 |
| 1200 if (hasText) |
| 1201 attrSpanElement.createTextChild('=\u200B"'); |
| 1202 |
| 1203 var attrValueElement = attrSpanElement.createChild('span', 'webkit-html-attr
ibute-value'); |
| 1204 |
| 1205 if (updateRecord && updateRecord.isAttributeModified(name)) |
| 1206 WebInspector.runCSSAnimationOnce(hasText ? attrValueElement : attrNameElem
ent, 'dom-update-highlight'); |
| 1207 |
| 1208 /** |
| 1209 * @this {WebInspector.ElementsTreeElement} |
| 1210 * @param {string} value |
| 1211 * @return {!Element} |
| 1212 */ |
| 1213 function linkifyValue(value) { |
| 1214 var rewrittenHref = node.resolveURL(value); |
| 1215 if (rewrittenHref === null) { |
| 1216 var span = createElement('span'); |
| 1217 setValueWithEntities.call(this, span, value); |
| 1218 return span; |
| 1219 } |
| 1220 value = value.replace(closingPunctuationRegex, '$&\u200B'); |
| 1221 if (value.startsWith('data:')) |
| 1222 value = value.trimMiddle(60); |
| 1223 var anchor = WebInspector.linkifyURLAsNode(rewrittenHref, value, '', node.
nodeName().toLowerCase() === 'a'); |
| 1224 anchor.preventFollow = true; |
| 1225 return anchor; |
| 1226 } |
| 1227 |
| 1228 if (node && (name === 'src' || name === 'href')) { |
| 1229 attrValueElement.appendChild(linkifyValue.call(this, value)); |
| 1230 } else if ( |
| 1231 node && (node.nodeName().toLowerCase() === 'img' || node.nodeName().toLo
werCase() === 'source') && |
| 1232 name === 'srcset') { |
| 1233 var sources = value.split(','); |
| 1234 for (var i = 0; i < sources.length; ++i) { |
| 1235 if (i > 0) |
| 1236 attrValueElement.createTextChild(', '); |
| 1237 var source = sources[i].trim(); |
| 1238 var indexOfSpace = source.indexOf(' '); |
| 1239 var url, tail; |
| 1240 |
| 1241 if (indexOfSpace === -1) { |
| 1242 url = source; |
| 1243 } else { |
| 1244 url = source.substring(0, indexOfSpace); |
| 1245 tail = source.substring(indexOfSpace); |
| 1246 } |
| 1247 |
| 1248 attrValueElement.appendChild(linkifyValue.call(this, url)); |
| 1249 |
| 1250 if (tail) |
| 1251 attrValueElement.createTextChild(tail); |
| 1252 } |
| 1253 } else { |
| 1254 setValueWithEntities.call(this, attrValueElement, value); |
| 1255 } |
| 1256 |
| 1257 if (hasText) |
| 1258 attrSpanElement.createTextChild('"'); |
| 1259 } |
| 1260 |
| 1261 /** |
| 1262 * @param {!Node} parentElement |
| 1263 * @param {string} pseudoElementName |
| 1264 */ |
| 1265 _buildPseudoElementDOM(parentElement, pseudoElementName) { |
| 1266 var pseudoElement = parentElement.createChild('span', 'webkit-html-pseudo-el
ement'); |
| 1267 pseudoElement.textContent = '::' + pseudoElementName; |
| 1268 parentElement.createTextChild('\u200B'); |
| 1269 } |
| 1270 |
| 1271 /** |
| 1272 * @param {!Node} parentElement |
| 1273 * @param {string} tagName |
| 1274 * @param {boolean} isClosingTag |
| 1275 * @param {boolean} isDistinctTreeElement |
| 1276 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord} updateRecord |
| 1277 */ |
| 1278 _buildTagDOM(parentElement, tagName, isClosingTag, isDistinctTreeElement, upda
teRecord) { |
| 1279 var node = this._node; |
| 1280 var classes = ['webkit-html-tag']; |
| 1281 if (isClosingTag && isDistinctTreeElement) |
| 1282 classes.push('close'); |
| 1283 var tagElement = parentElement.createChild('span', classes.join(' ')); |
| 1284 tagElement.createTextChild('<'); |
| 1285 var tagNameElement = |
| 1286 tagElement.createChild('span', isClosingTag ? 'webkit-html-close-tag-nam
e' : 'webkit-html-tag-name'); |
| 1287 tagNameElement.textContent = (isClosingTag ? '/' : '') + tagName; |
| 1288 if (!isClosingTag) { |
| 1289 if (node.hasAttributes()) { |
| 1290 var attributes = node.attributes(); |
| 1291 for (var i = 0; i < attributes.length; ++i) { |
| 1292 var attr = attributes[i]; |
| 1293 tagElement.createTextChild(' '); |
| 1294 this._buildAttributeDOM(tagElement, attr.name, attr.value, updateRecor
d, false, node); |
| 1295 } |
| 1296 } |
| 1297 if (updateRecord) { |
| 1298 var hasUpdates = updateRecord.hasRemovedAttributes() || updateRecord.has
RemovedChildren(); |
| 1299 hasUpdates |= !this.expanded && updateRecord.hasChangedChildren(); |
| 1300 if (hasUpdates) |
| 1301 WebInspector.runCSSAnimationOnce(tagNameElement, 'dom-update-highlight
'); |
| 1302 } |
| 1303 } |
| 1304 |
| 1305 tagElement.createTextChild('>'); |
| 1306 parentElement.createTextChild('\u200B'); |
| 1307 } |
| 1308 |
| 1309 /** |
| 1310 * @param {string} text |
| 1311 * @return {!{text: string, entityRanges: !Array.<!WebInspector.SourceRange>}} |
| 1312 */ |
| 1313 _convertWhitespaceToEntities(text) { |
| 1314 var result = ''; |
| 1315 var lastIndexAfterEntity = 0; |
| 1316 var entityRanges = []; |
| 1317 var charToEntity = WebInspector.ElementsTreeOutline.MappedCharToEntity; |
| 1318 for (var i = 0, size = text.length; i < size; ++i) { |
| 1319 var char = text.charAt(i); |
| 1320 if (charToEntity[char]) { |
| 1321 result += text.substring(lastIndexAfterEntity, i); |
| 1322 var entityValue = '&' + charToEntity[char] + ';'; |
| 1323 entityRanges.push({offset: result.length, length: entityValue.length}); |
| 1324 result += entityValue; |
| 1325 lastIndexAfterEntity = i + 1; |
| 1326 } |
| 1327 } |
| 1328 if (result) |
| 1329 result += text.substring(lastIndexAfterEntity); |
| 1330 return {text: result || text, entityRanges: entityRanges}; |
| 1331 } |
| 1332 |
| 1333 /** |
| 1334 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord} updateRecord |
| 1335 * @return {!DocumentFragment} result |
| 1336 */ |
| 1337 _nodeTitleInfo(updateRecord) { |
| 1338 var node = this._node; |
| 1339 var titleDOM = createDocumentFragment(); |
| 1340 |
| 1341 switch (node.nodeType()) { |
| 1342 case Node.ATTRIBUTE_NODE: |
| 1343 this._buildAttributeDOM( |
| 1344 titleDOM, /** @type {string} */ (node.name), /** @type {string} */ (
node.value), updateRecord, true); |
| 1345 break; |
| 1346 |
| 1347 case Node.ELEMENT_NODE: |
| 1348 var pseudoType = node.pseudoType(); |
| 1349 if (pseudoType) { |
| 1350 this._buildPseudoElementDOM(titleDOM, pseudoType); |
| 1351 break; |
| 1352 } |
| 1353 |
| 1354 var tagName = node.nodeNameInCorrectCase(); |
| 1355 if (this._elementCloseTag) { |
| 1356 this._buildTagDOM(titleDOM, tagName, true, true, updateRecord); |
| 1357 break; |
| 1358 } |
| 1359 |
| 1360 this._buildTagDOM(titleDOM, tagName, false, false, updateRecord); |
| 1361 |
| 1362 if (this.isExpandable()) { |
| 1363 if (!this.expanded) { |
| 1364 var textNodeElement = titleDOM.createChild('span', 'webkit-html-text
-node bogus'); |
| 1365 textNodeElement.textContent = '\u2026'; |
| 1366 titleDOM.createTextChild('\u200B'); |
| 1367 this._buildTagDOM(titleDOM, tagName, true, false, updateRecord); |
| 1368 } |
| 1369 break; |
| 1370 } |
| 1371 |
| 1372 if (WebInspector.ElementsTreeElement.canShowInlineText(node)) { |
| 1373 var textNodeElement = titleDOM.createChild('span', 'webkit-html-text-n
ode'); |
| 1374 var result = this._convertWhitespaceToEntities(node.firstChild.nodeVal
ue()); |
| 1375 textNodeElement.textContent = result.text; |
| 1376 WebInspector.highlightRangesWithStyleClass(textNodeElement, result.ent
ityRanges, 'webkit-html-entity-value'); |
| 1377 titleDOM.createTextChild('\u200B'); |
| 1378 this._buildTagDOM(titleDOM, tagName, true, false, updateRecord); |
| 1379 if (updateRecord && updateRecord.hasChangedChildren()) |
| 1380 WebInspector.runCSSAnimationOnce(textNodeElement, 'dom-update-highli
ght'); |
| 1381 if (updateRecord && updateRecord.isCharDataModified()) |
| 1382 WebInspector.runCSSAnimationOnce(textNodeElement, 'dom-update-highli
ght'); |
| 1383 break; |
| 1384 } |
| 1385 |
| 1386 if (this.treeOutline.isXMLMimeType || |
| 1387 !WebInspector.ElementsTreeElement.ForbiddenClosingTagElements.has(ta
gName)) |
| 1388 this._buildTagDOM(titleDOM, tagName, true, false, updateRecord); |
| 1389 break; |
| 1390 |
| 1391 case Node.TEXT_NODE: |
| 1392 if (node.parentNode && node.parentNode.nodeName().toLowerCase() === 'scr
ipt') { |
| 1393 var newNode = titleDOM.createChild('span', 'webkit-html-text-node webk
it-html-js-node'); |
| 1394 var text = node.nodeValue(); |
| 1395 newNode.textContent = text.startsWith('\n') ? text.substring(1) : text
; |
| 1396 |
| 1397 var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighte
r('text/javascript', true); |
| 1398 javascriptSyntaxHighlighter.syntaxHighlightNode(newNode).then(updateSe
archHighlight.bind(this)); |
| 1399 } else if (node.parentNode && node.parentNode.nodeName().toLowerCase() =
== 'style') { |
| 1400 var newNode = titleDOM.createChild('span', 'webkit-html-text-node webk
it-html-css-node'); |
| 1401 var text = node.nodeValue(); |
| 1402 newNode.textContent = text.startsWith('\n') ? text.substring(1) : text
; |
| 1403 |
| 1404 var cssSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter('text
/css', true); |
| 1405 cssSyntaxHighlighter.syntaxHighlightNode(newNode).then(updateSearchHig
hlight.bind(this)); |
| 1406 } else { |
| 1407 titleDOM.createTextChild('"'); |
| 1408 var textNodeElement = titleDOM.createChild('span', 'webkit-html-text-n
ode'); |
| 1409 var result = this._convertWhitespaceToEntities(node.nodeValue()); |
| 1410 textNodeElement.textContent = result.text; |
| 1411 WebInspector.highlightRangesWithStyleClass(textNodeElement, result.ent
ityRanges, 'webkit-html-entity-value'); |
| 1412 titleDOM.createTextChild('"'); |
| 1413 if (updateRecord && updateRecord.isCharDataModified()) |
| 1414 WebInspector.runCSSAnimationOnce(textNodeElement, 'dom-update-highli
ght'); |
| 1415 } |
| 1416 break; |
| 1417 |
| 1418 case Node.COMMENT_NODE: |
| 1419 var commentElement = titleDOM.createChild('span', 'webkit-html-comment')
; |
| 1420 commentElement.createTextChild('<!--' + node.nodeValue() + '-->'); |
| 1421 break; |
| 1422 |
| 1423 case Node.DOCUMENT_TYPE_NODE: |
| 1424 var docTypeElement = titleDOM.createChild('span', 'webkit-html-doctype')
; |
| 1425 docTypeElement.createTextChild('<!DOCTYPE ' + node.nodeName()); |
| 1426 if (node.publicId) { |
| 1427 docTypeElement.createTextChild(' PUBLIC "' + node.publicId + '"'); |
| 1428 if (node.systemId) |
| 1429 docTypeElement.createTextChild(' "' + node.systemId + '"'); |
| 1430 } else if (node.systemId) |
| 1431 docTypeElement.createTextChild(' SYSTEM "' + node.systemId + '"'); |
| 1432 |
| 1433 if (node.internalSubset) |
| 1434 docTypeElement.createTextChild(' [' + node.internalSubset + ']'); |
| 1435 |
| 1436 docTypeElement.createTextChild('>'); |
| 1437 break; |
| 1438 |
| 1439 case Node.CDATA_SECTION_NODE: |
| 1440 var cdataElement = titleDOM.createChild('span', 'webkit-html-text-node')
; |
| 1441 cdataElement.createTextChild('<![CDATA[' + node.nodeValue() + ']]>'); |
| 1442 break; |
| 1443 |
| 1444 case Node.DOCUMENT_FRAGMENT_NODE: |
| 1445 var fragmentElement = titleDOM.createChild('span', 'webkit-html-fragment
'); |
| 1446 fragmentElement.textContent = node.nodeNameInCorrectCase().collapseWhite
space(); |
| 1447 break; |
| 1448 default: |
| 1449 titleDOM.createTextChild(node.nodeNameInCorrectCase().collapseWhitespace
()); |
| 1450 } |
| 1451 |
| 1452 /** |
| 1453 * @this {WebInspector.ElementsTreeElement} |
| 1454 */ |
| 1455 function updateSearchHighlight() { |
| 1456 delete this._highlightResult; |
| 1457 this._highlightSearchResults(); |
| 1458 } |
| 1459 |
| 1460 return titleDOM; |
| 1461 } |
| 1462 |
| 1463 remove() { |
| 1464 if (this._node.pseudoType()) |
| 1465 return; |
| 1466 var parentElement = this.parent; |
| 1467 if (!parentElement) |
| 1468 return; |
| 1469 |
| 1470 if (!this._node.parentNode || this._node.parentNode.nodeType() === Node.DOCU
MENT_NODE) |
| 1471 return; |
| 1472 this._node.removeNode(); |
| 1473 } |
| 1474 |
| 1475 /** |
| 1476 * @param {function(boolean)=} callback |
| 1477 * @param {boolean=} startEditing |
| 1478 */ |
| 1479 toggleEditAsHTML(callback, startEditing) { |
| 1480 if (this._editing && this._htmlEditElement && WebInspector.isBeingEdited(thi
s._htmlEditElement)) { |
| 1481 this._editing.commit(); |
| 1482 return; |
| 1483 } |
| 1484 |
| 1485 if (startEditing === false) |
| 1486 return; |
| 1487 |
| 1488 /** |
| 1489 * @param {?Protocol.Error} error |
| 1490 */ |
| 1491 function selectNode(error) { |
| 1492 if (callback) |
| 1493 callback(!error); |
| 1494 } |
| 1495 |
| 1496 /** |
| 1497 * @param {string} initialValue |
| 1498 * @param {string} value |
| 1499 */ |
| 1500 function commitChange(initialValue, value) { |
| 1501 if (initialValue !== value) |
| 1502 node.setOuterHTML(value, selectNode); |
| 1503 } |
| 1504 |
| 1505 function disposeCallback() { |
| 1506 if (callback) |
| 1507 callback(false); |
| 1508 } |
| 1509 |
| 1510 var node = this._node; |
| 1511 node.getOuterHTML(this._startEditingAsHTML.bind(this, commitChange, disposeC
allback)); |
| 1512 } |
| 1513 |
| 1514 _copyCSSPath() { |
| 1515 InspectorFrontendHost.copyText(WebInspector.DOMPresentationUtils.cssPath(thi
s._node, true)); |
| 1516 } |
| 1517 |
| 1518 _copyXPath() { |
| 1519 InspectorFrontendHost.copyText(WebInspector.DOMPresentationUtils.xPath(this.
_node, true)); |
| 1520 } |
| 1521 |
| 1522 _highlightSearchResults() { |
| 1523 if (!this._searchQuery || !this._searchHighlightsVisible) |
| 1524 return; |
| 1525 this._hideSearchHighlight(); |
| 1526 |
| 1527 var text = this.listItemElement.textContent; |
| 1528 var regexObject = createPlainTextSearchRegex(this._searchQuery, 'gi'); |
| 1529 |
| 1530 var match = regexObject.exec(text); |
| 1531 var matchRanges = []; |
| 1532 while (match) { |
| 1533 matchRanges.push(new WebInspector.SourceRange(match.index, match[0].length
)); |
| 1534 match = regexObject.exec(text); |
| 1535 } |
| 1536 |
| 1537 // Fall back for XPath, etc. matches. |
| 1538 if (!matchRanges.length) |
| 1539 matchRanges.push(new WebInspector.SourceRange(0, text.length)); |
| 1540 |
| 1541 this._highlightResult = []; |
| 1542 WebInspector.highlightSearchResults(this.listItemElement, matchRanges, this.
_highlightResult); |
| 1543 } |
| 1544 |
| 1545 _scrollIntoView() { |
| 1546 function scrollIntoViewCallback(object) { |
| 1547 /** |
| 1548 * @suppressReceiverCheck |
| 1549 * @this {!Element} |
| 1550 */ |
| 1551 function scrollIntoView() { |
| 1552 this.scrollIntoViewIfNeeded(true); |
| 1553 } |
| 1554 |
| 1555 if (object) |
| 1556 object.callFunction(scrollIntoView); |
| 1557 } |
| 1558 |
| 1559 this._node.resolveToObject('', scrollIntoViewCallback); |
| 1560 } |
| 1561 |
| 1562 _editAsHTML() { |
| 1563 var promise = WebInspector.Revealer.revealPromise(this.node()); |
| 1564 promise.then(() => WebInspector.actionRegistry.action('elements.edit-as-html
').execute()); |
| 1565 } |
53 }; | 1566 }; |
54 | 1567 |
55 WebInspector.ElementsTreeElement.InitialChildrenLimit = 500; | 1568 WebInspector.ElementsTreeElement.InitialChildrenLimit = 500; |
56 | 1569 |
57 // A union of HTML4 and HTML5-Draft elements that explicitly | 1570 // A union of HTML4 and HTML5-Draft elements that explicitly |
58 // or implicitly (for HTML5) forbid the closing tag. | 1571 // or implicitly (for HTML5) forbid the closing tag. |
59 WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = new Set([ | 1572 WebInspector.ElementsTreeElement.ForbiddenClosingTagElements = new Set([ |
60 "area", "base", "basefont", "br", "canvas", "col", "command", "embed", "fram
e", | 1573 'area', 'base', 'basefont', 'br', 'canvas', 'col', 'command', 'embed',
'frame', 'hr', |
61 "hr", "img", "input", "keygen", "link", "menuitem", "meta", "param", "source
", "track", "wbr" | 1574 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source',
'track', 'wbr' |
62 ]); | 1575 ]); |
63 | 1576 |
64 // These tags we do not allow editing their tag name. | 1577 // These tags we do not allow editing their tag name. |
65 WebInspector.ElementsTreeElement.EditTagBlacklist = new Set([ | 1578 WebInspector.ElementsTreeElement.EditTagBlacklist = new Set(['html', 'head', 'bo
dy']); |
66 "html", "head", "body" | 1579 |
67 ]); | 1580 |
68 | |
69 /** | |
70 * @param {!WebInspector.ElementsTreeElement} treeElement | |
71 */ | |
72 WebInspector.ElementsTreeElement.animateOnDOMUpdate = function(treeElement) | |
73 { | |
74 var tagName = treeElement.listItemElement.querySelector(".webkit-html-tag-na
me"); | |
75 WebInspector.runCSSAnimationOnce(tagName || treeElement.listItemElement, "do
m-update-highlight"); | |
76 }; | |
77 | |
78 /** | |
79 * @param {!WebInspector.DOMNode} node | |
80 * @return {!Array<!WebInspector.DOMNode>} | |
81 */ | |
82 WebInspector.ElementsTreeElement.visibleShadowRoots = function(node) | |
83 { | |
84 var roots = node.shadowRoots(); | |
85 if (roots.length && !WebInspector.moduleSetting("showUAShadowDOM").get()) | |
86 roots = roots.filter(filter); | |
87 | |
88 /** | |
89 * @param {!WebInspector.DOMNode} root | |
90 */ | |
91 function filter(root) | |
92 { | |
93 return root.shadowRootType() !== WebInspector.DOMNode.ShadowRootTypes.Us
erAgent; | |
94 } | |
95 return roots; | |
96 }; | |
97 | |
98 /** | |
99 * @param {!WebInspector.DOMNode} node | |
100 * @return {boolean} | |
101 */ | |
102 WebInspector.ElementsTreeElement.canShowInlineText = function(node) | |
103 { | |
104 if (node.importedDocument() || node.templateContent() || WebInspector.Elemen
tsTreeElement.visibleShadowRoots(node).length || node.hasPseudoElements()) | |
105 return false; | |
106 if (node.nodeType() !== Node.ELEMENT_NODE) | |
107 return false; | |
108 if (!node.firstChild || node.firstChild !== node.lastChild || node.firstChil
d.nodeType() !== Node.TEXT_NODE) | |
109 return false; | |
110 var textChild = node.firstChild; | |
111 var maxInlineTextChildLength = 80; | |
112 if (textChild.nodeValue().length < maxInlineTextChildLength) | |
113 return true; | |
114 return false; | |
115 }; | |
116 | |
117 /** | |
118 * @param {!WebInspector.ContextSubMenuItem} subMenu | |
119 * @param {!WebInspector.DOMNode} node | |
120 */ | |
121 WebInspector.ElementsTreeElement.populateForcedPseudoStateItems = function(subMe
nu, node) | |
122 { | |
123 const pseudoClasses = ["active", "hover", "focus", "visited"]; | |
124 var forcedPseudoState = WebInspector.CSSModel.fromNode(node).pseudoState(nod
e); | |
125 for (var i = 0; i < pseudoClasses.length; ++i) { | |
126 var pseudoClassForced = forcedPseudoState.indexOf(pseudoClasses[i]) >= 0
; | |
127 subMenu.appendCheckboxItem(":" + pseudoClasses[i], setPseudoStateCallbac
k.bind(null, pseudoClasses[i], !pseudoClassForced), pseudoClassForced, false); | |
128 } | |
129 | |
130 /** | |
131 * @param {string} pseudoState | |
132 * @param {boolean} enabled | |
133 */ | |
134 function setPseudoStateCallback(pseudoState, enabled) | |
135 { | |
136 WebInspector.CSSModel.fromNode(node).forcePseudoState(node, pseudoState,
enabled); | |
137 } | |
138 }; | |
139 | |
140 WebInspector.ElementsTreeElement.prototype = { | |
141 /** | |
142 * @return {boolean} | |
143 */ | |
144 isClosingTag: function() | |
145 { | |
146 return !!this._elementCloseTag; | |
147 }, | |
148 | |
149 /** | |
150 * @return {!WebInspector.DOMNode} | |
151 */ | |
152 node: function() | |
153 { | |
154 return this._node; | |
155 }, | |
156 | |
157 /** | |
158 * @return {boolean} | |
159 */ | |
160 isEditing: function() | |
161 { | |
162 return !!this._editing; | |
163 }, | |
164 | |
165 /** | |
166 * @param {string} searchQuery | |
167 */ | |
168 highlightSearchResults: function(searchQuery) | |
169 { | |
170 if (this._searchQuery !== searchQuery) | |
171 this._hideSearchHighlight(); | |
172 | |
173 this._searchQuery = searchQuery; | |
174 this._searchHighlightsVisible = true; | |
175 this.updateTitle(null, true); | |
176 }, | |
177 | |
178 hideSearchHighlights: function() | |
179 { | |
180 delete this._searchHighlightsVisible; | |
181 this._hideSearchHighlight(); | |
182 }, | |
183 | |
184 _hideSearchHighlight: function() | |
185 { | |
186 if (!this._highlightResult) | |
187 return; | |
188 | |
189 function updateEntryHide(entry) | |
190 { | |
191 switch (entry.type) { | |
192 case "added": | |
193 entry.node.remove(); | |
194 break; | |
195 case "changed": | |
196 entry.node.textContent = entry.oldText; | |
197 break; | |
198 } | |
199 } | |
200 | |
201 for (var i = (this._highlightResult.length - 1); i >= 0; --i) | |
202 updateEntryHide(this._highlightResult[i]); | |
203 | |
204 delete this._highlightResult; | |
205 }, | |
206 | |
207 /** | |
208 * @param {boolean} inClipboard | |
209 */ | |
210 setInClipboard: function(inClipboard) | |
211 { | |
212 if (this._inClipboard === inClipboard) | |
213 return; | |
214 this._inClipboard = inClipboard; | |
215 this.listItemElement.classList.toggle("in-clipboard", inClipboard); | |
216 }, | |
217 | |
218 get hovered() | |
219 { | |
220 return this._hovered; | |
221 }, | |
222 | |
223 set hovered(x) | |
224 { | |
225 if (this._hovered === x) | |
226 return; | |
227 | |
228 this._hovered = x; | |
229 | |
230 if (this.listItemElement) { | |
231 if (x) { | |
232 this._createSelection(); | |
233 this.listItemElement.classList.add("hovered"); | |
234 } else { | |
235 this.listItemElement.classList.remove("hovered"); | |
236 } | |
237 } | |
238 }, | |
239 | |
240 /** | |
241 * @return {number} | |
242 */ | |
243 expandedChildrenLimit: function() | |
244 { | |
245 return this._expandedChildrenLimit; | |
246 }, | |
247 | |
248 /** | |
249 * @param {number} expandedChildrenLimit | |
250 */ | |
251 setExpandedChildrenLimit: function(expandedChildrenLimit) | |
252 { | |
253 this._expandedChildrenLimit = expandedChildrenLimit; | |
254 }, | |
255 | |
256 _createSelection: function() | |
257 { | |
258 var listItemElement = this.listItemElement; | |
259 if (!listItemElement) | |
260 return; | |
261 | |
262 if (!this.selectionElement) { | |
263 this.selectionElement = createElement("div"); | |
264 this.selectionElement.className = "selection fill"; | |
265 this.selectionElement.style.setProperty("margin-left", (-this._compu
teLeftIndent()) + "px"); | |
266 listItemElement.insertBefore(this.selectionElement, listItemElement.
firstChild); | |
267 } | |
268 }, | |
269 | |
270 /** | |
271 * @override | |
272 */ | |
273 onbind: function() | |
274 { | |
275 if (!this._elementCloseTag) | |
276 this._node[this.treeOutline.treeElementSymbol()] = this; | |
277 }, | |
278 | |
279 /** | |
280 * @override | |
281 */ | |
282 onunbind: function() | |
283 { | |
284 if (this._node[this.treeOutline.treeElementSymbol()] === this) | |
285 this._node[this.treeOutline.treeElementSymbol()] = null; | |
286 }, | |
287 | |
288 /** | |
289 * @override | |
290 */ | |
291 onattach: function() | |
292 { | |
293 if (this._hovered) { | |
294 this._createSelection(); | |
295 this.listItemElement.classList.add("hovered"); | |
296 } | |
297 | |
298 this.updateTitle(); | |
299 this._preventFollowingLinksOnDoubleClick(); | |
300 this.listItemElement.draggable = true; | |
301 }, | |
302 | |
303 _preventFollowingLinksOnDoubleClick: function() | |
304 { | |
305 var links = this.listItemElement.querySelectorAll("li .webkit-html-tag >
.webkit-html-attribute > .webkit-html-external-link, li .webkit-html-tag > .web
kit-html-attribute > .webkit-html-resource-link"); | |
306 if (!links) | |
307 return; | |
308 | |
309 for (var i = 0; i < links.length; ++i) | |
310 links[i].preventFollowOnDoubleClick = true; | |
311 }, | |
312 | |
313 onpopulate: function() | |
314 { | |
315 this.populated = true; | |
316 this.treeOutline.populateTreeElement(this); | |
317 }, | |
318 | |
319 expandRecursively: function() | |
320 { | |
321 /** | |
322 * @this {WebInspector.ElementsTreeElement} | |
323 */ | |
324 function callback() | |
325 { | |
326 TreeElement.prototype.expandRecursively.call(this, Number.MAX_VALUE)
; | |
327 } | |
328 | |
329 this._node.getSubtree(-1, callback.bind(this)); | |
330 }, | |
331 | |
332 /** | |
333 * @override | |
334 */ | |
335 onexpand: function() | |
336 { | |
337 if (this._elementCloseTag) | |
338 return; | |
339 | |
340 this.updateTitle(); | |
341 }, | |
342 | |
343 oncollapse: function() | |
344 { | |
345 if (this._elementCloseTag) | |
346 return; | |
347 | |
348 this.updateTitle(); | |
349 }, | |
350 | |
351 /** | |
352 * @override | |
353 * @param {boolean=} omitFocus | |
354 * @param {boolean=} selectedByUser | |
355 * @return {boolean} | |
356 */ | |
357 select: function(omitFocus, selectedByUser) | |
358 { | |
359 if (this._editing) | |
360 return false; | |
361 return TreeElement.prototype.select.call(this, omitFocus, selectedByUser
); | |
362 }, | |
363 | |
364 /** | |
365 * @override | |
366 * @param {boolean=} selectedByUser | |
367 * @return {boolean} | |
368 */ | |
369 onselect: function(selectedByUser) | |
370 { | |
371 this.treeOutline.suppressRevealAndSelect = true; | |
372 this.treeOutline.selectDOMNode(this._node, selectedByUser); | |
373 if (selectedByUser) | |
374 this._node.highlight(); | |
375 this._createSelection(); | |
376 this.treeOutline.suppressRevealAndSelect = false; | |
377 return true; | |
378 }, | |
379 | |
380 /** | |
381 * @override | |
382 * @return {boolean} | |
383 */ | |
384 ondelete: function() | |
385 { | |
386 var startTagTreeElement = this.treeOutline.findTreeElement(this._node); | |
387 startTagTreeElement ? startTagTreeElement.remove() : this.remove(); | |
388 return true; | |
389 }, | |
390 | |
391 /** | |
392 * @override | |
393 * @return {boolean} | |
394 */ | |
395 onenter: function() | |
396 { | |
397 // On Enter or Return start editing the first attribute | |
398 // or create a new attribute on the selected element. | |
399 if (this._editing) | |
400 return false; | |
401 | |
402 this._startEditing(); | |
403 | |
404 // prevent a newline from being immediately inserted | |
405 return true; | |
406 }, | |
407 | |
408 selectOnMouseDown: function(event) | |
409 { | |
410 TreeElement.prototype.selectOnMouseDown.call(this, event); | |
411 | |
412 if (this._editing) | |
413 return; | |
414 | |
415 // Prevent selecting the nearest word on double click. | |
416 if (event.detail >= 2) | |
417 event.preventDefault(); | |
418 }, | |
419 | |
420 /** | |
421 * @override | |
422 * @return {boolean} | |
423 */ | |
424 ondblclick: function(event) | |
425 { | |
426 if (this._editing || this._elementCloseTag) | |
427 return false; | |
428 | |
429 if (this._startEditingTarget(/** @type {!Element} */(event.target))) | |
430 return false; | |
431 | |
432 if (this.isExpandable() && !this.expanded) | |
433 this.expand(); | |
434 return false; | |
435 }, | |
436 | |
437 /** | |
438 * @return {boolean} | |
439 */ | |
440 hasEditableNode: function() | |
441 { | |
442 return !this._node.isShadowRoot() && !this._node.ancestorUserAgentShadow
Root(); | |
443 }, | |
444 | |
445 _insertInLastAttributePosition: function(tag, node) | |
446 { | |
447 if (tag.getElementsByClassName("webkit-html-attribute").length > 0) | |
448 tag.insertBefore(node, tag.lastChild); | |
449 else { | |
450 var nodeName = tag.textContent.match(/^<(.*?)>$/)[1]; | |
451 tag.textContent = ""; | |
452 tag.createTextChild("<" + nodeName); | |
453 tag.appendChild(node); | |
454 tag.createTextChild(">"); | |
455 } | |
456 }, | |
457 | |
458 /** | |
459 * @param {!Element} eventTarget | |
460 * @return {boolean} | |
461 */ | |
462 _startEditingTarget: function(eventTarget) | |
463 { | |
464 if (this.treeOutline.selectedDOMNode() !== this._node) | |
465 return false; | |
466 | |
467 if (this._node.nodeType() !== Node.ELEMENT_NODE && this._node.nodeType()
!== Node.TEXT_NODE) | |
468 return false; | |
469 | |
470 var textNode = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-tex
t-node"); | |
471 if (textNode) | |
472 return this._startEditingTextNode(textNode); | |
473 | |
474 var attribute = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-at
tribute"); | |
475 if (attribute) | |
476 return this._startEditingAttribute(attribute, eventTarget); | |
477 | |
478 var tagName = eventTarget.enclosingNodeOrSelfWithClass("webkit-html-tag-
name"); | |
479 if (tagName) | |
480 return this._startEditingTagName(tagName); | |
481 | |
482 var newAttribute = eventTarget.enclosingNodeOrSelfWithClass("add-attribu
te"); | |
483 if (newAttribute) | |
484 return this._addNewAttribute(); | |
485 | |
486 return false; | |
487 }, | |
488 | |
489 /** | |
490 * @param {!Event} event | |
491 */ | |
492 _showContextMenu: function(event) | |
493 { | |
494 this.treeOutline.showContextMenu(this, event); | |
495 }, | |
496 | |
497 /** | |
498 * @param {!WebInspector.ContextMenu} contextMenu | |
499 * @param {!Event} event | |
500 */ | |
501 populateTagContextMenu: function(contextMenu, event) | |
502 { | |
503 // Add attribute-related actions. | |
504 var treeElement = this._elementCloseTag ? this.treeOutline.findTreeEleme
nt(this._node) : this; | |
505 contextMenu.appendItem(WebInspector.UIString.capitalize("Add ^attribute"
), treeElement._addNewAttribute.bind(treeElement)); | |
506 | |
507 var attribute = event.target.enclosingNodeOrSelfWithClass("webkit-html-a
ttribute"); | |
508 var newAttribute = event.target.enclosingNodeOrSelfWithClass("add-attrib
ute"); | |
509 if (attribute && !newAttribute) | |
510 contextMenu.appendItem(WebInspector.UIString.capitalize("Edit ^attri
bute"), this._startEditingAttribute.bind(this, attribute, event.target)); | |
511 this.populateNodeContextMenu(contextMenu); | |
512 WebInspector.ElementsTreeElement.populateForcedPseudoStateItems(contextM
enu, treeElement.node()); | |
513 contextMenu.appendSeparator(); | |
514 this.populateScrollIntoView(contextMenu); | |
515 }, | |
516 | |
517 /** | |
518 * @param {!WebInspector.ContextMenu} contextMenu | |
519 */ | |
520 populateScrollIntoView: function(contextMenu) | |
521 { | |
522 contextMenu.appendItem(WebInspector.UIString.capitalize("Scroll into ^vi
ew"), this._scrollIntoView.bind(this)); | |
523 }, | |
524 | |
525 populateTextContextMenu: function(contextMenu, textNode) | |
526 { | |
527 if (!this._editing) | |
528 contextMenu.appendItem(WebInspector.UIString.capitalize("Edit ^text"
), this._startEditingTextNode.bind(this, textNode)); | |
529 this.populateNodeContextMenu(contextMenu); | |
530 }, | |
531 | |
532 populateNodeContextMenu: function(contextMenu) | |
533 { | |
534 // Add free-form node-related actions. | |
535 var openTagElement = this._node[this.treeOutline.treeElementSymbol()] ||
this; | |
536 var isEditable = this.hasEditableNode(); | |
537 if (isEditable && !this._editing) | |
538 contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), this._
editAsHTML.bind(this)); | |
539 var isShadowRoot = this._node.isShadowRoot(); | |
540 | |
541 // Place it here so that all "Copy"-ing items stick together. | |
542 var copyMenu = contextMenu.appendSubMenuItem(WebInspector.UIString("Copy
")); | |
543 var createShortcut = WebInspector.KeyboardShortcut.shortcutToString; | |
544 var modifier = WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta; | |
545 var treeOutline = this.treeOutline; | |
546 var menuItem; | |
547 if (!isShadowRoot) { | |
548 menuItem = copyMenu.appendItem(WebInspector.UIString("Copy outerHTML
"), treeOutline.performCopyOrCut.bind(treeOutline, false, this._node)); | |
549 menuItem.setShortcut(createShortcut("V", modifier)); | |
550 } | |
551 if (this._node.nodeType() === Node.ELEMENT_NODE) | |
552 copyMenu.appendItem(WebInspector.UIString.capitalize("Copy selector"
), this._copyCSSPath.bind(this)); | |
553 if (!isShadowRoot) | |
554 copyMenu.appendItem(WebInspector.UIString("Copy XPath"), this._copyX
Path.bind(this)); | |
555 if (!isShadowRoot) { | |
556 menuItem = copyMenu.appendItem(WebInspector.UIString("Cut element"),
treeOutline.performCopyOrCut.bind(treeOutline, true, this._node), !this.hasEdit
ableNode()); | |
557 menuItem.setShortcut(createShortcut("X", modifier)); | |
558 menuItem = copyMenu.appendItem(WebInspector.UIString("Copy element")
, treeOutline.performCopyOrCut.bind(treeOutline, false, this._node)); | |
559 menuItem.setShortcut(createShortcut("C", modifier)); | |
560 menuItem = copyMenu.appendItem(WebInspector.UIString("Paste element"
), treeOutline.pasteNode.bind(treeOutline, this._node), !treeOutline.canPaste(th
is._node)); | |
561 menuItem.setShortcut(createShortcut("V", modifier)); | |
562 } | |
563 | |
564 contextMenu.appendSeparator(); | |
565 menuItem = contextMenu.appendCheckboxItem(WebInspector.UIString("Hide el
ement"), treeOutline.toggleHideElement.bind(treeOutline, this._node), treeOutlin
e.isToggledToHidden(this._node)); | |
566 menuItem.setShortcut(WebInspector.shortcutRegistry.shortcutTitleForActio
n("elements.hide-element")); | |
567 | |
568 | |
569 if (isEditable) | |
570 contextMenu.appendItem(WebInspector.UIString("Delete element"), this
.remove.bind(this)); | |
571 contextMenu.appendSeparator(); | |
572 | |
573 contextMenu.appendItem(WebInspector.UIString("Expand all"), this.expandR
ecursively.bind(this)); | |
574 contextMenu.appendItem(WebInspector.UIString("Collapse all"), this.colla
pseRecursively.bind(this)); | |
575 contextMenu.appendSeparator(); | |
576 }, | |
577 | |
578 _startEditing: function() | |
579 { | |
580 if (this.treeOutline.selectedDOMNode() !== this._node) | |
581 return; | |
582 | |
583 var listItem = this._listItemNode; | |
584 | |
585 if (this._canAddAttributes) { | |
586 var attribute = listItem.getElementsByClassName("webkit-html-attribu
te")[0]; | |
587 if (attribute) | |
588 return this._startEditingAttribute(attribute, attribute.getEleme
ntsByClassName("webkit-html-attribute-value")[0]); | |
589 | |
590 return this._addNewAttribute(); | |
591 } | |
592 | |
593 if (this._node.nodeType() === Node.TEXT_NODE) { | |
594 var textNode = listItem.getElementsByClassName("webkit-html-text-nod
e")[0]; | |
595 if (textNode) | |
596 return this._startEditingTextNode(textNode); | |
597 return; | |
598 } | |
599 }, | |
600 | |
601 _addNewAttribute: function() | |
602 { | |
603 // Cannot just convert the textual html into an element without | |
604 // a parent node. Use a temporary span container for the HTML. | |
605 var container = createElement("span"); | |
606 this._buildAttributeDOM(container, " ", "", null); | |
607 var attr = container.firstElementChild; | |
608 attr.style.marginLeft = "2px"; // overrides the .editing margin rule | |
609 attr.style.marginRight = "2px"; // overrides the .editing margin rule | |
610 | |
611 var tag = this.listItemElement.getElementsByClassName("webkit-html-tag")
[0]; | |
612 this._insertInLastAttributePosition(tag, attr); | |
613 attr.scrollIntoViewIfNeeded(true); | |
614 return this._startEditingAttribute(attr, attr); | |
615 }, | |
616 | |
617 _triggerEditAttribute: function(attributeName) | |
618 { | |
619 var attributeElements = this.listItemElement.getElementsByClassName("web
kit-html-attribute-name"); | |
620 for (var i = 0, len = attributeElements.length; i < len; ++i) { | |
621 if (attributeElements[i].textContent === attributeName) { | |
622 for (var elem = attributeElements[i].nextSibling; elem; elem = e
lem.nextSibling) { | |
623 if (elem.nodeType !== Node.ELEMENT_NODE) | |
624 continue; | |
625 | |
626 if (elem.classList.contains("webkit-html-attribute-value")) | |
627 return this._startEditingAttribute(elem.parentNode, elem
); | |
628 } | |
629 } | |
630 } | |
631 }, | |
632 | |
633 _startEditingAttribute: function(attribute, elementForSelection) | |
634 { | |
635 console.assert(this.listItemElement.isAncestor(attribute)); | |
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.textContent; | |
645 var attributeValueElement = attribute.getElementsByClassName("webkit-htm
l-attribute-value")[0]; | |
646 | |
647 // Make sure elementForSelection is not a child of attributeValueElement
. | |
648 elementForSelection = attributeValueElement.isAncestor(elementForSelecti
on) ? attributeValueElement : elementForSelection; | |
649 | |
650 function removeZeroWidthSpaceRecursive(node) | |
651 { | |
652 if (node.nodeType === Node.TEXT_NODE) { | |
653 node.nodeValue = node.nodeValue.replace(/\u200B/g, ""); | |
654 return; | |
655 } | |
656 | |
657 if (node.nodeType !== Node.ELEMENT_NODE) | |
658 return; | |
659 | |
660 for (var child = node.firstChild; child; child = child.nextSibling) | |
661 removeZeroWidthSpaceRecursive(child); | |
662 } | |
663 | |
664 var attributeValue = attributeName && attributeValueElement ? this._node
.getAttribute(attributeName) : undefined; | |
665 if (attributeValue !== undefined) | |
666 attributeValueElement.setTextContentTruncatedIfNeeded(attributeValue
, WebInspector.UIString("<value is too large to edit>")); | |
667 | |
668 // Remove zero-width spaces that were added by nodeTitleInfo. | |
669 removeZeroWidthSpaceRecursive(attribute); | |
670 | |
671 var config = new WebInspector.InplaceEditor.Config(this._attributeEditin
gCommitted.bind(this), this._editingCancelled.bind(this), attributeName); | |
672 | |
673 /** | |
674 * @param {!Event} event | |
675 * @return {string} | |
676 */ | |
677 function postKeyDownFinishHandler(event) | |
678 { | |
679 WebInspector.handleElementValueModifications(event, attribute); | |
680 return ""; | |
681 } | |
682 | |
683 if (!attributeValueElement.textContent.asParsedURL()) | |
684 config.setPostKeydownFinishHandler(postKeyDownFinishHandler); | |
685 | |
686 this._editing = WebInspector.InplaceEditor.startEditing(attribute, confi
g); | |
687 | |
688 this.listItemElement.getComponentSelection().setBaseAndExtent(elementFor
Selection, 0, elementForSelection, 1); | |
689 | |
690 return true; | |
691 }, | |
692 | |
693 /** | |
694 * @param {!Element} textNodeElement | |
695 */ | |
696 _startEditingTextNode: function(textNodeElement) | |
697 { | |
698 if (WebInspector.isBeingEdited(textNodeElement)) | |
699 return true; | |
700 | |
701 var textNode = this._node; | |
702 // We only show text nodes inline in elements if the element only | |
703 // has a single child, and that child is a text node. | |
704 if (textNode.nodeType() === Node.ELEMENT_NODE && textNode.firstChild) | |
705 textNode = textNode.firstChild; | |
706 | |
707 var container = textNodeElement.enclosingNodeOrSelfWithClass("webkit-htm
l-text-node"); | |
708 if (container) | |
709 container.textContent = textNode.nodeValue(); // Strip the CSS or JS
highlighting if present. | |
710 var config = new WebInspector.InplaceEditor.Config(this._textNodeEditing
Committed.bind(this, textNode), this._editingCancelled.bind(this)); | |
711 this._editing = WebInspector.InplaceEditor.startEditing(textNodeElement,
config); | |
712 this.listItemElement.getComponentSelection().setBaseAndExtent(textNodeEl
ement, 0, textNodeElement, 1); | |
713 | |
714 return true; | |
715 }, | |
716 | |
717 /** | |
718 * @param {!Element=} tagNameElement | |
719 */ | |
720 _startEditingTagName: function(tagNameElement) | |
721 { | |
722 if (!tagNameElement) { | |
723 tagNameElement = this.listItemElement.getElementsByClassName("webkit
-html-tag-name")[0]; | |
724 if (!tagNameElement) | |
725 return false; | |
726 } | |
727 | |
728 var tagName = tagNameElement.textContent; | |
729 if (WebInspector.ElementsTreeElement.EditTagBlacklist.has(tagName.toLowe
rCase())) | |
730 return false; | |
731 | |
732 if (WebInspector.isBeingEdited(tagNameElement)) | |
733 return true; | |
734 | |
735 var closingTagElement = this._distinctClosingTagElement(); | |
736 | |
737 /** | |
738 * @param {!Event} event | |
739 */ | |
740 function keyupListener(event) | |
741 { | |
742 if (closingTagElement) | |
743 closingTagElement.textContent = "</" + tagNameElement.textConten
t + ">"; | |
744 } | |
745 | |
746 /** | |
747 * @param {!Element} element | |
748 * @param {string} newTagName | |
749 * @this {WebInspector.ElementsTreeElement} | |
750 */ | |
751 function editingComitted(element, newTagName) | |
752 { | |
753 tagNameElement.removeEventListener("keyup", keyupListener, false); | |
754 this._tagNameEditingCommitted.apply(this, arguments); | |
755 } | |
756 | |
757 /** | |
758 * @this {WebInspector.ElementsTreeElement} | |
759 */ | |
760 function editingCancelled() | |
761 { | |
762 tagNameElement.removeEventListener("keyup", keyupListener, false); | |
763 this._editingCancelled.apply(this, arguments); | |
764 } | |
765 | |
766 tagNameElement.addEventListener("keyup", keyupListener, false); | |
767 | |
768 var config = new WebInspector.InplaceEditor.Config(editingComitted.bind(
this), editingCancelled.bind(this), tagName); | |
769 this._editing = WebInspector.InplaceEditor.startEditing(tagNameElement,
config); | |
770 this.listItemElement.getComponentSelection().setBaseAndExtent(tagNameEle
ment, 0, tagNameElement, 1); | |
771 return true; | |
772 }, | |
773 | |
774 /** | |
775 * @param {function(string, string)} commitCallback | |
776 * @param {function()} disposeCallback | |
777 * @param {?Protocol.Error} error | |
778 * @param {string} initialValue | |
779 */ | |
780 _startEditingAsHTML: function(commitCallback, disposeCallback, error, initia
lValue) | |
781 { | |
782 if (error) | |
783 return; | |
784 if (this._editing) | |
785 return; | |
786 | |
787 function consume(event) | |
788 { | |
789 if (event.eventPhase === Event.AT_TARGET) | |
790 event.consume(true); | |
791 } | |
792 | |
793 initialValue = this._convertWhitespaceToEntities(initialValue).text; | |
794 | |
795 this._htmlEditElement = createElement("div"); | |
796 this._htmlEditElement.className = "source-code elements-tree-editor"; | |
797 | |
798 // Hide header items. | |
799 var child = this.listItemElement.firstChild; | |
800 while (child) { | |
801 child.style.display = "none"; | |
802 child = child.nextSibling; | |
803 } | |
804 // Hide children item. | |
805 if (this._childrenListNode) | |
806 this._childrenListNode.style.display = "none"; | |
807 // Append editor. | |
808 this.listItemElement.appendChild(this._htmlEditElement); | |
809 this.listItemElement.classList.add("editing-as-html"); | |
810 this.treeOutline.element.addEventListener("mousedown", consume, false); | |
811 | |
812 /** | |
813 * @param {!Element} element | |
814 * @param {string} newValue | |
815 * @this {WebInspector.ElementsTreeElement} | |
816 */ | |
817 function commit(element, newValue) | |
818 { | |
819 commitCallback(initialValue, newValue); | |
820 dispose.call(this); | |
821 } | |
822 | |
823 /** | |
824 * @this {WebInspector.ElementsTreeElement} | |
825 */ | |
826 function dispose() | |
827 { | |
828 disposeCallback(); | |
829 delete this._editing; | |
830 this.treeOutline.setMultilineEditing(null); | |
831 | |
832 this.listItemElement.classList.remove("editing-as-html"); | |
833 // Remove editor. | |
834 this.listItemElement.removeChild(this._htmlEditElement); | |
835 delete this._htmlEditElement; | |
836 // Unhide children item. | |
837 if (this._childrenListNode) | |
838 this._childrenListNode.style.removeProperty("display"); | |
839 // Unhide header items. | |
840 var child = this.listItemElement.firstChild; | |
841 while (child) { | |
842 child.style.removeProperty("display"); | |
843 child = child.nextSibling; | |
844 } | |
845 | |
846 this.treeOutline.element.removeEventListener("mousedown", consume, f
alse); | |
847 this.treeOutline.focus(); | |
848 } | |
849 | |
850 var config = new WebInspector.InplaceEditor.Config(commit.bind(this), di
spose.bind(this)); | |
851 config.setMultilineOptions(initialValue, { name: "xml", htmlMode: true }
, "web-inspector-html", WebInspector.moduleSetting("domWordWrap").get(), true); | |
852 WebInspector.InplaceEditor.startMultilineEditing(this._htmlEditElement,
config).then(markAsBeingEdited.bind(this)); | |
853 | |
854 /** | |
855 * @param {!Object} controller | |
856 * @this {WebInspector.ElementsTreeElement} | |
857 */ | |
858 function markAsBeingEdited(controller) | |
859 { | |
860 this._editing = /** @type {!WebInspector.InplaceEditor.Controller} *
/ (controller); | |
861 this._editing.setWidth(this.treeOutline.visibleWidth() - this._compu
teLeftIndent()); | |
862 this.treeOutline.setMultilineEditing(this._editing); | |
863 } | |
864 }, | |
865 | |
866 _attributeEditingCommitted: function(element, newText, oldText, attributeNam
e, moveDirection) | |
867 { | |
868 delete this._editing; | |
869 | |
870 var treeOutline = this.treeOutline; | |
871 | |
872 /** | |
873 * @param {?Protocol.Error=} error | |
874 * @this {WebInspector.ElementsTreeElement} | |
875 */ | |
876 function moveToNextAttributeIfNeeded(error) | |
877 { | |
878 if (error) | |
879 this._editingCancelled(element, attributeName); | |
880 | |
881 if (!moveDirection) | |
882 return; | |
883 | |
884 treeOutline.runPendingUpdates(); | |
885 | |
886 // Search for the attribute's position, and then decide where to mov
e to. | |
887 var attributes = this._node.attributes(); | |
888 for (var i = 0; i < attributes.length; ++i) { | |
889 if (attributes[i].name !== attributeName) | |
890 continue; | |
891 | |
892 if (moveDirection === "backward") { | |
893 if (i === 0) | |
894 this._startEditingTagName(); | |
895 else | |
896 this._triggerEditAttribute(attributes[i - 1].name); | |
897 } else { | |
898 if (i === attributes.length - 1) | |
899 this._addNewAttribute(); | |
900 else | |
901 this._triggerEditAttribute(attributes[i + 1].name); | |
902 } | |
903 return; | |
904 } | |
905 | |
906 // Moving From the "New Attribute" position. | |
907 if (moveDirection === "backward") { | |
908 if (newText === " ") { | |
909 // Moving from "New Attribute" that was not edited | |
910 if (attributes.length > 0) | |
911 this._triggerEditAttribute(attributes[attributes.length
- 1].name); | |
912 } else { | |
913 // Moving from "New Attribute" that holds new value | |
914 if (attributes.length > 1) | |
915 this._triggerEditAttribute(attributes[attributes.length
- 2].name); | |
916 } | |
917 } else if (moveDirection === "forward") { | |
918 if (!newText.isWhitespace()) | |
919 this._addNewAttribute(); | |
920 else | |
921 this._startEditingTagName(); | |
922 } | |
923 } | |
924 | |
925 | |
926 if ((attributeName.trim() || newText.trim()) && oldText !== newText) { | |
927 this._node.setAttribute(attributeName, newText, moveToNextAttributeI
fNeeded.bind(this)); | |
928 return; | |
929 } | |
930 | |
931 this.updateTitle(); | |
932 moveToNextAttributeIfNeeded.call(this); | |
933 }, | |
934 | |
935 _tagNameEditingCommitted: function(element, newText, oldText, tagName, moveD
irection) | |
936 { | |
937 delete this._editing; | |
938 var self = this; | |
939 | |
940 function cancel() | |
941 { | |
942 var closingTagElement = self._distinctClosingTagElement(); | |
943 if (closingTagElement) | |
944 closingTagElement.textContent = "</" + tagName + ">"; | |
945 | |
946 self._editingCancelled(element, tagName); | |
947 moveToNextAttributeIfNeeded.call(self); | |
948 } | |
949 | |
950 /** | |
951 * @this {WebInspector.ElementsTreeElement} | |
952 */ | |
953 function moveToNextAttributeIfNeeded() | |
954 { | |
955 if (moveDirection !== "forward") { | |
956 this._addNewAttribute(); | |
957 return; | |
958 } | |
959 | |
960 var attributes = this._node.attributes(); | |
961 if (attributes.length > 0) | |
962 this._triggerEditAttribute(attributes[0].name); | |
963 else | |
964 this._addNewAttribute(); | |
965 } | |
966 | |
967 newText = newText.trim(); | |
968 if (newText === oldText) { | |
969 cancel(); | |
970 return; | |
971 } | |
972 | |
973 var treeOutline = this.treeOutline; | |
974 var wasExpanded = this.expanded; | |
975 | |
976 function changeTagNameCallback(error, nodeId) | |
977 { | |
978 if (error || !nodeId) { | |
979 cancel(); | |
980 return; | |
981 } | |
982 var newTreeItem = treeOutline.selectNodeAfterEdit(wasExpanded, error
, nodeId); | |
983 moveToNextAttributeIfNeeded.call(newTreeItem); | |
984 } | |
985 this._node.setNodeName(newText, changeTagNameCallback); | |
986 }, | |
987 | |
988 /** | |
989 * @param {!WebInspector.DOMNode} textNode | |
990 * @param {!Element} element | |
991 * @param {string} newText | |
992 */ | |
993 _textNodeEditingCommitted: function(textNode, element, newText) | |
994 { | |
995 delete this._editing; | |
996 | |
997 /** | |
998 * @this {WebInspector.ElementsTreeElement} | |
999 */ | |
1000 function callback() | |
1001 { | |
1002 this.updateTitle(); | |
1003 } | |
1004 textNode.setNodeValue(newText, callback.bind(this)); | |
1005 }, | |
1006 | |
1007 /** | |
1008 * @param {!Element} element | |
1009 * @param {*} context | |
1010 */ | |
1011 _editingCancelled: function(element, context) | |
1012 { | |
1013 delete this._editing; | |
1014 | |
1015 // Need to restore attributes structure. | |
1016 this.updateTitle(); | |
1017 }, | |
1018 | |
1019 /** | |
1020 * @return {!Element} | |
1021 */ | |
1022 _distinctClosingTagElement: function() | |
1023 { | |
1024 // FIXME: Improve the Tree Element / Outline Abstraction to prevent craw
ling the DOM | |
1025 | |
1026 // For an expanded element, it will be the last element with class "clos
e" | |
1027 // in the child element list. | |
1028 if (this.expanded) { | |
1029 var closers = this._childrenListNode.querySelectorAll(".close"); | |
1030 return closers[closers.length - 1]; | |
1031 } | |
1032 | |
1033 // Remaining cases are single line non-expanded elements with a closing | |
1034 // tag, or HTML elements without a closing tag (such as <br>). Return | |
1035 // null in the case where there isn't a closing tag. | |
1036 var tags = this.listItemElement.getElementsByClassName("webkit-html-tag"
); | |
1037 return (tags.length === 1 ? null : tags[tags.length - 1]); | |
1038 }, | |
1039 | |
1040 /** | |
1041 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord=} updateRecord | |
1042 * @param {boolean=} onlySearchQueryChanged | |
1043 */ | |
1044 updateTitle: function(updateRecord, onlySearchQueryChanged) | |
1045 { | |
1046 // If we are editing, return early to prevent canceling the edit. | |
1047 // After editing is committed updateTitle will be called. | |
1048 if (this._editing) | |
1049 return; | |
1050 | |
1051 if (onlySearchQueryChanged) { | |
1052 this._hideSearchHighlight(); | |
1053 } else { | |
1054 var nodeInfo = this._nodeTitleInfo(updateRecord || null); | |
1055 if (this._node.nodeType() === Node.DOCUMENT_FRAGMENT_NODE && this._n
ode.isInShadowTree() && this._node.shadowRootType()) { | |
1056 this.childrenListElement.classList.add("shadow-root"); | |
1057 var depth = 4; | |
1058 for (var node = this._node; depth && node; node = node.parentNod
e) { | |
1059 if (node.nodeType() === Node.DOCUMENT_FRAGMENT_NODE) | |
1060 depth--; | |
1061 } | |
1062 if (!depth) | |
1063 this.childrenListElement.classList.add("shadow-root-deep"); | |
1064 else | |
1065 this.childrenListElement.classList.add("shadow-root-depth-"
+ depth); | |
1066 } | |
1067 var highlightElement = createElement("span"); | |
1068 highlightElement.className = "highlight"; | |
1069 highlightElement.appendChild(nodeInfo); | |
1070 this.title = highlightElement; | |
1071 this.updateDecorations(); | |
1072 this.listItemElement.insertBefore(this._gutterContainer, this.listIt
emElement.firstChild); | |
1073 delete this._highlightResult; | |
1074 } | |
1075 | |
1076 delete this.selectionElement; | |
1077 if (this.selected) | |
1078 this._createSelection(); | |
1079 this._preventFollowingLinksOnDoubleClick(); | |
1080 this._highlightSearchResults(); | |
1081 }, | |
1082 | |
1083 /** | |
1084 * @return {number} | |
1085 */ | |
1086 _computeLeftIndent: function() | |
1087 { | |
1088 var treeElement = this.parent; | |
1089 var depth = 0; | |
1090 while (treeElement !== null) { | |
1091 depth++; | |
1092 treeElement = treeElement.parent; | |
1093 } | |
1094 | |
1095 /** Keep it in sync with elementsTreeOutline.css **/ | |
1096 return 12 * (depth - 2) + (this.isExpandable() ? 1 : 12); | |
1097 }, | |
1098 | |
1099 updateDecorations: function() | |
1100 { | |
1101 this._gutterContainer.style.left = (-this._computeLeftIndent()) + "px"; | |
1102 | |
1103 if (this.isClosingTag()) | |
1104 return; | |
1105 | |
1106 var node = this._node; | |
1107 if (node.nodeType() !== Node.ELEMENT_NODE) | |
1108 return; | |
1109 | |
1110 if (!this.treeOutline._decoratorExtensions) | |
1111 /** @type {!Array.<!Runtime.Extension>} */ | |
1112 this.treeOutline._decoratorExtensions = runtime.extensions(WebInspec
tor.DOMPresentationUtils.MarkerDecorator); | |
1113 | |
1114 var markerToExtension = new Map(); | |
1115 for (var i = 0; i < this.treeOutline._decoratorExtensions.length; ++i) | |
1116 markerToExtension.set(this.treeOutline._decoratorExtensions[i].descr
iptor()["marker"], this.treeOutline._decoratorExtensions[i]); | |
1117 | |
1118 var promises = []; | |
1119 var decorations = []; | |
1120 var descendantDecorations = []; | |
1121 node.traverseMarkers(visitor); | |
1122 | |
1123 /** | |
1124 * @param {!WebInspector.DOMNode} n | |
1125 * @param {string} marker | |
1126 */ | |
1127 function visitor(n, marker) | |
1128 { | |
1129 var extension = markerToExtension.get(marker); | |
1130 if (!extension) | |
1131 return; | |
1132 promises.push(extension.instance().then(collectDecoration.bind(null,
n))); | |
1133 } | |
1134 | |
1135 /** | |
1136 * @param {!WebInspector.DOMNode} n | |
1137 * @param {!WebInspector.DOMPresentationUtils.MarkerDecorator} decorator | |
1138 */ | |
1139 function collectDecoration(n, decorator) | |
1140 { | |
1141 var decoration = decorator.decorate(n); | |
1142 if (!decoration) | |
1143 return; | |
1144 (n === node ? decorations : descendantDecorations).push(decoration); | |
1145 } | |
1146 | |
1147 Promise.all(promises).then(updateDecorationsUI.bind(this)); | |
1148 | |
1149 /** | |
1150 * @this {WebInspector.ElementsTreeElement} | |
1151 */ | |
1152 function updateDecorationsUI() | |
1153 { | |
1154 this._decorationsElement.removeChildren(); | |
1155 this._decorationsElement.classList.add("hidden"); | |
1156 this._gutterContainer.classList.toggle("has-decorations", decoration
s.length || descendantDecorations.length); | |
1157 | |
1158 if (!decorations.length && !descendantDecorations.length) | |
1159 return; | |
1160 | |
1161 var colors = new Set(); | |
1162 var titles = createElement("div"); | |
1163 | |
1164 for (var decoration of decorations) { | |
1165 var titleElement = titles.createChild("div"); | |
1166 titleElement.textContent = decoration.title; | |
1167 colors.add(decoration.color); | |
1168 } | |
1169 if (this.expanded && !decorations.length) | |
1170 return; | |
1171 | |
1172 var descendantColors = new Set(); | |
1173 if (descendantDecorations.length) { | |
1174 var element = titles.createChild("div"); | |
1175 element.textContent = WebInspector.UIString("Children:"); | |
1176 for (var decoration of descendantDecorations) { | |
1177 element = titles.createChild("div"); | |
1178 element.style.marginLeft = "15px"; | |
1179 element.textContent = decoration.title; | |
1180 descendantColors.add(decoration.color); | |
1181 } | |
1182 } | |
1183 | |
1184 var offset = 0; | |
1185 processColors.call(this, colors, "elements-gutter-decoration"); | |
1186 if (!this.expanded) | |
1187 processColors.call(this, descendantColors, "elements-gutter-deco
ration elements-has-decorated-children"); | |
1188 WebInspector.Tooltip.install(this._decorationsElement, titles); | |
1189 | |
1190 /** | |
1191 * @param {!Set<string>} colors | |
1192 * @param {string} className | |
1193 * @this {WebInspector.ElementsTreeElement} | |
1194 */ | |
1195 function processColors(colors, className) | |
1196 { | |
1197 for (var color of colors) { | |
1198 var child = this._decorationsElement.createChild("div", clas
sName); | |
1199 this._decorationsElement.classList.remove("hidden"); | |
1200 child.style.backgroundColor = color; | |
1201 child.style.borderColor = color; | |
1202 if (offset) | |
1203 child.style.marginLeft = offset + "px"; | |
1204 offset += 3; | |
1205 } | |
1206 } | |
1207 } | |
1208 }, | |
1209 | |
1210 /** | |
1211 * @param {!Node} parentElement | |
1212 * @param {string} name | |
1213 * @param {string} value | |
1214 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord} updateRecord | |
1215 * @param {boolean=} forceValue | |
1216 * @param {!WebInspector.DOMNode=} node | |
1217 */ | |
1218 _buildAttributeDOM: function(parentElement, name, value, updateRecord, force
Value, node) | |
1219 { | |
1220 var closingPunctuationRegex = /[\/;:\)\]\}]/g; | |
1221 var highlightIndex = 0; | |
1222 var highlightCount; | |
1223 var additionalHighlightOffset = 0; | |
1224 var result; | |
1225 | |
1226 /** | |
1227 * @param {string} match | |
1228 * @param {number} replaceOffset | |
1229 * @return {string} | |
1230 */ | |
1231 function replacer(match, replaceOffset) { | |
1232 while (highlightIndex < highlightCount && result.entityRanges[highli
ghtIndex].offset < replaceOffset) { | |
1233 result.entityRanges[highlightIndex].offset += additionalHighligh
tOffset; | |
1234 ++highlightIndex; | |
1235 } | |
1236 additionalHighlightOffset += 1; | |
1237 return match + "\u200B"; | |
1238 } | |
1239 | |
1240 /** | |
1241 * @param {!Element} element | |
1242 * @param {string} value | |
1243 * @this {WebInspector.ElementsTreeElement} | |
1244 */ | |
1245 function setValueWithEntities(element, value) | |
1246 { | |
1247 result = this._convertWhitespaceToEntities(value); | |
1248 highlightCount = result.entityRanges.length; | |
1249 value = result.text.replace(closingPunctuationRegex, replacer); | |
1250 while (highlightIndex < highlightCount) { | |
1251 result.entityRanges[highlightIndex].offset += additionalHighligh
tOffset; | |
1252 ++highlightIndex; | |
1253 } | |
1254 element.setTextContentTruncatedIfNeeded(value); | |
1255 WebInspector.highlightRangesWithStyleClass(element, result.entityRan
ges, "webkit-html-entity-value"); | |
1256 } | |
1257 | |
1258 var hasText = (forceValue || value.length > 0); | |
1259 var attrSpanElement = parentElement.createChild("span", "webkit-html-att
ribute"); | |
1260 var attrNameElement = attrSpanElement.createChild("span", "webkit-html-a
ttribute-name"); | |
1261 attrNameElement.textContent = name; | |
1262 | |
1263 if (hasText) | |
1264 attrSpanElement.createTextChild("=\u200B\""); | |
1265 | |
1266 var attrValueElement = attrSpanElement.createChild("span", "webkit-html-
attribute-value"); | |
1267 | |
1268 if (updateRecord && updateRecord.isAttributeModified(name)) | |
1269 WebInspector.runCSSAnimationOnce(hasText ? attrValueElement : attrNa
meElement, "dom-update-highlight"); | |
1270 | |
1271 /** | |
1272 * @this {WebInspector.ElementsTreeElement} | |
1273 * @param {string} value | |
1274 * @return {!Element} | |
1275 */ | |
1276 function linkifyValue(value) | |
1277 { | |
1278 var rewrittenHref = node.resolveURL(value); | |
1279 if (rewrittenHref === null) { | |
1280 var span = createElement("span"); | |
1281 setValueWithEntities.call(this, span, value); | |
1282 return span; | |
1283 } | |
1284 value = value.replace(closingPunctuationRegex, "$&\u200B"); | |
1285 if (value.startsWith("data:")) | |
1286 value = value.trimMiddle(60); | |
1287 var anchor = WebInspector.linkifyURLAsNode(rewrittenHref, value, "",
node.nodeName().toLowerCase() === "a"); | |
1288 anchor.preventFollow = true; | |
1289 return anchor; | |
1290 } | |
1291 | |
1292 if (node && (name === "src" || name === "href")) { | |
1293 attrValueElement.appendChild(linkifyValue.call(this, value)); | |
1294 } else if (node && (node.nodeName().toLowerCase() === "img" || node.node
Name().toLowerCase() === "source") && name === "srcset") { | |
1295 var sources = value.split(","); | |
1296 for (var i = 0; i < sources.length; ++i) { | |
1297 if (i > 0) | |
1298 attrValueElement.createTextChild(", "); | |
1299 var source = sources[i].trim(); | |
1300 var indexOfSpace = source.indexOf(" "); | |
1301 var url, tail; | |
1302 | |
1303 if (indexOfSpace === -1) { | |
1304 url = source; | |
1305 } else { | |
1306 url = source.substring(0, indexOfSpace); | |
1307 tail = source.substring(indexOfSpace); | |
1308 } | |
1309 | |
1310 attrValueElement.appendChild(linkifyValue.call(this, url)); | |
1311 | |
1312 if (tail) | |
1313 attrValueElement.createTextChild(tail); | |
1314 } | |
1315 } else { | |
1316 setValueWithEntities.call(this, attrValueElement, value); | |
1317 } | |
1318 | |
1319 if (hasText) | |
1320 attrSpanElement.createTextChild("\""); | |
1321 }, | |
1322 | |
1323 /** | |
1324 * @param {!Node} parentElement | |
1325 * @param {string} pseudoElementName | |
1326 */ | |
1327 _buildPseudoElementDOM: function(parentElement, pseudoElementName) | |
1328 { | |
1329 var pseudoElement = parentElement.createChild("span", "webkit-html-pseud
o-element"); | |
1330 pseudoElement.textContent = "::" + pseudoElementName; | |
1331 parentElement.createTextChild("\u200B"); | |
1332 }, | |
1333 | |
1334 /** | |
1335 * @param {!Node} parentElement | |
1336 * @param {string} tagName | |
1337 * @param {boolean} isClosingTag | |
1338 * @param {boolean} isDistinctTreeElement | |
1339 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord} updateRecord | |
1340 */ | |
1341 _buildTagDOM: function(parentElement, tagName, isClosingTag, isDistinctTreeE
lement, updateRecord) | |
1342 { | |
1343 var node = this._node; | |
1344 var classes = [ "webkit-html-tag" ]; | |
1345 if (isClosingTag && isDistinctTreeElement) | |
1346 classes.push("close"); | |
1347 var tagElement = parentElement.createChild("span", classes.join(" ")); | |
1348 tagElement.createTextChild("<"); | |
1349 var tagNameElement = tagElement.createChild("span", isClosingTag ? "webk
it-html-close-tag-name" : "webkit-html-tag-name"); | |
1350 tagNameElement.textContent = (isClosingTag ? "/" : "") + tagName; | |
1351 if (!isClosingTag) { | |
1352 if (node.hasAttributes()) { | |
1353 var attributes = node.attributes(); | |
1354 for (var i = 0; i < attributes.length; ++i) { | |
1355 var attr = attributes[i]; | |
1356 tagElement.createTextChild(" "); | |
1357 this._buildAttributeDOM(tagElement, attr.name, attr.value, u
pdateRecord, false, node); | |
1358 } | |
1359 } | |
1360 if (updateRecord) { | |
1361 var hasUpdates = updateRecord.hasRemovedAttributes() || updateRe
cord.hasRemovedChildren(); | |
1362 hasUpdates |= !this.expanded && updateRecord.hasChangedChildren(
); | |
1363 if (hasUpdates) | |
1364 WebInspector.runCSSAnimationOnce(tagNameElement, "dom-update
-highlight"); | |
1365 } | |
1366 } | |
1367 | |
1368 tagElement.createTextChild(">"); | |
1369 parentElement.createTextChild("\u200B"); | |
1370 }, | |
1371 | |
1372 /** | |
1373 * @param {string} text | |
1374 * @return {!{text: string, entityRanges: !Array.<!WebInspector.SourceRange>
}} | |
1375 */ | |
1376 _convertWhitespaceToEntities: function(text) | |
1377 { | |
1378 var result = ""; | |
1379 var lastIndexAfterEntity = 0; | |
1380 var entityRanges = []; | |
1381 var charToEntity = WebInspector.ElementsTreeOutline.MappedCharToEntity; | |
1382 for (var i = 0, size = text.length; i < size; ++i) { | |
1383 var char = text.charAt(i); | |
1384 if (charToEntity[char]) { | |
1385 result += text.substring(lastIndexAfterEntity, i); | |
1386 var entityValue = "&" + charToEntity[char] + ";"; | |
1387 entityRanges.push({offset: result.length, length: entityValue.le
ngth}); | |
1388 result += entityValue; | |
1389 lastIndexAfterEntity = i + 1; | |
1390 } | |
1391 } | |
1392 if (result) | |
1393 result += text.substring(lastIndexAfterEntity); | |
1394 return {text: result || text, entityRanges: entityRanges}; | |
1395 }, | |
1396 | |
1397 /** | |
1398 * @param {?WebInspector.ElementsTreeOutline.UpdateRecord} updateRecord | |
1399 * @return {!DocumentFragment} result | |
1400 */ | |
1401 _nodeTitleInfo: function(updateRecord) | |
1402 { | |
1403 var node = this._node; | |
1404 var titleDOM = createDocumentFragment(); | |
1405 | |
1406 switch (node.nodeType()) { | |
1407 case Node.ATTRIBUTE_NODE: | |
1408 this._buildAttributeDOM(titleDOM, /** @type {string} */ (node.name),
/** @type {string} */ (node.value), updateRecord, true); | |
1409 break; | |
1410 | |
1411 case Node.ELEMENT_NODE: | |
1412 var pseudoType = node.pseudoType(); | |
1413 if (pseudoType) { | |
1414 this._buildPseudoElementDOM(titleDOM, pseudoType); | |
1415 break; | |
1416 } | |
1417 | |
1418 var tagName = node.nodeNameInCorrectCase(); | |
1419 if (this._elementCloseTag) { | |
1420 this._buildTagDOM(titleDOM, tagName, true, true, updateRecord); | |
1421 break; | |
1422 } | |
1423 | |
1424 this._buildTagDOM(titleDOM, tagName, false, false, updateRecord); | |
1425 | |
1426 if (this.isExpandable()) { | |
1427 if (!this.expanded) { | |
1428 var textNodeElement = titleDOM.createChild("span", "webkit-h
tml-text-node bogus"); | |
1429 textNodeElement.textContent = "\u2026"; | |
1430 titleDOM.createTextChild("\u200B"); | |
1431 this._buildTagDOM(titleDOM, tagName, true, false, updateReco
rd); | |
1432 } | |
1433 break; | |
1434 } | |
1435 | |
1436 if (WebInspector.ElementsTreeElement.canShowInlineText(node)) { | |
1437 var textNodeElement = titleDOM.createChild("span", "webkit-html-
text-node"); | |
1438 var result = this._convertWhitespaceToEntities(node.firstChild.n
odeValue()); | |
1439 textNodeElement.textContent = result.text; | |
1440 WebInspector.highlightRangesWithStyleClass(textNodeElement, resu
lt.entityRanges, "webkit-html-entity-value"); | |
1441 titleDOM.createTextChild("\u200B"); | |
1442 this._buildTagDOM(titleDOM, tagName, true, false, updateRecord); | |
1443 if (updateRecord && updateRecord.hasChangedChildren()) | |
1444 WebInspector.runCSSAnimationOnce(textNodeElement, "dom-updat
e-highlight"); | |
1445 if (updateRecord && updateRecord.isCharDataModified()) | |
1446 WebInspector.runCSSAnimationOnce(textNodeElement, "dom-updat
e-highlight"); | |
1447 break; | |
1448 } | |
1449 | |
1450 if (this.treeOutline.isXMLMimeType || !WebInspector.ElementsTreeElem
ent.ForbiddenClosingTagElements.has(tagName)) | |
1451 this._buildTagDOM(titleDOM, tagName, true, false, updateRecord); | |
1452 break; | |
1453 | |
1454 case Node.TEXT_NODE: | |
1455 if (node.parentNode && node.parentNode.nodeName().toLowerCase() ===
"script") { | |
1456 var newNode = titleDOM.createChild("span", "webkit-html-text-nod
e webkit-html-js-node"); | |
1457 var text = node.nodeValue(); | |
1458 newNode.textContent = text.startsWith("\n") ? text.substring(1)
: text; | |
1459 | |
1460 var javascriptSyntaxHighlighter = new WebInspector.DOMSyntaxHigh
lighter("text/javascript", true); | |
1461 javascriptSyntaxHighlighter.syntaxHighlightNode(newNode).then(up
dateSearchHighlight.bind(this)); | |
1462 } else if (node.parentNode && node.parentNode.nodeName().toLowerCase
() === "style") { | |
1463 var newNode = titleDOM.createChild("span", "webkit-html-text-nod
e webkit-html-css-node"); | |
1464 var text = node.nodeValue(); | |
1465 newNode.textContent = text.startsWith("\n") ? text.substring(1)
: text; | |
1466 | |
1467 var cssSyntaxHighlighter = new WebInspector.DOMSyntaxHighlighter
("text/css", true); | |
1468 cssSyntaxHighlighter.syntaxHighlightNode(newNode).then(updateSea
rchHighlight.bind(this)); | |
1469 } else { | |
1470 titleDOM.createTextChild("\""); | |
1471 var textNodeElement = titleDOM.createChild("span", "webkit-html-
text-node"); | |
1472 var result = this._convertWhitespaceToEntities(node.nodeValue())
; | |
1473 textNodeElement.textContent = result.text; | |
1474 WebInspector.highlightRangesWithStyleClass(textNodeElement, resu
lt.entityRanges, "webkit-html-entity-value"); | |
1475 titleDOM.createTextChild("\""); | |
1476 if (updateRecord && updateRecord.isCharDataModified()) | |
1477 WebInspector.runCSSAnimationOnce(textNodeElement, "dom-updat
e-highlight"); | |
1478 } | |
1479 break; | |
1480 | |
1481 case Node.COMMENT_NODE: | |
1482 var commentElement = titleDOM.createChild("span", "webkit-html-comme
nt"); | |
1483 commentElement.createTextChild("<!--" + node.nodeValue() + "-->"); | |
1484 break; | |
1485 | |
1486 case Node.DOCUMENT_TYPE_NODE: | |
1487 var docTypeElement = titleDOM.createChild("span", "webkit-html-docty
pe"); | |
1488 docTypeElement.createTextChild("<!DOCTYPE " + node.nodeName()); | |
1489 if (node.publicId) { | |
1490 docTypeElement.createTextChild(" PUBLIC \"" + node.publicId + "\
""); | |
1491 if (node.systemId) | |
1492 docTypeElement.createTextChild(" \"" + node.systemId + "\"")
; | |
1493 } else if (node.systemId) | |
1494 docTypeElement.createTextChild(" SYSTEM \"" + node.systemId + "\
""); | |
1495 | |
1496 if (node.internalSubset) | |
1497 docTypeElement.createTextChild(" [" + node.internalSubset + "]")
; | |
1498 | |
1499 docTypeElement.createTextChild(">"); | |
1500 break; | |
1501 | |
1502 case Node.CDATA_SECTION_NODE: | |
1503 var cdataElement = titleDOM.createChild("span", "webkit-html-text-no
de"); | |
1504 cdataElement.createTextChild("<![CDATA[" + node.nodeValue() + "]]>")
; | |
1505 break; | |
1506 | |
1507 case Node.DOCUMENT_FRAGMENT_NODE: | |
1508 var fragmentElement = titleDOM.createChild("span", "webkit-html-frag
ment"); | |
1509 fragmentElement.textContent = node.nodeNameInCorrectCase().collapseW
hitespace(); | |
1510 break; | |
1511 default: | |
1512 titleDOM.createTextChild(node.nodeNameInCorrectCase().collapseWhites
pace()); | |
1513 } | |
1514 | |
1515 /** | |
1516 * @this {WebInspector.ElementsTreeElement} | |
1517 */ | |
1518 function updateSearchHighlight() | |
1519 { | |
1520 delete this._highlightResult; | |
1521 this._highlightSearchResults(); | |
1522 } | |
1523 | |
1524 return titleDOM; | |
1525 }, | |
1526 | |
1527 remove: function() | |
1528 { | |
1529 if (this._node.pseudoType()) | |
1530 return; | |
1531 var parentElement = this.parent; | |
1532 if (!parentElement) | |
1533 return; | |
1534 | |
1535 if (!this._node.parentNode || this._node.parentNode.nodeType() === Node.
DOCUMENT_NODE) | |
1536 return; | |
1537 this._node.removeNode(); | |
1538 }, | |
1539 | |
1540 /** | |
1541 * @param {function(boolean)=} callback | |
1542 * @param {boolean=} startEditing | |
1543 */ | |
1544 toggleEditAsHTML: function(callback, startEditing) | |
1545 { | |
1546 if (this._editing && this._htmlEditElement && WebInspector.isBeingEdited
(this._htmlEditElement)) { | |
1547 this._editing.commit(); | |
1548 return; | |
1549 } | |
1550 | |
1551 if (startEditing === false) | |
1552 return; | |
1553 | |
1554 /** | |
1555 * @param {?Protocol.Error} error | |
1556 */ | |
1557 function selectNode(error) | |
1558 { | |
1559 if (callback) | |
1560 callback(!error); | |
1561 } | |
1562 | |
1563 /** | |
1564 * @param {string} initialValue | |
1565 * @param {string} value | |
1566 */ | |
1567 function commitChange(initialValue, value) | |
1568 { | |
1569 if (initialValue !== value) | |
1570 node.setOuterHTML(value, selectNode); | |
1571 } | |
1572 | |
1573 function disposeCallback() | |
1574 { | |
1575 if (callback) | |
1576 callback(false); | |
1577 } | |
1578 | |
1579 var node = this._node; | |
1580 node.getOuterHTML(this._startEditingAsHTML.bind(this, commitChange, disp
oseCallback)); | |
1581 }, | |
1582 | |
1583 _copyCSSPath: function() | |
1584 { | |
1585 InspectorFrontendHost.copyText(WebInspector.DOMPresentationUtils.cssPath
(this._node, true)); | |
1586 }, | |
1587 | |
1588 _copyXPath: function() | |
1589 { | |
1590 InspectorFrontendHost.copyText(WebInspector.DOMPresentationUtils.xPath(t
his._node, true)); | |
1591 }, | |
1592 | |
1593 _highlightSearchResults: function() | |
1594 { | |
1595 if (!this._searchQuery || !this._searchHighlightsVisible) | |
1596 return; | |
1597 this._hideSearchHighlight(); | |
1598 | |
1599 var text = this.listItemElement.textContent; | |
1600 var regexObject = createPlainTextSearchRegex(this._searchQuery, "gi"); | |
1601 | |
1602 var match = regexObject.exec(text); | |
1603 var matchRanges = []; | |
1604 while (match) { | |
1605 matchRanges.push(new WebInspector.SourceRange(match.index, match[0].
length)); | |
1606 match = regexObject.exec(text); | |
1607 } | |
1608 | |
1609 // Fall back for XPath, etc. matches. | |
1610 if (!matchRanges.length) | |
1611 matchRanges.push(new WebInspector.SourceRange(0, text.length)); | |
1612 | |
1613 this._highlightResult = []; | |
1614 WebInspector.highlightSearchResults(this.listItemElement, matchRanges, t
his._highlightResult); | |
1615 }, | |
1616 | |
1617 _scrollIntoView: function() | |
1618 { | |
1619 function scrollIntoViewCallback(object) | |
1620 { | |
1621 /** | |
1622 * @suppressReceiverCheck | |
1623 * @this {!Element} | |
1624 */ | |
1625 function scrollIntoView() | |
1626 { | |
1627 this.scrollIntoViewIfNeeded(true); | |
1628 } | |
1629 | |
1630 if (object) | |
1631 object.callFunction(scrollIntoView); | |
1632 } | |
1633 | |
1634 this._node.resolveToObject("", scrollIntoViewCallback); | |
1635 }, | |
1636 | |
1637 _editAsHTML: function() | |
1638 { | |
1639 var promise = WebInspector.Revealer.revealPromise(this.node()); | |
1640 promise.then(() => WebInspector.actionRegistry.action("elements.edit-as-
html").execute()); | |
1641 }, | |
1642 | |
1643 __proto__: TreeElement.prototype | |
1644 }; | |
OLD | NEW |