Web Inspector: Search: allow DOM searches to be case sensitive
[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.settings.showPaintRects.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.settings.showPaintRects.value;
44         this._paintFlashingButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
45
46         WI.settings.showShadowDOM.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.allowsEmptySelection = false;
68         this._domTreeOutline.allowsMultipleSelection = true;
69         this._domTreeOutline.addEventListener(WI.TreeOutline.Event.ElementAdded, this._domTreeElementAdded, this);
70         this._domTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._domTreeSelectionDidChange, this);
71         this._domTreeOutline.addEventListener(WI.DOMTreeOutline.Event.SelectedNodeChanged, this._selectedNodeDidChange, this);
72         this._domTreeOutline.wireToDomAgent();
73         this._domTreeOutline.editable = true;
74         this.element.appendChild(this._domTreeOutline.element);
75
76         WI.domManager.addEventListener(WI.DOMManager.Event.AttributeModified, this._domNodeChanged, this);
77         WI.domManager.addEventListener(WI.DOMManager.Event.AttributeRemoved, this._domNodeChanged, this);
78         WI.domManager.addEventListener(WI.DOMManager.Event.CharacterDataModified, this._domNodeChanged, this);
79
80         WI.cssManager.addEventListener(WI.CSSManager.Event.DefaultAppearanceDidChange, this._defaultAppearanceDidChange, this);
81
82         this._lastSelectedNodePathSetting = new WI.Setting("last-selected-node-path", null);
83
84         this._numberOfSearchResults = null;
85
86         this._breakpointGutterEnabled = false;
87         this._pendingBreakpointNodeIdentifiers = new Set;
88
89         if (WI.cssManager.canForceAppearance())
90             this._defaultAppearanceDidChange();
91
92         if (WI.domDebuggerManager.supported) {
93             WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.BreakpointsEnabledDidChange, this._breakpointsEnabledDidChange, this);
94
95             WI.domDebuggerManager.addEventListener(WI.DOMDebuggerManager.Event.DOMBreakpointAdded, this._domBreakpointAddedOrRemoved, this);
96             WI.domDebuggerManager.addEventListener(WI.DOMDebuggerManager.Event.DOMBreakpointRemoved, this._domBreakpointAddedOrRemoved, this);
97
98             WI.DOMBreakpoint.addEventListener(WI.DOMBreakpoint.Event.DisabledStateChanged, this._handleDOMBreakpointDisabledStateChanged, this);
99             WI.DOMBreakpoint.addEventListener(WI.DOMBreakpoint.Event.DOMNodeChanged, this._handleDOMBreakpointDOMNodeChanged, this);
100
101             this._breakpointsEnabledDidChange();
102         }
103     }
104
105     // Public
106
107     get navigationItems()
108     {
109         let items = [this._showPrintStylesButtonNavigationItem, this._showsShadowDOMButtonNavigationItem];
110
111         if (this._forceAppearanceButtonNavigationItem)
112             items.unshift(this._forceAppearanceButtonNavigationItem);
113
114         // COMPATIBILITY (iOS 11.3)
115         if (window.PageAgent && PageAgent.setShowRulers)
116             items.unshift(this._showRulersButtonNavigationItem);
117
118         if (!WI.settings.experimentalEnableLayersTab.value)
119             items.push(this._compositingBordersButtonNavigationItem, this._paintFlashingButtonNavigationItem);
120
121         return items;
122     }
123
124     get domTreeOutline()
125     {
126         return this._domTreeOutline;
127     }
128
129     get scrollableElements()
130     {
131         return [this.element];
132     }
133
134     get breakpointGutterEnabled()
135     {
136         return this._breakpointGutterEnabled;
137     }
138
139     set breakpointGutterEnabled(flag)
140     {
141         if (this._breakpointGutterEnabled === flag)
142             return;
143
144         this._breakpointGutterEnabled = flag;
145         this.element.classList.toggle("show-gutter", this._breakpointGutterEnabled);
146     }
147
148     shown()
149     {
150         super.shown();
151
152         this._domTreeOutline.setVisible(true, WI.isConsoleFocused());
153         this._updateCompositingBordersButtonToMatchPageSettings();
154
155         if (!this._domTreeOutline.rootDOMNode)
156             return;
157
158         this._restoreBreakpointsAfterUpdate();
159     }
160
161     hidden()
162     {
163         super.hidden();
164
165         WI.domManager.hideDOMNodeHighlight();
166         this._domTreeOutline.setVisible(false);
167     }
168
169     closed()
170     {
171         super.closed();
172
173         WI.settings.showPaintRects.removeEventListener(null, null, this);
174         WI.settings.showShadowDOM.removeEventListener(null, null, this);
175         WI.settings.showRulers.removeEventListener(null, null, this);
176         WI.debuggerManager.removeEventListener(null, null, this);
177         WI.domManager.removeEventListener(null, null, this);
178         WI.domDebuggerManager.removeEventListener(null, null, this);
179         WI.DOMBreakpoint.removeEventListener(null, null, this);
180
181         this._domTreeOutline.close();
182         this._pendingBreakpointNodeIdentifiers.clear();
183     }
184
185     get selectionPathComponents()
186     {
187         var treeElement = this._domTreeOutline.selectedTreeElement;
188         var pathComponents = [];
189
190         while (treeElement && !treeElement.root) {
191             // The close tag is contained within the element it closes. So skip it since we don't want to
192             // show the same node twice in the hierarchy.
193             if (treeElement.isCloseTag()) {
194                 treeElement = treeElement.parent;
195                 continue;
196             }
197
198             var pathComponent = new WI.DOMTreeElementPathComponent(treeElement, treeElement.representedObject);
199             pathComponent.addEventListener(WI.HierarchicalPathComponent.Event.Clicked, this._pathComponentSelected, this);
200             pathComponents.unshift(pathComponent);
201             treeElement = treeElement.parent;
202         }
203
204         return pathComponents;
205     }
206
207     restoreFromCookie(cookie)
208     {
209         if (!cookie || !cookie.nodeToSelect)
210             return;
211
212         this.selectAndRevealDOMNode(cookie.nodeToSelect);
213
214         // Because nodeToSelect is ephemeral, we don't want to keep
215         // it around in the back-forward history entries.
216         cookie.nodeToSelect = undefined;
217     }
218
219     selectAndRevealDOMNode(domNode, preventFocusChange)
220     {
221         this._domTreeOutline.selectDOMNode(domNode, !preventFocusChange);
222     }
223
224     handleCopyEvent(event)
225     {
226         var selectedDOMNode = this._domTreeOutline.selectedDOMNode();
227         if (!selectedDOMNode)
228             return;
229
230         event.clipboardData.clearData();
231         event.preventDefault();
232
233         selectedDOMNode.copyNode();
234     }
235
236     get supportsSave()
237     {
238         return WI.canArchiveMainFrame();
239     }
240
241     get saveData()
242     {
243         return {customSaveHandler: () => { WI.archiveMainFrame(); }};
244     }
245
246     get supportsSearch()
247     {
248         return true;
249     }
250
251     get numberOfSearchResults()
252     {
253         return this._numberOfSearchResults;
254     }
255
256     get hasPerformedSearch()
257     {
258         return this._numberOfSearchResults !== null;
259     }
260
261     set automaticallyRevealFirstSearchResult(reveal)
262     {
263         this._automaticallyRevealFirstSearchResult = reveal;
264
265         // If we haven't shown a search result yet, reveal one now.
266         if (this._automaticallyRevealFirstSearchResult && this._numberOfSearchResults > 0) {
267             if (this._currentSearchResultIndex === -1)
268                 this.revealNextSearchResult();
269         }
270     }
271
272     performSearch(query)
273     {
274         if (this._searchQuery === query)
275             return;
276
277         if (this._searchIdentifier) {
278             DOMAgent.discardSearchResults(this._searchIdentifier);
279             this._hideSearchHighlights();
280         }
281
282         this._searchQuery = query;
283         this._searchIdentifier = null;
284         this._numberOfSearchResults = null;
285         this._currentSearchResultIndex = -1;
286
287         function searchResultsReady(error, searchIdentifier, resultsCount)
288         {
289             if (error)
290                 return;
291
292             this._searchIdentifier = searchIdentifier;
293             this._numberOfSearchResults = resultsCount;
294
295             this.dispatchEventToListeners(WI.ContentView.Event.NumberOfSearchResultsDidChange);
296
297             this._showSearchHighlights();
298
299             if (this._automaticallyRevealFirstSearchResult)
300                 this.revealNextSearchResult();
301         }
302
303         function contextNodesReady(nodeIds)
304         {
305             if (this._searchQuery !== query)
306                 return;
307
308             let commandArguments = {
309                 query: this._searchQuery,
310                 nodeIds,
311                 caseSensitive: WI.SearchUtilities.defaultSettings.caseSensitive.value,
312             };
313             DOMAgent.performSearch.invoke(commandArguments, searchResultsReady.bind(this));
314         }
315
316         this.getSearchContextNodes(contextNodesReady.bind(this));
317     }
318
319     getSearchContextNodes(callback)
320     {
321         // Overwrite this to limit the search to just a subtree.
322         // Passing undefined will make DOMAgent.performSearch search through all the documents.
323         callback(undefined);
324     }
325
326     searchCleared()
327     {
328         if (this._searchIdentifier) {
329             DOMAgent.discardSearchResults(this._searchIdentifier);
330             this._hideSearchHighlights();
331         }
332
333         this._searchQuery = null;
334         this._searchIdentifier = null;
335         this._numberOfSearchResults = null;
336         this._currentSearchResultIndex = -1;
337     }
338
339     revealPreviousSearchResult(changeFocus)
340     {
341         if (!this._numberOfSearchResults)
342             return;
343
344         if (this._currentSearchResultIndex > 0)
345             --this._currentSearchResultIndex;
346         else
347             this._currentSearchResultIndex = this._numberOfSearchResults - 1;
348
349         this._revealSearchResult(this._currentSearchResultIndex, changeFocus);
350     }
351
352     revealNextSearchResult(changeFocus)
353     {
354         if (!this._numberOfSearchResults)
355             return;
356
357         if (this._currentSearchResultIndex + 1 < this._numberOfSearchResults)
358             ++this._currentSearchResultIndex;
359         else
360             this._currentSearchResultIndex = 0;
361
362         this._revealSearchResult(this._currentSearchResultIndex, changeFocus);
363     }
364
365     // Protected
366
367     layout()
368     {
369         this._domTreeOutline.updateSelectionArea();
370
371         if (this.layoutReason === WI.View.LayoutReason.Resize)
372             this._domTreeOutline.selectDOMNode(this._domTreeOutline.selectedDOMNode());
373     }
374
375     // Private
376
377     _revealSearchResult(index, changeFocus)
378     {
379         console.assert(this._searchIdentifier);
380
381         var searchIdentifier = this._searchIdentifier;
382
383         function revealResult(error, nodeIdentifiers)
384         {
385             if (error)
386                 return;
387
388             // Bail if the searchIdentifier changed since we started.
389             if (this._searchIdentifier !== searchIdentifier)
390                 return;
391
392             console.assert(nodeIdentifiers.length === 1);
393
394             var domNode = WI.domManager.nodeForId(nodeIdentifiers[0]);
395             console.assert(domNode);
396             if (!domNode)
397                 return;
398
399             this._domTreeOutline.selectDOMNode(domNode, changeFocus);
400
401             var selectedTreeElement = this._domTreeOutline.selectedTreeElement;
402             if (selectedTreeElement)
403                 selectedTreeElement.emphasizeSearchHighlight();
404         }
405
406         DOMAgent.getSearchResults(this._searchIdentifier, index, index + 1, revealResult.bind(this));
407     }
408
409     _restoreSelectedNodeAfterUpdate(documentURL, defaultNode)
410     {
411         if (!WI.domManager.restoreSelectedNodeIsAllowed)
412             return;
413
414         function selectNode(lastSelectedNode)
415         {
416             var nodeToFocus = lastSelectedNode;
417             if (!nodeToFocus)
418                 nodeToFocus = defaultNode;
419
420             if (!nodeToFocus)
421                 return;
422
423             this._dontSetLastSelectedNodePath = true;
424             this.selectAndRevealDOMNode(nodeToFocus, WI.isConsoleFocused());
425             this._dontSetLastSelectedNodePath = false;
426
427             // If this wasn't the last selected node, then expand it.
428             if (!lastSelectedNode && this._domTreeOutline.selectedTreeElement)
429                 this._domTreeOutline.selectedTreeElement.expand();
430         }
431
432         function selectLastSelectedNode(nodeId)
433         {
434             if (!WI.domManager.restoreSelectedNodeIsAllowed)
435                 return;
436
437             selectNode.call(this, WI.domManager.nodeForId(nodeId));
438         }
439
440         if (documentURL && this._lastSelectedNodePathSetting.value && this._lastSelectedNodePathSetting.value.path && this._lastSelectedNodePathSetting.value.url === documentURL.hash)
441             WI.domManager.pushNodeByPathToFrontend(this._lastSelectedNodePathSetting.value.path, selectLastSelectedNode.bind(this));
442         else
443             selectNode.call(this);
444     }
445
446     _domTreeElementAdded(event)
447     {
448         if (!this._pendingBreakpointNodeIdentifiers.size)
449             return;
450
451         let treeElement = event.data.element;
452         let node = treeElement.representedObject;
453         console.assert(node instanceof WI.DOMNode);
454         if (!(node instanceof WI.DOMNode))
455             return;
456
457         if (!this._pendingBreakpointNodeIdentifiers.delete(node.id))
458             return;
459
460         this._updateBreakpointStatus(node.id);
461     }
462
463     _domTreeSelectionDidChange(event)
464     {
465         let treeElement = this._domTreeOutline.selectedTreeElement;
466         let domNode = treeElement ? treeElement.representedObject : null;
467         let selectedByUser = event.data.selectedByUser;
468
469         this._domTreeOutline.suppressRevealAndSelect = true;
470         this._domTreeOutline.selectDOMNode(domNode, selectedByUser);
471
472         if (domNode && selectedByUser)
473             WI.domManager.highlightDOMNode(domNode.id);
474
475         this._domTreeOutline.updateSelectionArea();
476         this._domTreeOutline.suppressRevealAndSelect = false;
477     }
478
479     _selectedNodeDidChange(event)
480     {
481         var selectedDOMNode = this._domTreeOutline.selectedDOMNode();
482         if (selectedDOMNode && !this._dontSetLastSelectedNodePath)
483             this._lastSelectedNodePathSetting.value = {url: WI.networkManager.mainFrame.url.hash, path: selectedDOMNode.path()};
484
485         if (selectedDOMNode)
486             WI.domManager.setInspectedNode(selectedDOMNode);
487
488         this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
489     }
490
491     _pathComponentSelected(event)
492     {
493         if (!event.data.pathComponent)
494             return;
495
496         console.assert(event.data.pathComponent instanceof WI.DOMTreeElementPathComponent);
497         console.assert(event.data.pathComponent.domTreeElement instanceof WI.DOMTreeElement);
498
499         this._domTreeOutline.selectDOMNode(event.data.pathComponent.domTreeElement.representedObject, true);
500     }
501
502     _domNodeChanged(event)
503     {
504         var selectedDOMNode = this._domTreeOutline.selectedDOMNode();
505         if (selectedDOMNode !== event.data.node)
506             return;
507
508         this.dispatchEventToListeners(WI.ContentView.Event.SelectionPathComponentsDidChange);
509     }
510
511     _mouseWasClicked(event)
512     {
513         var anchorElement = event.target.closest("a");
514         if (!anchorElement || !anchorElement.href)
515             return;
516
517         // Prevent the link from navigating, since we don't do any navigation by following links normally.
518         event.preventDefault();
519         event.stopPropagation();
520
521         if (WI.isBeingEdited(anchorElement)) {
522             // Don't follow the link when it is being edited.
523             return;
524         }
525
526         // Cancel any pending link navigation.
527         if (this._followLinkTimeoutIdentifier) {
528             clearTimeout(this._followLinkTimeoutIdentifier);
529             delete this._followLinkTimeoutIdentifier;
530         }
531
532         // If this is a double-click (or multiple-click), return early.
533         if (event.detail > 1)
534             return;
535
536         function followLink()
537         {
538             // Since followLink is delayed, the call to WI.openURL can't look at window.event
539             // to see if the command key is down like it normally would. So we need to do that check
540             // before calling WI.openURL.
541             const options = {
542                 alwaysOpenExternally: event ? event.metaKey : false,
543                 lineNumber: anchorElement.lineNumber,
544                 ignoreNetworkTab: true,
545                 ignoreSearchTab: true,
546             };
547             WI.openURL(anchorElement.href, this._frame, options);
548         }
549
550         // Start a timeout since this is a single click, if the timeout is canceled before it fires,
551         // then a double-click happened or another link was clicked.
552         // FIXME: The duration might be longer or shorter than the user's configured double click speed.
553         this._followLinkTimeoutIdentifier = setTimeout(followLink.bind(this), 333);
554     }
555
556     _toggleCompositingBorders(event)
557     {
558         console.assert(PageAgent.setCompositingBordersVisible);
559
560         var activated = !this._compositingBordersButtonNavigationItem.activated;
561         this._compositingBordersButtonNavigationItem.activated = activated;
562         PageAgent.setCompositingBordersVisible(activated);
563     }
564
565     _togglePaintFlashing(event)
566     {
567         WI.settings.showPaintRects.value = !WI.settings.showPaintRects.value;
568     }
569
570     _updateCompositingBordersButtonToMatchPageSettings()
571     {
572         if (WI.settings.experimentalEnableLayersTab.value)
573             return;
574
575         var button = this._compositingBordersButtonNavigationItem;
576
577         // We need to sync with the page settings since these can be controlled
578         // in a different way than just using the navigation bar button.
579         PageAgent.getCompositingBordersVisible(function(error, compositingBordersVisible) {
580             button.activated = error ? false : compositingBordersVisible;
581             button.enabled = error !== "unsupported";
582         });
583     }
584
585     _showPaintRectsSettingChanged(event)
586     {
587         console.assert(PageAgent.setShowPaintRects);
588
589         this._paintFlashingButtonNavigationItem.activated = WI.settings.showPaintRects.value;
590
591         PageAgent.setShowPaintRects(this._paintFlashingButtonNavigationItem.activated);
592     }
593
594     _showShadowDOMSettingChanged(event)
595     {
596         this._showsShadowDOMButtonNavigationItem.activated = WI.settings.showShadowDOM.value;
597     }
598
599     _toggleShowsShadowDOMSetting(event)
600     {
601         WI.settings.showShadowDOM.value = !WI.settings.showShadowDOM.value;
602     }
603
604     _showPrintStylesChanged()
605     {
606         this._showPrintStylesButtonNavigationItem.activated = WI.printStylesEnabled;
607
608         let mediaType = WI.printStylesEnabled ? "print" : "";
609         PageAgent.setEmulatedMedia(mediaType);
610
611         WI.cssManager.mediaTypeChanged();
612     }
613
614     _togglePrintStyles(event)
615     {
616         WI.printStylesEnabled = !WI.printStylesEnabled;
617         this._showPrintStylesChanged();
618     }
619
620     _defaultAppearanceDidChange()
621     {
622         let defaultAppearance = WI.cssManager.defaultAppearance;
623         if (!defaultAppearance) {
624             this._lastKnownDefaultAppearance = null;
625             this._forceAppearanceButtonNavigationItem = null;
626             this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
627             return;
628         }
629
630         // Don't update the navigation item if there is currently a forced appearance.
631         // The user will need to toggle it off to update it based on the new default appearance.
632         if (WI.cssManager.forcedAppearance && this._forceAppearanceButtonNavigationItem)
633             return;
634
635         this._forceAppearanceButtonNavigationItem = null;
636
637         switch (defaultAppearance) {
638         case WI.CSSManager.Appearance.Light:
639             this._forceAppearanceButtonNavigationItem = new WI.ActivateButtonNavigationItem("appearance", WI.UIString("Force Dark Appearance"), WI.UIString("Use Default Appearance"), "Images/Appearance.svg", 16, 16);
640             break;
641         case WI.CSSManager.Appearance.Dark:
642             this._forceAppearanceButtonNavigationItem = new WI.ActivateButtonNavigationItem("appearance", WI.UIString("Force Light Appearance"), WI.UIString("Use Default Appearance"), "Images/Appearance.svg", 16, 16);
643             break;
644         }
645
646         if (!this._forceAppearanceButtonNavigationItem) {
647             console.error("Unknown default appearance name:", defaultAppearance);
648             this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
649             return;
650         }
651
652         this._lastKnownDefaultAppearance = defaultAppearance;
653
654         this._forceAppearanceButtonNavigationItem.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._toggleAppearance, this);
655         this._forceAppearanceButtonNavigationItem.visibilityPriority = WI.NavigationItem.VisibilityPriority.Low;
656         this._forceAppearanceButtonNavigationItem.activated = !!WI.cssManager.forcedAppearance;
657
658         this.dispatchEventToListeners(WI.ContentView.Event.NavigationItemsDidChange);
659     }
660
661     _toggleAppearance(event)
662     {
663         // Use the last known default appearance, since that is the appearance this navigation item was generated for.
664         let appearanceToForce = null;
665         switch (this._lastKnownDefaultAppearance) {
666         case WI.CSSManager.Appearance.Light:
667             appearanceToForce = WI.CSSManager.Appearance.Dark;
668             break;
669         case WI.CSSManager.Appearance.Dark:
670             appearanceToForce = WI.CSSManager.Appearance.Light;
671             break;
672         }
673
674         console.assert(appearanceToForce);
675         WI.cssManager.forcedAppearance = WI.cssManager.forcedAppearance == appearanceToForce ? null : appearanceToForce;
676
677         // When no longer forcing an appearance, if the last known default appearance is different than the current
678         // default appearance, then update the navigation button now. Otherwise just toggle the activated state.
679         if (!WI.cssManager.forcedAppearance && this._lastKnownDefaultAppearance !== WI.cssManager.defaultAppearance)
680             this._defaultAppearanceDidChange();
681         else
682             this._forceAppearanceButtonNavigationItem.activated = !!WI.cssManager.forcedAppearance;
683     }
684
685     _showRulersChanged()
686     {
687         this._showRulersButtonNavigationItem.activated = WI.settings.showRulers.value;
688
689         // COMPATIBILITY (iOS 11.3)
690         if (!PageAgent.setShowRulers)
691             return;
692
693         PageAgent.setShowRulers(this._showRulersButtonNavigationItem.activated);
694     }
695
696     _toggleShowRulers(event)
697     {
698         WI.settings.showRulers.value = !WI.settings.showRulers.value;
699
700         this._showRulersChanged();
701     }
702
703     _showSearchHighlights()
704     {
705         console.assert(this._searchIdentifier);
706
707         this._searchResultNodes = [];
708
709         var searchIdentifier = this._searchIdentifier;
710
711         DOMAgent.getSearchResults(this._searchIdentifier, 0, this._numberOfSearchResults, function(error, nodeIdentifiers) {
712             if (error)
713                 return;
714
715             if (this._searchIdentifier !== searchIdentifier)
716                 return;
717
718             console.assert(nodeIdentifiers.length === this._numberOfSearchResults);
719
720             for (var i = 0; i < nodeIdentifiers.length; ++i) {
721                 var domNode = WI.domManager.nodeForId(nodeIdentifiers[i]);
722                 console.assert(domNode);
723                 if (!domNode)
724                     continue;
725
726                 this._searchResultNodes.push(domNode);
727
728                 var treeElement = this._domTreeOutline.findTreeElement(domNode);
729                 console.assert(treeElement);
730                 if (treeElement)
731                     treeElement.highlightSearchResults(this._searchQuery);
732             }
733         }.bind(this));
734     }
735
736     _hideSearchHighlights()
737     {
738         if (!this._searchResultNodes)
739             return;
740
741         for (var domNode of this._searchResultNodes) {
742             var treeElement = this._domTreeOutline.findTreeElement(domNode);
743             if (treeElement)
744                 treeElement.hideSearchHighlights();
745         }
746
747         delete this._searchResultNodes;
748     }
749
750     _domBreakpointAddedOrRemoved(event)
751     {
752         let breakpoint = event.data.breakpoint;
753         this._updateBreakpointStatus(breakpoint.domNodeIdentifier);
754     }
755
756     _handleDOMBreakpointDisabledStateChanged(event)
757     {
758         let breakpoint = event.target;
759         this._updateBreakpointStatus(breakpoint.domNodeIdentifier);
760     }
761
762     _handleDOMBreakpointDOMNodeChanged(event)
763     {
764         let breakpoint = event.target;
765         let nodeIdentifier = breakpoint.domNodeIdentifier || event.data.oldNodeIdentifier;
766         this._updateBreakpointStatus(nodeIdentifier);
767     }
768
769     _updateBreakpointStatus(nodeIdentifier)
770     {
771         let domNode = WI.domManager.nodeForId(nodeIdentifier);
772         if (!domNode)
773             return;
774
775         let treeElement = this._domTreeOutline.findTreeElement(domNode);
776         if (!treeElement) {
777             this._pendingBreakpointNodeIdentifiers.add(nodeIdentifier);
778             return;
779         }
780
781         let breakpoints = WI.domDebuggerManager.domBreakpointsForNode(domNode);
782         if (breakpoints.length) {
783             if (breakpoints.some((item) => item.disabled))
784                 treeElement.breakpointStatus = WI.DOMTreeElement.BreakpointStatus.DisabledBreakpoint;
785             else
786                 treeElement.breakpointStatus = WI.DOMTreeElement.BreakpointStatus.Breakpoint;
787         } else
788             treeElement.breakpointStatus = WI.DOMTreeElement.BreakpointStatus.None;
789
790         this.breakpointGutterEnabled = this._domTreeOutline.children.some((child) => child.hasBreakpoint);
791     }
792
793     _restoreBreakpointsAfterUpdate()
794     {
795         this._pendingBreakpointNodeIdentifiers.clear();
796
797         this.breakpointGutterEnabled = false;
798
799         let updatedNodes = new Set;
800         for (let breakpoint of WI.domDebuggerManager.domBreakpoints) {
801             if (updatedNodes.has(breakpoint.domNodeIdentifier))
802                 continue;
803
804             this._updateBreakpointStatus(breakpoint.domNodeIdentifier);
805         }
806     }
807
808     _breakpointsEnabledDidChange(event)
809     {
810         this._domTreeOutline.element.classList.toggle("breakpoints-disabled", !WI.debuggerManager.breakpointsEnabled);
811     }
812 };