diff options
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js b/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js new file mode 100644 index 000000000..20a77c268 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js @@ -0,0 +1,385 @@ +/* + * 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. + */ + +WebInspector.SearchSidebarPanel = class SearchSidebarPanel extends WebInspector.NavigationSidebarPanel +{ + constructor(contentBrowser) + { + super("search", WebInspector.UIString("Search"), true, true); + + this.contentBrowser = contentBrowser; + + var searchElement = document.createElement("div"); + searchElement.classList.add("search-bar"); + this.element.appendChild(searchElement); + + this._inputElement = document.createElement("input"); + this._inputElement.type = "search"; + this._inputElement.spellcheck = false; + this._inputElement.addEventListener("search", this._searchFieldChanged.bind(this)); + this._inputElement.addEventListener("input", this._searchFieldInput.bind(this)); + this._inputElement.setAttribute("results", 5); + this._inputElement.setAttribute("autosave", "inspector-search-autosave"); + this._inputElement.setAttribute("placeholder", WebInspector.UIString("Search Resource Content")); + searchElement.appendChild(this._inputElement); + + this.filterBar.placeholder = WebInspector.UIString("Filter Search Results"); + + this._searchQuerySetting = new WebInspector.Setting("search-sidebar-query", ""); + this._inputElement.value = this._searchQuerySetting.value; + + WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this); + + this.contentTreeOutline.addEventListener(WebInspector.TreeOutline.Event.SelectionDidChange, this._treeSelectionDidChange, this); + } + + // Public + + closed() + { + super.closed(); + + WebInspector.Frame.removeEventListener(null, null, this); + } + + focusSearchField(performSearch) + { + this.show(); + + this._inputElement.select(); + + if (performSearch) + this.performSearch(this._inputElement.value); + } + + performSearch(searchQuery) + { + // Before performing a new search, clear the old search. + this.contentTreeOutline.removeChildren(); + this.contentBrowser.contentViewContainer.closeAllContentViews(); + + this._inputElement.value = searchQuery; + this._searchQuerySetting.value = searchQuery; + + this.hideEmptyContentPlaceholder(); + + searchQuery = searchQuery.trim(); + if (!searchQuery.length) + return; + + // FIXME: Provide UI to toggle regex and case sensitive searches. + var isCaseSensitive = false; + var isRegex = false; + + var updateEmptyContentPlaceholderTimeout = null; + + function createTreeElementForMatchObject(matchObject, parentTreeElement) + { + let matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject); + matchTreeElement.addEventListener(WebInspector.TreeElement.Event.DoubleClick, this._treeElementDoubleClick, this); + + parentTreeElement.appendChild(matchTreeElement); + + if (!this.contentTreeOutline.selectedTreeElement) + matchTreeElement.revealAndSelect(false, true); + } + + function updateEmptyContentPlaceholderSoon() + { + if (updateEmptyContentPlaceholderTimeout) + return; + updateEmptyContentPlaceholderTimeout = setTimeout(updateEmptyContentPlaceholder.bind(this), 100); + } + + function updateEmptyContentPlaceholder() + { + if (updateEmptyContentPlaceholderTimeout) { + clearTimeout(updateEmptyContentPlaceholderTimeout); + updateEmptyContentPlaceholderTimeout = null; + } + + this.updateEmptyContentPlaceholder(WebInspector.UIString("No Search Results")); + } + + function forEachMatch(searchQuery, lineContent, callback) + { + var lineMatch; + var searchRegex = new RegExp(searchQuery.escapeForRegExp(), "gi"); + while ((searchRegex.lastIndex < lineContent.length) && (lineMatch = searchRegex.exec(lineContent))) + callback(lineMatch, searchRegex.lastIndex); + } + + function resourcesCallback(error, result) + { + updateEmptyContentPlaceholderSoon.call(this); + + if (error) + return; + + function resourceCallback(frameId, url, error, resourceMatches) + { + updateEmptyContentPlaceholderSoon.call(this); + + if (error || !resourceMatches || !resourceMatches.length) + return; + + var frame = WebInspector.frameResourceManager.frameForIdentifier(frameId); + if (!frame) + return; + + var resource = frame.url === url ? frame.mainResource : frame.resourceForURL(url); + if (!resource) + return; + + var resourceTreeElement = this._searchTreeElementForResource(resource); + + for (var i = 0; i < resourceMatches.length; ++i) { + var match = resourceMatches[i]; + forEachMatch(searchQuery, match.lineContent, (lineMatch, lastIndex) => { + var matchObject = new WebInspector.SourceCodeSearchMatchObject(resource, match.lineContent, searchQuery, new WebInspector.TextRange(match.lineNumber, lineMatch.index, match.lineNumber, lastIndex)); + createTreeElementForMatchObject.call(this, matchObject, resourceTreeElement); + }); + } + + updateEmptyContentPlaceholder.call(this); + } + + for (var i = 0; i < result.length; ++i) { + var searchResult = result[i]; + if (!searchResult.url || !searchResult.frameId) + continue; + + // COMPATIBILITY (iOS 9): Page.searchInResources did not have the optional requestId parameter. + PageAgent.searchInResource(searchResult.frameId, searchResult.url, searchQuery, isCaseSensitive, isRegex, searchResult.requestId, resourceCallback.bind(this, searchResult.frameId, searchResult.url)); + } + } + + function searchScripts(scriptsToSearch) + { + updateEmptyContentPlaceholderSoon.call(this); + + if (!scriptsToSearch.length) + return; + + function scriptCallback(script, error, scriptMatches) + { + updateEmptyContentPlaceholderSoon.call(this); + + if (error || !scriptMatches || !scriptMatches.length) + return; + + var scriptTreeElement = this._searchTreeElementForScript(script); + + for (var i = 0; i < scriptMatches.length; ++i) { + var match = scriptMatches[i]; + forEachMatch(searchQuery, match.lineContent, (lineMatch, lastIndex) => { + var matchObject = new WebInspector.SourceCodeSearchMatchObject(script, match.lineContent, searchQuery, new WebInspector.TextRange(match.lineNumber, lineMatch.index, match.lineNumber, lastIndex)); + createTreeElementForMatchObject.call(this, matchObject, scriptTreeElement); + }); + } + + updateEmptyContentPlaceholder.call(this); + } + + for (let script of scriptsToSearch) + script.target.DebuggerAgent.searchInContent(script.id, searchQuery, isCaseSensitive, isRegex, scriptCallback.bind(this, script)); + } + + function domCallback(error, searchId, resultsCount) + { + updateEmptyContentPlaceholderSoon.call(this); + + if (error || !resultsCount) + return; + + console.assert(searchId); + + this._domSearchIdentifier = searchId; + + function domSearchResults(error, nodeIds) + { + updateEmptyContentPlaceholderSoon.call(this); + + if (error) + return; + + for (var i = 0; i < nodeIds.length; ++i) { + // If someone started a new search, then return early and stop showing seach results from the old query. + if (this._domSearchIdentifier !== searchId) + return; + + var domNode = WebInspector.domTreeManager.nodeForId(nodeIds[i]); + if (!domNode || !domNode.ownerDocument) + continue; + + // We do not display the document node when the search query is "/". We don't have anything to display in the content view for it. + if (domNode.nodeType() === Node.DOCUMENT_NODE) + continue; + + // FIXME: This should use a frame to do resourceForURL, but DOMAgent does not provide a frameId. + var resource = WebInspector.frameResourceManager.resourceForURL(domNode.ownerDocument.documentURL); + if (!resource) + continue; + + var resourceTreeElement = this._searchTreeElementForResource(resource); + var domNodeTitle = WebInspector.DOMSearchMatchObject.titleForDOMNode(domNode); + + // Textual matches. + var didFindTextualMatch = false; + forEachMatch(searchQuery, domNodeTitle, (lineMatch, lastIndex) => { + var matchObject = new WebInspector.DOMSearchMatchObject(resource, domNode, domNodeTitle, searchQuery, new WebInspector.TextRange(0, lineMatch.index, 0, lastIndex)); + createTreeElementForMatchObject.call(this, matchObject, resourceTreeElement); + didFindTextualMatch = true; + }); + + // Non-textual matches are CSS Selector or XPath matches. In such cases, display the node entirely highlighted. + if (!didFindTextualMatch) { + var matchObject = new WebInspector.DOMSearchMatchObject(resource, domNode, domNodeTitle, domNodeTitle, new WebInspector.TextRange(0, 0, 0, domNodeTitle.length)); + createTreeElementForMatchObject.call(this, matchObject, resourceTreeElement); + } + + updateEmptyContentPlaceholder.call(this); + } + } + + DOMAgent.getSearchResults(searchId, 0, resultsCount, domSearchResults.bind(this)); + } + + if (window.DOMAgent) + WebInspector.domTreeManager.requestDocument(function(){}); + + if (window.PageAgent) + PageAgent.searchInResources(searchQuery, isCaseSensitive, isRegex, resourcesCallback.bind(this)); + + setTimeout(searchScripts.bind(this, WebInspector.debuggerManager.searchableScripts), 0); + + if (window.DOMAgent) { + if (this._domSearchIdentifier) { + DOMAgent.discardSearchResults(this._domSearchIdentifier); + this._domSearchIdentifier = undefined; + } + + DOMAgent.performSearch(searchQuery, domCallback.bind(this)); + } + + // FIXME: Resource search should work in JSContext inspection. + // <https://webkit.org/b/131252> Web Inspector: JSContext inspection Resource search does not work + if (!window.DOMAgent && !window.PageAgent) + updateEmptyContentPlaceholderSoon.call(this); + } + + // Private + + _searchFieldChanged(event) + { + this.performSearch(event.target.value); + } + + _searchFieldInput(event) + { + // If the search field is cleared, immediately clear the search results tree outline. + if (!event.target.value.length) + this.performSearch(""); + } + + _searchTreeElementForResource(resource) + { + var resourceTreeElement = this.contentTreeOutline.getCachedTreeElement(resource); + if (!resourceTreeElement) { + resourceTreeElement = new WebInspector.ResourceTreeElement(resource); + resourceTreeElement.hasChildren = true; + resourceTreeElement.expand(); + + this.contentTreeOutline.appendChild(resourceTreeElement); + } + + return resourceTreeElement; + } + + _searchTreeElementForScript(script) + { + var scriptTreeElement = this.contentTreeOutline.getCachedTreeElement(script); + if (!scriptTreeElement) { + scriptTreeElement = new WebInspector.ScriptTreeElement(script); + scriptTreeElement.hasChildren = true; + scriptTreeElement.expand(); + + this.contentTreeOutline.appendChild(scriptTreeElement); + } + + return scriptTreeElement; + } + + _mainResourceDidChange(event) + { + if (!event.target.isMainFrame()) + return; + + if (this._delayedSearchTimeout) { + clearTimeout(this._delayedSearchTimeout); + this._delayedSearchTimeout = undefined; + } + + this.contentTreeOutline.removeChildren(); + this.contentBrowser.contentViewContainer.closeAllContentViews(); + + if (this.visible) + this.focusSearchField(); + } + + _treeSelectionDidChange(event) + { + let treeElement = event.data.selectedElement; + if (!treeElement || treeElement instanceof WebInspector.FolderTreeElement) + return; + + if (treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement) { + WebInspector.showRepresentedObject(treeElement.representedObject); + return; + } + + console.assert(treeElement instanceof WebInspector.SearchResultTreeElement); + if (!(treeElement instanceof WebInspector.SearchResultTreeElement)) + return; + + if (treeElement.representedObject instanceof WebInspector.DOMSearchMatchObject) + WebInspector.showMainFrameDOMTree(treeElement.representedObject.domNode); + else if (treeElement.representedObject instanceof WebInspector.SourceCodeSearchMatchObject) + WebInspector.showOriginalOrFormattedSourceCodeTextRange(treeElement.representedObject.sourceCodeTextRange); + } + + _treeElementDoubleClick(event) + { + let treeElement = event.target; + if (!treeElement) + return; + + const options = {ignoreSearchTab: true}; + if (treeElement.representedObject instanceof WebInspector.DOMSearchMatchObject) + WebInspector.showMainFrameDOMTree(treeElement.representedObject.domNode, options); + else if (treeElement.representedObject instanceof WebInspector.SourceCodeSearchMatchObject) + WebInspector.showOriginalOrFormattedSourceCodeTextRange(treeElement.representedObject.sourceCodeTextRange, options); + } +}; |