Web Inspector: Debugger sidebar should have a filter button for breakpoints
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / NavigationSidebarPanel.js
1 /*
2  * Copyright (C) 2013 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 WebInspector.NavigationSidebarPanel = function(identifier, displayName, image, keyboardShortcutKey, autoPruneOldTopLevelResourceTreeElements, autoHideToolbarItemWhenEmpty, wantsTopOverflowShadow, element, role, label)
27 {
28     if (keyboardShortcutKey)
29         this._keyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Control, keyboardShortcutKey, this.toggle.bind(this));
30
31     if (this._keyboardShortcut) {
32         var showToolTip = WebInspector.UIString("Show the %s navigation sidebar (%s)").format(displayName, this._keyboardShortcut.displayName);
33         var hideToolTip = WebInspector.UIString("Hide the %s navigation sidebar (%s)").format(displayName, this._keyboardShortcut.displayName);
34     } else {
35         var showToolTip = WebInspector.UIString("Show the %s navigation sidebar").format(displayName);
36         var hideToolTip = WebInspector.UIString("Hide the %s navigation sidebar").format(displayName);
37     }
38
39     WebInspector.SidebarPanel.call(this, identifier, displayName, showToolTip, hideToolTip, image, element, role, label || displayName);
40
41     this.element.classList.add(WebInspector.NavigationSidebarPanel.StyleClassName);
42
43     this._autoHideToolbarItemWhenEmpty = autoHideToolbarItemWhenEmpty || false;
44
45     if (autoHideToolbarItemWhenEmpty)
46         this.toolbarItem.hidden = true;
47
48     this._visibleContentTreeOutlines = new Set;
49
50     this.contentElement.addEventListener("scroll", this._updateContentOverflowShadowVisibility.bind(this));
51
52     this._contentTreeOutline = this.createContentTreeOutline(true);
53
54     this._filterBar = new WebInspector.FilterBar();
55     this._filterBar.addEventListener(WebInspector.FilterBar.Event.FilterDidChange, this._filterDidChange, this);
56     this.element.appendChild(this._filterBar.element);
57
58     this._bottomOverflowShadowElement = document.createElement("div");
59     this._bottomOverflowShadowElement.className = WebInspector.NavigationSidebarPanel.OverflowShadowElementStyleClassName;
60     this.element.appendChild(this._bottomOverflowShadowElement);
61
62     if (wantsTopOverflowShadow) {
63         this._topOverflowShadowElement = document.createElement("div");
64         this._topOverflowShadowElement.classList.add(WebInspector.NavigationSidebarPanel.OverflowShadowElementStyleClassName);
65         this._topOverflowShadowElement.classList.add(WebInspector.NavigationSidebarPanel.TopOverflowShadowElementStyleClassName);
66         this.element.appendChild(this._topOverflowShadowElement);
67     }
68
69     window.addEventListener("resize", this._updateContentOverflowShadowVisibility.bind(this));
70
71     this._filtersSetting = new WebInspector.Setting(identifier + "-navigation-sidebar-filters", {});
72     this._filterBar.filters = this._filtersSetting.value;
73
74     this._emptyContentPlaceholderElement = document.createElement("div");
75     this._emptyContentPlaceholderElement.className = WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderElementStyleClassName;
76
77     this._emptyContentPlaceholderMessageElement = document.createElement("div");
78     this._emptyContentPlaceholderMessageElement.className = WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderMessageElementStyleClassName;
79     this._emptyContentPlaceholderElement.appendChild(this._emptyContentPlaceholderMessageElement);
80
81     this._generateStyleRulesIfNeeded();
82     this._generateDisclosureTrianglesIfNeeded();
83
84     if (autoPruneOldTopLevelResourceTreeElements) {
85         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._checkForOldResources, this);
86         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ChildFrameWasRemoved, this._checkForOldResources, this);
87         WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ResourceWasRemoved, this._checkForOldResources, this);
88     }
89 };
90
91 WebInspector.NavigationSidebarPanel.StyleClassName = "navigation";
92 WebInspector.NavigationSidebarPanel.OverflowShadowElementStyleClassName = "overflow-shadow";
93 WebInspector.NavigationSidebarPanel.TopOverflowShadowElementStyleClassName = "top";
94 WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName = "hidden";
95 WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName = "navigation-sidebar-panel-content-tree-outline";
96 WebInspector.NavigationSidebarPanel.HideDisclosureButtonsStyleClassName = "hide-disclosure-buttons";
97 WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderElementStyleClassName = "empty-content-placeholder";
98 WebInspector.NavigationSidebarPanel.EmptyContentPlaceholderMessageElementStyleClassName = "message";
99 WebInspector.NavigationSidebarPanel.DisclosureTriangleOpenCanvasIdentifier = "navigation-sidebar-panel-disclosure-triangle-open";
100 WebInspector.NavigationSidebarPanel.DisclosureTriangleClosedCanvasIdentifier = "navigation-sidebar-panel-disclosure-triangle-closed";
101 WebInspector.NavigationSidebarPanel.DisclosureTriangleNormalCanvasIdentifierSuffix = "-normal";
102 WebInspector.NavigationSidebarPanel.DisclosureTriangleSelectedCanvasIdentifierSuffix = "-selected";
103
104 WebInspector.NavigationSidebarPanel.prototype = {
105     constructor: WebInspector.NavigationSidebarPanel,
106     __proto__: WebInspector.SidebarPanel.prototype,
107
108     // Public
109
110     get contentTreeOutlineElement()
111     {
112         return this._contentTreeOutline.element;
113     },
114
115     get contentTreeOutline()
116     {
117         return this._contentTreeOutline;
118     },
119
120     set contentTreeOutline(newTreeOutline)
121     {
122         console.assert(newTreeOutline);
123         if (!newTreeOutline)
124             return;
125
126         if (this._contentTreeOutline)
127             this._contentTreeOutline.element.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
128
129         this._contentTreeOutline = newTreeOutline;
130         this._contentTreeOutline.element.classList.remove(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
131
132         this._visibleContentTreeOutlines.delete(this._contentTreeOutline);
133         this._visibleContentTreeOutlines.add(newTreeOutline);
134
135         this._updateFilter();
136     },
137
138     get contentTreeOutlineToAutoPrune()
139     {
140         return this._contentTreeOutline;
141     },
142
143     get visibleContentTreeOutlines()
144     {
145         return this._visibleContentTreeOutlines;
146     },
147
148     get hasSelectedElement()
149     {
150         return !!this._contentTreeOutline.selectedTreeElement;
151     },
152
153     get filterBar()
154     {
155         return this._filterBar;
156     },
157
158     get restoringState()
159     {
160         return this._restoringState;
161     },
162
163     createContentTreeOutline: function(dontHideByDefault, suppressFiltering)
164     {
165         var contentTreeOutlineElement = document.createElement("ol");
166         contentTreeOutlineElement.className = WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName;
167         if (!dontHideByDefault)
168             contentTreeOutlineElement.classList.add(WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementHiddenStyleClassName);
169         this.contentElement.appendChild(contentTreeOutlineElement);
170
171         var contentTreeOutline = new TreeOutline(contentTreeOutlineElement);
172         contentTreeOutline.allowsRepeatSelection = true;
173
174         if (!suppressFiltering) {
175             contentTreeOutline.onadd = this._treeElementAddedOrChanged.bind(this);
176             contentTreeOutline.onchange = this._treeElementAddedOrChanged.bind(this);
177             contentTreeOutline.onexpand = this._treeElementExpandedOrCollapsed.bind(this);
178             contentTreeOutline.oncollapse = this._treeElementExpandedOrCollapsed.bind(this);
179         }
180
181         if (dontHideByDefault)
182             this._visibleContentTreeOutlines.add(contentTreeOutline);
183
184         return contentTreeOutline;
185     },
186
187     treeElementForRepresentedObject: function(representedObject)
188     {
189         return this._contentTreeOutline.getCachedTreeElement(representedObject);
190     },
191
192     showDefaultContentView: function()
193     {
194         // Implemneted by subclasses if needed to show a content view when no existing tree element is selected.
195     },
196
197     showContentViewForCurrentSelection: function()
198     {
199         // Reselect the selected tree element to cause the content view to be shown as well. <rdar://problem/10854727>
200         var selectedTreeElement = this._contentTreeOutline.selectedTreeElement;
201         if (selectedTreeElement)
202             selectedTreeElement.select();
203     },
204
205     saveStateToCookie: function(cookie)
206     {
207         console.assert(cookie);
208
209         // This does not save folder selections, which lack a represented object and content view.
210         var selectedTreeElement = null;
211         this._visibleContentTreeOutlines.forEach(function(outline) {
212             if (outline.selectedTreeElement)
213                 selectedTreeElement = outline.selectedTreeElement;
214         });
215
216         if (!selectedTreeElement)
217             return;
218
219         if (this._isTreeElementWithoutRepresentedObject(selectedTreeElement))
220             return;
221
222         var representedObject = selectedTreeElement.representedObject;
223         cookie[WebInspector.TypeIdentifierCookieKey] = representedObject.constructor.TypeIdentifier;
224
225         if (representedObject.saveIdentityToCookie)
226             representedObject.saveIdentityToCookie(cookie);
227         else
228             console.error("Error: TreeElement.representedObject is missing a saveIdentityToCookie implementation. TreeElement.constructor: ", selectedTreeElement.constructor);
229     },
230
231     // This can be supplemented by subclasses that admit a simpler strategy for static tree elements.
232     restoreStateFromCookie: function(cookie, relaxedMatchDelay)
233     {
234         this._pendingViewStateCookie = cookie;
235         this._restoringState = true;
236
237         // Check if any existing tree elements in any outline match the cookie.
238         this._checkOutlinesForPendingViewStateCookie();
239
240         if (this._finalAttemptToRestoreViewStateTimeout)
241             clearTimeout(this._finalAttemptToRestoreViewStateTimeout);
242
243         function finalAttemptToRestoreViewStateFromCookie()
244         {
245             delete this._finalAttemptToRestoreViewStateTimeout;
246
247             this._checkOutlinesForPendingViewStateCookie(true);
248
249             delete this._pendingViewStateCookie;
250             delete this._restoringState;
251         }
252
253         // If the specific tree element wasn't found, we may need to wait for the resources
254         // to be registered. We try one last time (match type only) after an arbitrary amount of timeout.
255         this._finalAttemptToRestoreViewStateTimeout = setTimeout(finalAttemptToRestoreViewStateFromCookie.bind(this), relaxedMatchDelay);
256     },
257
258     showEmptyContentPlaceholder: function(message, hideToolbarItem)
259     {
260         console.assert(message);
261
262         if (this._emptyContentPlaceholderElement.parentNode && this._emptyContentPlaceholderMessageElement.textContent === message)
263             return;
264
265         this._emptyContentPlaceholderMessageElement.textContent = message;
266         this.element.appendChild(this._emptyContentPlaceholderElement);
267
268         this._hideToolbarItemWhenEmpty = hideToolbarItem || false;
269         this._updateToolbarItemVisibility();
270         this._updateContentOverflowShadowVisibility();
271     },
272
273     hideEmptyContentPlaceholder: function()
274     {
275         if (!this._emptyContentPlaceholderElement.parentNode)
276             return;
277
278         this._emptyContentPlaceholderElement.parentNode.removeChild(this._emptyContentPlaceholderElement);
279
280         this._hideToolbarItemWhenEmpty = false;
281         this._updateToolbarItemVisibility();
282         this._updateContentOverflowShadowVisibility();
283     },
284
285     updateEmptyContentPlaceholder: function(message)
286     {
287         this._updateToolbarItemVisibility();
288
289         if (!this._contentTreeOutline.children.length) {
290             // No tree elements, so no results.
291             this.showEmptyContentPlaceholder(message);
292         } else if (!this._emptyFilterResults) {
293             // There are tree elements, and not all of them are hidden by the filter.
294             this.hideEmptyContentPlaceholder();
295         }
296     },
297
298     updateFilter: function()
299     {
300         this._updateFilter();
301     },
302
303     hasCustomFilters: function()
304     {
305         // Implemented by subclasses if needed.
306         return false;
307     },
308
309     matchTreeElementAgainstCustomFilters: function(treeElement)
310     {
311         // Implemented by subclasses if needed.
312         return true;
313     },
314
315     matchTreeElementAgainstFilterFunctions: function(treeElement)
316     {
317         for (var filterFunction of this._filterFunctions) {
318             if (filterFunction(treeElement))
319                 return true;
320         }
321         return false;
322     },
323
324     applyFiltersToTreeElement: function(treeElement)
325     {
326         if (!this._filterBar.hasActiveFilters() && !this.hasCustomFilters()) {
327             // No filters, so make everything visible.
328             treeElement.hidden = false;
329
330             // If this tree element was expanded during filtering, collapse it again.
331             if (treeElement.expanded && treeElement.__wasExpandedDuringFiltering) {
332                 delete treeElement.__wasExpandedDuringFiltering;
333                 treeElement.collapse();
334             }
335
336             return;
337         }
338
339         var filterableData = treeElement.filterableData || {};
340
341         var matchedBuiltInFilters = false;
342
343         var self = this;
344         function matchTextFilter(inputs)
345         {
346             if (!inputs || !self._textFilterRegex)
347                 return true;
348
349             // Convert to a single item array if needed.
350             if (!(inputs instanceof Array))
351                 inputs = [inputs];
352
353             // Loop over all the inputs and try to match them.
354             for (var input of inputs) {
355                 if (!input)
356                     continue;
357                 if (self._textFilterRegex.test(input)) {
358                     matchedBuiltInFilters = true;
359                     return true;
360                 }
361             }
362
363             // No inputs matched.
364             return false;
365         }
366
367         function makeVisible()
368         {
369             // Make this element visible.
370             treeElement.hidden = false;
371
372             // Make the ancestors visible and expand them.
373             var currentAncestor = treeElement.parent;
374             while (currentAncestor && !currentAncestor.root) {
375                 currentAncestor.hidden = false;
376
377                 // Only expand if the built-in filters matched, not custom filters.
378                 if (matchedBuiltInFilters && !currentAncestor.expanded) {
379                     currentAncestor.__wasExpandedDuringFiltering = true;
380                     currentAncestor.expand();
381                 }
382
383                 currentAncestor = currentAncestor.parent;
384             }
385         }
386
387         if (matchTextFilter(filterableData.text) && this.matchTreeElementAgainstFilterFunctions(treeElement) && this.matchTreeElementAgainstCustomFilters(treeElement)) {
388             // Make this element visible since it matches.
389             makeVisible();
390
391             // If this tree element didn't match a built-in filter and was expanded earlier during filtering, collapse it again.
392             if (!matchedBuiltInFilters && treeElement.expanded && treeElement.__wasExpandedDuringFiltering) {
393                 delete treeElement.__wasExpandedDuringFiltering;
394                 treeElement.collapse();
395             }
396
397             return;
398         }
399
400         // Make this element invisible since it does not match.
401         treeElement.hidden = true;
402     },
403
404     show: function()
405     {
406         if (!this.parentSidebar)
407             return;
408
409         WebInspector.SidebarPanel.prototype.show.call(this);
410
411         this.contentTreeOutlineElement.focus();
412     },
413
414     shown: function()
415     {
416         WebInspector.SidebarPanel.prototype.shown.call(this);
417
418         this._updateContentOverflowShadowVisibility();
419
420         // Force the navigation item to be visible. This makes sure it is
421         // always visible when the panel is shown.
422         this.toolbarItem.hidden = false;
423     },
424
425     hidden: function()
426     {
427         WebInspector.SidebarPanel.prototype.hidden.call(this);
428
429         this._updateToolbarItemVisibility();
430     },
431
432     // Private
433     
434     _updateContentOverflowShadowVisibilitySoon: function()
435     {
436         if (this._updateContentOverflowShadowVisibilityIdentifier)
437             return;
438
439         this._updateContentOverflowShadowVisibilityIdentifier = setTimeout(this._updateContentOverflowShadowVisibility.bind(this), 0);
440     },
441
442     _updateContentOverflowShadowVisibility: function()
443     {
444         delete this._updateContentOverflowShadowVisibilityIdentifier;
445
446         var scrollHeight = this.contentElement.scrollHeight;
447         var offsetHeight = this.contentElement.offsetHeight;
448
449         if (scrollHeight < offsetHeight) {
450             if (this._topOverflowShadowElement)
451                 this._topOverflowShadowElement.style.opacity = 0;
452             this._bottomOverflowShadowElement.style.opacity = 0;
453             return;
454         }
455
456         if (WebInspector.Platform.isLegacyMacOS)
457             const edgeThreshold = 10;
458         else
459             const edgeThreshold = 1;
460
461         var scrollTop = this.contentElement.scrollTop;
462
463         var topCoverage = Math.min(scrollTop, edgeThreshold);
464         var bottomCoverage = Math.max(0, (offsetHeight + scrollTop) - (scrollHeight - edgeThreshold));
465
466         if (this._topOverflowShadowElement)
467             this._topOverflowShadowElement.style.opacity = (topCoverage / edgeThreshold).toFixed(1);
468         this._bottomOverflowShadowElement.style.opacity = (1 - (bottomCoverage / edgeThreshold)).toFixed(1);
469     },
470
471     _updateToolbarItemVisibility: function()
472     {
473         // Hide the navigation item if requested or auto-hiding and we are not visible and we are empty.
474         var shouldHide = ((this._hideToolbarItemWhenEmpty || this._autoHideToolbarItemWhenEmpty) && !this.selected && !this._contentTreeOutline.children.length);
475         this.toolbarItem.hidden = shouldHide;
476     },
477
478     _checkForEmptyFilterResults: function()
479     {
480         // No tree elements, so don't touch the empty content placeholder.
481         if (!this._contentTreeOutline.children.length)
482             return;
483
484         // Iterate over all the top level tree elements. If any are visible, return early.
485         var currentTreeElement = this._contentTreeOutline.children[0];
486         while (currentTreeElement) {
487             if (!currentTreeElement.hidden) {
488                 // Not hidden, so hide any empty content message.
489                 this.hideEmptyContentPlaceholder();
490                 this._emptyFilterResults = false;
491                 return;
492             }
493
494             currentTreeElement = currentTreeElement.nextSibling;
495         }
496
497         // All top level tree elements are hidden, so filtering hid everything. Show a message.
498         this.showEmptyContentPlaceholder(WebInspector.UIString("No Filter Results"));
499         this._emptyFilterResults = true;
500     },
501
502     _filterDidChange: function()
503     {
504         this._updateFilter();
505     },
506
507     _updateFilter: function()
508     {
509         var selectedTreeElement = this._contentTreeOutline.selectedTreeElement;
510         var selectionWasHidden = selectedTreeElement && selectedTreeElement.hidden;
511
512         var filters = this._filterBar.filters;
513         this._textFilterRegex = simpleGlobStringToRegExp(filters.text, "i");
514         this._filtersSetting.value = filters;
515         this._filterFunctions = filters.functions;
516
517         // Don't populate if we don't have any active filters.
518         // We only need to populate when a filter needs to reveal.
519         var dontPopulate = !this._filterBar.hasActiveFilters();
520
521         // Update the whole tree.
522         var currentTreeElement = this._contentTreeOutline.children[0];
523         while (currentTreeElement && !currentTreeElement.root) {
524             this.applyFiltersToTreeElement(currentTreeElement);
525             currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, dontPopulate);
526         }
527
528         this._checkForEmptyFilterResults();
529         this._updateContentOverflowShadowVisibility();
530
531         // Filter may have hidden the selected resource in the timeline view, which should now notify its listeners.
532         if (selectedTreeElement && selectedTreeElement.hidden !== selectionWasHidden) {
533             var currentContentView = WebInspector.contentBrowser.currentContentView;
534             if (currentContentView instanceof WebInspector.TimelineRecordingContentView && typeof currentContentView.currentTimelineView.filterUpdated === "function")
535                 currentContentView.currentTimelineView.filterUpdated();
536         }
537     },
538
539     _treeElementAddedOrChanged: function(treeElement)
540     {
541         // Don't populate if we don't have any active filters.
542         // We only need to populate when a filter needs to reveal.
543         var dontPopulate = !this._filterBar.hasActiveFilters();
544
545         // Apply the filters to the tree element and its descendants.
546         var currentTreeElement = treeElement;
547         while (currentTreeElement && !currentTreeElement.root) {
548             this.applyFiltersToTreeElement(currentTreeElement);
549             currentTreeElement = currentTreeElement.traverseNextTreeElement(false, treeElement, dontPopulate);
550         }
551
552         this._checkForEmptyFilterResults();
553         this._updateContentOverflowShadowVisibilitySoon();
554
555         if (this.selected)
556             this._checkElementsForPendingViewStateCookie(treeElement);
557     },
558
559     _treeElementExpandedOrCollapsed: function(treeElement)
560     {
561         this._updateContentOverflowShadowVisibility();
562     },
563
564     _generateStyleRulesIfNeeded: function()
565     {
566         if (WebInspector.NavigationSidebarPanel._styleElement)
567             return;
568
569         WebInspector.NavigationSidebarPanel._styleElement = document.createElement("style");
570
571         const maximumSidebarTreeDepth = 32;
572         const baseLeftPadding = 5; // Matches the padding in NavigationSidebarPanel.css for the item class. Keep in sync.
573         const depthPadding = 10;
574
575         var styleText = "";
576         var childrenSubstring = "";
577         for (var i = 1; i <= maximumSidebarTreeDepth; ++i) {
578             // Keep all the elements at the same depth once the maximum is reached.
579             childrenSubstring += i === maximumSidebarTreeDepth ? " .children" : " > .children";
580             styleText += "." + WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName + childrenSubstring + " > .item { ";
581             styleText += "padding-left: " + (baseLeftPadding + (depthPadding * i)) + "px; }\n";
582         }
583
584         WebInspector.NavigationSidebarPanel._styleElement.textContent = styleText;
585
586         document.head.appendChild(WebInspector.NavigationSidebarPanel._styleElement);
587     },
588
589     _generateDisclosureTrianglesIfNeeded: function()
590     {
591         if (WebInspector.NavigationSidebarPanel._generatedDisclosureTriangles)
592             return;
593
594         // Set this early instead of in _generateDisclosureTriangle because we don't want multiple panels that are
595         // created at the same time to duplicate the work (even though it would be harmless.)
596         WebInspector.NavigationSidebarPanel._generatedDisclosureTriangles = true;
597
598         var specifications = {};
599
600         if (WebInspector.Platform.isLegacyMacOS) {
601             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleNormalCanvasIdentifierSuffix] = {
602                 fillColor: [112, 126, 139],
603                 shadowColor: [255, 255, 255, 0.8],
604                 shadowOffsetX: 0,
605                 shadowOffsetY: 1,
606                 shadowBlur: 0
607             };
608
609             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleSelectedCanvasIdentifierSuffix] = {
610                 fillColor: [255, 255, 255],
611                 shadowColor: [61, 91, 110, 0.8],
612                 shadowOffsetX: 0,
613                 shadowOffsetY: 1,
614                 shadowBlur: 2
615             };
616         } else {
617             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleNormalCanvasIdentifierSuffix] = {
618                 fillColor: [140, 140, 140]
619             };
620
621             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleSelectedCanvasIdentifierSuffix] = {
622                 fillColor: [255, 255, 255]
623             };
624         }
625
626         generateColoredImagesForCSS("Images/DisclosureTriangleSmallOpen.svg", specifications, 13, 13, WebInspector.NavigationSidebarPanel.DisclosureTriangleOpenCanvasIdentifier);
627         generateColoredImagesForCSS("Images/DisclosureTriangleSmallClosed.svg", specifications, 13, 13, WebInspector.NavigationSidebarPanel.DisclosureTriangleClosedCanvasIdentifier);
628     },
629
630     _checkForOldResources: function(event)
631     {
632         if (this._checkForOldResourcesTimeoutIdentifier)
633             return;
634
635         function delayedWork()
636         {
637             delete this._checkForOldResourcesTimeoutIdentifier;
638
639             var contentTreeOutline = this.contentTreeOutlineToAutoPrune;
640
641             // Check all the ResourceTreeElements at the top level to make sure their Resource still has a parentFrame in the frame hierarchy.
642             // If the parentFrame is no longer in the frame hierarchy we know it was removed due to a navigation or some other page change and
643             // we should remove the issues for that resource.
644             for (var i = contentTreeOutline.children.length - 1; i >= 0; --i) {
645                 var treeElement = contentTreeOutline.children[i];
646                 if (!(treeElement instanceof WebInspector.ResourceTreeElement))
647                     continue;
648
649                 var resource = treeElement.resource;
650                 if (!resource.parentFrame || resource.parentFrame.isDetached())
651                     contentTreeOutline.removeChildAtIndex(i, true, true);
652             }
653
654             if (typeof this._updateEmptyContentPlaceholder === "function")
655                 this._updateEmptyContentPlaceholder();
656         }
657
658         // Check on a delay to coalesce multiple calls to _checkForOldResources.
659         this._checkForOldResourcesTimeoutIdentifier = setTimeout(delayedWork.bind(this), 0);
660     },
661
662     _isTreeElementWithoutRepresentedObject: function(treeElement)
663     {
664         return treeElement instanceof WebInspector.FolderTreeElement
665             || treeElement instanceof WebInspector.DatabaseHostTreeElement
666             || typeof treeElement.representedObject === "string"
667             || treeElement.representedObject instanceof String;
668     },
669
670     _checkOutlinesForPendingViewStateCookie: function(matchTypeOnly)
671     {
672         if (!this._pendingViewStateCookie)
673             return;
674
675         var visibleTreeElements = [];
676         this._visibleContentTreeOutlines.forEach(function(outline) {
677             var currentTreeElement = outline.hasChildren ? outline.children[0] : null;
678             while (currentTreeElement) {
679                 visibleTreeElements.push(currentTreeElement);
680                 currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
681             }
682         });
683
684         return this._checkElementsForPendingViewStateCookie(visibleTreeElements, matchTypeOnly);
685     },
686
687     _checkElementsForPendingViewStateCookie: function(treeElements, matchTypeOnly)
688     {
689         if (!this._pendingViewStateCookie)
690             return;
691
692         var cookie = this._pendingViewStateCookie;
693
694         function treeElementMatchesCookie(treeElement)
695         {
696             if (this._isTreeElementWithoutRepresentedObject(treeElement))
697                 return false;
698
699             var representedObject = treeElement.representedObject;
700             if (!representedObject)
701                 return false;
702
703             var typeIdentifier = cookie[WebInspector.TypeIdentifierCookieKey];
704             if (typeIdentifier !== representedObject.constructor.TypeIdentifier)
705                 return false;
706
707             if (matchTypeOnly)
708                 return true;
709
710             var candidateObjectCookie = {};
711             if (representedObject.saveIdentityToCookie)
712                 representedObject.saveIdentityToCookie(candidateObjectCookie);
713
714             var candidateCookieKeys = Object.keys(candidateObjectCookie);
715             return candidateCookieKeys.length && candidateCookieKeys.every(function valuesMatchForKey(key) {
716                 return candidateObjectCookie[key] === cookie[key];
717             });
718         }
719
720         if (!(treeElements instanceof Array))
721             treeElements = [treeElements];
722
723         var matchedElement = null;
724         treeElements.some(function(element) {
725             if (treeElementMatchesCookie.call(this, element)) {
726                 matchedElement = element;
727                 return true;
728             }
729         }, this);
730
731         if (matchedElement) {
732             matchedElement.revealAndSelect();
733
734             delete this._pendingViewStateCookie;
735
736             // Delay clearing the restoringState flag until the next runloop so listeners
737             // checking for it in this runloop still know state was being restored.
738             setTimeout(function() {
739                 delete this._restoringState;
740             }.bind(this));
741
742             if (this._finalAttemptToRestoreViewStateTimeout) {
743                 clearTimeout(this._finalAttemptToRestoreViewStateTimeout);
744                 delete this._finalAttemptToRestoreViewStateTimeout;
745             }
746         }
747     }
748 };