| /* |
| * Copyright (C) 2013, 2015 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WI.NavigationSidebarPanel = class NavigationSidebarPanel extends WI.SidebarPanel |
| { |
| constructor(identifier, displayName, shouldAutoPruneStaleTopLevelResourceTreeElements, wantsTopOverflowShadow) |
| { |
| super(identifier, displayName); |
| |
| this.element.classList.add("navigation"); |
| |
| this._updateContentOverflowShadowVisibilityDebouncer = new Debouncer(() => { |
| this._updateContentOverflowShadowVisibility(); |
| }); |
| this._boundUpdateContentOverflowShadowVisibilitySoon = (event) => { |
| this._updateContentOverflowShadowVisibilityDebouncer.delayForTime(0); |
| }; |
| |
| this.contentView.element.addEventListener("scroll", this._boundUpdateContentOverflowShadowVisibilitySoon); |
| |
| this._contentTreeOutlineGroup = new WI.TreeOutlineGroup; |
| this._contentTreeOutline = this.createContentTreeOutline(); |
| |
| this._filterBar = new WI.FilterBar; |
| this._filterBar.addEventListener(WI.FilterBar.Event.FilterDidChange, this._filterDidChange, this); |
| this.element.appendChild(this._filterBar.element); |
| |
| this._bottomOverflowShadowElement = document.createElement("div"); |
| this._bottomOverflowShadowElement.className = WI.NavigationSidebarPanel.OverflowShadowElementStyleClassName; |
| this.element.appendChild(this._bottomOverflowShadowElement); |
| |
| if (wantsTopOverflowShadow) { |
| this._topOverflowShadowElement = this.element.appendChild(document.createElement("div")); |
| this._topOverflowShadowElement.classList.add(WI.NavigationSidebarPanel.OverflowShadowElementStyleClassName, "top"); |
| } |
| |
| window.addEventListener("resize", this._boundUpdateContentOverflowShadowVisibilitySoon); |
| |
| this._filtersSetting = new WI.Setting(identifier + "-navigation-sidebar-filters", {}); |
| this._filterBar.filters = this._filtersSetting.value; |
| |
| this._emptyContentPlaceholderElements = new Map; |
| this._emptyFilterResults = new Set; |
| |
| this._shouldAutoPruneStaleTopLevelResourceTreeElements = shouldAutoPruneStaleTopLevelResourceTreeElements || false; |
| |
| if (this._shouldAutoPruneStaleTopLevelResourceTreeElements) { |
| WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._checkForStaleResources, this); |
| WI.Frame.addEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._checkForStaleResources, this); |
| WI.Frame.addEventListener(WI.Frame.Event.ResourceWasRemoved, this._checkForStaleResources, this); |
| } |
| |
| this._pendingViewStateCookie = null; |
| this._restoringState = false; |
| } |
| |
| // Public |
| |
| get filterBar() { return this._filterBar; } |
| get hasActiveFilters() { return this._filterBar.hasActiveFilters(); } |
| |
| closed() |
| { |
| window.removeEventListener("resize", this._boundUpdateContentOverflowShadowVisibilitySoon); |
| WI.Frame.removeEventListener(null, null, this); |
| } |
| |
| get contentBrowser() |
| { |
| return this._contentBrowser; |
| } |
| |
| set contentBrowser(contentBrowser) |
| { |
| this._contentBrowser = contentBrowser || null; |
| } |
| |
| get contentTreeOutline() |
| { |
| return this._contentTreeOutline; |
| } |
| |
| get contentTreeOutlines() |
| { |
| return Array.from(this._contentTreeOutlineGroup); |
| } |
| |
| get currentRepresentedObject() |
| { |
| if (!this._contentBrowser) |
| return null; |
| |
| return this._contentBrowser.currentRepresentedObjects[0] || null; |
| } |
| |
| get restoringState() |
| { |
| return this._restoringState; |
| } |
| |
| cancelRestoringState() |
| { |
| this._pendingViewStateCookie = null; |
| if (!this._finalAttemptToRestoreViewStateTimeout) |
| return; |
| |
| clearTimeout(this._finalAttemptToRestoreViewStateTimeout); |
| this._finalAttemptToRestoreViewStateTimeout = undefined; |
| } |
| |
| createContentTreeOutline({ignoreCookieRestoration, suppressFiltering} = {}) |
| { |
| let contentTreeOutline = new WI.TreeOutline; |
| contentTreeOutline.allowsRepeatSelection = true; |
| contentTreeOutline.element.classList.add(WI.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName); |
| |
| this._contentTreeOutlineGroup.add(contentTreeOutline); |
| |
| this.contentView.element.appendChild(contentTreeOutline.element); |
| |
| if (!suppressFiltering) { |
| contentTreeOutline.addEventListener(WI.TreeOutline.Event.ElementAdded, this._treeElementAddedOrChanged, this); |
| contentTreeOutline.addEventListener(WI.TreeOutline.Event.ElementDidChange, this._treeElementAddedOrChanged, this); |
| contentTreeOutline.addEventListener(WI.TreeOutline.Event.ElementDisclosureDidChanged, this._treeElementDisclosureDidChange, this); |
| } |
| |
| contentTreeOutline[WI.NavigationSidebarPanel.IgnoreCookieRestoration] = ignoreCookieRestoration; |
| contentTreeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol] = suppressFiltering; |
| |
| return contentTreeOutline; |
| } |
| |
| suppressFilteringOnTreeElements(treeElements) |
| { |
| console.assert(Array.isArray(treeElements), "TreeElements should be an array."); |
| |
| for (let treeElement of treeElements) |
| treeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol] = true; |
| |
| this.updateFilter(); |
| } |
| |
| treeElementForRepresentedObject(representedObject) |
| { |
| let treeElement = null; |
| for (let treeOutline of this.contentTreeOutlines) { |
| treeElement = treeOutline.getCachedTreeElement(representedObject); |
| if (treeElement) |
| break; |
| } |
| |
| return treeElement; |
| } |
| |
| showDefaultContentView() |
| { |
| // Implemented by subclasses if needed to show a content view when no existing tree element is selected. |
| } |
| |
| showDefaultContentViewForTreeElement(treeElement) |
| { |
| console.assert(treeElement); |
| console.assert(treeElement.representedObject); |
| if (!treeElement || !treeElement.representedObject) |
| return false; |
| |
| // FIXME: <https://webkit.org/b/153634> Web Inspector: some background tabs think they are the foreground tab and do unnecessary work |
| // Do not steal a content view if we are not the active tab/sidebar. |
| if (!this.selected) { |
| let contentView = this.contentBrowser.contentViewForRepresentedObject(treeElement.representedObject); |
| if (contentView && contentView.parentContainer && contentView.parentContainer !== this.contentBrowser.contentViewContainer) |
| return false; |
| |
| // contentView.parentContainer may be null. Check for selected tab, too. |
| let selectedTabContentView = WI.tabBrowser.selectedTabContentView; |
| if (selectedTabContentView && selectedTabContentView.contentBrowser !== this.contentBrowser) |
| return false; |
| } |
| |
| let contentView = this.contentBrowser.showContentViewForRepresentedObject(treeElement.representedObject); |
| if (!contentView) |
| return false; |
| |
| treeElement.revealAndSelect(true, false, true); |
| return true; |
| } |
| |
| canShowRepresentedObject(representedObject) |
| { |
| let selectedTabContentView = WI.tabBrowser.selectedTabContentView; |
| console.assert(selectedTabContentView instanceof WI.TabContentView, "Missing TabContentView for NavigationSidebarPanel."); |
| return selectedTabContentView && selectedTabContentView.canShowRepresentedObject(representedObject); |
| } |
| |
| saveStateToCookie(cookie) |
| { |
| console.assert(cookie); |
| |
| if (!this._contentBrowser) |
| return; |
| |
| let representedObject = this.currentRepresentedObject; |
| if (!representedObject) |
| return; |
| |
| cookie[WI.TypeIdentifierCookieKey] = representedObject.constructor.TypeIdentifier; |
| |
| if (representedObject.saveIdentityToCookie) { |
| representedObject.saveIdentityToCookie(cookie); |
| return; |
| } |
| |
| console.error("NavigationSidebarPanel representedObject is missing a saveIdentityToCookie implementation.", representedObject); |
| } |
| |
| // This can be supplemented by subclasses that admit a simpler strategy for static tree elements. |
| restoreStateFromCookie(cookie, relaxedMatchDelay) |
| { |
| this._pendingViewStateCookie = cookie; |
| this._restoringState = true; |
| |
| // Check if any existing tree elements in any outline match the cookie. |
| this._checkOutlinesForPendingViewStateCookie(); |
| |
| if (this._finalAttemptToRestoreViewStateTimeout) |
| clearTimeout(this._finalAttemptToRestoreViewStateTimeout); |
| |
| if (relaxedMatchDelay === 0) |
| return; |
| |
| function finalAttemptToRestoreViewStateFromCookie() |
| { |
| this._finalAttemptToRestoreViewStateTimeout = undefined; |
| |
| this._checkOutlinesForPendingViewStateCookie(true); |
| |
| this._pendingViewStateCookie = null; |
| this._restoringState = false; |
| } |
| |
| // If the specific tree element wasn't found, we may need to wait for the resources |
| // to be registered. We try one last time (match type only) after an arbitrary amount of timeout. |
| this._finalAttemptToRestoreViewStateTimeout = setTimeout(finalAttemptToRestoreViewStateFromCookie.bind(this), relaxedMatchDelay); |
| } |
| |
| showEmptyContentPlaceholder(message, treeOutline) |
| { |
| console.assert(message); |
| |
| treeOutline = treeOutline || this._contentTreeOutline; |
| |
| let emptyContentPlaceholderElement = this._emptyContentPlaceholderElements.get(treeOutline); |
| if (emptyContentPlaceholderElement) |
| emptyContentPlaceholderElement.remove(); |
| |
| emptyContentPlaceholderElement = message instanceof Node ? message : WI.createMessageTextView(message); |
| this._emptyContentPlaceholderElements.set(treeOutline, emptyContentPlaceholderElement); |
| |
| let emptyContentPlaceholderParentElement = treeOutline.element.parentNode; |
| emptyContentPlaceholderParentElement.appendChild(emptyContentPlaceholderElement); |
| |
| this._updateContentOverflowShadowVisibilityDebouncer.force(); |
| |
| return emptyContentPlaceholderElement; |
| } |
| |
| hideEmptyContentPlaceholder(treeOutline) |
| { |
| treeOutline = treeOutline || this._contentTreeOutline; |
| |
| let emptyContentPlaceholderElement = this._emptyContentPlaceholderElements.get(treeOutline); |
| if (!emptyContentPlaceholderElement || !emptyContentPlaceholderElement.parentNode) |
| return; |
| |
| emptyContentPlaceholderElement.remove(); |
| this._emptyContentPlaceholderElements.delete(treeOutline); |
| |
| this._updateContentOverflowShadowVisibilityDebouncer.force(); |
| } |
| |
| updateEmptyContentPlaceholder(message, treeOutline) |
| { |
| treeOutline = treeOutline || this._contentTreeOutline; |
| |
| if (!treeOutline.children.length) { |
| // No tree elements, so no results. |
| this.showEmptyContentPlaceholder(message, treeOutline); |
| } else if (!this._emptyFilterResults.has(treeOutline)) { |
| // There are tree elements, and not all of them are hidden by the filter. |
| this.hideEmptyContentPlaceholder(treeOutline); |
| } |
| } |
| |
| updateFilter() |
| { |
| let selectedTreeElement; |
| for (let treeOutline of this.contentTreeOutlines) { |
| if (treeOutline.hidden || treeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol]) |
| continue; |
| |
| selectedTreeElement = treeOutline.selectedTreeElement; |
| if (selectedTreeElement) |
| break; |
| } |
| |
| let filters = this._filterBar.filters; |
| this._textFilterRegex = simpleGlobStringToRegExp(filters.text, "i"); |
| this._filtersSetting.value = filters; |
| this._filterFunctions = filters.functions; |
| |
| // Don't populate if we don't have any active filters. |
| // We only need to populate when a filter needs to reveal. |
| let dontPopulate = !this._filterBar.hasActiveFilters() && !this.shouldFilterPopulate(); |
| |
| // Update all trees that allow filtering. |
| for (let treeOutline of this.contentTreeOutlines) { |
| if (treeOutline.hidden || treeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol]) |
| continue; |
| |
| let currentTreeElement = treeOutline.children[0]; |
| while (currentTreeElement && !currentTreeElement.root) { |
| if (!currentTreeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol]) { |
| const currentTreeElementWasHidden = currentTreeElement.hidden; |
| this.applyFiltersToTreeElement(currentTreeElement); |
| if (currentTreeElementWasHidden !== currentTreeElement.hidden) |
| this._treeElementWasFiltered(currentTreeElement); |
| } |
| |
| currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, dontPopulate); |
| } |
| } |
| |
| this._checkForEmptyFilterResults(); |
| this._updateContentOverflowShadowVisibilityDebouncer.force(); |
| } |
| |
| resetFilter() |
| { |
| this._filterBar.clear(); |
| } |
| |
| shouldFilterPopulate() |
| { |
| // Overridden by subclasses if needed. |
| return this.hasCustomFilters(); |
| } |
| |
| hasCustomFilters() |
| { |
| // Implemented by subclasses if needed. |
| return false; |
| } |
| |
| matchTreeElementAgainstCustomFilters(treeElement) |
| { |
| // Implemented by subclasses if needed. |
| return true; |
| } |
| |
| matchTreeElementAgainstFilterFunctions(treeElement) |
| { |
| if (!this._filterFunctions || !this._filterFunctions.length) |
| return true; |
| |
| for (var filterFunction of this._filterFunctions) { |
| if (filterFunction(treeElement)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| applyFiltersToTreeElement(treeElement) |
| { |
| if (!this._filterBar.hasActiveFilters() && !this.hasCustomFilters()) { |
| // No filters, so make everything visible. |
| treeElement.hidden = false; |
| |
| // If this tree element was expanded during filtering, collapse it again. |
| if (treeElement.expanded && treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol]) { |
| treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol] = false; |
| treeElement.collapse(); |
| } |
| |
| return; |
| } |
| |
| var filterableData = treeElement.filterableData || {}; |
| |
| var flags = {expandTreeElement: false}; |
| var filterRegex = this._textFilterRegex; |
| |
| function matchTextFilter(inputs) |
| { |
| if (!inputs || !filterRegex) |
| return true; |
| |
| console.assert(inputs instanceof Array, "filterableData.text should be an array of text inputs"); |
| |
| // Loop over all the inputs and try to match them. |
| for (var input of inputs) { |
| if (!input) |
| continue; |
| if (filterRegex.test(input)) { |
| flags.expandTreeElement = true; |
| return true; |
| } |
| } |
| |
| // No inputs matched. |
| return false; |
| } |
| |
| function makeVisible() |
| { |
| // Make this element visible. |
| treeElement.hidden = false; |
| |
| // Make the ancestors visible and expand them. |
| var currentAncestor = treeElement.parent; |
| while (currentAncestor && !currentAncestor.root) { |
| currentAncestor.hidden = false; |
| |
| // Only expand if the built-in filters matched, not custom filters. |
| if (flags.expandTreeElement && !currentAncestor.expanded) { |
| currentAncestor.__wasExpandedDuringFiltering = true; |
| currentAncestor.expand(); |
| } |
| |
| currentAncestor = currentAncestor.parent; |
| } |
| } |
| |
| let suppressFiltering = treeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol]; |
| |
| if (suppressFiltering || (matchTextFilter(filterableData.text) && this.matchTreeElementAgainstFilterFunctions(treeElement, flags) && this.matchTreeElementAgainstCustomFilters(treeElement, flags))) { |
| // Make this element visible since it matches. |
| makeVisible(); |
| |
| // If this tree element didn't match a built-in filter and was expanded earlier during filtering, collapse it again. |
| if (!flags.expandTreeElement && treeElement.expanded && treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol]) { |
| treeElement[WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol] = false; |
| treeElement.collapse(); |
| } |
| |
| return; |
| } |
| |
| // Make this element invisible since it does not match. |
| treeElement.hidden = true; |
| } |
| |
| shown() |
| { |
| super.shown(); |
| |
| this._updateContentOverflowShadowVisibilityDebouncer.force(); |
| } |
| |
| // Protected |
| |
| pruneStaleResourceTreeElements() |
| { |
| if (this._checkForStaleResourcesTimeoutIdentifier) { |
| clearTimeout(this._checkForStaleResourcesTimeoutIdentifier); |
| this._checkForStaleResourcesTimeoutIdentifier = undefined; |
| } |
| |
| for (let contentTreeOutline of this.contentTreeOutlines) { |
| // Check all the ResourceTreeElements at the top level to make sure their Resource still has a parentFrame in the frame hierarchy. |
| // 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 |
| // we should remove the issues for that resource. |
| for (var i = contentTreeOutline.children.length - 1; i >= 0; --i) { |
| var treeElement = contentTreeOutline.children[i]; |
| if (!(treeElement instanceof WI.ResourceTreeElement)) |
| continue; |
| |
| var resource = treeElement.resource; |
| if (!resource.parentFrame || resource.parentFrame.isDetached()) |
| contentTreeOutline.removeChildAtIndex(i, true, true); |
| } |
| } |
| } |
| |
| // Private |
| |
| _updateContentOverflowShadowVisibility() |
| { |
| if (!this.visible) |
| return; |
| |
| let scrollHeight = this.contentView.element.scrollHeight; |
| let offsetHeight = this.contentView.element.offsetHeight; |
| |
| if (scrollHeight < offsetHeight) { |
| if (this._topOverflowShadowElement) |
| this._topOverflowShadowElement.style.opacity = 0; |
| this._bottomOverflowShadowElement.style.opacity = 0; |
| return; |
| } |
| |
| let edgeThreshold = 1; |
| let scrollTop = this.contentView.element.scrollTop; |
| |
| let topCoverage = Math.min(scrollTop, edgeThreshold); |
| let bottomCoverage = Math.max(0, (offsetHeight + scrollTop) - (scrollHeight - edgeThreshold)); |
| |
| if (this._topOverflowShadowElement) |
| this._topOverflowShadowElement.style.opacity = (topCoverage / edgeThreshold).toFixed(1); |
| this._bottomOverflowShadowElement.style.opacity = (1 - (bottomCoverage / edgeThreshold)).toFixed(1); |
| } |
| |
| _checkForEmptyFilterResults() |
| { |
| function checkTreeOutlineForEmptyFilterResults(treeOutline) |
| { |
| // No tree elements, so don't touch the empty content placeholder. |
| if (!treeOutline.children.length) |
| return; |
| |
| // Iterate over all the top level tree elements. If any filterable elements are visible, return early. |
| let filterableTreeElementFound = false; |
| let unfilteredTreeElementFound = false; |
| let currentTreeElement = treeOutline.children[0]; |
| while (currentTreeElement) { |
| let suppressFilteringForTreeElement = currentTreeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol]; |
| if (!suppressFilteringForTreeElement) { |
| filterableTreeElementFound = true; |
| |
| if (!currentTreeElement.hidden) { |
| unfilteredTreeElementFound = true; |
| break; |
| } |
| } |
| |
| currentTreeElement = currentTreeElement.nextSibling; |
| } |
| |
| if (unfilteredTreeElementFound || !filterableTreeElementFound) { |
| this.hideEmptyContentPlaceholder(treeOutline); |
| this._emptyFilterResults.delete(treeOutline); |
| return; |
| } |
| |
| let message = WI.createMessageTextView(WI.UIString("No Filter Results")); |
| |
| let buttonElement = message.appendChild(document.createElement("button")); |
| buttonElement.textContent = WI.UIString("Clear Filters"); |
| buttonElement.addEventListener("click", () => { |
| this.resetFilter(); |
| }); |
| |
| // All top level tree elements are hidden, so filtering hid everything. Show a message. |
| this.showEmptyContentPlaceholder(message, treeOutline); |
| this._emptyFilterResults.add(treeOutline); |
| } |
| |
| for (let treeOutline of this.contentTreeOutlines) { |
| if (treeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol]) |
| continue; |
| |
| checkTreeOutlineForEmptyFilterResults.call(this, treeOutline); |
| } |
| } |
| |
| _filterDidChange() |
| { |
| this.updateFilter(); |
| } |
| |
| _treeElementAddedOrChanged(event) |
| { |
| // Don't populate if we don't have any active filters. |
| // We only need to populate when a filter needs to reveal. |
| var dontPopulate = !this._filterBar.hasActiveFilters() && !this.shouldFilterPopulate(); |
| |
| // Apply the filters to the tree element and its descendants. |
| let treeElement = event.data.element; |
| let currentTreeElement = treeElement; |
| while (currentTreeElement && !currentTreeElement.root) { |
| if (!currentTreeElement[WI.NavigationSidebarPanel.SuppressFilteringSymbol]) { |
| const currentTreeElementWasHidden = currentTreeElement.hidden; |
| this.applyFiltersToTreeElement(currentTreeElement); |
| if (currentTreeElementWasHidden !== currentTreeElement.hidden) |
| this._treeElementWasFiltered(currentTreeElement); |
| } |
| |
| currentTreeElement = currentTreeElement.traverseNextTreeElement(false, treeElement, dontPopulate); |
| } |
| |
| this._checkForEmptyFilterResults(); |
| |
| if (this.visible) |
| this._updateContentOverflowShadowVisibilityDebouncer.delayForTime(0); |
| |
| if (this.selected && !treeElement.treeOutline[WI.NavigationSidebarPanel.IgnoreCookieRestoration]) |
| this._checkElementsForPendingViewStateCookie([treeElement]); |
| } |
| |
| _treeElementDisclosureDidChange(event) |
| { |
| this._updateContentOverflowShadowVisibilityDebouncer.delayForTime(0); |
| } |
| |
| _checkForStaleResourcesIfNeeded() |
| { |
| if (!this._checkForStaleResourcesTimeoutIdentifier || !this._shouldAutoPruneStaleTopLevelResourceTreeElements) |
| return; |
| this.pruneStaleResourceTreeElements(); |
| } |
| |
| _checkForStaleResources(event) |
| { |
| console.assert(this._shouldAutoPruneStaleTopLevelResourceTreeElements); |
| |
| if (this._checkForStaleResourcesTimeoutIdentifier) |
| return; |
| |
| // Check on a delay to coalesce multiple calls to _checkForStaleResources. |
| this._checkForStaleResourcesTimeoutIdentifier = setTimeout(this.pruneStaleResourceTreeElements.bind(this)); |
| } |
| |
| _isTreeElementWithoutRepresentedObject(treeElement) |
| { |
| return treeElement instanceof WI.FolderTreeElement |
| || treeElement instanceof WI.DatabaseHostTreeElement |
| || treeElement instanceof WI.IndexedDatabaseHostTreeElement |
| || treeElement instanceof WI.ApplicationCacheManifestTreeElement |
| || treeElement instanceof WI.ThreadTreeElement |
| || treeElement instanceof WI.IdleTreeElement |
| || treeElement instanceof WI.DOMBreakpointTreeElement |
| || treeElement instanceof WI.EventBreakpointTreeElement |
| || treeElement instanceof WI.URLBreakpointTreeElement |
| || treeElement instanceof WI.CSSStyleSheetTreeElement |
| || typeof treeElement.representedObject === "string" |
| || treeElement.representedObject instanceof String; |
| } |
| |
| _checkOutlinesForPendingViewStateCookie(matchTypeOnly) |
| { |
| if (!this._pendingViewStateCookie) |
| return; |
| |
| this._checkForStaleResourcesIfNeeded(); |
| |
| var visibleTreeElements = []; |
| this.contentTreeOutlines.forEach(function(outline) { |
| if (outline[WI.NavigationSidebarPanel.IgnoreCookieRestoration]) |
| return; |
| |
| var currentTreeElement = outline.hasChildren ? outline.children[0] : null; |
| while (currentTreeElement) { |
| visibleTreeElements.push(currentTreeElement); |
| currentTreeElement = currentTreeElement.traverseNextTreeElement(false, null, false); |
| } |
| }); |
| |
| this._checkElementsForPendingViewStateCookie(visibleTreeElements, matchTypeOnly); |
| } |
| |
| _checkElementsForPendingViewStateCookie(treeElements, matchTypeOnly) |
| { |
| if (!this._pendingViewStateCookie) |
| return; |
| |
| var cookie = this._pendingViewStateCookie; |
| |
| function treeElementMatchesCookie(treeElement) |
| { |
| if (this._isTreeElementWithoutRepresentedObject(treeElement)) |
| return false; |
| |
| var representedObject = treeElement.representedObject; |
| if (!representedObject) |
| return false; |
| |
| var typeIdentifier = cookie[WI.TypeIdentifierCookieKey]; |
| if (typeIdentifier !== representedObject.constructor.TypeIdentifier) |
| return false; |
| |
| if (matchTypeOnly) |
| return true; |
| |
| var candidateObjectCookie = {}; |
| if (representedObject.saveIdentityToCookie) |
| representedObject.saveIdentityToCookie(candidateObjectCookie); |
| |
| var candidateCookieKeys = Object.keys(candidateObjectCookie); |
| return candidateCookieKeys.length && candidateCookieKeys.every((key) => candidateObjectCookie[key] === cookie[key]); |
| } |
| |
| var matchedElement = null; |
| treeElements.some((element) => { |
| if (treeElementMatchesCookie.call(this, element)) { |
| matchedElement = element; |
| return true; |
| } |
| return false; |
| }); |
| |
| if (matchedElement) { |
| let didShowContentView = this.showDefaultContentViewForTreeElement(matchedElement); |
| if (!didShowContentView) |
| return; |
| |
| this._pendingViewStateCookie = null; |
| |
| // Delay clearing the restoringState flag until the next runloop so listeners |
| // checking for it in this runloop still know state was being restored. |
| setTimeout(() => { |
| this._restoringState = false; |
| }, 0); |
| |
| if (this._finalAttemptToRestoreViewStateTimeout) { |
| clearTimeout(this._finalAttemptToRestoreViewStateTimeout); |
| this._finalAttemptToRestoreViewStateTimeout = undefined; |
| } |
| } |
| } |
| |
| _treeElementWasFiltered(treeElement) |
| { |
| if (treeElement.selected || treeElement.hidden) |
| return; |
| |
| let representedObject = this.currentRepresentedObject; |
| if (!representedObject || treeElement.representedObject !== representedObject) |
| return; |
| |
| const omitFocus = true; |
| const selectedByUser = false; |
| const suppressNotification = true; |
| treeElement.revealAndSelect(omitFocus, selectedByUser, suppressNotification); |
| } |
| }; |
| |
| WI.NavigationSidebarPanel.IgnoreCookieRestoration = Symbol("ignore-cookie-restoration"); |
| WI.NavigationSidebarPanel.SuppressFilteringSymbol = Symbol("suppress-filtering"); |
| WI.NavigationSidebarPanel.WasExpandedDuringFilteringSymbol = Symbol("was-expanded-during-filtering"); |
| |
| WI.NavigationSidebarPanel.OverflowShadowElementStyleClassName = "overflow-shadow"; |
| WI.NavigationSidebarPanel.ContentTreeOutlineElementStyleClassName = "navigation-sidebar-panel-content-tree-outline"; |