| /* |
| * 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() |
| { |
| this.show(); |
| |
| this._inputElement.select(); |
| } |
| |
| 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 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(url, error, resourceMatches) |
| { |
| updateEmptyContentPlaceholderSoon.call(this); |
| |
| if (error || !resourceMatches || !resourceMatches.length) |
| return; |
| |
| var frame = WebInspector.frameResourceManager.frameForIdentifier(searchResult.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, function(lineMatch, lastIndex) { |
| var matchObject = new WebInspector.SourceCodeSearchMatchObject(resource, match.lineContent, searchQuery, new WebInspector.TextRange(match.lineNumber, lineMatch.index, match.lineNumber, lastIndex)); |
| var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject); |
| resourceTreeElement.appendChild(matchTreeElement); |
| if (!this.contentTreeOutline.selectedTreeElement) |
| matchTreeElement.revealAndSelect(false, true); |
| }.bind(this)); |
| } |
| |
| updateEmptyContentPlaceholder.call(this); |
| } |
| |
| for (var i = 0; i < result.length; ++i) { |
| var searchResult = result[i]; |
| if (!searchResult.url || !searchResult.frameId) |
| continue; |
| |
| PageAgent.searchInResource(searchResult.frameId, searchResult.url, searchQuery, isCaseSensitive, isRegex, resourceCallback.bind(this, 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, function(lineMatch, lastIndex) { |
| var matchObject = new WebInspector.SourceCodeSearchMatchObject(script, match.lineContent, searchQuery, new WebInspector.TextRange(match.lineNumber, lineMatch.index, match.lineNumber, lastIndex)); |
| var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject); |
| scriptTreeElement.appendChild(matchTreeElement); |
| if (!this.contentTreeOutline.selectedTreeElement) |
| matchTreeElement.revealAndSelect(false, true); |
| }.bind(this)); |
| } |
| |
| updateEmptyContentPlaceholder.call(this); |
| } |
| |
| for (var script of scriptsToSearch) |
| 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, function(lineMatch, lastIndex) { |
| var matchObject = new WebInspector.DOMSearchMatchObject(resource, domNode, domNodeTitle, searchQuery, new WebInspector.TextRange(0, lineMatch.index, 0, lastIndex)); |
| var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject); |
| resourceTreeElement.appendChild(matchTreeElement); |
| if (!this.contentTreeOutline.selectedTreeElement) |
| matchTreeElement.revealAndSelect(false, true); |
| didFindTextualMatch = true; |
| }.bind(this)); |
| |
| // 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)); |
| var matchTreeElement = new WebInspector.SearchResultTreeElement(matchObject); |
| resourceTreeElement.appendChild(matchTreeElement); |
| if (!this.contentTreeOutline.selectedTreeElement) |
| matchTreeElement.revealAndSelect(false, true); |
| } |
| |
| 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.knownNonResourceScripts), 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); |
| } |
| }; |