773 lines
30 KiB
JavaScript
773 lines
30 KiB
JavaScript
/*
|
|
* 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);
|
|
|
|
if (this._shouldAutoPruneStaleTopLevelResourceTreeElements) {
|
|
WI.Frame.removeEventListener(WI.Frame.Event.MainResourceDidChange, this._checkForStaleResources, this);
|
|
WI.Frame.removeEventListener(WI.Frame.Event.ChildFrameWasRemoved, this._checkForStaleResources, this);
|
|
WI.Frame.removeEventListener(WI.Frame.Event.ResourceWasRemoved, this._checkForStaleResources, 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.addEventListener(WI.TreeOutline.Event.ElementRemoved, this._handleTreeElementRemoved, 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 filters = this._filterBar.filters;
|
|
this._textFilterRegex = filters.text ? WI.SearchUtilities.filterRegExpForString(filters.text, WI.SearchUtilities.defaultSettings) : null;
|
|
this._filtersSetting.value = filters;
|
|
this._filterFunctions = filters.functions;
|
|
|
|
this._filterBar.invalid = filters.text && !this._textFilterRegex;
|
|
|
|
// 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(treeOutline);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
attached()
|
|
{
|
|
super.attached();
|
|
|
|
this._updateContentOverflowShadowVisibilityDebouncer.force();
|
|
|
|
if (this._contentBrowser && !this._contentBrowser.currentContentView)
|
|
this.showDefaultContentView();
|
|
}
|
|
|
|
// 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 (let i = contentTreeOutline.children.length - 1; i >= 0; --i) {
|
|
let treeElement = contentTreeOutline.children[i];
|
|
if (!(treeElement instanceof WI.ResourceTreeElement))
|
|
continue;
|
|
|
|
// Local Overrides are never stale resources.
|
|
let resource = treeElement.resource;
|
|
if (resource.localResourceOverride)
|
|
continue;
|
|
|
|
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(treeOutline)
|
|
{
|
|
if (treeOutline[WI.NavigationSidebarPanel.SuppressFilteringSymbol])
|
|
return;
|
|
|
|
// 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);
|
|
}
|
|
|
|
_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(event.target);
|
|
|
|
if (this.visible)
|
|
this._updateContentOverflowShadowVisibilityDebouncer.delayForTime(0);
|
|
|
|
if (this.selected && !treeElement.treeOutline[WI.NavigationSidebarPanel.IgnoreCookieRestoration])
|
|
this._checkElementsForPendingViewStateCookie([treeElement]);
|
|
}
|
|
|
|
_treeElementDisclosureDidChange(event)
|
|
{
|
|
this._updateContentOverflowShadowVisibilityDebouncer.delayForTime(0);
|
|
}
|
|
|
|
_handleTreeElementRemoved(event)
|
|
{
|
|
this._checkForEmptyFilterResults(event.target);
|
|
|
|
if (this.visible)
|
|
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.SymbolicBreakpointTreeElement
|
|
|| 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 !!typeIdentifier;
|
|
|
|
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";
|