Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(16)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/elements/ElementsTreeOutline.js

Issue 2466123002: DevTools: reformat front-end code to match chromium style. (Closed)
Patch Set: all done Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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 {TreeOutline}
34 * @param {!WebInspector.DOMModel} domModel
35 * @param {boolean=} omitRootDOMNode
36 * @param {boolean=} selectEnabled
37 */ 32 */
38 WebInspector.ElementsTreeOutline = function(domModel, omitRootDOMNode, selectEna bled) 33 WebInspector.ElementsTreeOutline = class extends TreeOutline {
39 { 34 /**
40 TreeOutline.call(this); 35 * @param {!WebInspector.DOMModel} domModel
36 * @param {boolean=} omitRootDOMNode
37 * @param {boolean=} selectEnabled
38 */
39 constructor(domModel, omitRootDOMNode, selectEnabled) {
40 super();
41 41
42 this._domModel = domModel; 42 this._domModel = domModel;
43 this._treeElementSymbol = Symbol("treeElement"); 43 this._treeElementSymbol = Symbol('treeElement');
44 var shadowContainer = createElement("div"); 44 var shadowContainer = createElement('div');
45 this._shadowRoot = WebInspector.createShadowRootWithCoreStyles(shadowContain er, "elements/elementsTreeOutline.css"); 45 this._shadowRoot = WebInspector.createShadowRootWithCoreStyles(shadowContain er, 'elements/elementsTreeOutline.css');
46 var outlineDisclosureElement = this._shadowRoot.createChild("div", "elements -disclosure"); 46 var outlineDisclosureElement = this._shadowRoot.createChild('div', 'elements -disclosure');
47 47
48 this._element = this.element; 48 this._element = this.element;
49 this._element.classList.add("elements-tree-outline", "source-code"); 49 this._element.classList.add('elements-tree-outline', 'source-code');
50 this._element.addEventListener("mousedown", this._onmousedown.bind(this), fa lse); 50 this._element.addEventListener('mousedown', this._onmousedown.bind(this), fa lse);
51 this._element.addEventListener("mousemove", this._onmousemove.bind(this), fa lse); 51 this._element.addEventListener('mousemove', this._onmousemove.bind(this), fa lse);
52 this._element.addEventListener("mouseleave", this._onmouseleave.bind(this), false); 52 this._element.addEventListener('mouseleave', this._onmouseleave.bind(this), false);
53 this._element.addEventListener("dragstart", this._ondragstart.bind(this), fa lse); 53 this._element.addEventListener('dragstart', this._ondragstart.bind(this), fa lse);
54 this._element.addEventListener("dragover", this._ondragover.bind(this), fals e); 54 this._element.addEventListener('dragover', this._ondragover.bind(this), fals e);
55 this._element.addEventListener("dragleave", this._ondragleave.bind(this), fa lse); 55 this._element.addEventListener('dragleave', this._ondragleave.bind(this), fa lse);
56 this._element.addEventListener("drop", this._ondrop.bind(this), false); 56 this._element.addEventListener('drop', this._ondrop.bind(this), false);
57 this._element.addEventListener("dragend", this._ondragend.bind(this), false) ; 57 this._element.addEventListener('dragend', this._ondragend.bind(this), false) ;
58 this._element.addEventListener("contextmenu", this._contextMenuEventFired.bi nd(this), false); 58 this._element.addEventListener('contextmenu', this._contextMenuEventFired.bi nd(this), false);
59 this._element.addEventListener("clipboard-beforecopy", this._onBeforeCopy.bi nd(this), false); 59 this._element.addEventListener('clipboard-beforecopy', this._onBeforeCopy.bi nd(this), false);
60 this._element.addEventListener("clipboard-copy", this._onCopyOrCut.bind(this , false), false); 60 this._element.addEventListener('clipboard-copy', this._onCopyOrCut.bind(this , false), false);
61 this._element.addEventListener("clipboard-cut", this._onCopyOrCut.bind(this, true), false); 61 this._element.addEventListener('clipboard-cut', this._onCopyOrCut.bind(this, true), false);
62 this._element.addEventListener("clipboard-paste", this._onPaste.bind(this), false); 62 this._element.addEventListener('clipboard-paste', this._onPaste.bind(this), false);
63 63
64 outlineDisclosureElement.appendChild(this._element); 64 outlineDisclosureElement.appendChild(this._element);
65 this.element = shadowContainer; 65 this.element = shadowContainer;
66 66
67 this._includeRootDOMNode = !omitRootDOMNode; 67 this._includeRootDOMNode = !omitRootDOMNode;
68 this._selectEnabled = selectEnabled; 68 this._selectEnabled = selectEnabled;
69 /** @type {?WebInspector.DOMNode} */ 69 /** @type {?WebInspector.DOMNode} */
70 this._rootDOMNode = null; 70 this._rootDOMNode = null;
71 /** @type {?WebInspector.DOMNode} */ 71 /** @type {?WebInspector.DOMNode} */
72 this._selectedDOMNode = null; 72 this._selectedDOMNode = null;
73 73
74 this._visible = false; 74 this._visible = false;
75 75
76 this._popoverHelper = new WebInspector.PopoverHelper(this._element); 76 this._popoverHelper = new WebInspector.PopoverHelper(this._element);
77 this._popoverHelper.initializeCallbacks(this._getPopoverAnchor.bind(this), t his._showPopover.bind(this)); 77 this._popoverHelper.initializeCallbacks(this._getPopoverAnchor.bind(this), t his._showPopover.bind(this));
78 this._popoverHelper.setTimeout(0, 100); 78 this._popoverHelper.setTimeout(0, 100);
79 79
80 /** @type {!Map<!WebInspector.DOMNode, !WebInspector.ElementsTreeOutline.Upd ateRecord>} */ 80 /** @type {!Map<!WebInspector.DOMNode, !WebInspector.ElementsTreeOutline.Upd ateRecord>} */
81 this._updateRecords = new Map(); 81 this._updateRecords = new Map();
82 /** @type {!Set<!WebInspector.ElementsTreeElement>} */ 82 /** @type {!Set<!WebInspector.ElementsTreeElement>} */
83 this._treeElementsBeingUpdated = new Set(); 83 this._treeElementsBeingUpdated = new Set();
84 84
85 this._domModel.addEventListener(WebInspector.DOMModel.Events.MarkersChanged, this._markersChanged, this); 85 this._domModel.addEventListener(WebInspector.DOMModel.Events.MarkersChanged, this._markersChanged, this);
86 this._showHTMLCommentsSetting = WebInspector.moduleSetting("showHTMLComments "); 86 this._showHTMLCommentsSetting = WebInspector.moduleSetting('showHTMLComments ');
87 this._showHTMLCommentsSetting.addChangeListener(this._onShowHTMLCommentsChan ge.bind(this)); 87 this._showHTMLCommentsSetting.addChangeListener(this._onShowHTMLCommentsChan ge.bind(this));
88 }
89
90 /**
91 * @param {!WebInspector.DOMModel} domModel
92 * @return {?WebInspector.ElementsTreeOutline}
93 */
94 static forDOMModel(domModel) {
95 return domModel[WebInspector.ElementsTreeOutline._treeOutlineSymbol] || null ;
96 }
97
98 _onShowHTMLCommentsChange() {
99 var selectedNode = this.selectedDOMNode();
100 if (selectedNode && selectedNode.nodeType() === Node.COMMENT_NODE && !this._ showHTMLCommentsSetting.get())
101 this.selectDOMNode(selectedNode.parentNode);
102 this.update();
103 }
104
105 /**
106 * @return {symbol}
107 */
108 treeElementSymbol() {
109 return this._treeElementSymbol;
110 }
111
112 /**
113 * @override
114 */
115 focus() {
116 this._element.focus();
117 }
118
119 /**
120 * @param {boolean} wrap
121 */
122 setWordWrap(wrap) {
123 this._element.classList.toggle('elements-tree-nowrap', !wrap);
124 }
125
126 /**
127 * @return {!WebInspector.DOMModel}
128 */
129 domModel() {
130 return this._domModel;
131 }
132
133 /**
134 * @param {?WebInspector.InplaceEditor.Controller} multilineEditing
135 */
136 setMultilineEditing(multilineEditing) {
137 this._multilineEditing = multilineEditing;
138 }
139
140 /**
141 * @return {number}
142 */
143 visibleWidth() {
144 return this._visibleWidth;
145 }
146
147 /**
148 * @param {number} width
149 */
150 setVisibleWidth(width) {
151 this._visibleWidth = width;
152 if (this._multilineEditing)
153 this._multilineEditing.setWidth(this._visibleWidth);
154 }
155
156 /**
157 * @param {?WebInspector.ElementsTreeOutline.ClipboardData} data
158 */
159 _setClipboardData(data) {
160 if (this._clipboardNodeData) {
161 var treeElement = this.findTreeElement(this._clipboardNodeData.node);
162 if (treeElement)
163 treeElement.setInClipboard(false);
164 delete this._clipboardNodeData;
165 }
166
167 if (data) {
168 var treeElement = this.findTreeElement(data.node);
169 if (treeElement)
170 treeElement.setInClipboard(true);
171 this._clipboardNodeData = data;
172 }
173 }
174
175 /**
176 * @param {!WebInspector.DOMNode} removedNode
177 */
178 resetClipboardIfNeeded(removedNode) {
179 if (this._clipboardNodeData && this._clipboardNodeData.node === removedNode)
180 this._setClipboardData(null);
181 }
182
183 /**
184 * @param {!Event} event
185 */
186 _onBeforeCopy(event) {
187 event.handled = true;
188 }
189
190 /**
191 * @param {boolean} isCut
192 * @param {!Event} event
193 */
194 _onCopyOrCut(isCut, event) {
195 this._setClipboardData(null);
196 var originalEvent = event['original'];
197
198 // Don't prevent the normal copy if the user has a selection.
199 if (!originalEvent.target.isComponentSelectionCollapsed())
200 return;
201
202 // Do not interfere with text editing.
203 if (WebInspector.isEditing())
204 return;
205
206 var targetNode = this.selectedDOMNode();
207 if (!targetNode)
208 return;
209
210 originalEvent.clipboardData.clearData();
211 event.handled = true;
212
213 this.performCopyOrCut(isCut, targetNode);
214 }
215
216 /**
217 * @param {boolean} isCut
218 * @param {?WebInspector.DOMNode} node
219 */
220 performCopyOrCut(isCut, node) {
221 if (isCut && (node.isShadowRoot() || node.ancestorUserAgentShadowRoot()))
222 return;
223
224 node.copyNode();
225 this._setClipboardData({node: node, isCut: isCut});
226 }
227
228 /**
229 * @param {!WebInspector.DOMNode} targetNode
230 * @return {boolean}
231 */
232 canPaste(targetNode) {
233 if (targetNode.isShadowRoot() || targetNode.ancestorUserAgentShadowRoot())
234 return false;
235
236 if (!this._clipboardNodeData)
237 return false;
238
239 var node = this._clipboardNodeData.node;
240 if (this._clipboardNodeData.isCut && (node === targetNode || node.isAncestor (targetNode)))
241 return false;
242
243 if (targetNode.target() !== node.target())
244 return false;
245 return true;
246 }
247
248 /**
249 * @param {!WebInspector.DOMNode} targetNode
250 */
251 pasteNode(targetNode) {
252 if (this.canPaste(targetNode))
253 this._performPaste(targetNode);
254 }
255
256 /**
257 * @param {!Event} event
258 */
259 _onPaste(event) {
260 // Do not interfere with text editing.
261 if (WebInspector.isEditing())
262 return;
263
264 var targetNode = this.selectedDOMNode();
265 if (!targetNode || !this.canPaste(targetNode))
266 return;
267
268 event.handled = true;
269 this._performPaste(targetNode);
270 }
271
272 /**
273 * @param {!WebInspector.DOMNode} targetNode
274 */
275 _performPaste(targetNode) {
276 if (this._clipboardNodeData.isCut) {
277 this._clipboardNodeData.node.moveTo(targetNode, null, expandCallback.bind( this));
278 this._setClipboardData(null);
279 } else {
280 this._clipboardNodeData.node.copyTo(targetNode, null, expandCallback.bind( this));
281 }
282
283 /**
284 * @param {?Protocol.Error} error
285 * @param {!DOMAgent.NodeId} nodeId
286 * @this {WebInspector.ElementsTreeOutline}
287 */
288 function expandCallback(error, nodeId) {
289 if (error)
290 return;
291 var pastedNode = this._domModel.nodeForId(nodeId);
292 if (!pastedNode)
293 return;
294 this.selectDOMNode(pastedNode);
295 }
296 }
297
298 /**
299 * @param {boolean} visible
300 */
301 setVisible(visible) {
302 this._visible = visible;
303 if (!this._visible) {
304 this._popoverHelper.hidePopover();
305 if (this._multilineEditing)
306 this._multilineEditing.cancel();
307 return;
308 }
309
310 this.runPendingUpdates();
311 if (this._selectedDOMNode)
312 this._revealAndSelectNode(this._selectedDOMNode, false);
313 }
314
315 get rootDOMNode() {
316 return this._rootDOMNode;
317 }
318
319 set rootDOMNode(x) {
320 if (this._rootDOMNode === x)
321 return;
322
323 this._rootDOMNode = x;
324
325 this._isXMLMimeType = x && x.isXMLNode();
326
327 this.update();
328 }
329
330 get isXMLMimeType() {
331 return this._isXMLMimeType;
332 }
333
334 /**
335 * @return {?WebInspector.DOMNode}
336 */
337 selectedDOMNode() {
338 return this._selectedDOMNode;
339 }
340
341 /**
342 * @param {?WebInspector.DOMNode} node
343 * @param {boolean=} focus
344 */
345 selectDOMNode(node, focus) {
346 if (this._selectedDOMNode === node) {
347 this._revealAndSelectNode(node, !focus);
348 return;
349 }
350
351 this._selectedDOMNode = node;
352 this._revealAndSelectNode(node, !focus);
353
354 // The _revealAndSelectNode() method might find a different element if there is inlined text,
355 // and the select() call would change the selectedDOMNode and reenter this s etter. So to
356 // avoid calling _selectedNodeChanged() twice, first check if _selectedDOMNo de is the same
357 // node as the one passed in.
358 if (this._selectedDOMNode === node)
359 this._selectedNodeChanged(!!focus);
360 }
361
362 /**
363 * @return {boolean}
364 */
365 editing() {
366 var node = this.selectedDOMNode();
367 if (!node)
368 return false;
369 var treeElement = this.findTreeElement(node);
370 if (!treeElement)
371 return false;
372 return treeElement.isEditing() || false;
373 }
374
375 update() {
376 var selectedNode = this.selectedDOMNode();
377 this.removeChildren();
378 if (!this.rootDOMNode)
379 return;
380
381 if (this._includeRootDOMNode) {
382 var treeElement = this._createElementTreeElement(this.rootDOMNode);
383 this.appendChild(treeElement);
384 } else {
385 // FIXME: this could use findTreeElement to reuse a tree element if it alr eady exists
386 var children = this._visibleChildren(this.rootDOMNode);
387 for (var child of children) {
388 var treeElement = this._createElementTreeElement(child);
389 this.appendChild(treeElement);
390 }
391 }
392
393 if (selectedNode)
394 this._revealAndSelectNode(selectedNode, true);
395 }
396
397 /**
398 * @param {boolean} focus
399 */
400 _selectedNodeChanged(focus) {
401 this.dispatchEventToListeners(
402 WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, {node: this ._selectedDOMNode, focus: focus});
403 }
404
405 /**
406 * @param {!Array.<!WebInspector.DOMNode>} nodes
407 */
408 _fireElementsTreeUpdated(nodes) {
409 this.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.Elemen tsTreeUpdated, nodes);
410 }
411
412 /**
413 * @param {!WebInspector.DOMNode} node
414 * @return {?WebInspector.ElementsTreeElement}
415 */
416 findTreeElement(node) {
417 var treeElement = this._lookUpTreeElement(node);
418 if (!treeElement && node.nodeType() === Node.TEXT_NODE) {
419 // The text node might have been inlined if it was short, so try to find t he parent element.
420 treeElement = this._lookUpTreeElement(node.parentNode);
421 }
422
423 return /** @type {?WebInspector.ElementsTreeElement} */ (treeElement);
424 }
425
426 /**
427 * @param {?WebInspector.DOMNode} node
428 * @return {?TreeElement}
429 */
430 _lookUpTreeElement(node) {
431 if (!node)
432 return null;
433
434 var cachedElement = node[this._treeElementSymbol];
435 if (cachedElement)
436 return cachedElement;
437
438 // Walk up the parent pointers from the desired node
439 var ancestors = [];
440 for (var currentNode = node.parentNode; currentNode; currentNode = currentNo de.parentNode) {
441 ancestors.push(currentNode);
442 if (currentNode[this._treeElementSymbol]) // stop climbing as soon as we hit
443 break;
444 }
445
446 if (!currentNode)
447 return null;
448
449 // Walk down to populate each ancestor's children, to fill in the tree and t he cache.
450 for (var i = ancestors.length - 1; i >= 0; --i) {
451 var treeElement = ancestors[i][this._treeElementSymbol];
452 if (treeElement)
453 treeElement.onpopulate(); // fill the cache with the children of treeEl ement
454 }
455
456 return node[this._treeElementSymbol];
457 }
458
459 /**
460 * @param {!WebInspector.DOMNode} node
461 * @return {?WebInspector.ElementsTreeElement}
462 */
463 createTreeElementFor(node) {
464 var treeElement = this.findTreeElement(node);
465 if (treeElement)
466 return treeElement;
467 if (!node.parentNode)
468 return null;
469
470 treeElement = this.createTreeElementFor(node.parentNode);
471 return treeElement ? this._showChild(treeElement, node) : null;
472 }
473
474 set suppressRevealAndSelect(x) {
475 if (this._suppressRevealAndSelect === x)
476 return;
477 this._suppressRevealAndSelect = x;
478 }
479
480 /**
481 * @param {?WebInspector.DOMNode} node
482 * @param {boolean} omitFocus
483 */
484 _revealAndSelectNode(node, omitFocus) {
485 if (this._suppressRevealAndSelect)
486 return;
487
488 if (!this._includeRootDOMNode && node === this.rootDOMNode && this.rootDOMNo de)
489 node = this.rootDOMNode.firstChild;
490 if (!node)
491 return;
492 var treeElement = this.createTreeElementFor(node);
493 if (!treeElement)
494 return;
495
496 treeElement.revealAndSelect(omitFocus);
497 }
498
499 /**
500 * @return {?TreeElement}
501 */
502 _treeElementFromEvent(event) {
503 var scrollContainer = this.element.parentElement;
504
505 // We choose this X coordinate based on the knowledge that our list
506 // items extend at least to the right edge of the outer <ol> container.
507 // In the no-word-wrap mode the outer <ol> may be wider than the tree contai ner
508 // (and partially hidden), in which case we are left to use only its right b oundary.
509 var x = scrollContainer.totalOffsetLeft() + scrollContainer.offsetWidth - 36 ;
510
511 var y = event.pageY;
512
513 // Our list items have 1-pixel cracks between them vertically. We avoid
514 // the cracks by checking slightly above and slightly below the mouse
515 // and seeing if we hit the same element each time.
516 var elementUnderMouse = this.treeElementFromPoint(x, y);
517 var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
518 var element;
519 if (elementUnderMouse === elementAboveMouse)
520 element = elementUnderMouse;
521 else
522 element = this.treeElementFromPoint(x, y + 2);
523
524 return element;
525 }
526
527 /**
528 * @param {!Element} element
529 * @param {!Event} event
530 * @return {!Element|!AnchorBox|undefined}
531 */
532 _getPopoverAnchor(element, event) {
533 var anchor = element.enclosingNodeOrSelfWithClass('webkit-html-resource-link ');
534 if (!anchor || !anchor.href)
535 return;
536
537 return anchor;
538 }
539
540 /**
541 * @param {!WebInspector.DOMNode} node
542 * @param {function()} callback
543 */
544 _loadDimensionsForNode(node, callback) {
545 if (!node.nodeName() || node.nodeName().toLowerCase() !== 'img') {
546 callback();
547 return;
548 }
549
550 node.resolveToObject('', resolvedNode);
551
552 function resolvedNode(object) {
553 if (!object) {
554 callback();
555 return;
556 }
557
558 object.callFunctionJSON(features, undefined, callback);
559 object.release();
560
561 /**
562 * @return {!{offsetWidth: number, offsetHeight: number, naturalWidth: num ber, naturalHeight: number, currentSrc: (string|undefined)}}
563 * @suppressReceiverCheck
564 * @this {!Element}
565 */
566 function features() {
567 return {
568 offsetWidth: this.offsetWidth,
569 offsetHeight: this.offsetHeight,
570 naturalWidth: this.naturalWidth,
571 naturalHeight: this.naturalHeight,
572 currentSrc: this.currentSrc
573 };
574 }
575 }
576 }
577
578 /**
579 * @param {!Element} anchor
580 * @param {!WebInspector.Popover} popover
581 */
582 _showPopover(anchor, popover) {
583 var listItem = anchor.enclosingNodeOrSelfWithNodeName('li');
584 var node = /** @type {!WebInspector.ElementsTreeElement} */ (listItem.treeEl ement).node();
585 this._loadDimensionsForNode(
586 node, WebInspector.DOMPresentationUtils.buildImagePreviewContents.bind(
587 WebInspector.DOMPresentationUtils, node.target(), anchor.href, true, showPopover));
588
589 /**
590 * @param {!Element=} contents
591 */
592 function showPopover(contents) {
593 if (!contents)
594 return;
595 popover.setCanShrink(false);
596 popover.showForAnchor(contents, anchor);
597 }
598 }
599
600 _onmousedown(event) {
601 var element = this._treeElementFromEvent(event);
602
603 if (!element || element.isEventWithinDisclosureTriangle(event))
604 return;
605
606 element.select();
607 }
608
609 /**
610 * @param {?TreeElement} treeElement
611 */
612 setHoverEffect(treeElement) {
613 if (this._previousHoveredElement === treeElement)
614 return;
615
616 if (this._previousHoveredElement) {
617 this._previousHoveredElement.hovered = false;
618 delete this._previousHoveredElement;
619 }
620
621 if (treeElement) {
622 treeElement.hovered = true;
623 this._previousHoveredElement = treeElement;
624 }
625 }
626
627 _onmousemove(event) {
628 var element = this._treeElementFromEvent(event);
629 if (element && this._previousHoveredElement === element)
630 return;
631
632 this.setHoverEffect(element);
633
634 if (element instanceof WebInspector.ElementsTreeElement) {
635 this._domModel.highlightDOMNodeWithConfig(
636 element.node().id, {mode: 'all', showInfo: !WebInspector.KeyboardShort cut.eventHasCtrlOrMeta(event)});
637 return;
638 }
639
640 if (element instanceof WebInspector.ElementsTreeOutline.ShortcutTreeElement)
641 this._domModel.highlightDOMNodeWithConfig(
642 undefined, {mode: 'all', showInfo: !WebInspector.KeyboardShortcut.even tHasCtrlOrMeta(event)},
643 element.backendNodeId());
644 }
645
646 _onmouseleave(event) {
647 this.setHoverEffect(null);
648 WebInspector.DOMModel.hideDOMNodeHighlight();
649 }
650
651 _ondragstart(event) {
652 if (!event.target.isComponentSelectionCollapsed())
653 return false;
654 if (event.target.nodeName === 'A')
655 return false;
656
657 var treeElement = this._treeElementFromEvent(event);
658 if (!this._isValidDragSourceOrTarget(treeElement))
659 return false;
660
661 if (treeElement.node().nodeName() === 'BODY' || treeElement.node().nodeName( ) === 'HEAD')
662 return false;
663
664 event.dataTransfer.setData('text/plain', treeElement.listItemElement.textCon tent.replace(/\u200b/g, ''));
665 event.dataTransfer.effectAllowed = 'copyMove';
666 this._treeElementBeingDragged = treeElement;
667
668 WebInspector.DOMModel.hideDOMNodeHighlight();
669
670 return true;
671 }
672
673 _ondragover(event) {
674 if (!this._treeElementBeingDragged)
675 return false;
676
677 var treeElement = this._treeElementFromEvent(event);
678 if (!this._isValidDragSourceOrTarget(treeElement))
679 return false;
680
681 var node = treeElement.node();
682 while (node) {
683 if (node === this._treeElementBeingDragged._node)
684 return false;
685 node = node.parentNode;
686 }
687
688 treeElement.listItemElement.classList.add('elements-drag-over');
689 this._dragOverTreeElement = treeElement;
690 event.preventDefault();
691 event.dataTransfer.dropEffect = 'move';
692 return false;
693 }
694
695 _ondragleave(event) {
696 this._clearDragOverTreeElementMarker();
697 event.preventDefault();
698 return false;
699 }
700
701 /**
702 * @param {?TreeElement} treeElement
703 * @return {boolean}
704 */
705 _isValidDragSourceOrTarget(treeElement) {
706 if (!treeElement)
707 return false;
708
709 if (!(treeElement instanceof WebInspector.ElementsTreeElement))
710 return false;
711 var elementsTreeElement = /** @type {!WebInspector.ElementsTreeElement} */ ( treeElement);
712
713 var node = elementsTreeElement.node();
714 if (!node.parentNode || node.parentNode.nodeType() !== Node.ELEMENT_NODE)
715 return false;
716
717 return true;
718 }
719
720 _ondrop(event) {
721 event.preventDefault();
722 var treeElement = this._treeElementFromEvent(event);
723 if (treeElement)
724 this._doMove(treeElement);
725 }
726
727 /**
728 * @param {!TreeElement} treeElement
729 */
730 _doMove(treeElement) {
731 if (!this._treeElementBeingDragged)
732 return;
733
734 var parentNode;
735 var anchorNode;
736
737 if (treeElement.isClosingTag()) {
738 // Drop onto closing tag -> insert as last child.
739 parentNode = treeElement.node();
740 } else {
741 var dragTargetNode = treeElement.node();
742 parentNode = dragTargetNode.parentNode;
743 anchorNode = dragTargetNode;
744 }
745
746 var wasExpanded = this._treeElementBeingDragged.expanded;
747 this._treeElementBeingDragged._node.moveTo(
748 parentNode, anchorNode, this.selectNodeAfterEdit.bind(this, wasExpanded) );
749
750 delete this._treeElementBeingDragged;
751 }
752
753 _ondragend(event) {
754 event.preventDefault();
755 this._clearDragOverTreeElementMarker();
756 delete this._treeElementBeingDragged;
757 }
758
759 _clearDragOverTreeElementMarker() {
760 if (this._dragOverTreeElement) {
761 this._dragOverTreeElement.listItemElement.classList.remove('elements-drag- over');
762 delete this._dragOverTreeElement;
763 }
764 }
765
766 _contextMenuEventFired(event) {
767 var treeElement = this._treeElementFromEvent(event);
768 if (treeElement instanceof WebInspector.ElementsTreeElement)
769 this.showContextMenu(treeElement, event);
770 }
771
772 /**
773 * @param {!WebInspector.ElementsTreeElement} treeElement
774 * @param {!Event} event
775 */
776 showContextMenu(treeElement, event) {
777 if (WebInspector.isEditing())
778 return;
779
780 var contextMenu = new WebInspector.ContextMenu(event);
781 var isPseudoElement = !!treeElement.node().pseudoType();
782 var isTag = treeElement.node().nodeType() === Node.ELEMENT_NODE && !isPseudo Element;
783 var textNode = event.target.enclosingNodeOrSelfWithClass('webkit-html-text-n ode');
784 if (textNode && textNode.classList.contains('bogus'))
785 textNode = null;
786 var commentNode = event.target.enclosingNodeOrSelfWithClass('webkit-html-com ment');
787 contextMenu.appendApplicableItems(event.target);
788 if (textNode) {
789 contextMenu.appendSeparator();
790 treeElement.populateTextContextMenu(contextMenu, textNode);
791 } else if (isTag) {
792 contextMenu.appendSeparator();
793 treeElement.populateTagContextMenu(contextMenu, event);
794 } else if (commentNode) {
795 contextMenu.appendSeparator();
796 treeElement.populateNodeContextMenu(contextMenu);
797 } else if (isPseudoElement) {
798 treeElement.populateScrollIntoView(contextMenu);
799 }
800
801 contextMenu.appendApplicableItems(treeElement.node());
802 contextMenu.show();
803 }
804
805 runPendingUpdates() {
806 this._updateModifiedNodes();
807 }
808
809 handleShortcut(event) {
810 var node = this.selectedDOMNode();
811 if (!node)
812 return;
813 var treeElement = node[this._treeElementSymbol];
814 if (!treeElement)
815 return;
816
817 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && node.parentNo de) {
818 if (event.key === 'ArrowUp' && node.previousSibling) {
819 node.moveTo(node.parentNode, node.previousSibling, this.selectNodeAfterE dit.bind(this, treeElement.expanded));
820 event.handled = true;
821 return;
822 }
823 if (event.key === 'ArrowDown' && node.nextSibling) {
824 node.moveTo(
825 node.parentNode, node.nextSibling.nextSibling, this.selectNodeAfterE dit.bind(this, treeElement.expanded));
826 event.handled = true;
827 return;
828 }
829 }
830 }
831
832 /**
833 * @param {!WebInspector.DOMNode} node
834 * @param {boolean=} startEditing
835 * @param {function()=} callback
836 */
837 toggleEditAsHTML(node, startEditing, callback) {
838 var treeElement = node[this._treeElementSymbol];
839 if (!treeElement || !treeElement.hasEditableNode())
840 return;
841
842 if (node.pseudoType())
843 return;
844
845 var parentNode = node.parentNode;
846 var index = node.index;
847 var wasExpanded = treeElement.expanded;
848
849 treeElement.toggleEditAsHTML(editingFinished.bind(this), startEditing);
850
851 /**
852 * @this {WebInspector.ElementsTreeOutline}
853 * @param {boolean} success
854 */
855 function editingFinished(success) {
856 if (callback)
857 callback();
858 if (!success)
859 return;
860
861 // Select it and expand if necessary. We force tree update so that it proc esses dom events and is up to date.
862 this.runPendingUpdates();
863
864 var newNode = parentNode ? parentNode.children()[index] || parentNode : nu ll;
865 if (!newNode)
866 return;
867
868 this.selectDOMNode(newNode, true);
869
870 if (wasExpanded) {
871 var newTreeItem = this.findTreeElement(newNode);
872 if (newTreeItem)
873 newTreeItem.expand();
874 }
875 }
876 }
877
878 /**
879 * @param {boolean} wasExpanded
880 * @param {?Protocol.Error} error
881 * @param {!DOMAgent.NodeId=} nodeId
882 * @return {?WebInspector.ElementsTreeElement} nodeId
883 */
884 selectNodeAfterEdit(wasExpanded, error, nodeId) {
885 if (error)
886 return null;
887
888 // Select it and expand if necessary. We force tree update so that it proces ses dom events and is up to date.
889 this.runPendingUpdates();
890
891 var newNode = nodeId ? this._domModel.nodeForId(nodeId) : null;
892 if (!newNode)
893 return null;
894
895 this.selectDOMNode(newNode, true);
896
897 var newTreeItem = this.findTreeElement(newNode);
898 if (wasExpanded) {
899 if (newTreeItem)
900 newTreeItem.expand();
901 }
902 return newTreeItem;
903 }
904
905 /**
906 * Runs a script on the node's remote object that toggles a class name on
907 * the node and injects a stylesheet into the head of the node's document
908 * containing a rule to set "visibility: hidden" on the class and all it's
909 * ancestors.
910 *
911 * @param {!WebInspector.DOMNode} node
912 * @param {function(?WebInspector.RemoteObject, boolean=)=} userCallback
913 */
914 toggleHideElement(node, userCallback) {
915 var pseudoType = node.pseudoType();
916 var effectiveNode = pseudoType ? node.parentNode : node;
917 if (!effectiveNode)
918 return;
919
920 var hidden = node.marker('hidden-marker');
921
922 function resolvedNode(object) {
923 if (!object)
924 return;
925
926 /**
927 * @param {?string} pseudoType
928 * @param {boolean} hidden
929 * @suppressGlobalPropertiesCheck
930 * @suppressReceiverCheck
931 * @this {!Element}
932 */
933 function toggleClassAndInjectStyleRule(pseudoType, hidden) {
934 const classNamePrefix = '__web-inspector-hide';
935 const classNameSuffix = '-shortcut__';
936 const styleTagId = '__web-inspector-hide-shortcut-style__';
937 var selectors = [];
938 selectors.push('.__web-inspector-hide-shortcut__');
939 selectors.push('.__web-inspector-hide-shortcut__ *');
940 selectors.push('.__web-inspector-hidebefore-shortcut__::before');
941 selectors.push('.__web-inspector-hideafter-shortcut__::after');
942 var selector = selectors.join(', ');
943 var ruleBody = ' visibility: hidden !important;';
944 var rule = '\n' + selector + '\n{\n' + ruleBody + '\n}\n';
945 var className = classNamePrefix + (pseudoType || '') + classNameSuffix;
946 this.classList.toggle(className, hidden);
947
948 var localRoot = this;
949 while (localRoot.parentNode)
950 localRoot = localRoot.parentNode;
951 if (localRoot.nodeType === Node.DOCUMENT_NODE)
952 localRoot = document.head;
953
954 var style = localRoot.querySelector('style#' + styleTagId);
955 if (style)
956 return;
957
958 style = document.createElement('style');
959 style.id = styleTagId;
960 style.type = 'text/css';
961 style.textContent = rule;
962
963 localRoot.appendChild(style);
964 }
965
966 object.callFunction(toggleClassAndInjectStyleRule, [{value: pseudoType}, { value: !hidden}], userCallback);
967 object.release();
968 node.setMarker('hidden-marker', hidden ? null : true);
969 }
970
971 effectiveNode.resolveToObject('', resolvedNode);
972 }
973
974 /**
975 * @param {!WebInspector.DOMNode} node
976 * @return {boolean}
977 */
978 isToggledToHidden(node) {
979 return !!node.marker('hidden-marker');
980 }
981
982 _reset() {
983 this.rootDOMNode = null;
984 this.selectDOMNode(null, false);
985 this._popoverHelper.hidePopover();
986 delete this._clipboardNodeData;
987 WebInspector.DOMModel.hideDOMNodeHighlight();
988 this._updateRecords.clear();
989 }
990
991 wireToDOMModel() {
992 this._domModel[WebInspector.ElementsTreeOutline._treeOutlineSymbol] = this;
993 this._domModel.addEventListener(WebInspector.DOMModel.Events.NodeInserted, t his._nodeInserted, this);
994 this._domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved, th is._nodeRemoved, this);
995 this._domModel.addEventListener(WebInspector.DOMModel.Events.AttrModified, t his._attributeModified, this);
996 this._domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved, th is._attributeRemoved, this);
997 this._domModel.addEventListener(
998 WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataM odified, this);
999 this._domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated , this._documentUpdated, this);
1000 this._domModel.addEventListener(
1001 WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCount Updated, this);
1002 this._domModel.addEventListener(
1003 WebInspector.DOMModel.Events.DistributedNodesChanged, this._distributedN odesChanged, this);
1004 }
1005
1006 unwireFromDOMModel() {
1007 this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeInserted , this._nodeInserted, this);
1008 this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
1009 this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModified , this._attributeModified, this);
1010 this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemoved, this._attributeRemoved, this);
1011 this._domModel.removeEventListener(
1012 WebInspector.DOMModel.Events.CharacterDataModified, this._characterDataM odified, this);
1013 this._domModel.removeEventListener(WebInspector.DOMModel.Events.DocumentUpda ted, this._documentUpdated, this);
1014 this._domModel.removeEventListener(
1015 WebInspector.DOMModel.Events.ChildNodeCountUpdated, this._childNodeCount Updated, this);
1016 this._domModel.removeEventListener(
1017 WebInspector.DOMModel.Events.DistributedNodesChanged, this._distributedN odesChanged, this);
1018 delete this._domModel[WebInspector.ElementsTreeOutline._treeOutlineSymbol];
1019 }
1020
1021 /**
1022 * @param {!WebInspector.DOMNode} node
1023 * @return {!WebInspector.ElementsTreeOutline.UpdateRecord}
1024 */
1025 _addUpdateRecord(node) {
1026 var record = this._updateRecords.get(node);
1027 if (!record) {
1028 record = new WebInspector.ElementsTreeOutline.UpdateRecord();
1029 this._updateRecords.set(node, record);
1030 }
1031 return record;
1032 }
1033
1034 /**
1035 * @param {!WebInspector.DOMNode} node
1036 * @return {?WebInspector.ElementsTreeOutline.UpdateRecord}
1037 */
1038 _updateRecordForHighlight(node) {
1039 if (!this._visible)
1040 return null;
1041 return this._updateRecords.get(node) || null;
1042 }
1043
1044 /**
1045 * @param {!WebInspector.Event} event
1046 */
1047 _documentUpdated(event) {
1048 var inspectedRootDocument = event.data;
1049
1050 this._reset();
1051
1052 if (!inspectedRootDocument)
1053 return;
1054
1055 this.rootDOMNode = inspectedRootDocument;
1056 }
1057
1058 /**
1059 * @param {!WebInspector.Event} event
1060 */
1061 _attributeModified(event) {
1062 var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
1063 this._addUpdateRecord(node).attributeModified(event.data.name);
1064 this._updateModifiedNodesSoon();
1065 }
1066
1067 /**
1068 * @param {!WebInspector.Event} event
1069 */
1070 _attributeRemoved(event) {
1071 var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
1072 this._addUpdateRecord(node).attributeRemoved(event.data.name);
1073 this._updateModifiedNodesSoon();
1074 }
1075
1076 /**
1077 * @param {!WebInspector.Event} event
1078 */
1079 _characterDataModified(event) {
1080 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1081 this._addUpdateRecord(node).charDataModified();
1082 // Text could be large and force us to render itself as the child in the tre e outline.
1083 if (node.parentNode && node.parentNode.firstChild === node.parentNode.lastCh ild)
1084 this._addUpdateRecord(node.parentNode).childrenModified();
1085 this._updateModifiedNodesSoon();
1086 }
1087
1088 /**
1089 * @param {!WebInspector.Event} event
1090 */
1091 _nodeInserted(event) {
1092 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1093 this._addUpdateRecord(/** @type {!WebInspector.DOMNode} */ (node.parentNode) ).nodeInserted(node);
1094 this._updateModifiedNodesSoon();
1095 }
1096
1097 /**
1098 * @param {!WebInspector.Event} event
1099 */
1100 _nodeRemoved(event) {
1101 var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
1102 var parentNode = /** @type {!WebInspector.DOMNode} */ (event.data.parent);
1103 this.resetClipboardIfNeeded(node);
1104 this._addUpdateRecord(parentNode).nodeRemoved(node);
1105 this._updateModifiedNodesSoon();
1106 }
1107
1108 /**
1109 * @param {!WebInspector.Event} event
1110 */
1111 _childNodeCountUpdated(event) {
1112 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1113 this._addUpdateRecord(node).childrenModified();
1114 this._updateModifiedNodesSoon();
1115 }
1116
1117 /**
1118 * @param {!WebInspector.Event} event
1119 */
1120 _distributedNodesChanged(event) {
1121 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1122 this._addUpdateRecord(node).childrenModified();
1123 this._updateModifiedNodesSoon();
1124 }
1125
1126 _updateModifiedNodesSoon() {
1127 if (!this._updateRecords.size)
1128 return;
1129 if (this._updateModifiedNodesTimeout)
1130 return;
1131 this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes.bind (this), 50);
1132 }
1133
1134 _updateModifiedNodes() {
1135 if (this._updateModifiedNodesTimeout) {
1136 clearTimeout(this._updateModifiedNodesTimeout);
1137 delete this._updateModifiedNodesTimeout;
1138 }
1139
1140 var updatedNodes = this._updateRecords.keysArray();
1141 var hidePanelWhileUpdating = updatedNodes.length > 10;
1142 if (hidePanelWhileUpdating) {
1143 var treeOutlineContainerElement = this.element.parentNode;
1144 var originalScrollTop = treeOutlineContainerElement ? treeOutlineContainer Element.scrollTop : 0;
1145 this._element.classList.add('hidden');
1146 }
1147
1148 if (this._rootDOMNode && this._updateRecords.get(this._rootDOMNode) &&
1149 this._updateRecords.get(this._rootDOMNode).hasChangedChildren()) {
1150 // Document's children have changed, perform total update.
1151 this.update();
1152 } else {
1153 for (var node of this._updateRecords.keys()) {
1154 if (this._updateRecords.get(node).hasChangedChildren())
1155 this._updateModifiedParentNode(node);
1156 else
1157 this._updateModifiedNode(node);
1158 }
1159 }
1160
1161 if (hidePanelWhileUpdating) {
1162 this._element.classList.remove('hidden');
1163 if (originalScrollTop)
1164 treeOutlineContainerElement.scrollTop = originalScrollTop;
1165 }
1166
1167 this._updateRecords.clear();
1168 this._fireElementsTreeUpdated(updatedNodes);
1169 }
1170
1171 _updateModifiedNode(node) {
1172 var treeElement = this.findTreeElement(node);
1173 if (treeElement)
1174 treeElement.updateTitle(this._updateRecordForHighlight(node));
1175 }
1176
1177 _updateModifiedParentNode(node) {
1178 var parentTreeElement = this.findTreeElement(node);
1179 if (parentTreeElement) {
1180 parentTreeElement.setExpandable(this._hasVisibleChildren(node));
1181 parentTreeElement.updateTitle(this._updateRecordForHighlight(node));
1182 if (parentTreeElement.populated)
1183 this._updateChildren(parentTreeElement);
1184 }
1185 }
1186
1187 /**
1188 * @param {!WebInspector.ElementsTreeElement} treeElement
1189 */
1190 populateTreeElement(treeElement) {
1191 if (treeElement.childCount() || !treeElement.isExpandable())
1192 return;
1193
1194 this._updateModifiedParentNode(treeElement.node());
1195 }
1196
1197 /**
1198 * @param {!WebInspector.DOMNode} node
1199 * @param {boolean=} closingTag
1200 * @return {!WebInspector.ElementsTreeElement}
1201 */
1202 _createElementTreeElement(node, closingTag) {
1203 var treeElement = new WebInspector.ElementsTreeElement(node, closingTag);
1204 treeElement.setExpandable(!closingTag && this._hasVisibleChildren(node));
1205 if (node.nodeType() === Node.ELEMENT_NODE && node.parentNode && node.parentN ode.nodeType() === Node.DOCUMENT_NODE &&
1206 !node.parentNode.parentNode)
1207 treeElement.setCollapsible(false);
1208 treeElement.selectable = this._selectEnabled;
1209 return treeElement;
1210 }
1211
1212 /**
1213 * @param {!WebInspector.ElementsTreeElement} treeElement
1214 * @param {!WebInspector.DOMNode} child
1215 * @return {?WebInspector.ElementsTreeElement}
1216 */
1217 _showChild(treeElement, child) {
1218 if (treeElement.isClosingTag())
1219 return null;
1220
1221 var index = this._visibleChildren(treeElement.node()).indexOf(child);
1222 if (index === -1)
1223 return null;
1224
1225 if (index >= treeElement.expandedChildrenLimit())
1226 this.setExpandedChildrenLimit(treeElement, index + 1);
1227 return /** @type {!WebInspector.ElementsTreeElement} */ (treeElement.childAt (index));
1228 }
1229
1230 /**
1231 * @param {!WebInspector.DOMNode} node
1232 * @return {!Array.<!WebInspector.DOMNode>} visibleChildren
1233 */
1234 _visibleChildren(node) {
1235 var visibleChildren = WebInspector.ElementsTreeElement.visibleShadowRoots(no de);
1236
1237 var importedDocument = node.importedDocument();
1238 if (importedDocument)
1239 visibleChildren.push(importedDocument);
1240
1241 var templateContent = node.templateContent();
1242 if (templateContent)
1243 visibleChildren.push(templateContent);
1244
1245 var beforePseudoElement = node.beforePseudoElement();
1246 if (beforePseudoElement)
1247 visibleChildren.push(beforePseudoElement);
1248
1249 if (node.childNodeCount()) {
1250 var children = node.children();
1251 if (!this._showHTMLCommentsSetting.get())
1252 children = children.filter(n => n.nodeType() !== Node.COMMENT_NODE);
1253 visibleChildren = visibleChildren.concat(children);
1254 }
1255
1256 var afterPseudoElement = node.afterPseudoElement();
1257 if (afterPseudoElement)
1258 visibleChildren.push(afterPseudoElement);
1259
1260 return visibleChildren;
1261 }
1262
1263 /**
1264 * @param {!WebInspector.DOMNode} node
1265 * @return {boolean}
1266 */
1267 _hasVisibleChildren(node) {
1268 if (node.importedDocument())
1269 return true;
1270 if (node.templateContent())
1271 return true;
1272 if (WebInspector.ElementsTreeElement.visibleShadowRoots(node).length)
1273 return true;
1274 if (node.hasPseudoElements())
1275 return true;
1276 if (node.isInsertionPoint())
1277 return true;
1278 return !!node.childNodeCount() && !WebInspector.ElementsTreeElement.canShowI nlineText(node);
1279 }
1280
1281 /**
1282 * @param {!WebInspector.ElementsTreeElement} treeElement
1283 */
1284 _createExpandAllButtonTreeElement(treeElement) {
1285 var button = createTextButton('', handleLoadAllChildren.bind(this));
1286 button.value = '';
1287 var expandAllButtonElement = new TreeElement(button);
1288 expandAllButtonElement.selectable = false;
1289 expandAllButtonElement.expandAllButton = true;
1290 expandAllButtonElement.button = button;
1291 return expandAllButtonElement;
1292
1293 /**
1294 * @this {WebInspector.ElementsTreeOutline}
1295 * @param {!Event} event
1296 */
1297 function handleLoadAllChildren(event) {
1298 var visibleChildCount = this._visibleChildren(treeElement.node()).length;
1299 this.setExpandedChildrenLimit(
1300 treeElement, Math.max(
1301 visibleChildCount, treeElement.expandedChildrenLimit( ) +
1302 WebInspector.ElementsTreeElement.InitialChildrenL imit));
1303 event.consume();
1304 }
1305 }
1306
1307 /**
1308 * @param {!WebInspector.ElementsTreeElement} treeElement
1309 * @param {number} expandedChildrenLimit
1310 */
1311 setExpandedChildrenLimit(treeElement, expandedChildrenLimit) {
1312 if (treeElement.expandedChildrenLimit() === expandedChildrenLimit)
1313 return;
1314
1315 treeElement.setExpandedChildrenLimit(expandedChildrenLimit);
1316 if (treeElement.treeOutline && !this._treeElementsBeingUpdated.has(treeEleme nt))
1317 this._updateModifiedParentNode(treeElement.node());
1318 }
1319
1320 /**
1321 * @param {!WebInspector.ElementsTreeElement} treeElement
1322 */
1323 _updateChildren(treeElement) {
1324 if (!treeElement.isExpandable()) {
1325 var selectedTreeElement = treeElement.treeOutline.selectedTreeElement;
1326 if (selectedTreeElement && selectedTreeElement.hasAncestor(treeElement))
1327 treeElement.select(true);
1328 treeElement.removeChildren();
1329 return;
1330 }
1331
1332 console.assert(!treeElement.isClosingTag());
1333
1334 treeElement.node().getChildNodes(childNodesLoaded.bind(this));
1335
1336 /**
1337 * @param {?Array.<!WebInspector.DOMNode>} children
1338 * @this {WebInspector.ElementsTreeOutline}
1339 */
1340 function childNodesLoaded(children) {
1341 // FIXME: sort this out, it should not happen.
1342 if (!children)
1343 return;
1344 this._innerUpdateChildren(treeElement);
1345 }
1346 }
1347
1348 /**
1349 * @param {!WebInspector.ElementsTreeElement} treeElement
1350 * @param {!WebInspector.DOMNode} child
1351 * @param {number} index
1352 * @param {boolean=} closingTag
1353 * @return {!WebInspector.ElementsTreeElement}
1354 */
1355 insertChildElement(treeElement, child, index, closingTag) {
1356 var newElement = this._createElementTreeElement(child, closingTag);
1357 treeElement.insertChild(newElement, index);
1358 return newElement;
1359 }
1360
1361 /**
1362 * @param {!WebInspector.ElementsTreeElement} treeElement
1363 * @param {!WebInspector.ElementsTreeElement} child
1364 * @param {number} targetIndex
1365 */
1366 _moveChild(treeElement, child, targetIndex) {
1367 if (treeElement.indexOfChild(child) === targetIndex)
1368 return;
1369 var wasSelected = child.selected;
1370 if (child.parent)
1371 child.parent.removeChild(child);
1372 treeElement.insertChild(child, targetIndex);
1373 if (wasSelected)
1374 child.select();
1375 }
1376
1377 /**
1378 * @param {!WebInspector.ElementsTreeElement} treeElement
1379 */
1380 _innerUpdateChildren(treeElement) {
1381 if (this._treeElementsBeingUpdated.has(treeElement))
1382 return;
1383
1384 this._treeElementsBeingUpdated.add(treeElement);
1385
1386 var node = treeElement.node();
1387 var visibleChildren = this._visibleChildren(node);
1388 var visibleChildrenSet = new Set(visibleChildren);
1389
1390 // Remove any tree elements that no longer have this node as their parent an d save
1391 // all existing elements that could be reused. This also removes closing tag element.
1392 var existingTreeElements = new Map();
1393 for (var i = treeElement.childCount() - 1; i >= 0; --i) {
1394 var existingTreeElement = treeElement.childAt(i);
1395 if (!(existingTreeElement instanceof WebInspector.ElementsTreeElement)) {
1396 // Remove expand all button and shadow host toolbar.
1397 treeElement.removeChildAtIndex(i);
1398 continue;
1399 }
1400 var elementsTreeElement = /** @type {!WebInspector.ElementsTreeElement} */ (existingTreeElement);
1401 var existingNode = elementsTreeElement.node();
1402
1403 if (visibleChildrenSet.has(existingNode)) {
1404 existingTreeElements.set(existingNode, existingTreeElement);
1405 continue;
1406 }
1407
1408 treeElement.removeChildAtIndex(i);
1409 }
1410
1411 for (var i = 0; i < visibleChildren.length && i < treeElement.expandedChildr enLimit(); ++i) {
1412 var child = visibleChildren[i];
1413 var existingTreeElement = existingTreeElements.get(child) || this.findTree Element(child);
1414 if (existingTreeElement && existingTreeElement !== treeElement) {
1415 // If an existing element was found, just move it.
1416 this._moveChild(treeElement, existingTreeElement, i);
1417 } else {
1418 // No existing element found, insert a new element.
1419 var newElement = this.insertChildElement(treeElement, child, i);
1420 if (this._updateRecordForHighlight(node) && treeElement.expanded)
1421 WebInspector.ElementsTreeElement.animateOnDOMUpdate(newElement);
1422 // If a node was inserted in the middle of existing list dynamically we might need to increase the limit.
1423 if (treeElement.childCount() > treeElement.expandedChildrenLimit())
1424 this.setExpandedChildrenLimit(treeElement, treeElement.expandedChildre nLimit() + 1);
1425 }
1426 }
1427
1428 // Update expand all button.
1429 var expandedChildCount = treeElement.childCount();
1430 if (visibleChildren.length > expandedChildCount) {
1431 var targetButtonIndex = expandedChildCount;
1432 if (!treeElement.expandAllButtonElement)
1433 treeElement.expandAllButtonElement = this._createExpandAllButtonTreeElem ent(treeElement);
1434 treeElement.insertChild(treeElement.expandAllButtonElement, targetButtonIn dex);
1435 treeElement.expandAllButtonElement.button.textContent =
1436 WebInspector.UIString('Show All Nodes (%d More)', visibleChildren.leng th - expandedChildCount);
1437 } else if (treeElement.expandAllButtonElement) {
1438 delete treeElement.expandAllButtonElement;
1439 }
1440
1441 // Insert shortcuts to distrubuted children.
1442 if (node.isInsertionPoint()) {
1443 for (var distributedNode of node.distributedNodes())
1444 treeElement.appendChild(new WebInspector.ElementsTreeOutline.ShortcutTre eElement(distributedNode));
1445 }
1446
1447 // Insert close tag.
1448 if (node.nodeType() === Node.ELEMENT_NODE && treeElement.isExpandable())
1449 this.insertChildElement(treeElement, node, treeElement.childCount(), true) ;
1450
1451 this._treeElementsBeingUpdated.delete(treeElement);
1452 }
1453
1454 /**
1455 * @param {!WebInspector.Event} event
1456 */
1457 _markersChanged(event) {
1458 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1459 var treeElement = node[this._treeElementSymbol];
1460 if (treeElement)
1461 treeElement.updateDecorations();
1462 }
88 }; 1463 };
89 1464
90 WebInspector.ElementsTreeOutline._treeOutlineSymbol = Symbol("treeOutline"); 1465 WebInspector.ElementsTreeOutline._treeOutlineSymbol = Symbol('treeOutline');
91 1466
92 /**
93 * @param {!WebInspector.DOMModel} domModel
94 * @return {?WebInspector.ElementsTreeOutline}
95 */
96 WebInspector.ElementsTreeOutline.forDOMModel = function(domModel)
97 {
98 return domModel[WebInspector.ElementsTreeOutline._treeOutlineSymbol] || null ;
99 };
100 1467
101 /** @typedef {{node: !WebInspector.DOMNode, isCut: boolean}} */ 1468 /** @typedef {{node: !WebInspector.DOMNode, isCut: boolean}} */
102 WebInspector.ElementsTreeOutline.ClipboardData; 1469 WebInspector.ElementsTreeOutline.ClipboardData;
103 1470
104 /** @enum {symbol} */ 1471 /** @enum {symbol} */
105 WebInspector.ElementsTreeOutline.Events = { 1472 WebInspector.ElementsTreeOutline.Events = {
106 SelectedNodeChanged: Symbol("SelectedNodeChanged"), 1473 SelectedNodeChanged: Symbol('SelectedNodeChanged'),
107 ElementsTreeUpdated: Symbol("ElementsTreeUpdated") 1474 ElementsTreeUpdated: Symbol('ElementsTreeUpdated')
108 }; 1475 };
109 1476
110 /** 1477 /**
111 * @const 1478 * @const
112 * @type {!Object.<string, string>} 1479 * @type {!Object.<string, string>}
113 */ 1480 */
114 WebInspector.ElementsTreeOutline.MappedCharToEntity = { 1481 WebInspector.ElementsTreeOutline.MappedCharToEntity = {
115 "\u00a0": "nbsp", 1482 '\u00a0': 'nbsp',
116 "\u0093": "#147", // <control> 1483 '\u0093': '#147', // <control>
117 "\u00ad": "shy", 1484 '\u00ad': 'shy',
118 "\u2002": "ensp", 1485 '\u2002': 'ensp',
119 "\u2003": "emsp", 1486 '\u2003': 'emsp',
120 "\u2009": "thinsp", 1487 '\u2009': 'thinsp',
121 "\u200a": "#8202", // Hairspace 1488 '\u200a': '#8202', // Hairspace
122 "\u200b": "#8203", // ZWSP 1489 '\u200b': '#8203', // ZWSP
123 "\u200c": "zwnj", 1490 '\u200c': 'zwnj',
124 "\u200d": "zwj", 1491 '\u200d': 'zwj',
125 "\u200e": "lrm", 1492 '\u200e': 'lrm',
126 "\u200f": "rlm", 1493 '\u200f': 'rlm',
127 "\u202a": "#8234", // LRE 1494 '\u202a': '#8234', // LRE
128 "\u202b": "#8235", // RLE 1495 '\u202b': '#8235', // RLE
129 "\u202c": "#8236", // PDF 1496 '\u202c': '#8236', // PDF
130 "\u202d": "#8237", // LRO 1497 '\u202d': '#8237', // LRO
131 "\u202e": "#8238", // RLO 1498 '\u202e': '#8238', // RLO
132 "\ufeff": "#65279" // BOM 1499 '\ufeff': '#65279' // BOM
133 }; 1500 };
134 1501
135 WebInspector.ElementsTreeOutline.prototype = { 1502 /**
136 _onShowHTMLCommentsChange: function() 1503 * @unrestricted
137 { 1504 */
138 var selectedNode = this.selectedDOMNode(); 1505 WebInspector.ElementsTreeOutline.UpdateRecord = class {
139 if (selectedNode && selectedNode.nodeType() === Node.COMMENT_NODE && !th is._showHTMLCommentsSetting.get()) 1506 /**
140 this.selectDOMNode(selectedNode.parentNode); 1507 * @param {string} attrName
141 this.update(); 1508 */
142 }, 1509 attributeModified(attrName) {
1510 if (this._removedAttributes && this._removedAttributes.has(attrName))
1511 this._removedAttributes.delete(attrName);
1512 if (!this._modifiedAttributes)
1513 this._modifiedAttributes = /** @type {!Set.<string>} */ (new Set());
1514 this._modifiedAttributes.add(attrName);
1515 }
1516
1517 /**
1518 * @param {string} attrName
1519 */
1520 attributeRemoved(attrName) {
1521 if (this._modifiedAttributes && this._modifiedAttributes.has(attrName))
1522 this._modifiedAttributes.delete(attrName);
1523 if (!this._removedAttributes)
1524 this._removedAttributes = /** @type {!Set.<string>} */ (new Set());
1525 this._removedAttributes.add(attrName);
1526 }
1527
1528 /**
1529 * @param {!WebInspector.DOMNode} node
1530 */
1531 nodeInserted(node) {
1532 this._hasChangedChildren = true;
1533 }
1534
1535 nodeRemoved(node) {
1536 this._hasChangedChildren = true;
1537 this._hasRemovedChildren = true;
1538 }
1539
1540 charDataModified() {
1541 this._charDataModified = true;
1542 }
1543
1544 childrenModified() {
1545 this._hasChangedChildren = true;
1546 }
1547
1548 /**
1549 * @param {string} attributeName
1550 * @return {boolean}
1551 */
1552 isAttributeModified(attributeName) {
1553 return this._modifiedAttributes && this._modifiedAttributes.has(attributeNam e);
1554 }
1555
1556 /**
1557 * @return {boolean}
1558 */
1559 hasRemovedAttributes() {
1560 return !!this._removedAttributes && !!this._removedAttributes.size;
1561 }
1562
1563 /**
1564 * @return {boolean}
1565 */
1566 isCharDataModified() {
1567 return !!this._charDataModified;
1568 }
1569
1570 /**
1571 * @return {boolean}
1572 */
1573 hasChangedChildren() {
1574 return !!this._hasChangedChildren;
1575 }
1576
1577 /**
1578 * @return {boolean}
1579 */
1580 hasRemovedChildren() {
1581 return !!this._hasRemovedChildren;
1582 }
1583 };
1584
1585 /**
1586 * @implements {WebInspector.Renderer}
1587 * @unrestricted
1588 */
1589 WebInspector.ElementsTreeOutline.Renderer = class {
1590 /**
1591 * @override
1592 * @param {!Object} object
1593 * @return {!Promise.<!Element>}
1594 */
1595 render(object) {
1596 return new Promise(renderPromise);
143 1597
144 /** 1598 /**
145 * @return {symbol} 1599 * @param {function(!Element)} resolve
1600 * @param {function(!Error)} reject
146 */ 1601 */
147 treeElementSymbol: function() 1602 function renderPromise(resolve, reject) {
148 { 1603 if (object instanceof WebInspector.DOMNode) {
149 return this._treeElementSymbol; 1604 onNodeResolved(/** @type {!WebInspector.DOMNode} */ (object));
150 }, 1605 } else if (object instanceof WebInspector.DeferredDOMNode) {
151 1606 (/** @type {!WebInspector.DeferredDOMNode} */ (object)).resolve(onNodeRe solved);
152 focus: function() 1607 } else if (object instanceof WebInspector.RemoteObject) {
153 { 1608 var domModel = WebInspector.DOMModel.fromTarget((/** @type {!WebInspecto r.RemoteObject} */ (object)).target());
154 this._element.focus(); 1609 if (domModel)
155 }, 1610 domModel.pushObjectAsNodeToFrontend(object, onNodeResolved);
156 1611 else
157 /** 1612 reject(new Error('No dom model for given JS object target found.'));
158 * @param {boolean} wrap 1613 } else {
159 */ 1614 reject(new Error('Can\'t reveal not a node.'));
160 setWordWrap: function(wrap) 1615 }
161 { 1616
162 this._element.classList.toggle("elements-tree-nowrap", !wrap); 1617 /**
163 }, 1618 * @param {?WebInspector.DOMNode} node
164 1619 */
165 /** 1620 function onNodeResolved(node) {
166 * @return {!WebInspector.DOMModel} 1621 if (!node) {
167 */ 1622 reject(new Error('Could not resolve node.'));
168 domModel: function() 1623 return;
169 {
170 return this._domModel;
171 },
172
173 /**
174 * @param {?WebInspector.InplaceEditor.Controller} multilineEditing
175 */
176 setMultilineEditing: function(multilineEditing)
177 {
178 this._multilineEditing = multilineEditing;
179 },
180
181 /**
182 * @return {number}
183 */
184 visibleWidth: function()
185 {
186 return this._visibleWidth;
187 },
188
189 /**
190 * @param {number} width
191 */
192 setVisibleWidth: function(width)
193 {
194 this._visibleWidth = width;
195 if (this._multilineEditing)
196 this._multilineEditing.setWidth(this._visibleWidth);
197 },
198
199 /**
200 * @param {?WebInspector.ElementsTreeOutline.ClipboardData} data
201 */
202 _setClipboardData: function(data)
203 {
204 if (this._clipboardNodeData) {
205 var treeElement = this.findTreeElement(this._clipboardNodeData.node) ;
206 if (treeElement)
207 treeElement.setInClipboard(false);
208 delete this._clipboardNodeData;
209 } 1624 }
210 1625 var treeOutline = new WebInspector.ElementsTreeOutline(node.domModel(), false, false);
211 if (data) { 1626 treeOutline.rootDOMNode = node;
212 var treeElement = this.findTreeElement(data.node); 1627 if (!treeOutline.firstChild().isExpandable())
213 if (treeElement) 1628 treeOutline._element.classList.add('single-node');
214 treeElement.setInClipboard(true); 1629 treeOutline.setVisible(true);
215 this._clipboardNodeData = data; 1630 treeOutline.element.treeElementForTest = treeOutline.firstChild();
216 } 1631 resolve(treeOutline.element);
217 }, 1632 }
218 1633 }
219 /** 1634 }
220 * @param {!WebInspector.DOMNode} removedNode 1635 };
221 */ 1636
222 resetClipboardIfNeeded: function(removedNode) 1637 /**
223 { 1638 * @unrestricted
224 if (this._clipboardNodeData && this._clipboardNodeData.node === removedN ode) 1639 */
225 this._setClipboardData(null); 1640 WebInspector.ElementsTreeOutline.ShortcutTreeElement = class extends TreeElement {
226 }, 1641 /**
227 1642 * @param {!WebInspector.DOMNodeShortcut} nodeShortcut
228 /** 1643 */
229 * @param {!Event} event 1644 constructor(nodeShortcut) {
230 */ 1645 super('');
231 _onBeforeCopy: function(event) 1646 this.listItemElement.createChild('div', 'selection fill');
232 { 1647 var title = this.listItemElement.createChild('span', 'elements-tree-shortcut -title');
233 event.handled = true; 1648 var text = nodeShortcut.nodeName.toLowerCase();
234 }, 1649 if (nodeShortcut.nodeType === Node.ELEMENT_NODE)
235 1650 text = '<' + text + '>';
236 /** 1651 title.textContent = '\u21AA ' + text;
237 * @param {boolean} isCut 1652
238 * @param {!Event} event 1653 var link = WebInspector.DOMPresentationUtils.linkifyDeferredNodeReference(no deShortcut.deferredNode);
239 */ 1654 this.listItemElement.createTextChild(' ');
240 _onCopyOrCut: function(isCut, event) 1655 link.classList.add('elements-tree-shortcut-link');
241 { 1656 link.textContent = WebInspector.UIString('reveal');
242 this._setClipboardData(null); 1657 this.listItemElement.appendChild(link);
243 var originalEvent = event["original"]; 1658 this._nodeShortcut = nodeShortcut;
244 1659 }
245 // Don't prevent the normal copy if the user has a selection. 1660
246 if (!originalEvent.target.isComponentSelectionCollapsed()) 1661 /**
247 return; 1662 * @return {boolean}
248 1663 */
249 // Do not interfere with text editing. 1664 get hovered() {
250 if (WebInspector.isEditing()) 1665 return this._hovered;
251 return; 1666 }
252 1667
253 var targetNode = this.selectedDOMNode(); 1668 /**
254 if (!targetNode) 1669 * @param {boolean} x
255 return; 1670 */
256 1671 set hovered(x) {
257 originalEvent.clipboardData.clearData(); 1672 if (this._hovered === x)
258 event.handled = true; 1673 return;
259 1674 this._hovered = x;
260 this.performCopyOrCut(isCut, targetNode); 1675 this.listItemElement.classList.toggle('hovered', x);
261 }, 1676 }
262 1677
263 /** 1678 /**
264 * @param {boolean} isCut 1679 * @return {number}
265 * @param {?WebInspector.DOMNode} node 1680 */
266 */ 1681 backendNodeId() {
267 performCopyOrCut: function(isCut, node) 1682 return this._nodeShortcut.deferredNode.backendNodeId();
268 { 1683 }
269 if (isCut && (node.isShadowRoot() || node.ancestorUserAgentShadowRoot()) ) 1684
270 return; 1685 /**
271 1686 * @override
272 node.copyNode(); 1687 * @param {boolean=} selectedByUser
273 this._setClipboardData({ node: node, isCut: isCut }); 1688 * @return {boolean}
274 }, 1689 */
275 1690 onselect(selectedByUser) {
276 /** 1691 if (!selectedByUser)
277 * @param {!WebInspector.DOMNode} targetNode 1692 return true;
278 * @return {boolean} 1693 this._nodeShortcut.deferredNode.highlight();
279 */ 1694 this._nodeShortcut.deferredNode.resolve(resolved.bind(this));
280 canPaste: function(targetNode)
281 {
282 if (targetNode.isShadowRoot() || targetNode.ancestorUserAgentShadowRoot( ))
283 return false;
284
285 if (!this._clipboardNodeData)
286 return false;
287
288 var node = this._clipboardNodeData.node;
289 if (this._clipboardNodeData.isCut && (node === targetNode || node.isAnce stor(targetNode)))
290 return false;
291
292 if (targetNode.target() !== node.target())
293 return false;
294 return true;
295 },
296
297 /**
298 * @param {!WebInspector.DOMNode} targetNode
299 */
300 pasteNode: function(targetNode)
301 {
302 if (this.canPaste(targetNode))
303 this._performPaste(targetNode);
304 },
305
306 /**
307 * @param {!Event} event
308 */
309 _onPaste: function(event)
310 {
311 // Do not interfere with text editing.
312 if (WebInspector.isEditing())
313 return;
314
315 var targetNode = this.selectedDOMNode();
316 if (!targetNode || !this.canPaste(targetNode))
317 return;
318
319 event.handled = true;
320 this._performPaste(targetNode);
321 },
322
323 /**
324 * @param {!WebInspector.DOMNode} targetNode
325 */
326 _performPaste: function(targetNode)
327 {
328 if (this._clipboardNodeData.isCut) {
329 this._clipboardNodeData.node.moveTo(targetNode, null, expandCallback .bind(this));
330 this._setClipboardData(null);
331 } else {
332 this._clipboardNodeData.node.copyTo(targetNode, null, expandCallback .bind(this));
333 }
334
335 /**
336 * @param {?Protocol.Error} error
337 * @param {!DOMAgent.NodeId} nodeId
338 * @this {WebInspector.ElementsTreeOutline}
339 */
340 function expandCallback(error, nodeId)
341 {
342 if (error)
343 return;
344 var pastedNode = this._domModel.nodeForId(nodeId);
345 if (!pastedNode)
346 return;
347 this.selectDOMNode(pastedNode);
348 }
349 },
350
351 /**
352 * @param {boolean} visible
353 */
354 setVisible: function(visible)
355 {
356 this._visible = visible;
357 if (!this._visible) {
358 this._popoverHelper.hidePopover();
359 if (this._multilineEditing)
360 this._multilineEditing.cancel();
361 return;
362 }
363
364 this.runPendingUpdates();
365 if (this._selectedDOMNode)
366 this._revealAndSelectNode(this._selectedDOMNode, false);
367 },
368
369 get rootDOMNode()
370 {
371 return this._rootDOMNode;
372 },
373
374 set rootDOMNode(x)
375 {
376 if (this._rootDOMNode === x)
377 return;
378
379 this._rootDOMNode = x;
380
381 this._isXMLMimeType = x && x.isXMLNode();
382
383 this.update();
384 },
385
386 get isXMLMimeType()
387 {
388 return this._isXMLMimeType;
389 },
390
391 /**
392 * @return {?WebInspector.DOMNode}
393 */
394 selectedDOMNode: function()
395 {
396 return this._selectedDOMNode;
397 },
398
399 /** 1695 /**
400 * @param {?WebInspector.DOMNode} node 1696 * @param {?WebInspector.DOMNode} node
401 * @param {boolean=} focus 1697 * @this {WebInspector.ElementsTreeOutline.ShortcutTreeElement}
402 */ 1698 */
403 selectDOMNode: function(node, focus) 1699 function resolved(node) {
404 { 1700 if (node) {
405 if (this._selectedDOMNode === node) { 1701 this.treeOutline._selectedDOMNode = node;
406 this._revealAndSelectNode(node, !focus); 1702 this.treeOutline._selectedNodeChanged();
407 return; 1703 }
408 } 1704 }
409 1705 return true;
410 this._selectedDOMNode = node; 1706 }
411 this._revealAndSelectNode(node, !focus);
412
413 // The _revealAndSelectNode() method might find a different element if t here is inlined text,
414 // and the select() call would change the selectedDOMNode and reenter th is setter. So to
415 // avoid calling _selectedNodeChanged() twice, first check if _selectedD OMNode is the same
416 // node as the one passed in.
417 if (this._selectedDOMNode === node)
418 this._selectedNodeChanged(!!focus);
419 },
420
421 /**
422 * @return {boolean}
423 */
424 editing: function()
425 {
426 var node = this.selectedDOMNode();
427 if (!node)
428 return false;
429 var treeElement = this.findTreeElement(node);
430 if (!treeElement)
431 return false;
432 return treeElement.isEditing() || false;
433 },
434
435 update: function()
436 {
437 var selectedNode = this.selectedDOMNode();
438 this.removeChildren();
439 if (!this.rootDOMNode)
440 return;
441
442 if (this._includeRootDOMNode) {
443 var treeElement = this._createElementTreeElement(this.rootDOMNode);
444 this.appendChild(treeElement);
445 } else {
446 // FIXME: this could use findTreeElement to reuse a tree element if it already exists
447 var children = this._visibleChildren(this.rootDOMNode);
448 for (var child of children) {
449 var treeElement = this._createElementTreeElement(child);
450 this.appendChild(treeElement);
451 }
452 }
453
454 if (selectedNode)
455 this._revealAndSelectNode(selectedNode, true);
456 },
457
458 /**
459 * @param {boolean} focus
460 */
461 _selectedNodeChanged: function(focus)
462 {
463 this.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.Se lectedNodeChanged, {node: this._selectedDOMNode, focus: focus});
464 },
465
466 /**
467 * @param {!Array.<!WebInspector.DOMNode>} nodes
468 */
469 _fireElementsTreeUpdated: function(nodes)
470 {
471 this.dispatchEventToListeners(WebInspector.ElementsTreeOutline.Events.El ementsTreeUpdated, nodes);
472 },
473
474 /**
475 * @param {!WebInspector.DOMNode} node
476 * @return {?WebInspector.ElementsTreeElement}
477 */
478 findTreeElement: function(node)
479 {
480 var treeElement = this._lookUpTreeElement(node);
481 if (!treeElement && node.nodeType() === Node.TEXT_NODE) {
482 // The text node might have been inlined if it was short, so try to find the parent element.
483 treeElement = this._lookUpTreeElement(node.parentNode);
484 }
485
486 return /** @type {?WebInspector.ElementsTreeElement} */ (treeElement);
487 },
488
489 /**
490 * @param {?WebInspector.DOMNode} node
491 * @return {?TreeElement}
492 */
493 _lookUpTreeElement: function(node)
494 {
495 if (!node)
496 return null;
497
498 var cachedElement = node[this._treeElementSymbol];
499 if (cachedElement)
500 return cachedElement;
501
502 // Walk up the parent pointers from the desired node
503 var ancestors = [];
504 for (var currentNode = node.parentNode; currentNode; currentNode = curre ntNode.parentNode) {
505 ancestors.push(currentNode);
506 if (currentNode[this._treeElementSymbol]) // stop climbing as soon as we hit
507 break;
508 }
509
510 if (!currentNode)
511 return null;
512
513 // Walk down to populate each ancestor's children, to fill in the tree a nd the cache.
514 for (var i = ancestors.length - 1; i >= 0; --i) {
515 var treeElement = ancestors[i][this._treeElementSymbol];
516 if (treeElement)
517 treeElement.onpopulate(); // fill the cache with the children o f treeElement
518 }
519
520 return node[this._treeElementSymbol];
521 },
522
523 /**
524 * @param {!WebInspector.DOMNode} node
525 * @return {?WebInspector.ElementsTreeElement}
526 */
527 createTreeElementFor: function(node)
528 {
529 var treeElement = this.findTreeElement(node);
530 if (treeElement)
531 return treeElement;
532 if (!node.parentNode)
533 return null;
534
535 treeElement = this.createTreeElementFor(node.parentNode);
536 return treeElement ? this._showChild(treeElement, node) : null;
537 },
538
539 set suppressRevealAndSelect(x)
540 {
541 if (this._suppressRevealAndSelect === x)
542 return;
543 this._suppressRevealAndSelect = x;
544 },
545
546 /**
547 * @param {?WebInspector.DOMNode} node
548 * @param {boolean} omitFocus
549 */
550 _revealAndSelectNode: function(node, omitFocus)
551 {
552 if (this._suppressRevealAndSelect)
553 return;
554
555 if (!this._includeRootDOMNode && node === this.rootDOMNode && this.rootD OMNode)
556 node = this.rootDOMNode.firstChild;
557 if (!node)
558 return;
559 var treeElement = this.createTreeElementFor(node);
560 if (!treeElement)
561 return;
562
563 treeElement.revealAndSelect(omitFocus);
564 },
565
566 /**
567 * @return {?TreeElement}
568 */
569 _treeElementFromEvent: function(event)
570 {
571 var scrollContainer = this.element.parentElement;
572
573 // We choose this X coordinate based on the knowledge that our list
574 // items extend at least to the right edge of the outer <ol> container.
575 // In the no-word-wrap mode the outer <ol> may be wider than the tree co ntainer
576 // (and partially hidden), in which case we are left to use only its rig ht boundary.
577 var x = scrollContainer.totalOffsetLeft() + scrollContainer.offsetWidth - 36;
578
579 var y = event.pageY;
580
581 // Our list items have 1-pixel cracks between them vertically. We avoid
582 // the cracks by checking slightly above and slightly below the mouse
583 // and seeing if we hit the same element each time.
584 var elementUnderMouse = this.treeElementFromPoint(x, y);
585 var elementAboveMouse = this.treeElementFromPoint(x, y - 2);
586 var element;
587 if (elementUnderMouse === elementAboveMouse)
588 element = elementUnderMouse;
589 else
590 element = this.treeElementFromPoint(x, y + 2);
591
592 return element;
593 },
594
595 /**
596 * @param {!Element} element
597 * @param {!Event} event
598 * @return {!Element|!AnchorBox|undefined}
599 */
600 _getPopoverAnchor: function(element, event)
601 {
602 var anchor = element.enclosingNodeOrSelfWithClass("webkit-html-resource- link");
603 if (!anchor || !anchor.href)
604 return;
605
606 return anchor;
607 },
608
609 /**
610 * @param {!WebInspector.DOMNode} node
611 * @param {function()} callback
612 */
613 _loadDimensionsForNode: function(node, callback)
614 {
615 if (!node.nodeName() || node.nodeName().toLowerCase() !== "img") {
616 callback();
617 return;
618 }
619
620 node.resolveToObject("", resolvedNode);
621
622 function resolvedNode(object)
623 {
624 if (!object) {
625 callback();
626 return;
627 }
628
629 object.callFunctionJSON(features, undefined, callback);
630 object.release();
631
632 /**
633 * @return {!{offsetWidth: number, offsetHeight: number, naturalWidt h: number, naturalHeight: number, currentSrc: (string|undefined)}}
634 * @suppressReceiverCheck
635 * @this {!Element}
636 */
637 function features()
638 {
639 return { offsetWidth: this.offsetWidth, offsetHeight: this.offse tHeight, naturalWidth: this.naturalWidth, naturalHeight: this.naturalHeight, cur rentSrc: this.currentSrc };
640 }
641 }
642 },
643
644 /**
645 * @param {!Element} anchor
646 * @param {!WebInspector.Popover} popover
647 */
648 _showPopover: function(anchor, popover)
649 {
650 var listItem = anchor.enclosingNodeOrSelfWithNodeName("li");
651 var node = /** @type {!WebInspector.ElementsTreeElement} */ (listItem.tr eeElement).node();
652 this._loadDimensionsForNode(node, WebInspector.DOMPresentationUtils.buil dImagePreviewContents.bind(WebInspector.DOMPresentationUtils, node.target(), anc hor.href, true, showPopover));
653
654 /**
655 * @param {!Element=} contents
656 */
657 function showPopover(contents)
658 {
659 if (!contents)
660 return;
661 popover.setCanShrink(false);
662 popover.showForAnchor(contents, anchor);
663 }
664 },
665
666 _onmousedown: function(event)
667 {
668 var element = this._treeElementFromEvent(event);
669
670 if (!element || element.isEventWithinDisclosureTriangle(event))
671 return;
672
673 element.select();
674 },
675
676 /**
677 * @param {?TreeElement} treeElement
678 */
679 setHoverEffect: function(treeElement)
680 {
681 if (this._previousHoveredElement === treeElement)
682 return;
683
684 if (this._previousHoveredElement) {
685 this._previousHoveredElement.hovered = false;
686 delete this._previousHoveredElement;
687 }
688
689 if (treeElement) {
690 treeElement.hovered = true;
691 this._previousHoveredElement = treeElement;
692 }
693 },
694
695 _onmousemove: function(event)
696 {
697 var element = this._treeElementFromEvent(event);
698 if (element && this._previousHoveredElement === element)
699 return;
700
701 this.setHoverEffect(element);
702
703 if (element instanceof WebInspector.ElementsTreeElement) {
704 this._domModel.highlightDOMNodeWithConfig(element.node().id, { mode: "all", showInfo: !WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) });
705 return;
706 }
707
708 if (element instanceof WebInspector.ElementsTreeOutline.ShortcutTreeElem ent)
709 this._domModel.highlightDOMNodeWithConfig(undefined, { mode: "all", showInfo: !WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) }, element.ba ckendNodeId());
710 },
711
712 _onmouseleave: function(event)
713 {
714 this.setHoverEffect(null);
715 WebInspector.DOMModel.hideDOMNodeHighlight();
716 },
717
718 _ondragstart: function(event)
719 {
720 if (!event.target.isComponentSelectionCollapsed())
721 return false;
722 if (event.target.nodeName === "A")
723 return false;
724
725 var treeElement = this._treeElementFromEvent(event);
726 if (!this._isValidDragSourceOrTarget(treeElement))
727 return false;
728
729 if (treeElement.node().nodeName() === "BODY" || treeElement.node().nodeN ame() === "HEAD")
730 return false;
731
732 event.dataTransfer.setData("text/plain", treeElement.listItemElement.tex tContent.replace(/\u200b/g, ""));
733 event.dataTransfer.effectAllowed = "copyMove";
734 this._treeElementBeingDragged = treeElement;
735
736 WebInspector.DOMModel.hideDOMNodeHighlight();
737
738 return true;
739 },
740
741 _ondragover: function(event)
742 {
743 if (!this._treeElementBeingDragged)
744 return false;
745
746 var treeElement = this._treeElementFromEvent(event);
747 if (!this._isValidDragSourceOrTarget(treeElement))
748 return false;
749
750 var node = treeElement.node();
751 while (node) {
752 if (node === this._treeElementBeingDragged._node)
753 return false;
754 node = node.parentNode;
755 }
756
757 treeElement.listItemElement.classList.add("elements-drag-over");
758 this._dragOverTreeElement = treeElement;
759 event.preventDefault();
760 event.dataTransfer.dropEffect = "move";
761 return false;
762 },
763
764 _ondragleave: function(event)
765 {
766 this._clearDragOverTreeElementMarker();
767 event.preventDefault();
768 return false;
769 },
770
771 /**
772 * @param {?TreeElement} treeElement
773 * @return {boolean}
774 */
775 _isValidDragSourceOrTarget: function(treeElement)
776 {
777 if (!treeElement)
778 return false;
779
780 if (!(treeElement instanceof WebInspector.ElementsTreeElement))
781 return false;
782 var elementsTreeElement = /** @type {!WebInspector.ElementsTreeElement} */ (treeElement);
783
784 var node = elementsTreeElement.node();
785 if (!node.parentNode || node.parentNode.nodeType() !== Node.ELEMENT_NODE )
786 return false;
787
788 return true;
789 },
790
791 _ondrop: function(event)
792 {
793 event.preventDefault();
794 var treeElement = this._treeElementFromEvent(event);
795 if (treeElement)
796 this._doMove(treeElement);
797 },
798
799 /**
800 * @param {!TreeElement} treeElement
801 */
802 _doMove: function(treeElement)
803 {
804 if (!this._treeElementBeingDragged)
805 return;
806
807 var parentNode;
808 var anchorNode;
809
810 if (treeElement.isClosingTag()) {
811 // Drop onto closing tag -> insert as last child.
812 parentNode = treeElement.node();
813 } else {
814 var dragTargetNode = treeElement.node();
815 parentNode = dragTargetNode.parentNode;
816 anchorNode = dragTargetNode;
817 }
818
819 var wasExpanded = this._treeElementBeingDragged.expanded;
820 this._treeElementBeingDragged._node.moveTo(parentNode, anchorNode, this. selectNodeAfterEdit.bind(this, wasExpanded));
821
822 delete this._treeElementBeingDragged;
823 },
824
825 _ondragend: function(event)
826 {
827 event.preventDefault();
828 this._clearDragOverTreeElementMarker();
829 delete this._treeElementBeingDragged;
830 },
831
832 _clearDragOverTreeElementMarker: function()
833 {
834 if (this._dragOverTreeElement) {
835 this._dragOverTreeElement.listItemElement.classList.remove("elements -drag-over");
836 delete this._dragOverTreeElement;
837 }
838 },
839
840 _contextMenuEventFired: function(event)
841 {
842 var treeElement = this._treeElementFromEvent(event);
843 if (treeElement instanceof WebInspector.ElementsTreeElement)
844 this.showContextMenu(treeElement, event);
845 },
846
847 /**
848 * @param {!WebInspector.ElementsTreeElement} treeElement
849 * @param {!Event} event
850 */
851 showContextMenu: function(treeElement, event)
852 {
853 if (WebInspector.isEditing())
854 return;
855
856 var contextMenu = new WebInspector.ContextMenu(event);
857 var isPseudoElement = !!treeElement.node().pseudoType();
858 var isTag = treeElement.node().nodeType() === Node.ELEMENT_NODE && !isPs eudoElement;
859 var textNode = event.target.enclosingNodeOrSelfWithClass("webkit-html-te xt-node");
860 if (textNode && textNode.classList.contains("bogus"))
861 textNode = null;
862 var commentNode = event.target.enclosingNodeOrSelfWithClass("webkit-html -comment");
863 contextMenu.appendApplicableItems(event.target);
864 if (textNode) {
865 contextMenu.appendSeparator();
866 treeElement.populateTextContextMenu(contextMenu, textNode);
867 } else if (isTag) {
868 contextMenu.appendSeparator();
869 treeElement.populateTagContextMenu(contextMenu, event);
870 } else if (commentNode) {
871 contextMenu.appendSeparator();
872 treeElement.populateNodeContextMenu(contextMenu);
873 } else if (isPseudoElement) {
874 treeElement.populateScrollIntoView(contextMenu);
875 }
876
877 contextMenu.appendApplicableItems(treeElement.node());
878 contextMenu.show();
879 },
880
881 runPendingUpdates: function()
882 {
883 this._updateModifiedNodes();
884 },
885
886 handleShortcut: function(event)
887 {
888 var node = this.selectedDOMNode();
889 if (!node)
890 return;
891 var treeElement = node[this._treeElementSymbol];
892 if (!treeElement)
893 return;
894
895 if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) && node.pare ntNode) {
896 if (event.key === "ArrowUp" && node.previousSibling) {
897 node.moveTo(node.parentNode, node.previousSibling, this.selectNo deAfterEdit.bind(this, treeElement.expanded));
898 event.handled = true;
899 return;
900 }
901 if (event.key === "ArrowDown" && node.nextSibling) {
902 node.moveTo(node.parentNode, node.nextSibling.nextSibling, this. selectNodeAfterEdit.bind(this, treeElement.expanded));
903 event.handled = true;
904 return;
905 }
906 }
907 },
908
909 /**
910 * @param {!WebInspector.DOMNode} node
911 * @param {boolean=} startEditing
912 * @param {function()=} callback
913 */
914 toggleEditAsHTML: function(node, startEditing, callback)
915 {
916 var treeElement = node[this._treeElementSymbol];
917 if (!treeElement || !treeElement.hasEditableNode())
918 return;
919
920 if (node.pseudoType())
921 return;
922
923 var parentNode = node.parentNode;
924 var index = node.index;
925 var wasExpanded = treeElement.expanded;
926
927 treeElement.toggleEditAsHTML(editingFinished.bind(this), startEditing);
928
929 /**
930 * @this {WebInspector.ElementsTreeOutline}
931 * @param {boolean} success
932 */
933 function editingFinished(success)
934 {
935 if (callback)
936 callback();
937 if (!success)
938 return;
939
940 // Select it and expand if necessary. We force tree update so that i t processes dom events and is up to date.
941 this.runPendingUpdates();
942
943 var newNode = parentNode ? parentNode.children()[index] || parentNod e : null;
944 if (!newNode)
945 return;
946
947 this.selectDOMNode(newNode, true);
948
949 if (wasExpanded) {
950 var newTreeItem = this.findTreeElement(newNode);
951 if (newTreeItem)
952 newTreeItem.expand();
953 }
954 }
955 },
956
957 /**
958 * @param {boolean} wasExpanded
959 * @param {?Protocol.Error} error
960 * @param {!DOMAgent.NodeId=} nodeId
961 * @return {?WebInspector.ElementsTreeElement} nodeId
962 */
963 selectNodeAfterEdit: function(wasExpanded, error, nodeId)
964 {
965 if (error)
966 return null;
967
968 // Select it and expand if necessary. We force tree update so that it pr ocesses dom events and is up to date.
969 this.runPendingUpdates();
970
971 var newNode = nodeId ? this._domModel.nodeForId(nodeId) : null;
972 if (!newNode)
973 return null;
974
975 this.selectDOMNode(newNode, true);
976
977 var newTreeItem = this.findTreeElement(newNode);
978 if (wasExpanded) {
979 if (newTreeItem)
980 newTreeItem.expand();
981 }
982 return newTreeItem;
983 },
984
985 /**
986 * Runs a script on the node's remote object that toggles a class name on
987 * the node and injects a stylesheet into the head of the node's document
988 * containing a rule to set "visibility: hidden" on the class and all it's
989 * ancestors.
990 *
991 * @param {!WebInspector.DOMNode} node
992 * @param {function(?WebInspector.RemoteObject, boolean=)=} userCallback
993 */
994 toggleHideElement: function(node, userCallback)
995 {
996 var pseudoType = node.pseudoType();
997 var effectiveNode = pseudoType ? node.parentNode : node;
998 if (!effectiveNode)
999 return;
1000
1001 var hidden = node.marker("hidden-marker");
1002
1003 function resolvedNode(object)
1004 {
1005 if (!object)
1006 return;
1007
1008 /**
1009 * @param {?string} pseudoType
1010 * @param {boolean} hidden
1011 * @suppressGlobalPropertiesCheck
1012 * @suppressReceiverCheck
1013 * @this {!Element}
1014 */
1015 function toggleClassAndInjectStyleRule(pseudoType, hidden)
1016 {
1017 const classNamePrefix = "__web-inspector-hide";
1018 const classNameSuffix = "-shortcut__";
1019 const styleTagId = "__web-inspector-hide-shortcut-style__";
1020 var selectors = [];
1021 selectors.push(".__web-inspector-hide-shortcut__");
1022 selectors.push(".__web-inspector-hide-shortcut__ *");
1023 selectors.push(".__web-inspector-hidebefore-shortcut__::before") ;
1024 selectors.push(".__web-inspector-hideafter-shortcut__::after");
1025 var selector = selectors.join(", ");
1026 var ruleBody = " visibility: hidden !important;";
1027 var rule = "\n" + selector + "\n{\n" + ruleBody + "\n}\n";
1028 var className = classNamePrefix + (pseudoType || "") + className Suffix;
1029 this.classList.toggle(className, hidden);
1030
1031 var localRoot = this;
1032 while (localRoot.parentNode)
1033 localRoot = localRoot.parentNode;
1034 if (localRoot.nodeType === Node.DOCUMENT_NODE)
1035 localRoot = document.head;
1036
1037 var style = localRoot.querySelector("style#" + styleTagId);
1038 if (style)
1039 return;
1040
1041 style = document.createElement("style");
1042 style.id = styleTagId;
1043 style.type = "text/css";
1044 style.textContent = rule;
1045
1046 localRoot.appendChild(style);
1047 }
1048
1049 object.callFunction(toggleClassAndInjectStyleRule, [{ value: pseudoT ype }, { value: !hidden}], userCallback);
1050 object.release();
1051 node.setMarker("hidden-marker", hidden ? null : true);
1052 }
1053
1054 effectiveNode.resolveToObject("", resolvedNode);
1055 },
1056
1057 /**
1058 * @param {!WebInspector.DOMNode} node
1059 * @return {boolean}
1060 */
1061 isToggledToHidden: function(node)
1062 {
1063 return !!node.marker("hidden-marker");
1064 },
1065
1066 _reset: function()
1067 {
1068 this.rootDOMNode = null;
1069 this.selectDOMNode(null, false);
1070 this._popoverHelper.hidePopover();
1071 delete this._clipboardNodeData;
1072 WebInspector.DOMModel.hideDOMNodeHighlight();
1073 this._updateRecords.clear();
1074 },
1075
1076 wireToDOMModel: function()
1077 {
1078 this._domModel[WebInspector.ElementsTreeOutline._treeOutlineSymbol] = th is;
1079 this._domModel.addEventListener(WebInspector.DOMModel.Events.NodeInserte d, this._nodeInserted, this);
1080 this._domModel.addEventListener(WebInspector.DOMModel.Events.NodeRemoved , this._nodeRemoved, this);
1081 this._domModel.addEventListener(WebInspector.DOMModel.Events.AttrModifie d, this._attributeModified, this);
1082 this._domModel.addEventListener(WebInspector.DOMModel.Events.AttrRemoved , this._attributeRemoved, this);
1083 this._domModel.addEventListener(WebInspector.DOMModel.Events.CharacterDa taModified, this._characterDataModified, this);
1084 this._domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpd ated, this._documentUpdated, this);
1085 this._domModel.addEventListener(WebInspector.DOMModel.Events.ChildNodeCo untUpdated, this._childNodeCountUpdated, this);
1086 this._domModel.addEventListener(WebInspector.DOMModel.Events.Distributed NodesChanged, this._distributedNodesChanged, this);
1087 },
1088
1089 unwireFromDOMModel: function()
1090 {
1091 this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeInse rted, this._nodeInserted, this);
1092 this._domModel.removeEventListener(WebInspector.DOMModel.Events.NodeRemo ved, this._nodeRemoved, this);
1093 this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrModi fied, this._attributeModified, this);
1094 this._domModel.removeEventListener(WebInspector.DOMModel.Events.AttrRemo ved, this._attributeRemoved, this);
1095 this._domModel.removeEventListener(WebInspector.DOMModel.Events.Characte rDataModified, this._characterDataModified, this);
1096 this._domModel.removeEventListener(WebInspector.DOMModel.Events.Document Updated, this._documentUpdated, this);
1097 this._domModel.removeEventListener(WebInspector.DOMModel.Events.ChildNod eCountUpdated, this._childNodeCountUpdated, this);
1098 this._domModel.removeEventListener(WebInspector.DOMModel.Events.Distribu tedNodesChanged, this._distributedNodesChanged, this);
1099 delete this._domModel[WebInspector.ElementsTreeOutline._treeOutlineSymbo l];
1100 },
1101
1102 /**
1103 * @param {!WebInspector.DOMNode} node
1104 * @return {!WebInspector.ElementsTreeOutline.UpdateRecord}
1105 */
1106 _addUpdateRecord: function(node)
1107 {
1108 var record = this._updateRecords.get(node);
1109 if (!record) {
1110 record = new WebInspector.ElementsTreeOutline.UpdateRecord();
1111 this._updateRecords.set(node, record);
1112 }
1113 return record;
1114 },
1115
1116 /**
1117 * @param {!WebInspector.DOMNode} node
1118 * @return {?WebInspector.ElementsTreeOutline.UpdateRecord}
1119 */
1120 _updateRecordForHighlight: function(node)
1121 {
1122 if (!this._visible)
1123 return null;
1124 return this._updateRecords.get(node) || null;
1125 },
1126
1127 /**
1128 * @param {!WebInspector.Event} event
1129 */
1130 _documentUpdated: function(event)
1131 {
1132 var inspectedRootDocument = event.data;
1133
1134 this._reset();
1135
1136 if (!inspectedRootDocument)
1137 return;
1138
1139 this.rootDOMNode = inspectedRootDocument;
1140 },
1141
1142 /**
1143 * @param {!WebInspector.Event} event
1144 */
1145 _attributeModified: function(event)
1146 {
1147 var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
1148 this._addUpdateRecord(node).attributeModified(event.data.name);
1149 this._updateModifiedNodesSoon();
1150 },
1151
1152 /**
1153 * @param {!WebInspector.Event} event
1154 */
1155 _attributeRemoved: function(event)
1156 {
1157 var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
1158 this._addUpdateRecord(node).attributeRemoved(event.data.name);
1159 this._updateModifiedNodesSoon();
1160 },
1161
1162 /**
1163 * @param {!WebInspector.Event} event
1164 */
1165 _characterDataModified: function(event)
1166 {
1167 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1168 this._addUpdateRecord(node).charDataModified();
1169 // Text could be large and force us to render itself as the child in the tree outline.
1170 if (node.parentNode && node.parentNode.firstChild === node.parentNode.la stChild)
1171 this._addUpdateRecord(node.parentNode).childrenModified();
1172 this._updateModifiedNodesSoon();
1173 },
1174
1175 /**
1176 * @param {!WebInspector.Event} event
1177 */
1178 _nodeInserted: function(event)
1179 {
1180 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1181 this._addUpdateRecord(/** @type {!WebInspector.DOMNode} */ (node.parentN ode)).nodeInserted(node);
1182 this._updateModifiedNodesSoon();
1183 },
1184
1185 /**
1186 * @param {!WebInspector.Event} event
1187 */
1188 _nodeRemoved: function(event)
1189 {
1190 var node = /** @type {!WebInspector.DOMNode} */ (event.data.node);
1191 var parentNode = /** @type {!WebInspector.DOMNode} */ (event.data.parent );
1192 this.resetClipboardIfNeeded(node);
1193 this._addUpdateRecord(parentNode).nodeRemoved(node);
1194 this._updateModifiedNodesSoon();
1195 },
1196
1197 /**
1198 * @param {!WebInspector.Event} event
1199 */
1200 _childNodeCountUpdated: function(event)
1201 {
1202 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1203 this._addUpdateRecord(node).childrenModified();
1204 this._updateModifiedNodesSoon();
1205 },
1206
1207 /**
1208 * @param {!WebInspector.Event} event
1209 */
1210 _distributedNodesChanged: function(event)
1211 {
1212 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1213 this._addUpdateRecord(node).childrenModified();
1214 this._updateModifiedNodesSoon();
1215 },
1216
1217 _updateModifiedNodesSoon: function()
1218 {
1219 if (!this._updateRecords.size)
1220 return;
1221 if (this._updateModifiedNodesTimeout)
1222 return;
1223 this._updateModifiedNodesTimeout = setTimeout(this._updateModifiedNodes. bind(this), 50);
1224 },
1225
1226 _updateModifiedNodes: function()
1227 {
1228 if (this._updateModifiedNodesTimeout) {
1229 clearTimeout(this._updateModifiedNodesTimeout);
1230 delete this._updateModifiedNodesTimeout;
1231 }
1232
1233 var updatedNodes = this._updateRecords.keysArray();
1234 var hidePanelWhileUpdating = updatedNodes.length > 10;
1235 if (hidePanelWhileUpdating) {
1236 var treeOutlineContainerElement = this.element.parentNode;
1237 var originalScrollTop = treeOutlineContainerElement ? treeOutlineCon tainerElement.scrollTop : 0;
1238 this._element.classList.add("hidden");
1239 }
1240
1241 if (this._rootDOMNode && this._updateRecords.get(this._rootDOMNode) && t his._updateRecords.get(this._rootDOMNode).hasChangedChildren()) {
1242 // Document's children have changed, perform total update.
1243 this.update();
1244 } else {
1245 for (var node of this._updateRecords.keys()) {
1246 if (this._updateRecords.get(node).hasChangedChildren())
1247 this._updateModifiedParentNode(node);
1248 else
1249 this._updateModifiedNode(node);
1250 }
1251 }
1252
1253 if (hidePanelWhileUpdating) {
1254 this._element.classList.remove("hidden");
1255 if (originalScrollTop)
1256 treeOutlineContainerElement.scrollTop = originalScrollTop;
1257 }
1258
1259 this._updateRecords.clear();
1260 this._fireElementsTreeUpdated(updatedNodes);
1261 },
1262
1263 _updateModifiedNode: function(node)
1264 {
1265 var treeElement = this.findTreeElement(node);
1266 if (treeElement)
1267 treeElement.updateTitle(this._updateRecordForHighlight(node));
1268 },
1269
1270 _updateModifiedParentNode: function(node)
1271 {
1272 var parentTreeElement = this.findTreeElement(node);
1273 if (parentTreeElement) {
1274 parentTreeElement.setExpandable(this._hasVisibleChildren(node));
1275 parentTreeElement.updateTitle(this._updateRecordForHighlight(node));
1276 if (parentTreeElement.populated)
1277 this._updateChildren(parentTreeElement);
1278 }
1279 },
1280
1281 /**
1282 * @param {!WebInspector.ElementsTreeElement} treeElement
1283 */
1284 populateTreeElement: function(treeElement)
1285 {
1286 if (treeElement.childCount() || !treeElement.isExpandable())
1287 return;
1288
1289 this._updateModifiedParentNode(treeElement.node());
1290 },
1291
1292 /**
1293 * @param {!WebInspector.DOMNode} node
1294 * @param {boolean=} closingTag
1295 * @return {!WebInspector.ElementsTreeElement}
1296 */
1297 _createElementTreeElement: function(node, closingTag)
1298 {
1299 var treeElement = new WebInspector.ElementsTreeElement(node, closingTag) ;
1300 treeElement.setExpandable(!closingTag && this._hasVisibleChildren(node)) ;
1301 if (node.nodeType() === Node.ELEMENT_NODE && node.parentNode && node.par entNode.nodeType() === Node.DOCUMENT_NODE && !node.parentNode.parentNode)
1302 treeElement.setCollapsible(false);
1303 treeElement.selectable = this._selectEnabled;
1304 return treeElement;
1305 },
1306
1307 /**
1308 * @param {!WebInspector.ElementsTreeElement} treeElement
1309 * @param {!WebInspector.DOMNode} child
1310 * @return {?WebInspector.ElementsTreeElement}
1311 */
1312 _showChild: function(treeElement, child)
1313 {
1314 if (treeElement.isClosingTag())
1315 return null;
1316
1317 var index = this._visibleChildren(treeElement.node()).indexOf(child);
1318 if (index === -1)
1319 return null;
1320
1321 if (index >= treeElement.expandedChildrenLimit())
1322 this.setExpandedChildrenLimit(treeElement, index + 1);
1323 return /** @type {!WebInspector.ElementsTreeElement} */ (treeElement.chi ldAt(index));
1324 },
1325
1326 /**
1327 * @param {!WebInspector.DOMNode} node
1328 * @return {!Array.<!WebInspector.DOMNode>} visibleChildren
1329 */
1330 _visibleChildren: function(node)
1331 {
1332 var visibleChildren = WebInspector.ElementsTreeElement.visibleShadowRoot s(node);
1333
1334 var importedDocument = node.importedDocument();
1335 if (importedDocument)
1336 visibleChildren.push(importedDocument);
1337
1338 var templateContent = node.templateContent();
1339 if (templateContent)
1340 visibleChildren.push(templateContent);
1341
1342 var beforePseudoElement = node.beforePseudoElement();
1343 if (beforePseudoElement)
1344 visibleChildren.push(beforePseudoElement);
1345
1346 if (node.childNodeCount()) {
1347 var children = node.children();
1348 if (!this._showHTMLCommentsSetting.get())
1349 children = children.filter(n => n.nodeType() !== Node.COMMENT_NO DE);
1350 visibleChildren = visibleChildren.concat(children);
1351 }
1352
1353 var afterPseudoElement = node.afterPseudoElement();
1354 if (afterPseudoElement)
1355 visibleChildren.push(afterPseudoElement);
1356
1357 return visibleChildren;
1358 },
1359
1360 /**
1361 * @param {!WebInspector.DOMNode} node
1362 * @return {boolean}
1363 */
1364 _hasVisibleChildren: function(node)
1365 {
1366 if (node.importedDocument())
1367 return true;
1368 if (node.templateContent())
1369 return true;
1370 if (WebInspector.ElementsTreeElement.visibleShadowRoots(node).length)
1371 return true;
1372 if (node.hasPseudoElements())
1373 return true;
1374 if (node.isInsertionPoint())
1375 return true;
1376 return !!node.childNodeCount() && !WebInspector.ElementsTreeElement.canS howInlineText(node);
1377 },
1378
1379 /**
1380 * @param {!WebInspector.ElementsTreeElement} treeElement
1381 */
1382 _createExpandAllButtonTreeElement: function(treeElement)
1383 {
1384 var button = createTextButton("", handleLoadAllChildren.bind(this));
1385 button.value = "";
1386 var expandAllButtonElement = new TreeElement(button);
1387 expandAllButtonElement.selectable = false;
1388 expandAllButtonElement.expandAllButton = true;
1389 expandAllButtonElement.button = button;
1390 return expandAllButtonElement;
1391
1392 /**
1393 * @this {WebInspector.ElementsTreeOutline}
1394 * @param {!Event} event
1395 */
1396 function handleLoadAllChildren(event)
1397 {
1398 var visibleChildCount = this._visibleChildren(treeElement.node()).le ngth;
1399 this.setExpandedChildrenLimit(treeElement, Math.max(visibleChildCoun t, treeElement.expandedChildrenLimit() + WebInspector.ElementsTreeElement.Initia lChildrenLimit));
1400 event.consume();
1401 }
1402 },
1403
1404 /**
1405 * @param {!WebInspector.ElementsTreeElement} treeElement
1406 * @param {number} expandedChildrenLimit
1407 */
1408 setExpandedChildrenLimit: function(treeElement, expandedChildrenLimit)
1409 {
1410 if (treeElement.expandedChildrenLimit() === expandedChildrenLimit)
1411 return;
1412
1413 treeElement.setExpandedChildrenLimit(expandedChildrenLimit);
1414 if (treeElement.treeOutline && !this._treeElementsBeingUpdated.has(treeE lement))
1415 this._updateModifiedParentNode(treeElement.node());
1416 },
1417
1418 /**
1419 * @param {!WebInspector.ElementsTreeElement} treeElement
1420 */
1421 _updateChildren: function(treeElement)
1422 {
1423 if (!treeElement.isExpandable()) {
1424 var selectedTreeElement = treeElement.treeOutline.selectedTreeElemen t;
1425 if (selectedTreeElement && selectedTreeElement.hasAncestor(treeEleme nt))
1426 treeElement.select(true);
1427 treeElement.removeChildren();
1428 return;
1429 }
1430
1431 console.assert(!treeElement.isClosingTag());
1432
1433 treeElement.node().getChildNodes(childNodesLoaded.bind(this));
1434
1435 /**
1436 * @param {?Array.<!WebInspector.DOMNode>} children
1437 * @this {WebInspector.ElementsTreeOutline}
1438 */
1439 function childNodesLoaded(children)
1440 {
1441 // FIXME: sort this out, it should not happen.
1442 if (!children)
1443 return;
1444 this._innerUpdateChildren(treeElement);
1445 }
1446 },
1447
1448 /**
1449 * @param {!WebInspector.ElementsTreeElement} treeElement
1450 * @param {!WebInspector.DOMNode} child
1451 * @param {number} index
1452 * @param {boolean=} closingTag
1453 * @return {!WebInspector.ElementsTreeElement}
1454 */
1455 insertChildElement: function(treeElement, child, index, closingTag)
1456 {
1457 var newElement = this._createElementTreeElement(child, closingTag);
1458 treeElement.insertChild(newElement, index);
1459 return newElement;
1460 },
1461
1462 /**
1463 * @param {!WebInspector.ElementsTreeElement} treeElement
1464 * @param {!WebInspector.ElementsTreeElement} child
1465 * @param {number} targetIndex
1466 */
1467 _moveChild: function(treeElement, child, targetIndex)
1468 {
1469 if (treeElement.indexOfChild(child) === targetIndex)
1470 return;
1471 var wasSelected = child.selected;
1472 if (child.parent)
1473 child.parent.removeChild(child);
1474 treeElement.insertChild(child, targetIndex);
1475 if (wasSelected)
1476 child.select();
1477 },
1478
1479 /**
1480 * @param {!WebInspector.ElementsTreeElement} treeElement
1481 */
1482 _innerUpdateChildren: function(treeElement)
1483 {
1484 if (this._treeElementsBeingUpdated.has(treeElement))
1485 return;
1486
1487 this._treeElementsBeingUpdated.add(treeElement);
1488
1489 var node = treeElement.node();
1490 var visibleChildren = this._visibleChildren(node);
1491 var visibleChildrenSet = new Set(visibleChildren);
1492
1493 // Remove any tree elements that no longer have this node as their paren t and save
1494 // all existing elements that could be reused. This also removes closing tag element.
1495 var existingTreeElements = new Map();
1496 for (var i = treeElement.childCount() - 1; i >= 0; --i) {
1497 var existingTreeElement = treeElement.childAt(i);
1498 if (!(existingTreeElement instanceof WebInspector.ElementsTreeElemen t)) {
1499 // Remove expand all button and shadow host toolbar.
1500 treeElement.removeChildAtIndex(i);
1501 continue;
1502 }
1503 var elementsTreeElement = /** @type {!WebInspector.ElementsTreeEleme nt} */ (existingTreeElement);
1504 var existingNode = elementsTreeElement.node();
1505
1506 if (visibleChildrenSet.has(existingNode)) {
1507 existingTreeElements.set(existingNode, existingTreeElement);
1508 continue;
1509 }
1510
1511 treeElement.removeChildAtIndex(i);
1512 }
1513
1514 for (var i = 0; i < visibleChildren.length && i < treeElement.expandedCh ildrenLimit(); ++i) {
1515 var child = visibleChildren[i];
1516 var existingTreeElement = existingTreeElements.get(child) || this.fi ndTreeElement(child);
1517 if (existingTreeElement && existingTreeElement !== treeElement) {
1518 // If an existing element was found, just move it.
1519 this._moveChild(treeElement, existingTreeElement, i);
1520 } else {
1521 // No existing element found, insert a new element.
1522 var newElement = this.insertChildElement(treeElement, child, i);
1523 if (this._updateRecordForHighlight(node) && treeElement.expanded )
1524 WebInspector.ElementsTreeElement.animateOnDOMUpdate(newEleme nt);
1525 // If a node was inserted in the middle of existing list dynamic ally we might need to increase the limit.
1526 if (treeElement.childCount() > treeElement.expandedChildrenLimit ())
1527 this.setExpandedChildrenLimit(treeElement, treeElement.expan dedChildrenLimit() + 1);
1528 }
1529 }
1530
1531 // Update expand all button.
1532 var expandedChildCount = treeElement.childCount();
1533 if (visibleChildren.length > expandedChildCount) {
1534 var targetButtonIndex = expandedChildCount;
1535 if (!treeElement.expandAllButtonElement)
1536 treeElement.expandAllButtonElement = this._createExpandAllButton TreeElement(treeElement);
1537 treeElement.insertChild(treeElement.expandAllButtonElement, targetBu ttonIndex);
1538 treeElement.expandAllButtonElement.button.textContent = WebInspector .UIString("Show All Nodes (%d More)", visibleChildren.length - expandedChildCoun t);
1539 } else if (treeElement.expandAllButtonElement) {
1540 delete treeElement.expandAllButtonElement;
1541 }
1542
1543 // Insert shortcuts to distrubuted children.
1544 if (node.isInsertionPoint()) {
1545 for (var distributedNode of node.distributedNodes())
1546 treeElement.appendChild(new WebInspector.ElementsTreeOutline.Sho rtcutTreeElement(distributedNode));
1547 }
1548
1549 // Insert close tag.
1550 if (node.nodeType() === Node.ELEMENT_NODE && treeElement.isExpandable())
1551 this.insertChildElement(treeElement, node, treeElement.childCount(), true);
1552
1553 this._treeElementsBeingUpdated.delete(treeElement);
1554 },
1555
1556 /**
1557 * @param {!WebInspector.Event} event
1558 */
1559 _markersChanged: function(event)
1560 {
1561 var node = /** @type {!WebInspector.DOMNode} */ (event.data);
1562 var treeElement = node[this._treeElementSymbol];
1563 if (treeElement)
1564 treeElement.updateDecorations();
1565 },
1566
1567 __proto__: TreeOutline.prototype
1568 }; 1707 };
1569
1570 /**
1571 * @constructor
1572 */
1573 WebInspector.ElementsTreeOutline.UpdateRecord = function()
1574 {
1575 };
1576
1577 WebInspector.ElementsTreeOutline.UpdateRecord.prototype = {
1578 /**
1579 * @param {string} attrName
1580 */
1581 attributeModified: function(attrName)
1582 {
1583 if (this._removedAttributes && this._removedAttributes.has(attrName))
1584 this._removedAttributes.delete(attrName);
1585 if (!this._modifiedAttributes)
1586 this._modifiedAttributes = /** @type {!Set.<string>} */ (new Set());
1587 this._modifiedAttributes.add(attrName);
1588 },
1589
1590 /**
1591 * @param {string} attrName
1592 */
1593 attributeRemoved: function(attrName)
1594 {
1595 if (this._modifiedAttributes && this._modifiedAttributes.has(attrName))
1596 this._modifiedAttributes.delete(attrName);
1597 if (!this._removedAttributes)
1598 this._removedAttributes = /** @type {!Set.<string>} */ (new Set());
1599 this._removedAttributes.add(attrName);
1600 },
1601
1602 /**
1603 * @param {!WebInspector.DOMNode} node
1604 */
1605 nodeInserted: function(node)
1606 {
1607 this._hasChangedChildren = true;
1608 },
1609
1610 nodeRemoved: function(node)
1611 {
1612 this._hasChangedChildren = true;
1613 this._hasRemovedChildren = true;
1614 },
1615
1616 charDataModified: function()
1617 {
1618 this._charDataModified = true;
1619 },
1620
1621 childrenModified: function()
1622 {
1623 this._hasChangedChildren = true;
1624 },
1625
1626 /**
1627 * @param {string} attributeName
1628 * @return {boolean}
1629 */
1630 isAttributeModified: function(attributeName)
1631 {
1632 return this._modifiedAttributes && this._modifiedAttributes.has(attribut eName);
1633 },
1634
1635 /**
1636 * @return {boolean}
1637 */
1638 hasRemovedAttributes: function()
1639 {
1640 return !!this._removedAttributes && !!this._removedAttributes.size;
1641 },
1642
1643 /**
1644 * @return {boolean}
1645 */
1646 isCharDataModified: function()
1647 {
1648 return !!this._charDataModified;
1649 },
1650
1651 /**
1652 * @return {boolean}
1653 */
1654 hasChangedChildren: function()
1655 {
1656 return !!this._hasChangedChildren;
1657 },
1658
1659 /**
1660 * @return {boolean}
1661 */
1662 hasRemovedChildren: function()
1663 {
1664 return !!this._hasRemovedChildren;
1665 }
1666 };
1667
1668 /**
1669 * @constructor
1670 * @implements {WebInspector.Renderer}
1671 */
1672 WebInspector.ElementsTreeOutline.Renderer = function()
1673 {
1674 };
1675
1676 WebInspector.ElementsTreeOutline.Renderer.prototype = {
1677 /**
1678 * @override
1679 * @param {!Object} object
1680 * @return {!Promise.<!Element>}
1681 */
1682 render: function(object)
1683 {
1684 return new Promise(renderPromise);
1685
1686 /**
1687 * @param {function(!Element)} resolve
1688 * @param {function(!Error)} reject
1689 */
1690 function renderPromise(resolve, reject)
1691 {
1692 if (object instanceof WebInspector.DOMNode) {
1693 onNodeResolved(/** @type {!WebInspector.DOMNode} */ (object));
1694 } else if (object instanceof WebInspector.DeferredDOMNode) {
1695 (/** @type {!WebInspector.DeferredDOMNode} */ (object)).resolve( onNodeResolved);
1696 } else if (object instanceof WebInspector.RemoteObject) {
1697 var domModel = WebInspector.DOMModel.fromTarget((/** @type {!Web Inspector.RemoteObject} */ (object)).target());
1698 if (domModel)
1699 domModel.pushObjectAsNodeToFrontend(object, onNodeResolved);
1700 else
1701 reject(new Error("No dom model for given JS object target fo und."));
1702 } else {
1703 reject(new Error("Can't reveal not a node."));
1704 }
1705
1706 /**
1707 * @param {?WebInspector.DOMNode} node
1708 */
1709 function onNodeResolved(node)
1710 {
1711 if (!node) {
1712 reject(new Error("Could not resolve node."));
1713 return;
1714 }
1715 var treeOutline = new WebInspector.ElementsTreeOutline(node.domM odel(), false, false);
1716 treeOutline.rootDOMNode = node;
1717 if (!treeOutline.firstChild().isExpandable())
1718 treeOutline._element.classList.add("single-node");
1719 treeOutline.setVisible(true);
1720 treeOutline.element.treeElementForTest = treeOutline.firstChild( );
1721 resolve(treeOutline.element);
1722 }
1723 }
1724 }
1725 };
1726
1727 /**
1728 * @constructor
1729 * @extends {TreeElement}
1730 * @param {!WebInspector.DOMNodeShortcut} nodeShortcut
1731 */
1732 WebInspector.ElementsTreeOutline.ShortcutTreeElement = function(nodeShortcut)
1733 {
1734 TreeElement.call(this, "");
1735 this.listItemElement.createChild("div", "selection fill");
1736 var title = this.listItemElement.createChild("span", "elements-tree-shortcut -title");
1737 var text = nodeShortcut.nodeName.toLowerCase();
1738 if (nodeShortcut.nodeType === Node.ELEMENT_NODE)
1739 text = "<" + text + ">";
1740 title.textContent = "\u21AA " + text;
1741
1742 var link = WebInspector.DOMPresentationUtils.linkifyDeferredNodeReference(no deShortcut.deferredNode);
1743 this.listItemElement.createTextChild(" ");
1744 link.classList.add("elements-tree-shortcut-link");
1745 link.textContent = WebInspector.UIString("reveal");
1746 this.listItemElement.appendChild(link);
1747 this._nodeShortcut = nodeShortcut;
1748 };
1749
1750 WebInspector.ElementsTreeOutline.ShortcutTreeElement.prototype = {
1751 /**
1752 * @return {boolean}
1753 */
1754 get hovered()
1755 {
1756 return this._hovered;
1757 },
1758
1759 set hovered(x)
1760 {
1761 if (this._hovered === x)
1762 return;
1763 this._hovered = x;
1764 this.listItemElement.classList.toggle("hovered", x);
1765 },
1766
1767 /**
1768 * @return {number}
1769 */
1770 backendNodeId: function()
1771 {
1772 return this._nodeShortcut.deferredNode.backendNodeId();
1773 },
1774
1775 /**
1776 * @override
1777 * @param {boolean=} selectedByUser
1778 * @return {boolean}
1779 */
1780 onselect: function(selectedByUser)
1781 {
1782 if (!selectedByUser)
1783 return true;
1784 this._nodeShortcut.deferredNode.highlight();
1785 this._nodeShortcut.deferredNode.resolve(resolved.bind(this));
1786 /**
1787 * @param {?WebInspector.DOMNode} node
1788 * @this {WebInspector.ElementsTreeOutline.ShortcutTreeElement}
1789 */
1790 function resolved(node)
1791 {
1792 if (node) {
1793 this.treeOutline._selectedDOMNode = node;
1794 this.treeOutline._selectedNodeChanged();
1795 }
1796 }
1797 return true;
1798 },
1799
1800 __proto__: TreeElement.prototype
1801 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698