9d4d576cd370d0dbbcc36825f9e47ba72aa41383
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / DOMTreeContentView.js
1 /*
2  * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WI.DOMTreeContentView = class DOMTreeContentView extends WI.ContentView
27 {
28     constructor(representedObject)
29     {
30         console.assert(representedObject);
31
32         super(representedObject);
33
34         this._compositingBordersButtonNavigationItem = new WI.ActivateButtonNavigationItem("layer-borders", WI.UIString("Show compositing borders"), WI.UIString("Hide compositing borders"), "Images/LayerBorders.svg", 13, 13);
35         this._compositingBordersButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._toggleCompositingBorders, this);
36         this._compositingBordersButtonNavigationItem.enabled = !!PageAgent.getCompositingBordersVisible;
37         this._compositingBordersButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
38
39         WI.showPaintRectsSetting.addEventListener(WI.Setting.Event.Changed, this._showPaintRectsSettingChanged, this);
40         this._paintFlashingButtonNavigationItem = new WI.ActivateButtonNavigationItem("paint-flashing", WI.UIString("Enable paint flashing"), WI.UIString("Disable paint flashing"), "Images/Paint.svg", 16, 16);
41         this._paintFlashingButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._togglePaintFlashing, this);
42         this._paintFlashingButtonNavigationItem.enabled = !!PageAgent.setShowPaintRects;
43         this._paintFlashingButtonNavigationItem.activated = PageAgent.setShowPaintRects && WI.showPaintRectsSetting.value;
44         this._paintFlashingButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
45
46         WI.showShadowDOMSetting.addEventListener(WI.Setting.Event.Changed, this._showShadowDOMSettingChanged, this);
47         this._showsShadowDOMButtonNavigationItem = new WI.ActivateButtonNavigationItem("shows-shadow-DOM", WI.UIString("Show shadow DOM nodes"), WI.UIString("Hide shadow DOM nodes"), "Images/ShadowDOM.svg", 13, 13);
48         this._showsShadowDOMButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._toggleShowsShadowDOMSetting, this);
49         this._showsShadowDOMButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
50         this._showShadowDOMSettingChanged();
51
52         this._showPrintStylesButtonNavigationItem = new WI.ActivateButtonNavigationItem("print-styles", WI.UIString("Force Print Media Styles"), WI.UIString("Use Default Media Styles"), "Images/Printer.svg", 16, 16);
53         this._showPrintStylesButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._togglePrintStyles, this);
54         this._showPrintStylesButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
55         this._showPrintStylesChanged();
56
57         WI.settings.showRulers.addEventListener(WI.Setting.Event.Changed, this._showRulersChanged, this);
58         this._showRulersButtonNavigationItem = new WI.ActivateButtonNavigationItem("show-rulers", WI.UIString("Show Rulers"), WI.UIString("Hide Rulers"), "Images/Rulers.svg", 16, 16);
59         this._showRulersButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._toggleShowRulers, this);
60         this._showRulersButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
61         this._showRulersChanged();
62
63         this.element.classList.add("dom-tree");
64         this.element.addEventListener("click", this._mouseWasClicked.bind(this), false);
65
66         this._domTreeOutline = new WI.DOMTreeOutline(true, true, true);
67         this._domTreeOutline.addEventListener(WI.TreeOutline.Event.ElementAdded, this._domTreeElementAdded, this);
68         this._domTreeOutline.addEventListener(WI.DOMTreeOutline.Event.SelectedNodeChanged, this._selectedNodeDidChange, this);
69         this._domTreeOutline.wireToDomAgent();
70         this._domTreeOutline.editable = true;
71         this.element.appendChild(this._domTreeOutline.element);
72
73         WI.domManager.addEventListener(WI.DOMManager.Event.AttributeModified, this._domNodeChanged, this);
74         WI.domManager.addEventListener(WI.DOMManager.Event.AttributeRemoved, this._domNodeChanged, this);
75         WI.domManager.addEventListener(WI.DOMManager.Event.CharacterDataModified, this._domNodeChanged, this);
76
77         this._lastSelectedNodePathSetting = new WI.Setting("last-selected-node-path", null);
78
79         this._numberOfSearchResults = null;
80
81         this._breakpointGutterEnabled = false;
82         this._pendingBreakpointNodeIdentifiers = new Set;
83
84         if (WI.domDebuggerManager.supported) {
85             WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
86
87             WI.domDebuggerManager.addEventListener(WI.DOMDebuggerManager.Event.DOMBreakpointAdded, this._domBreakpointAddedOrRemoved, this);
88             WI.domDebuggerManager.addEventListener(WI.DOMDebuggerManager.Event.DOMBreakpointRemoved, this._domBreakpointAddedOrRemoved, this);
89
90             WI.DOMBreakpoint.addEventListener(WI.DOMBreakpoint.Event.DisabledStateDidChange, this._domBreakpointDisabledStateDidChange, this);
91             WI.DOMBreakpoint.addEventListener(WI.DOMBreakpoint.Event.ResolvedStateDidChange, this._domBreakpointResolvedStateDidChange, this);
92
93             this._breakpointsEnabledDidChange();
94         }
95     }
96
97     // Public
98
99     get navigationItems()
100     {
101         let items = [this._showPrintStylesButtonNavigationItem, this._showsShadowDOMButtonNavigationItem];
102
103         // COMPATIBILITY (iOS 11.3)
104         if (PageAgent.setShowRulers)
105             items.unshift(this._showRulersButtonNavigationItem);
106
107         if (!WI.settings.experimentalEnableLayersTab.value)
108             items.push(this._compositingBordersButtonNavigationItem, this._paintFlashingButtonNavigationItem);
109
110         return items;
111     }
112
113     get domTreeOutline()
114     {
115         return this._domTreeOutline;
116     }
117
118     get scrollableElements()
119     {
120         return [this.element];
121     }
122
123     get breakpointGutterEnabled()
124     {
125         return this._breakpointGutterEnabled;
126     }
127
128     set breakpointGutterEnabled(flag)
129     {
130         if (this._breakpointGutterEnabled === flag)
131             return;
132
133         this._breakpointGutterEnabled = flag;
134         this.element.classList.toggle("show-gutter", this._breakpointGutterEnabled);
135     }
136
137     shown()
138     {
139         super.shown();
140
141         this._domTreeOutline.setVisible(true, WI.isConsoleFocused());
142         this._updateCompositingBordersButtonToMatchPageSettings();
143
144         if (!this._domTreeOutline.rootDOMNode)
145             return;
146
147         this._restoreBreakpointsAfterUpdate();
148     }
149
150     hidden()
151     {
152         super.hidden();
153
154         WI.domManager.hideDOMNodeHighlight();
155         this._domTreeOutline.setVisible(false);
156     }
157
158     closed()
159     {
160         super.closed();
161
162         WI.showPaintRectsSetting.removeEventListener(null, null, this);
163         WI.showShadowDOMSetting.removeEventListener(null, null, this);
164         WI.settings.showRulers.removeEventListener(null, null, this);
165         WI.debuggerManager.removeEventListener(null, null, this);
166         WI.domManager.removeEventListener(null, null, this);
167         WI.domDebuggerManager.removeEventListener(null, null, this);
168         WI.DOMBreakpoint.removeEventListener(null, null, this);
169
170         this._domTreeOutline.close();
171         this._pendingBreakpointNodeIdentifiers.clear();
172     }
173
174     get selectionPathComponents()
175     {
176         var treeElement = this._domTreeOutline.selectedTreeElement;
177         var pathComponents = [];
178
179         while (treeElement && !treeElement.root) {
180             // The close tag is contained within the element it closes. So skip it since we don't want to
181             // show the same node twice in the hierarchy.
182             if (treeElement.isCloseTag()) {
183                 treeElement = treeElement.parent;
184                 continue;
185             }
186
187             var pathComponent = new WI.DOMTreeElementPathComponent(treeElement, treeElement.representedObject);
188             pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.Clicked, this._pathComponentSelected, this);
189             pathComponents.unshift(pathComponent);
190             treeElement = treeElement.parent;
191         }
192
193         return pathComponents;
194     }
195
196     restoreFromCookie(cookie)
197     {
198         if (!cookie || !cookie.nodeToSelect)
199             return;
200
201         this.selectAndRevealDOMNode(cookie.nodeToSelect);
202
203         // Because nodeToSelect is ephemeral, we don't want to keep
204         // it around in the back-forward history entries.
205         cookie.nodeToSelect = undefined;
206     }
207
208     selectAndRevealDOMNode(domNode, preventFocusChange)
209     {
210         this._domTreeOutline.selectDOMNode(domNode, !preventFocusChange);
211     }
212
213     handleCopyEvent(event)
214     {
215         var selectedDOMNode = this._domTreeOutline.selectedDOMNode();
216         if (!selectedDOMNode)
217             return;
218
219         event.clipboardData.clearData();
220         event.preventDefault();
221
222         selectedDOMNode.copyNode();
223     }
224
225     get supportsSave()
226     {
227         return WI.canArchiveMainFrame();
228     }
229
230     get saveData()
231     {
232         return {customSaveHandler: () => { WI.archiveMainFrame(); }};
233     }
234
235     get supportsSearch()
236     {
237         return true;
238     }
239
240     get numberOfSearchResults()
241     {
242         return this._numberOfSearchResults;
243     }
244
245     get hasPerformedSearch()
246     {
247         return this._numberOfSearchResults !== null;
248     }
249
250     set automaticallyRevealFirstSearchResult(reveal)
251     {
252         this._automaticallyRevealFirstSearchResult = reveal;
253
254         // If we haven't shown a search result yet, reveal one now.
255         if (this._automaticallyRevealFirstSearchResult && this._numberOfSearchResults > 0) {
256             if (this._currentSearchResultIndex === -1)
257                 this.revealNextSearchResult();
258         }
259     }
260
261     performSearch(query)
262     {
263         if (this._searchQuery === query)
264             return;
265
266         if (this._searchIdentifier) {
267             DOMAgent.discardSearchResults(this._searchIdentifier);
268             this._hideSearchHighlights();
269         }
270
271         this._searchQuery = query;
272         this._searchIdentifier = null;
273         this._numberOfSearchResults = null;
274         this._currentSearchResultIndex = -1;
275
276         function searchResultsReady(error, searchIdentifier, resultsCount)
277         {
278             if (error)
279                 return;
280
281             this._searchIdentifier = searchIdentifier;
282             this._numberOfSearchResults = resultsCount;
283
284             this.dispatchEventToListeners(WI.ContentView.Event.NumberOfSearchResultsDidChange);
285
286             this._showSearchHighlights();
287
288             if (this._automaticallyRevealFirstSearchResult)
289                 this.revealNextSearchResult();
290         }
291
292         function contextNodesReady(nodeIds)
293         {
294             DOMAgent.performSearch(query, nodeIds, searchResultsReady.bind(this));
295         }
296
297         this.getSearchContextNodes(contextNodesReady.bind(this));
298     }
299
300     getSearchContextNodes(callback)
301     {
302         // Overwrite this to limit the search to just a subtree.
303         // Passing undefined will make DOMAgent.performSearch search through all the documents.
304         callback(undefined);
305     }
306
307     searchCleared()
308     {
309         if (this._searchIdentifier) {
310             DOMAgent.discardSearchResults(this._searchIdentifier);
311             this._hideSearchHighlights();
312         }
313
314         this._searchQuery = null;
315         this._searchIdentifier = null;
316         this._numberOfSearchResults = null;
317         this._currentSearchResultIndex = -1;
318     }
319
320     revealPreviousSearchResult(changeFocus)
321     {
322         if (!this._numberOfSearchResults)
323             return;
324
325         if (this._currentSearchResultIndex > 0)
326             --this._currentSearchResultIndex;
327         else
328             this._currentSearchResultIndex = this._numberOfSearchResults - 1;
329
330         this._revealSearchResult(this._currentSearchResultIndex, changeFocus);
331     }
332
333     revealNextSearchResult(changeFocus)
334     {
335         if (!this._numberOfSearchResults)
336             return;
337
338         if (this._currentSearchResultIndex + 1 < this._numberOfSearchResults)
339             ++this._currentSearchResultIndex;
340         else
341             this._currentSearchResultIndex = 0;
342
343         this._revealSearchResult(this._currentSearchResultIndex, changeFocus);
344     }
345
346     // Protected
347
348     layout()
349     {
350         this._domTreeOutline.updateSelection();
351
352         if (this.layoutReason === WI.View.LayoutReason.Resize)
353             this._domTreeOutline.selectDOMNode(this._domTreeOutline.selectedDOMNode());
354     }
355
356     // Private
357
358     _revealSearchResult(index, changeFocus)
359     {
360         console.assert(this._searchIdentifier);
361
362         var searchIdentifier = this._searchIdentifier;
363
364         function revealResult(error, nodeIdentifiers)
365         {
366             if (error)
367                 return;
368
369             // Bail if the searchIdentifier changed since we started.
370             if (this._searchIdentifier !== searchIdentifier)
371                 return;
372
373             console.assert(nodeIdentifiers.length === 1);
374
375             var domNode = WI.domManager.nodeForId(nodeIdentifiers[0]);
376             console.assert(domNode);
377             if (!domNode)
378                 return;
379
380             this._domTreeOutline.selectDOMNode(domNode, changeFocus);
381
382             var selectedTreeElement = this._domTreeOutline.selectedTreeElement;
383             if (selectedTreeElement)
384                 selectedTreeElement.emphasizeSearchHighlight();
385         }
386
387         DOMAgent.getSearchResults(this._searchIdentifier, index, index + 1, revealResult.bind(this));
388     }
389
390     _restoreSelectedNodeAfterUpdate(documentURL, defaultNode)
391     {
392         if (!WI.domManager.restoreSelectedNodeIsAllowed)
393             return;
394
395         function selectNode(lastSelectedNode)
396         {
397             var nodeToFocus = lastSelectedNode;
398             if (!nodeToFocus)
399                 nodeToFocus = defaultNode;
400
401             if (!nodeToFocus)
402                 return;
403
404             this._dontSetLastSelectedNodePath = true;
405             this.selectAndRevealDOMNode(nodeToFocus, WI.isConsoleFocused());
406             this._dontSetLastSelectedNodePath = false;
407
408             // If this wasn't the last selected node, then expand it.
409             if (!lastSelectedNode && this._domTreeOutline.selectedTreeElement)
410                 this._domTreeOutline.selectedTreeElement.expand();
411         }
412
413         function selectLastSelectedNode(nodeId)
414         {
415             if (!WI.domManager.restoreSelectedNodeIsAllowed)
416                 return;
417
418             selectNode.call(this, WI.domManager.nodeForId(nodeId));
419         }
420
421         if (documentURL && this._lastSelectedNodePathSetting.value && this._lastSelectedNodePathSetting.value.path && this._lastSelectedNodePathSetting.value.url === documentURL.hash)
422             WI.domManager.pushNodeByPathToFrontend(this._lastSelectedNodePathSetting.value.path, selectLastSelectedNode.bind(this));
423         else
424             selectNode.call(this);
425     }
426
427     _domTreeElementAdded(event)
428     {
429         if (!this._pendingBreakpointNodeIdentifiers.size)
430             return;
431
432         let treeElement = event.data.element;
433         let node = treeElement.representedObject;
434         console.assert(node instanceof WI.DOMNode);
435         if (!(node instanceof WI.DOMNode))
436             return;
437
438         if (!this._pendingBreakpointNodeIdentifiers.delete(node.id))
439             return;
440
441         this._updateBreakpointStatus(node.id);
442     }
443
444     _selectedNodeDidChange(event)
445     {
446         var selectedDOMNode = this._domTreeOutline.selectedDOMNode();
447         if (selectedDOMNode && !this._dontSetLastSelectedNodePath)
448             this._lastSelectedNodePathSetting.value = {url: WI.networkManager.mainFrame.url.hash, path: selectedDOMNode.path()};
449
450         if (selectedDOMNode)
451             WI.domManager.setInspectedNode(selectedDOMNode);
452
453         this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
454     }
455
456     _pathComponentSelected(event)
457     {
458         if (!event.data.pathComponent)
459             return;
460
461         console.assert(event.data.pathComponent instanceof WI.DOMTreeElementPathComponent);
462         console.assert(event.data.pathComponent.domTreeElement instanceof WI.DOMTreeElement);
463
464         this._domTreeOutline.selectDOMNode(event.data.pathComponent.domTreeElement.representedObject, true);
465     }
466
467     _domNodeChanged(event)
468     {
469         var selectedDOMNode = this._domTreeOutline.selectedDOMNode();
470         if (selectedDOMNode !== event.data.node)
471             return;
472
473         this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
474     }
475
476     _mouseWasClicked(event)
477     {
478         var anchorElement = event.target.enclosingNodeOrSelfWithNodeName("a");
479         if (!anchorElement || !anchorElement.href)
480             return;
481
482         // Prevent the link from navigating, since we don't do any navigation by following links normally.
483         event.preventDefault();
484         event.stopPropagation();
485
486         if (WI.isBeingEdited(anchorElement)) {
487             // Don't follow the link when it is being edited.
488             return;
489         }
490
491         // Cancel any pending link navigation.
492         if (this._followLinkTimeoutIdentifier) {
493             clearTimeout(this._followLinkTimeoutIdentifier);
494             delete this._followLinkTimeoutIdentifier;
495         }
496
497         // If this is a double-click (or multiple-click), return early.
498         if (event.detail > 1)
499             return;
500
501         function followLink()
502         {
503             // Since followLink is delayed, the call to WI.openURL can't look at window.event
504             // to see if the command key is down like it normally would. So we need to do that check
505             // before calling WI.openURL.
506             const options = {
507                 alwaysOpenExternally: event ? event.metaKey : false,
508                 lineNumber: anchorElement.lineNumber,
509                 ignoreNetworkTab: true,
510                 ignoreSearchTab: true,
511             };
512             WI.openURL(anchorElement.href, this._frame, options);
513         }
514
515         // Start a timeout since this is a single click, if the timeout is canceled before it fires,
516         // then a double-click happened or another link was clicked.
517         // FIXME: The duration might be longer or shorter than the user's configured double click speed.
518         this._followLinkTimeoutIdentifier = setTimeout(followLink.bind(this), 333);
519     }
520
521     _toggleCompositingBorders(event)
522     {
523         console.assert(PageAgent.setCompositingBordersVisible);
524
525         var activated = !this._compositingBordersButtonNavigationItem.activated;
526         this._compositingBordersButtonNavigationItem.activated = activated;
527         PageAgent.setCompositingBordersVisible(activated);
528     }
529
530     _togglePaintFlashing(event)
531     {
532         WI.showPaintRectsSetting.value = !WI.showPaintRectsSetting.value;
533     }
534
535     _updateCompositingBordersButtonToMatchPageSettings()
536     {
537         if (WI.settings.experimentalEnableLayersTab.value)
538             return;
539
540         var button = this._compositingBordersButtonNavigationItem;
541
542         // We need to sync with the page settings since these can be controlled
543         // in a different way than just using the navigation bar button.
544         PageAgent.getCompositingBordersVisible(function(error, compositingBordersVisible) {
545             button.activated = error ? false : compositingBordersVisible;
546             button.enabled = error !== "unsupported";
547         });
548     }
549
550     _showPaintRectsSettingChanged(event)
551     {
552         console.assert(PageAgent.setShowPaintRects);
553
554         this._paintFlashingButtonNavigationItem.activated = WI.showPaintRectsSetting.value;
555
556         PageAgent.setShowPaintRects(this._paintFlashingButtonNavigationItem.activated);
557     }
558
559     _showShadowDOMSettingChanged(event)
560     {
561         this._showsShadowDOMButtonNavigationItem.activated = WI.showShadowDOMSetting.value;
562     }
563
564     _toggleShowsShadowDOMSetting(event)
565     {
566         WI.showShadowDOMSetting.value = !WI.showShadowDOMSetting.value;
567     }
568
569     _showPrintStylesChanged()
570     {
571         this._showPrintStylesButtonNavigationItem.activated = WI.printStylesEnabled;
572
573         let mediaType = WI.printStylesEnabled ? "print" : "";
574         PageAgent.setEmulatedMedia(mediaType);
575
576         WI.cssManager.mediaTypeChanged();
577     }
578
579     _togglePrintStyles(event)
580     {
581         WI.printStylesEnabled = !WI.printStylesEnabled;
582         this._showPrintStylesChanged();
583     }
584
585     _showRulersChanged()
586     {
587         this._showRulersButtonNavigationItem.activated = WI.settings.showRulers.value;
588
589         // COMPATIBILITY (iOS 11.3)
590         if (!PageAgent.setShowRulers)
591             return;
592
593         PageAgent.setShowRulers(this._showRulersButtonNavigationItem.activated);
594     }
595
596     _toggleShowRulers(event)
597     {
598         WI.settings.showRulers.value = !WI.settings.showRulers.value;
599
600         this._showRulersChanged();
601     }
602
603     _showSearchHighlights()
604     {
605         console.assert(this._searchIdentifier);
606
607         this._searchResultNodes = [];
608
609         var searchIdentifier = this._searchIdentifier;
610
611         DOMAgent.getSearchResults(this._searchIdentifier, 0, this._numberOfSearchResults, function(error, nodeIdentifiers) {
612             if (error)
613                 return;
614
615             if (this._searchIdentifier !== searchIdentifier)
616                 return;
617
618             console.assert(nodeIdentifiers.length === this._numberOfSearchResults);
619
620             for (var i = 0; i < nodeIdentifiers.length; ++i) {
621                 var domNode = WI.domManager.nodeForId(nodeIdentifiers[i]);
622                 console.assert(domNode);
623                 if (!domNode)
624                     continue;
625
626                 this._searchResultNodes.push(domNode);
627
628                 var treeElement = this._domTreeOutline.findTreeElement(domNode);
629                 console.assert(treeElement);
630                 if (treeElement)
631                     treeElement.highlightSearchResults(this._searchQuery);
632             }
633         }.bind(this));
634     }
635
636     _hideSearchHighlights()
637     {
638         if (!this._searchResultNodes)
639             return;
640
641         for (var domNode of this._searchResultNodes) {
642             var treeElement = this._domTreeOutline.findTreeElement(domNode);
643             if (treeElement)
644                 treeElement.hideSearchHighlights();
645         }
646
647         delete this._searchResultNodes;
648     }
649
650     _domBreakpointAddedOrRemoved(event)
651     {
652         let breakpoint = event.data.breakpoint;
653         this._updateBreakpointStatus(breakpoint.domNodeIdentifier);
654     }
655
656     _domBreakpointDisabledStateDidChange(event)
657     {
658         let breakpoint = event.target;
659         this._updateBreakpointStatus(breakpoint.domNodeIdentifier);
660     }
661
662     _domBreakpointResolvedStateDidChange(event)
663     {
664         let breakpoint = event.target;
665         let nodeIdentifier = breakpoint.domNodeIdentifier || event.data.oldNodeIdentifier;
666         this._updateBreakpointStatus(nodeIdentifier);
667     }
668
669     _updateBreakpointStatus(nodeIdentifier)
670     {
671         let domNode = WI.domManager.nodeForId(nodeIdentifier);
672         if (!domNode)
673             return;
674
675         let treeElement = this._domTreeOutline.findTreeElement(domNode);
676         if (!treeElement) {
677             this._pendingBreakpointNodeIdentifiers.add(nodeIdentifier);
678             return;
679         }
680
681         let breakpoints = WI.domDebuggerManager.domBreakpointsForNode(domNode);
682         if (!breakpoints.length) {
683             treeElement.breakpointStatus = WI.DOMTreeElement.BreakpointStatus.None;
684             return;
685         }
686
687         this.breakpointGutterEnabled = true;
688
689         let disabled = breakpoints.some((item) => item.disabled);
690         treeElement.breakpointStatus = disabled ? WI.DOMTreeElement.BreakpointStatus.DisabledBreakpoint : WI.DOMTreeElement.BreakpointStatus.Breakpoint;
691     }
692
693     _restoreBreakpointsAfterUpdate()
694     {
695         this._pendingBreakpointNodeIdentifiers.clear();
696
697         this.breakpointGutterEnabled = false;
698
699         let updatedNodes = new Set;
700         for (let breakpoint of WI.domDebuggerManager.domBreakpoints) {
701             if (updatedNodes.has(breakpoint.domNodeIdentifier))
702                 continue;
703
704             this._updateBreakpointStatus(breakpoint.domNodeIdentifier);
705         }
706     }
707
708     _breakpointsEnabledDidChange(event)
709     {
710         this._domTreeOutline.element.classList.toggle("breakpoints-disabled", !WI.debuggerManager.breakpointsEnabled);
711     }
712 };