blob: 0b7f62d4ac46f1af0200ba7ca26c7047a99983aa [file] [log] [blame]
/*
* Copyright (C) 2018 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.SourcesSidebarPanel = class SourcesSidebarPanel extends WI.NavigationSidebarPanel
{
constructor()
{
super("sources", WI.UIString("Sources"), true);
this._navigationBar = new WI.NavigationBar;
this.addSubview(this._navigationBar);
this._targetTreeElementMap = new Map;
let scopeItemPrefix = "sources-sidebar-";
let scopeBarItems = [];
scopeBarItems.push(new WI.ScopeBarItem(scopeItemPrefix + "type-all", WI.UIString("All Resources"), true));
for (let key in WI.Resource.Type) {
let value = WI.Resource.Type[key];
let scopeBarItem = new WI.ScopeBarItem(scopeItemPrefix + value, WI.Resource.displayNameForType(value, true));
scopeBarItem[WI.SourcesSidebarPanel.ResourceTypeSymbol] = value;
scopeBarItems.push(scopeBarItem);
}
this._scopeBar = new WI.ScopeBar("sources-sidebar-scope-bar", scopeBarItems, scopeBarItems[0], true);
this._scopeBar.addEventListener(WI.ScopeBar.Event.SelectionChanged, this._scopeBarSelectionDidChange, this);
this._navigationBar.addNavigationItem(this._scopeBar);
WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
WI.frameResourceManager.addEventListener(WI.FrameResourceManager.Event.MainFrameDidChange, this._mainFrameDidChange, this);
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ScriptAdded, this._scriptWasAdded, this);
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ScriptRemoved, this._scriptWasRemoved, this);
WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.ScriptsCleared, this._scriptsCleared, this);
WI.cssStyleManager.addEventListener(WI.CSSStyleManager.Event.StyleSheetAdded, this._styleSheetAdded, this);
WI.targetManager.addEventListener(WI.TargetManager.Event.TargetRemoved, this._targetRemoved, this);
WI.notifications.addEventListener(WI.Notification.ExtraDomainsActivated, this._extraDomainsActivated, this);
this.contentTreeOutline.addEventListener(WI.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this);
this.contentTreeOutline.includeSourceMapResourceChildren = true;
if (SourcesSidebarPanel.shouldPlaceResourcesAtTopLevel()) {
this.contentTreeOutline.disclosureButtons = false;
WI.SourceCode.addEventListener(WI.SourceCode.Event.SourceMapAdded, () => { this.contentTreeOutline.disclosureButtons = true; }, this);
}
}
// Static
static shouldPlaceResourcesAtTopLevel()
{
return (WI.sharedApp.debuggableType === WI.DebuggableType.JavaScript && !WI.sharedApp.hasExtraDomains)
|| WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker;
}
// Public
get minimumWidth()
{
return this._navigationBar.minimumWidth;
}
closed()
{
super.closed();
WI.Frame.removeEventListener(null, null, this);
WI.frameResourceManager.removeEventListener(null, null, this);
WI.debuggerManager.removeEventListener(null, null, this);
WI.notifications.removeEventListener(null, null, this);
}
showDefaultContentView()
{
if (WI.frameResourceManager.mainFrame) {
this.contentBrowser.showContentViewForRepresentedObject(WI.frameResourceManager.mainFrame);
return;
}
let firstTreeElement = this.contentTreeOutline.children[0];
if (firstTreeElement)
this.showDefaultContentViewForTreeElement(firstTreeElement);
}
treeElementForRepresentedObject(representedObject)
{
// A custom implementation is needed for this since the frames are populated lazily.
if (!this._mainFrameTreeElement && (representedObject instanceof WI.Resource || representedObject instanceof WI.Frame || representedObject instanceof WI.Collection)) {
// All resources are under the main frame, so we need to return early if we don't have the main frame yet.
return null;
}
// The Frame is used as the representedObject instead of the main resource in our tree.
if (representedObject instanceof WI.Resource && representedObject.parentFrame && representedObject.parentFrame.mainResource === representedObject)
representedObject = representedObject.parentFrame;
function isAncestor(ancestor, resourceOrFrame)
{
// SourceMapResources are descendants of another SourceCode object.
if (resourceOrFrame instanceof WI.SourceMapResource) {
if (resourceOrFrame.sourceMap.originalSourceCode === ancestor)
return true;
// Not a direct ancestor, so check the ancestors of the parent SourceCode object.
resourceOrFrame = resourceOrFrame.sourceMap.originalSourceCode;
}
let currentFrame = resourceOrFrame.parentFrame;
while (currentFrame) {
if (currentFrame === ancestor)
return true;
currentFrame = currentFrame.parentFrame;
}
return false;
}
function getParent(resourceOrFrame)
{
// SourceMapResources are descendants of another SourceCode object.
if (resourceOrFrame instanceof WI.SourceMapResource)
return resourceOrFrame.sourceMap.originalSourceCode;
return resourceOrFrame.parentFrame;
}
let treeElement = this.contentTreeOutline.findTreeElement(representedObject, isAncestor, getParent);
if (treeElement)
return treeElement;
// Only special case Script objects.
if (!(representedObject instanceof WI.Script)) {
console.error("Didn't find a TreeElement for representedObject", representedObject);
return null;
}
// If the Script has a URL we should have found it earlier.
if (representedObject.url) {
console.error("Didn't find a ScriptTreeElement for a Script with a URL.");
return null;
}
// Since the Script does not have a URL we consider it an 'anonymous' script. These scripts happen from calls to
// window.eval() or browser features like Auto Fill and Reader. They are not normally added to the sidebar, but since
// we have a ScriptContentView asking for the tree element we will make a ScriptTreeElement on demand and add it.
if (!this._anonymousScriptsFolderTreeElement) {
let collection = new WI.ScriptCollection;
this._anonymousScriptsFolderTreeElement = new WI.FolderTreeElement(WI.UIString("Anonymous Scripts"), collection);
}
if (!this._anonymousScriptsFolderTreeElement.parent) {
let index = insertionIndexForObjectInListSortedByFunction(this._anonymousScriptsFolderTreeElement, this.contentTreeOutline.children, this._compareTreeElements);
this.contentTreeOutline.insertChild(this._anonymousScriptsFolderTreeElement, index);
}
this._anonymousScriptsFolderTreeElement.representedObject.add(representedObject);
let scriptTreeElement = new WI.ScriptTreeElement(representedObject);
this._anonymousScriptsFolderTreeElement.appendChild(scriptTreeElement);
return scriptTreeElement;
}
// Protected
initialLayout()
{
super.initialLayout();
if (WI.frameResourceManager.mainFrame)
this._mainFrameMainResourceDidChange(WI.frameResourceManager.mainFrame);
for (let script of WI.debuggerManager.knownNonResourceScripts) {
this._addScript(script);
if (script.sourceMaps.length && SourcesSidebarPanel.shouldPlaceResourcesAtTopLevel())
this.contentTreeOutline.disclosureButtons = true;
}
}
hasCustomFilters()
{
console.assert(this._scopeBar.selectedItems.length === 1);
let selectedScopeBarItem = this._scopeBar.selectedItems[0];
return selectedScopeBarItem && !selectedScopeBarItem.exclusive;
}
matchTreeElementAgainstCustomFilters(treeElement, flags)
{
console.assert(this._scopeBar.selectedItems.length === 1);
let selectedScopeBarItem = this._scopeBar.selectedItems[0];
// Show everything if there is no selection or "All Resources" is selected (the exclusive item).
if (!selectedScopeBarItem || selectedScopeBarItem.exclusive)
return true;
// Folders are hidden on the first pass, but visible childen under the folder will force the folder visible again.
if (treeElement instanceof WI.FolderTreeElement)
return false;
function match()
{
if (treeElement instanceof WI.FrameTreeElement)
return selectedScopeBarItem[WI.SourcesSidebarPanel.ResourceTypeSymbol] === WI.Resource.Type.Document;
if (treeElement instanceof WI.ScriptTreeElement)
return selectedScopeBarItem[WI.SourcesSidebarPanel.ResourceTypeSymbol] === WI.Resource.Type.Script;
if (treeElement instanceof WI.CSSStyleSheetTreeElement)
return selectedScopeBarItem[WI.SourcesSidebarPanel.ResourceTypeSymbol] === WI.Resource.Type.Stylesheet;
console.assert(treeElement instanceof WI.ResourceTreeElement, "Unknown treeElement", treeElement);
if (!(treeElement instanceof WI.ResourceTreeElement))
return false;
return treeElement.resource.type === selectedScopeBarItem[WI.SourcesSidebarPanel.ResourceTypeSymbol];
}
let matched = match();
if (matched)
flags.expandTreeElement = true;
return matched;
}
// Private
_mainResourceDidChange(event)
{
if (!event.target.isMainFrame())
return;
this._mainFrameMainResourceDidChange(event.target);
}
_mainFrameDidChange(event)
{
this._mainFrameMainResourceDidChange(WI.frameResourceManager.mainFrame);
}
_mainFrameMainResourceDidChange(mainFrame)
{
this.contentBrowser.contentViewContainer.closeAllContentViews();
if (this._mainFrameTreeElement) {
this.contentTreeOutline.removeChild(this._mainFrameTreeElement);
this._mainFrameTreeElement = null;
}
if (!mainFrame)
return;
this._mainFrameTreeElement = new WI.FrameTreeElement(mainFrame);
this.contentTreeOutline.insertChild(this._mainFrameTreeElement, 0);
function delayedWork()
{
if (!this.contentTreeOutline.selectedTreeElement) {
let currentContentView = this.contentBrowser.currentContentView;
let treeElement = currentContentView ? this.treeElementForRepresentedObject(currentContentView.representedObject) : null;
if (!treeElement)
treeElement = this._mainFrameTreeElement;
this.showDefaultContentViewForTreeElement(treeElement);
}
}
// Cookie restoration will attempt to re-select the resource we were showing.
// Give it time to do that before selecting the main frame resource.
setTimeout(delayedWork.bind(this));
}
_scriptWasAdded(event)
{
this._addScript(event.data.script);
}
_addScript(script)
{
// We don't add scripts without URLs here. Those scripts can quickly clutter the interface and
// are usually more transient. They will get added if/when they need to be shown in a content view.
if (!script.url && !script.sourceURL)
return;
// Target main resource.
if (script.target !== WI.pageTarget) {
if (script.isMainResource())
this._addTargetWithMainResource(script.target);
this.contentTreeOutline.disclosureButtons = true;
return;
}
// If the script URL matches a resource we can assume it is part of that resource and does not need added.
if (script.resource || script.dynamicallyAddedScriptElement)
return;
let insertIntoTopLevel = false;
let parentFolderTreeElement = null;
if (script.injected) {
if (!this._extensionScriptsFolderTreeElement) {
let collection = new WI.ScriptCollection;
this._extensionScriptsFolderTreeElement = new WI.FolderTreeElement(WI.UIString("Extension Scripts"), collection);
}
parentFolderTreeElement = this._extensionScriptsFolderTreeElement;
} else {
if (SourcesSidebarPanel.shouldPlaceResourcesAtTopLevel())
insertIntoTopLevel = true;
else {
if (!this._extraScriptsFolderTreeElement) {
let collection = new WI.ScriptCollection;
this._extraScriptsFolderTreeElement = new WI.FolderTreeElement(WI.UIString("Extra Scripts"), collection);
}
parentFolderTreeElement = this._extraScriptsFolderTreeElement;
}
}
if (parentFolderTreeElement)
parentFolderTreeElement.representedObject.add(script);
let scriptTreeElement = new WI.ScriptTreeElement(script);
if (insertIntoTopLevel) {
let index = insertionIndexForObjectInListSortedByFunction(scriptTreeElement, this.contentTreeOutline.children, this._compareTreeElements);
this.contentTreeOutline.insertChild(scriptTreeElement, index);
} else {
if (!parentFolderTreeElement.parent) {
let index = insertionIndexForObjectInListSortedByFunction(parentFolderTreeElement, this.contentTreeOutline.children, this._compareTreeElements);
this.contentTreeOutline.insertChild(parentFolderTreeElement, index);
}
parentFolderTreeElement.appendChild(scriptTreeElement);
}
}
_scriptWasRemoved(event)
{
let script = event.data.script;
let scriptTreeElement = this.contentTreeOutline.getCachedTreeElement(script);
if (!scriptTreeElement)
return;
let parentTreeElement = scriptTreeElement.parent;
parentTreeElement.removeChild(scriptTreeElement);
if (parentTreeElement instanceof WI.FolderTreeElement) {
parentTreeElement.representedObject.remove(script);
if (!parentTreeElement.children.length)
parentTreeElement.parent.removeChild(parentTreeElement);
}
}
_scriptsCleared(event)
{
const suppressOnDeselect = true;
const suppressSelectSibling = true;
if (this._extensionScriptsFolderTreeElement) {
if (this._extensionScriptsFolderTreeElement.parent)
this._extensionScriptsFolderTreeElement.parent.removeChild(this._extensionScriptsFolderTreeElement, suppressOnDeselect, suppressSelectSibling);
this._extensionScriptsFolderTreeElement.representedObject.clear();
this._extensionScriptsFolderTreeElement = null;
}
if (this._extraScriptsFolderTreeElement) {
if (this._extraScriptsFolderTreeElement.parent)
this._extraScriptsFolderTreeElement.parent.removeChild(this._extraScriptsFolderTreeElement, suppressOnDeselect, suppressSelectSibling);
this._extraScriptsFolderTreeElement.representedObject.clear();
this._extraScriptsFolderTreeElement = null;
}
if (this._anonymousScriptsFolderTreeElement) {
if (this._anonymousScriptsFolderTreeElement.parent)
this._anonymousScriptsFolderTreeElement.parent.removeChild(this._anonymousScriptsFolderTreeElement, suppressOnDeselect, suppressSelectSibling);
this._anonymousScriptsFolderTreeElement.representedObject.clear();
this._anonymousScriptsFolderTreeElement = null;
}
if (this._targetTreeElementMap.size) {
for (let treeElement of this._targetTreeElementMap)
treeElement.parent.removeChild(treeElement, suppressOnDeselect, suppressSelectSibling);
this._targetTreeElementMap.clear();
}
}
_styleSheetAdded(event)
{
let styleSheet = event.data.styleSheet;
if (!styleSheet.isInspectorStyleSheet())
return;
let frameTreeElement = this.treeElementForRepresentedObject(styleSheet.parentFrame);
if (!frameTreeElement)
return;
frameTreeElement.addRepresentedObjectToNewChildQueue(styleSheet);
}
_addTargetWithMainResource(target)
{
console.assert(target.type === WI.Target.Type.Worker || target.type === WI.Target.Type.ServiceWorker);
let targetTreeElement = new WI.WorkerTreeElement(target);
this._targetTreeElementMap.set(target, targetTreeElement);
let index = insertionIndexForObjectInListSortedByFunction(targetTreeElement, this.contentTreeOutline.children, this._compareTreeElements);
this.contentTreeOutline.insertChild(targetTreeElement, index);
}
_targetRemoved(event)
{
let removedTarget = event.data.target;
let targetTreeElement = this._targetTreeElementMap.take(removedTarget);
if (targetTreeElement)
targetTreeElement.parent.removeChild(targetTreeElement);
}
_treeSelectionDidChange(event)
{
if (!this.visible)
return;
let treeElement = event.data.selectedElement;
if (!treeElement)
return;
if (treeElement instanceof WI.FolderTreeElement
|| treeElement instanceof WI.ResourceTreeElement
|| treeElement instanceof WI.ScriptTreeElement
|| treeElement instanceof WI.CSSStyleSheetTreeElement) {
const cookie = null;
const options = {
ignoreNetworkTab: true,
ignoreSearchTab: true,
};
WI.showRepresentedObject(treeElement.representedObject, cookie, options);
return;
}
console.error("Unknown tree element", treeElement);
}
_compareTreeElements(a, b)
{
// Always sort the main frame element first.
if (a instanceof WI.FrameTreeElement)
return -1;
if (b instanceof WI.FrameTreeElement)
return 1;
console.assert(a.mainTitle);
console.assert(b.mainTitle);
return (a.mainTitle || "").extendedLocaleCompare(b.mainTitle || "");
}
_extraDomainsActivated()
{
if (SourcesSidebarPanel.shouldPlaceResourcesAtTopLevel())
this.contentTreeOutline.disclosureButtons = true;
}
_scopeBarSelectionDidChange(event)
{
this.updateFilter();
}
};
WI.SourcesSidebarPanel.ResourceTypeSymbol = Symbol("resource-type");