Home | History | Annotate | Download | only in front-end
      1 /*
      2  * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
      3  * Copyright (C) 2007 Matt Lilek (pewtermoose (at) 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 }
    270