Web Inspector: Layer summary should be bottom sticky
[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.TextFilterDidChange, this._textFilterDidChange, 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: %s", 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     applyFiltersToTreeElement: function(treeElement)
316     {
317         if (!this._filterBar.hasActiveFilters() && !this.hasCustomFilters()) {
318             // No filters, so make everything visible.
319             treeElement.hidden = false;
320
321             // If this tree element was expanded during filtering, collapse it again.
322             if (treeElement.expanded && treeElement.__wasExpandedDuringFiltering) {
323                 delete treeElement.__wasExpandedDuringFiltering;
324                 treeElement.collapse();
325             }
326
327             return;
328         }
329
330         var filterableData = treeElement.filterableData || {};
331
332         var matchedBuiltInFilters = false;
333
334         var self = this;
335         function matchTextFilter(inputs)
336         {
337             if (!inputs || !self._textFilterRegex)
338                 return true;
339
340             // Convert to a single item array if needed.
341             if (!(inputs instanceof Array))
342                 inputs = [inputs];
343
344             // Loop over all the inputs and try to match them.
345             for (var input of inputs) {
346                 if (!input)
347                     continue;
348                 if (self._textFilterRegex.test(input)) {
349                     matchedBuiltInFilters = true;
350                     return true;
351                 }
352             }
353
354             // No inputs matched.
355             return false;
356         }
357
358         function makeVisible()
359         {
360             // Make this element visible.
361             treeElement.hidden = false;
362
363             // Make the ancestors visible and expand them.
364             var currentAncestor = treeElement.parent;
365             while (currentAncestor && !currentAncestor.root) {
366                 currentAncestor.hidden = false;
367
368                 // Only expand if the built-in filters matched, not custom filters.
369                 if (matchedBuiltInFilters && !currentAncestor.expanded) {
370                     currentAncestor.__wasExpandedDuringFiltering = true;
371                     currentAncestor.expand();
372                 }
373
374                 currentAncestor = currentAncestor.parent;
375             }
376         }
377
378         if (matchTextFilter(filterableData.text) && this.matchTreeElementAgainstCustomFilters(treeElement)) {
379             // Make this element visible since it matches.
380             makeVisible();
381
382             // If this tree element didn't match a built-in filter and was expanded earlier during filtering, collapse it again.
383             if (!matchedBuiltInFilters && treeElement.expanded && treeElement.__wasExpandedDuringFiltering) {
384                 delete treeElement.__wasExpandedDuringFiltering;
385                 treeElement.collapse();
386             }
387
388             return;
389         }
390
391         // Make this element invisible since it does not match.
392         treeElement.hidden = true;
393     },
394
395     show: function()
396     {
397         if (!this.parentSidebar)
398             return;
399
400         WebInspector.SidebarPanel.prototype.show.call(this);
401
402         this.contentTreeOutlineElement.focus();
403     },
404
405     shown: function()
406     {
407         WebInspector.SidebarPanel.prototype.shown.call(this);
408
409         this._updateContentOverflowShadowVisibility();
410
411         // Force the navigation item to be visible. This makes sure it is
412         // always visible when the panel is shown.
413         this.toolbarItem.hidden = false;
414     },
415
416     hidden: function()
417     {
418         WebInspector.SidebarPanel.prototype.hidden.call(this);
419
420         this._updateToolbarItemVisibility();
421     },
422
423     // Private
424     
425     _updateContentOverflowShadowVisibilitySoon: function()
426     {
427         if (this._updateContentOverflowShadowVisibilityIdentifier)
428             return;
429
430         this._updateContentOverflowShadowVisibilityIdentifier = setTimeout(this._updateContentOverflowShadowVisibility.bind(this), 0);
431     },
432
433     _updateContentOverflowShadowVisibility: function()
434     {
435         delete this._updateContentOverflowShadowVisibilityIdentifier;
436
437         var scrollHeight = this.contentElement.scrollHeight;
438         var offsetHeight = this.contentElement.offsetHeight;
439
440         if (scrollHeight < offsetHeight) {
441             if (this._topOverflowShadowElement)
442                 this._topOverflowShadowElement.style.opacity = 0;
443             this._bottomOverflowShadowElement.style.opacity = 0;
444             return;
445         }
446
447         if (WebInspector.Platform.isLegacyMacOS)
448             const edgeThreshold = 10;
449         else
450             const edgeThreshold = 1;
451
452         var scrollTop = this.contentElement.scrollTop;
453
454         var topCoverage = Math.min(scrollTop, edgeThreshold);
455         var bottomCoverage = Math.max(0, (offsetHeight + scrollTop) - (scrollHeight - edgeThreshold));
456
457         if (this._topOverflowShadowElement)
458             this._topOverflowShadowElement.style.opacity = (topCoverage / edgeThreshold).toFixed(1);
459         this._bottomOverflowShadowElement.style.opacity = (1 - (bottomCoverage / edgeThreshold)).toFixed(1);
460     },
461
462     _updateToolbarItemVisibility: function()
463     {
464         // Hide the navigation item if requested or auto-hiding and we are not visible and we are empty.
465         var shouldHide = ((this._hideToolbarItemWhenEmpty || this._autoHideToolbarItemWhenEmpty) && !this.selected && !this._contentTreeOutline.children.length);
466         this.toolbarItem.hidden = shouldHide;
467     },
468
469     _checkForEmptyFilterResults: function()
470     {
471         // No tree elements, so don't touch the empty content placeholder.
472         if (!this._contentTreeOutline.children.length)
473             return;
474
475         // Iterate over all the top level tree elements. If any are visible, return early.
476         var currentTreeElement = this._contentTreeOutline.children[0];
477         while (currentTreeElement) {
478             if (!currentTreeElement.hidden) {
479                 // Not hidden, so hide any empty content message.
480                 this.hideEmptyContentPlaceholder();
481                 this._emptyFilterResults = false;
482                 return;
483             }
484
485             currentTreeElement = currentTreeElement.nextSibling;
486         }
487
488         // All top level tree elements are hidden, so filtering hid everything. Show a message.
489         this.showEmptyContentPlaceholder(WebInspector.UIString("No Filter Results"));
490         this._emptyFilterResults = true;
491     },
492
493     _textFilterDidChange: function()
494     {
495         this._updateFilter();
496     },
497
498     _updateFilter: function()
499     {
500         var filters = this._filterBar.filters;
501         this._textFilterRegex = simpleGlobStringToRegExp(filters.text, "i");
502         this._filtersSetting.value = filters;
503
504         // Don't populate if we don't have any active filters.
505         // We only need to populate when a filter needs to reveal.
506         var dontPopulate = !this._filterBar.hasActiveFilters();
507
508         // Update the whole tree.
509         var currentTreeElement = this._contentTreeOutline.children[0];
510         while (currentTreeElement && !currentTreeElement.root) {
511             this.applyFiltersToTreeElement(currentTreeElement);
512             currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, dontPopulate);
513         }
514
515         this._checkForEmptyFilterResults();
516         this._updateContentOverflowShadowVisibility();
517     },
518
519     _treeElementAddedOrChanged: function(treeElement)
520     {
521         // Don't populate if we don't have any active filters.
522         // We only need to populate when a filter needs to reveal.
523         var dontPopulate = !this._filterBar.hasActiveFilters();
524
525         // Apply the filters to the tree element and its descendants.
526         var currentTreeElement = treeElement;
527         while (currentTreeElement && !currentTreeElement.root) {
528             this.applyFiltersToTreeElement(currentTreeElement);
529             currentTreeElement = currentTreeElement.traverseNextTreeElement(false, treeElement, dontPopulate);
530         }
531
532         this._checkForEmptyFilterResults();
533         this._updateContentOverflowShadowVisibilitySoon();
534
535         if (this.selected)
536             this._checkElementsForPendingViewStateCookie(treeElement);
537     },
538
539     _treeElementExpandedOrCollapsed: function(treeElement)
540     {
541         this._updateContentOverflowShadowVisibility();
542     },
543
544     _generateStyleRulesIfNeeded: function()
545     {
546         if (WebInspector.NavigationSidebarPanel._styleElement)
547             return;
548
549         WebInspector.NavigationSidebarPanel._styleElement = document.createElement("style");
550
551         const maximumSidebarTreeDepth = 32;
552         const baseLeftPadding = 5; // Matches the padding in NavigationSidebarPanel.css for the item class. Keep in sync.
553         const depthPadding = 10;
554
555         var styleText = "";
556         var childrenSubstring = "";
557         for (var i = 1; i <= maximumSidebarTreeDepth; ++i) {
558             // Keep all the elements at the same depth once the maximum is reached.
559             childrenSubstring += i === maximumSidebarTreeDepth ? " .children" : " > .children";
560             styleText += "." + WebInspector.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName + childrenSubstring + " > .item { ";
561             styleText += "padding-left: " + (baseLeftPadding + (depthPadding * i)) + "px; }\n";
562         }
563
564         WebInspector.NavigationSidebarPanel._styleElement.textContent = styleText;
565
566         document.head.appendChild(WebInspector.NavigationSidebarPanel._styleElement);
567     },
568
569     _generateDisclosureTrianglesIfNeeded: function()
570     {
571         if (WebInspector.NavigationSidebarPanel._generatedDisclosureTriangles)
572             return;
573
574         // Set this early instead of in _generateDisclosureTriangle because we don't want multiple panels that are
575         // created at the same time to duplicate the work (even though it would be harmless.)
576         WebInspector.NavigationSidebarPanel._generatedDisclosureTriangles = true;
577
578         var specifications = {};
579
580         if (WebInspector.Platform.isLegacyMacOS) {
581             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleNormalCanvasIdentifierSuffix] = {
582                 fillColor: [112, 126, 139],
583                 shadowColor: [255, 255, 255, 0.8],
584                 shadowOffsetX: 0,
585                 shadowOffsetY: 1,
586                 shadowBlur: 0
587             };
588
589             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleSelectedCanvasIdentifierSuffix] = {
590                 fillColor: [255, 255, 255],
591                 shadowColor: [61, 91, 110, 0.8],
592                 shadowOffsetX: 0,
593                 shadowOffsetY: 1,
594                 shadowBlur: 2
595             };
596         } else {
597             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleNormalCanvasIdentifierSuffix] = {
598                 fillColor: [140, 140, 140]
599             };
600
601             specifications[WebInspector.NavigationSidebarPanel.DisclosureTriangleSelectedCanvasIdentifierSuffix] = {
602                 fillColor: [255, 255, 255]
603             };
604         }
605
606         generateColoredImagesForCSS("Images/DisclosureTriangleSmallOpen.svg", specifications, 13, 13, WebInspector.NavigationSidebarPanel.DisclosureTriangleOpenCanvasIdentifier);
607         generateColoredImagesForCSS("Images/DisclosureTriangleSmallClosed.svg", specifications, 13, 13, WebInspector.NavigationSidebarPanel.DisclosureTriangleClosedCanvasIdentifier);
608     },
609
610     _checkForOldResources: function(event)
611     {
612         if (this._checkForOldResourcesTimeoutIdentifier)
613             return;
614
615         function delayedWork()
616         {
617             delete this._checkForOldResourcesTimeoutIdentifier;
618
619             var contentTreeOutline = this.contentTreeOutlineToAutoPrune;
620
621             // Check all the ResourceTreeElements at the top level to make sure their Resource still has a parentFrame in the frame hierarchy.
622             // 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
623             // we should remove the issues for that resource.
624             for (var i = contentTreeOutline.children.length - 1; i >= 0; --i) {
625                 var treeElement = contentTreeOutline.children[i];
626                 if (!(treeElement instanceof WebInspector.ResourceTreeElement))
627                     continue;
628
629                 var resource = treeElement.resource;
630                 if (!resource.parentFrame || resource.parentFrame.isDetached())
631                     contentTreeOutline.removeChildAtIndex(i, true, true);
632             }
633
634             if (typeof this._updateEmptyContentPlaceholder === "function")
635                 this._updateEmptyContentPlaceholder();
636         }
637
638         // Check on a delay to coalesce multiple calls to _checkForOldResources.
639         this._checkForOldResourcesTimeoutIdentifier = setTimeout(delayedWork.bind(this), 0);
640     },
641
642     _isTreeElementWithoutRepresentedObject: function(treeElement)
643     {
644         return treeElement instanceof WebInspector.FolderTreeElement
645             || treeElement instanceof WebInspector.DatabaseHostTreeElement
646             || typeof treeElement.representedObject === "string"
647             || treeElement.representedObject instanceof String;
648     },
649
650     _checkOutlinesForPendingViewStateCookie: function(matchTypeOnly)
651     {
652         if (!this._pendingViewStateCookie)
653             return;
654
655         var visibleTreeElements = [];
656         this._visibleContentTreeOutlines.forEach(function(outline) {
657             var currentTreeElement = outline.hasChildren ? outline.children[0] : null;
658             while (currentTreeElement) {
659                 visibleTreeElements.push(currentTreeElement);
660                 currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false);
661             }
662         });
663
664         return this._checkElementsForPendingViewStateCookie(visibleTreeElements, matchTypeOnly);
665     },
666
667     _checkElementsForPendingViewStateCookie: function(treeElements, matchTypeOnly)
668     {
669         if (!this._pendingViewStateCookie)
670             return;
671
672         var cookie = this._pendingViewStateCookie;
673
674         function treeElementMatchesCookie(treeElement)
675         {
676             if (this._isTreeElementWithoutRepresentedObject(treeElement))
677                 return false;
678
679             var representedObject = treeElement.representedObject;
680             if (!representedObject)
681                 return false;
682
683             var typeIdentifier = cookie[WebInspector.TypeIdentifierCookieKey];
684             if (typeIdentifier !== representedObject.constructor.TypeIdentifier)
685                 return false;
686
687             if (matchTypeOnly)
688                 return true;
689
690             var candidateObjectCookie = {};
691             if (representedObject.saveIdentityToCookie)
692                 representedObject.saveIdentityToCookie(candidateObjectCookie);
693
694             var candidateCookieKeys = Object.keys(candidateObjectCookie);
695             return candidateCookieKeys.length && candidateCookieKeys.every(function valuesMatchForKey(key) {
696                 return candidateObjectCookie[key] === cookie[key];
697             });
698         }
699
700         if (!(treeElements instanceof Array))
701             treeElements = [treeElements];
702
703         var matchedElement = null;
704         treeElements.some(function(element) {
705             if (treeElementMatchesCookie.call(this, element)) {
706                 matchedElement = element;
707                 return true;
708             }
709         }, this);
710
711         if (matchedElement) {
712             matchedElement.revealAndSelect();
713
714             delete this._pendingViewStateCookie;
715
716             // Delay clearing the restoringState flag until the next runloop so listeners
717             // checking for it in this runloop still know state was being restored.
718             setTimeout(function() {
719                 delete this._restoringState;
720             }.bind(this));
721
722             if (this._finalAttemptToRestoreViewStateTimeout) {
723                 clearTimeout(this._finalAttemptToRestoreViewStateTimeout);
724                 delete this._finalAttemptToRestoreViewStateTimeout;
725             }
726         }
727     }
728 };