2011-02-25 Andrey Kosyakov <caseq@chromium.org>
[WebKit-https.git] / Source / WebCore / inspector / front-end / SearchController.js
1 /*
2  * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
4  * Copyright (C) 2009 Joseph Pecoraro
5  * Copyright (C) 2011 Google Inc. All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1.  Redistributions of source code must retain the above copyright
12  *     notice, this list of conditions and the following disclaimer.
13  * 2.  Redistributions in binary form must reproduce the above copyright
14  *     notice, this list of conditions and the following disclaimer in the
15  *     documentation and/or other materials provided with the distribution.
16  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
17  *     its contributors may be used to endorse or promote products derived
18  *     from this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
24  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 WebInspector.SearchController = function()
33 {
34     this.element = document.getElementById("search");
35     this._matchesElement = document.getElementById("search-results-matches");
36     this._toolbarLabelElement = document.getElementById("search-toolbar-label");
37
38     this.element.addEventListener("search", this._onSearch.bind(this), false); // when the search is emptied
39     this.element.addEventListener("mousedown", this._onSearchFieldManualFocus.bind(this), false); // when the search field is manually selected
40     this.element.addEventListener("keydown", this._onKeyDown.bind(this), true);
41 }
42
43 WebInspector.SearchController.prototype = {
44     updateSearchMatchesCount: function(matches, panel)
45     {
46         if (!panel)
47             panel = WebInspector.currentPanel;
48
49         panel.currentSearchMatches = matches;
50
51         if (panel === WebInspector.currentPanel)
52             this._updateSearchMatchesCount(WebInspector.currentPanel.currentQuery && matches);
53     },
54
55     updateSearchLabel: function()
56     {
57         var panelName = WebInspector.currentPanel && WebInspector.currentPanel.toolbarItemLabel;
58         if (!panelName)
59             return;
60         var newLabel = WebInspector.UIString("Search %s", panelName);
61         if (WebInspector.attached)
62             this.element.setAttribute("placeholder", newLabel);
63         else {
64             this.element.removeAttribute("placeholder");
65             this._toolbarLabelElement.textContent = newLabel;
66         }
67     },
68
69     cancelSearch: function()
70     {
71         this.element.value = "";
72         this._performSearch("");
73     },
74
75     handleShortcut: function(event)
76     {
77         var isMac = WebInspector.isMac();
78
79         switch (event.keyIdentifier) {
80             case "U+0046": // F key
81                 if (isMac)
82                     var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
83                 else
84                     var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey;
85
86                 if (isFindKey) {
87                     this._focusSearchField();
88                     event.handled = true;
89                 }
90                 break;
91
92
93             case "F3":
94                 if (!isMac) {
95                     this._focusSearchField();
96                     event.handled = true;
97                 }
98                 break;
99
100             case "U+0047": // G key
101                 var currentPanel = WebInspector.currentPanel;
102
103                 if (isMac && event.metaKey && !event.ctrlKey && !event.altKey) {
104                     if (event.shiftKey) {
105                         if (currentPanel.jumpToPreviousSearchResult)
106                             currentPanel.jumpToPreviousSearchResult();
107                     } else if (currentPanel.jumpToNextSearchResult)
108                         currentPanel.jumpToNextSearchResult();
109                     event.handled = true;
110                 }
111                 break;
112         }
113     },
114
115     activePanelChanged: function()
116     {
117         this.updateSearchLabel();
118
119         if (!this._currentQuery)
120             return;
121          
122         panel = WebInspector.currentPanel;
123         if (panel.performSearch) {
124             function performPanelSearch()
125             {
126                 this._updateSearchMatchesCount();
127
128                 panel.currentQuery = this._currentQuery;
129                 panel.performSearch(this._currentQuery);
130             }
131
132             // Perform the search on a timeout so the panel switches fast.
133             setTimeout(performPanelSearch.bind(this), 0);
134         } else {
135             // Update to show Not found for panels that can't be searched.
136             this._updateSearchMatchesCount();
137         }
138     },
139
140     _updateSearchMatchesCount: function(matches)
141     {
142         if (matches == null) {
143             this._matchesElement.addStyleClass("hidden");
144             return;
145         }
146
147         if (matches) {
148             if (matches === 1)
149                 var matchesString = WebInspector.UIString("1 match");
150             else
151                 var matchesString = WebInspector.UIString("%d matches", matches);
152         } else
153             var matchesString = WebInspector.UIString("Not Found");
154
155         this._matchesElement.removeStyleClass("hidden");
156         this._matchesElement.textContent = matchesString;
157         WebInspector.toolbar.resize();
158     },
159
160     _focusSearchField: function()
161     {
162         this.element.focus();
163         this.element.select();
164     },
165
166     _onSearchFieldManualFocus: function(event)
167     {
168         WebInspector.currentFocusElement = event.target;
169     },
170
171     _onKeyDown: function(event)
172     {
173         // Escape Key will clear the field and clear the search results
174         if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
175             // If focus belongs here and text is empty - nothing to do, return unhandled.
176             // When search was selected manually and is currently blank, we'd like Esc stay unhandled
177             // and hit console drawer handler.
178             if (event.target.value === "" && this.currentFocusElement === this.previousFocusElement)
179                 return;
180             event.preventDefault();
181             event.stopPropagation();
182
183             this.cancelSearch(event);
184             WebInspector.currentFocusElement = WebInspector.previousFocusElement;
185             if (WebInspector.currentFocusElement === event.target)
186                 WebInspector.currentFocusElement.currentFocusElement.select();
187             return false;
188         }
189
190         if (!isEnterKey(event))
191             return false;
192
193         // Select all of the text so the user can easily type an entirely new query.
194         event.target.select();
195
196         // Only call performSearch if the Enter key was pressed. Otherwise the search
197         // performance is poor because of searching on every key. The search field has
198         // the incremental attribute set, so we still get incremental searches.
199         this._onSearch(event);
200
201         // Call preventDefault since this was the Enter key. This prevents a "search" event
202         // from firing for key down. This stops performSearch from being called twice in a row.
203         event.preventDefault();
204     },
205
206     _onSearch: function(event)
207     {
208         var forceSearch = event.keyIdentifier === "Enter";
209         this._performSearch(event.target.value, forceSearch, event.shiftKey, false);
210     },
211
212     _performSearch: function(query, forceSearch, isBackwardSearch, repeatSearch)
213     {
214         var isShortSearch = (query.length < 3);
215
216         // Clear a leftover short search flag due to a non-conflicting forced search.
217         if (isShortSearch && this._shortSearchWasForcedByKeyEvent && this._currentQuery !== query)
218             delete this._shortSearchWasForcedByKeyEvent;
219
220         // Indicate this was a forced search on a short query.
221         if (isShortSearch && forceSearch)
222             this._shortSearchWasForcedByKeyEvent = true;
223
224         if (!query || !query.length || (!forceSearch && isShortSearch)) {
225             // Prevent clobbering a short search forced by the user.
226             if (this._shortSearchWasForcedByKeyEvent) {
227                 delete this._shortSearchWasForcedByKeyEvent;
228                 return;
229             }
230
231             delete this._currentQuery;
232
233             for (var panelName in WebInspector.panels) {
234                 var panel = WebInspector.panels[panelName];
235                 var hadCurrentQuery = !!panel.currentQuery;
236                 delete panel.currentQuery;
237                 if (hadCurrentQuery && panel.searchCanceled)
238                     panel.searchCanceled();
239             }
240
241             this._updateSearchMatchesCount();
242
243             return;
244         }
245
246         var currentPanel = WebInspector.currentPanel;
247         if (!repeatSearch && query === currentPanel.currentQuery && currentPanel.currentQuery === this._currentQuery) {
248             // When this is the same query and a forced search, jump to the next
249             // search result for a good user experience.
250             if (forceSearch) {
251                 if (!isBackwardSearch && currentPanel.jumpToNextSearchResult)
252                     currentPanel.jumpToNextSearchResult();
253                 else if (isBackwardSearch && currentPanel.jumpToPreviousSearchResult)
254                     currentPanel.jumpToPreviousSearchResult();
255             }
256             return;
257         }
258
259         this._currentQuery = query;
260
261         this._updateSearchMatchesCount();
262
263         if (!currentPanel.performSearch)
264             return;
265
266         currentPanel.currentQuery = query;
267         currentPanel.performSearch(query);
268     }
269 }