browser/components/tabview/search.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/browser/components/tabview/search.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,616 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +/* ******************************
     1.9 + *
    1.10 + * This file incorporates work from:
    1.11 + * Quicksilver Score (qs_score):
    1.12 + * http://rails-oceania.googlecode.com/svn/lachiecox/qs_score/trunk/qs_score.js
    1.13 + * This incorporated work is covered by the following copyright and
    1.14 + * permission notice:
    1.15 + * Copyright 2008 Lachie Cox
    1.16 + * Licensed under the MIT license.
    1.17 + * http://jquery.org/license
    1.18 + *
    1.19 + *  ***************************** */
    1.20 +
    1.21 +// **********
    1.22 +// Title: search.js
    1.23 +// Implementation for the search functionality of Firefox Panorama.
    1.24 +
    1.25 +// ##########
    1.26 +// Class: TabUtils
    1.27 +//
    1.28 +// A collection of helper functions for dealing with both <TabItem>s and
    1.29 +// <xul:tab>s without having to worry which one is which.
    1.30 +let TabUtils = {
    1.31 +  // ----------
    1.32 +  // Function: toString
    1.33 +  // Prints [TabUtils] for debug use.
    1.34 +  toString: function TabUtils_toString() {
    1.35 +    return "[TabUtils]";
    1.36 +  },
    1.37 +
    1.38 +  // ---------
    1.39 +  // Function: nameOfTab
    1.40 +  // Given a <TabItem> or a <xul:tab> returns the tab's name.
    1.41 +  nameOf: function TabUtils_nameOf(tab) {
    1.42 +    // We can have two types of tabs: A <TabItem> or a <xul:tab>
    1.43 +    // because we have to deal with both tabs represented inside
    1.44 +    // of active Panoramas as well as for windows in which
    1.45 +    // Panorama has yet to be activated. We uses object sniffing to
    1.46 +    // determine the type of tab and then returns its name.     
    1.47 +    return tab.label != undefined ? tab.label : tab.$tabTitle[0].textContent;
    1.48 +  },
    1.49 +
    1.50 +  // ---------
    1.51 +  // Function: URLOf
    1.52 +  // Given a <TabItem> or a <xul:tab> returns the URL of tab.
    1.53 +  URLOf: function TabUtils_URLOf(tab) {
    1.54 +    // Convert a <TabItem> to <xul:tab>
    1.55 +    if ("tab" in tab)
    1.56 +      tab = tab.tab;
    1.57 +    return tab.linkedBrowser.currentURI.spec;
    1.58 +  },
    1.59 +
    1.60 +  // ---------
    1.61 +  // Function: faviconURLOf
    1.62 +  // Given a <TabItem> or a <xul:tab> returns the URL of tab's favicon.
    1.63 +  faviconURLOf: function TabUtils_faviconURLOf(tab) {
    1.64 +    return tab.image != undefined ? tab.image : tab.$favImage[0].src;
    1.65 +  },
    1.66 +
    1.67 +  // ---------
    1.68 +  // Function: focus
    1.69 +  // Given a <TabItem> or a <xul:tab>, focuses it and it's window.
    1.70 +  focus: function TabUtils_focus(tab) {
    1.71 +    // Convert a <TabItem> to a <xul:tab>
    1.72 +    if ("tab" in tab)
    1.73 +      tab = tab.tab;
    1.74 +    tab.ownerDocument.defaultView.gBrowser.selectedTab = tab;
    1.75 +    tab.ownerDocument.defaultView.focus();
    1.76 +  }
    1.77 +};
    1.78 +
    1.79 +// ##########
    1.80 +// Class: TabMatcher
    1.81 +//
    1.82 +// A class that allows you to iterate over matching and not-matching tabs, 
    1.83 +// given a case-insensitive search term.
    1.84 +function TabMatcher(term) {
    1.85 +  this.term = term;
    1.86 +}
    1.87 +
    1.88 +TabMatcher.prototype = {
    1.89 +  // ----------
    1.90 +  // Function: toString
    1.91 +  // Prints [TabMatcher (term)] for debug use.
    1.92 +  toString: function TabMatcher_toString() {
    1.93 +    return "[TabMatcher (" + this.term + ")]";
    1.94 +  },
    1.95 +
    1.96 +  // ---------
    1.97 +  // Function: _filterAndSortForMatches
    1.98 +  // Given an array of <TabItem>s and <xul:tab>s returns a new array
    1.99 +  // of tabs whose name matched the search term, sorted by lexical
   1.100 +  // closeness.
   1.101 +  _filterAndSortForMatches: function TabMatcher__filterAndSortForMatches(tabs) {
   1.102 +    let self = this;
   1.103 +    tabs = tabs.filter(function TabMatcher__filterAndSortForMatches_filter(tab) {
   1.104 +      let name = TabUtils.nameOf(tab);
   1.105 +      let url = TabUtils.URLOf(tab);
   1.106 +      return name.match(self.term, "i") || url.match(self.term, "i");
   1.107 +    });
   1.108 +
   1.109 +    tabs.sort(function TabMatcher__filterAndSortForMatches_sort(x, y) {
   1.110 +      let yScore = self._scorePatternMatch(self.term, TabUtils.nameOf(y));
   1.111 +      let xScore = self._scorePatternMatch(self.term, TabUtils.nameOf(x));
   1.112 +      return yScore - xScore;
   1.113 +    });
   1.114 +
   1.115 +    return tabs;
   1.116 +  },
   1.117 +
   1.118 +  // ---------
   1.119 +  // Function: _filterForUnmatches
   1.120 +  // Given an array of <TabItem>s returns an unsorted array of tabs whose name
   1.121 +  // does not match the the search term.
   1.122 +  _filterForUnmatches: function TabMatcher__filterForUnmatches(tabs) {
   1.123 +    let self = this;
   1.124 +    return tabs.filter(function TabMatcher__filterForUnmatches_filter(tab) {
   1.125 +      let name = tab.$tabTitle[0].textContent;
   1.126 +      let url = TabUtils.URLOf(tab);
   1.127 +      return !name.match(self.term, "i") && !url.match(self.term, "i");
   1.128 +    });
   1.129 +  },
   1.130 +
   1.131 +  // ---------
   1.132 +  // Function: _getTabsForOtherWindows
   1.133 +  // Returns an array of <TabItem>s and <xul:tabs>s representing tabs
   1.134 +  // from all windows but the current window. <TabItem>s will be returned
   1.135 +  // for windows in which Panorama has been activated at least once, while
   1.136 +  // <xul:tab>s will be returned for windows in which Panorama has never
   1.137 +  // been activated.
   1.138 +  _getTabsForOtherWindows: function TabMatcher__getTabsForOtherWindows() {
   1.139 +    let enumerator = Services.wm.getEnumerator("navigator:browser");
   1.140 +    let allTabs = [];
   1.141 +
   1.142 +    while (enumerator.hasMoreElements()) {
   1.143 +      let win = enumerator.getNext();
   1.144 +      // This function gets tabs from other windows, not from the current window
   1.145 +      if (win != gWindow)
   1.146 +        allTabs.push.apply(allTabs, win.gBrowser.tabs);
   1.147 +    }
   1.148 +    return allTabs;
   1.149 +  },
   1.150 +
   1.151 +  // ----------
   1.152 +  // Function: matchedTabsFromOtherWindows
   1.153 +  // Returns an array of <TabItem>s and <xul:tab>s that match the search term
   1.154 +  // from all windows but the current window. <TabItem>s will be returned for
   1.155 +  // windows in which Panorama has been activated at least once, while
   1.156 +  // <xul:tab>s will be returned for windows in which Panorama has never
   1.157 +  // been activated.
   1.158 +  // (new TabMatcher("app")).matchedTabsFromOtherWindows();
   1.159 +  matchedTabsFromOtherWindows: function TabMatcher_matchedTabsFromOtherWindows() {
   1.160 +    if (this.term.length < 2)
   1.161 +      return [];
   1.162 +
   1.163 +    let tabs = this._getTabsForOtherWindows();
   1.164 +    return this._filterAndSortForMatches(tabs);
   1.165 +  },
   1.166 +
   1.167 +  // ----------
   1.168 +  // Function: matched
   1.169 +  // Returns an array of <TabItem>s which match the current search term.
   1.170 +  // If the term is less than 2 characters in length, it returns nothing.
   1.171 +  matched: function TabMatcher_matched() {
   1.172 +    if (this.term.length < 2)
   1.173 +      return [];
   1.174 +
   1.175 +    let tabs = TabItems.getItems();
   1.176 +    return this._filterAndSortForMatches(tabs);
   1.177 +  },
   1.178 +
   1.179 +  // ----------
   1.180 +  // Function: unmatched
   1.181 +  // Returns all of <TabItem>s that .matched() doesn't return.
   1.182 +  unmatched: function TabMatcher_unmatched() {
   1.183 +    let tabs = TabItems.getItems();
   1.184 +    if (this.term.length < 2)
   1.185 +      return tabs;
   1.186 +
   1.187 +    return this._filterForUnmatches(tabs);
   1.188 +  },
   1.189 +
   1.190 +  // ----------
   1.191 +  // Function: doSearch
   1.192 +  // Performs the search. Lets you provide three functions.
   1.193 +  // The first is on all matched tabs in the window, the second on all unmatched
   1.194 +  // tabs in the window, and the third on all matched tabs in other windows.
   1.195 +  // The first two functions take two parameters: A <TabItem> and its integer index
   1.196 +  // indicating the absolute rank of the <TabItem> in terms of match to
   1.197 +  // the search term. The last function also takes two paramaters, but can be
   1.198 +  // passed both <TabItem>s and <xul:tab>s and the index is offset by the
   1.199 +  // number of matched tabs inside the window.
   1.200 +  doSearch: function TabMatcher_doSearch(matchFunc, unmatchFunc, otherFunc) {
   1.201 +    let matches = this.matched();
   1.202 +    let unmatched = this.unmatched();
   1.203 +    let otherMatches = this.matchedTabsFromOtherWindows();
   1.204 +    
   1.205 +    matches.forEach(function(tab, i) {
   1.206 +      matchFunc(tab, i);
   1.207 +    });
   1.208 +
   1.209 +    otherMatches.forEach(function(tab,i) {
   1.210 +      otherFunc(tab, i+matches.length);
   1.211 +    });
   1.212 +
   1.213 +    unmatched.forEach(function(tab, i) {
   1.214 +      unmatchFunc(tab, i);
   1.215 +    });
   1.216 +  },
   1.217 +
   1.218 +  // ----------
   1.219 +  // Function: _scorePatternMatch
   1.220 +  // Given a pattern string, returns a score between 0 and 1 of how well
   1.221 +  // that pattern matches the original string. It mimics the heuristics
   1.222 +  // of the Mac application launcher Quicksilver.
   1.223 +  _scorePatternMatch: function TabMatcher__scorePatternMatch(pattern, matched, offset) {
   1.224 +    offset = offset || 0;
   1.225 +    pattern = pattern.toLowerCase();
   1.226 +    matched = matched.toLowerCase();
   1.227 +
   1.228 +    if (pattern.length == 0)
   1.229 +      return 0.9;
   1.230 +    if (pattern.length > matched.length)
   1.231 +      return 0.0;
   1.232 +
   1.233 +    for (let i = pattern.length; i > 0; i--) {
   1.234 +      let sub_pattern = pattern.substring(0,i);
   1.235 +      let index = matched.indexOf(sub_pattern);
   1.236 +
   1.237 +      if (index < 0)
   1.238 +        continue;
   1.239 +      if (index + pattern.length > matched.length + offset)
   1.240 +        continue;
   1.241 +
   1.242 +      let next_string = matched.substring(index+sub_pattern.length);
   1.243 +      let next_pattern = null;
   1.244 +
   1.245 +      if (i >= pattern.length)
   1.246 +        next_pattern = '';
   1.247 +      else
   1.248 +        next_pattern = pattern.substring(i);
   1.249 +
   1.250 +      let remaining_score = this._scorePatternMatch(next_pattern, next_string, offset + index);
   1.251 +
   1.252 +      if (remaining_score > 0) {
   1.253 +        let score = matched.length-next_string.length;
   1.254 +
   1.255 +        if (index != 0) {
   1.256 +          let c = matched.charCodeAt(index-1);
   1.257 +          if (c == 32 || c == 9) {
   1.258 +            for (let j = (index - 2); j >= 0; j--) {
   1.259 +              c = matched.charCodeAt(j);
   1.260 +              score -= ((c == 32 || c == 9) ? 1 : 0.15);
   1.261 +            }
   1.262 +          } else {
   1.263 +            score -= index;
   1.264 +          }
   1.265 +        }
   1.266 +
   1.267 +        score += remaining_score * next_string.length;
   1.268 +        score /= matched.length;
   1.269 +        return score;
   1.270 +      }
   1.271 +    }
   1.272 +    return 0.0;
   1.273 +  }
   1.274 +};
   1.275 +
   1.276 +// ##########
   1.277 +// Class: TabHandlers
   1.278 +// 
   1.279 +// A object that handles all of the event handlers.
   1.280 +let TabHandlers = {
   1.281 +  _mouseDownLocation: null,
   1.282 +
   1.283 +  // ---------
   1.284 +  // Function: onMatch
   1.285 +  // Adds styles and event listeners to the matched tab items.
   1.286 +  onMatch: function TabHandlers_onMatch(tab, index) {
   1.287 +    tab.addClass("onTop");
   1.288 +    index != 0 ? tab.addClass("notMainMatch") : tab.removeClass("notMainMatch");
   1.289 +
   1.290 +    // Remove any existing handlers before adding the new ones.
   1.291 +    // If we don't do this, then we may add more handlers than
   1.292 +    // we remove.
   1.293 +    tab.$canvas
   1.294 +      .unbind("mousedown", TabHandlers._hideHandler)
   1.295 +      .unbind("mouseup", TabHandlers._showHandler);
   1.296 +
   1.297 +    tab.$canvas
   1.298 +      .mousedown(TabHandlers._hideHandler)
   1.299 +      .mouseup(TabHandlers._showHandler);
   1.300 +  },
   1.301 +
   1.302 +  // ---------
   1.303 +  // Function: onUnmatch
   1.304 +  // Removes styles and event listeners from the unmatched tab items.
   1.305 +  onUnmatch: function TabHandlers_onUnmatch(tab, index) {
   1.306 +    tab.$container.removeClass("onTop");
   1.307 +    tab.removeClass("notMainMatch");
   1.308 +
   1.309 +    tab.$canvas
   1.310 +      .unbind("mousedown", TabHandlers._hideHandler)
   1.311 +      .unbind("mouseup", TabHandlers._showHandler);
   1.312 +  },
   1.313 +
   1.314 +  // ---------
   1.315 +  // Function: onOther
   1.316 +  // Removes styles and event listeners from the unmatched tabs.
   1.317 +  onOther: function TabHandlers_onOther(tab, index) {
   1.318 +    // Unlike the other on* functions, in this function tab can
   1.319 +    // either be a <TabItem> or a <xul:tab>. In other functions
   1.320 +    // it is always a <TabItem>. Also note that index is offset
   1.321 +    // by the number of matches within the window.
   1.322 +    let item = iQ("<div/>")
   1.323 +      .addClass("inlineMatch")
   1.324 +      .click(function TabHandlers_onOther_click(event) {
   1.325 +        Search.hide(event);
   1.326 +        TabUtils.focus(tab);
   1.327 +      });
   1.328 +
   1.329 +    iQ("<img/>")
   1.330 +      .attr("src", TabUtils.faviconURLOf(tab))
   1.331 +      .appendTo(item);
   1.332 +
   1.333 +    iQ("<span/>")
   1.334 +      .text(TabUtils.nameOf(tab))
   1.335 +      .appendTo(item);
   1.336 +
   1.337 +    index != 0 ? item.addClass("notMainMatch") : item.removeClass("notMainMatch");
   1.338 +    item.appendTo("#results");
   1.339 +    iQ("#otherresults").show();
   1.340 +  },
   1.341 +
   1.342 +  // ---------
   1.343 +  // Function: _hideHandler
   1.344 +  // Performs when mouse down on a canvas of tab item.
   1.345 +  _hideHandler: function TabHandlers_hideHandler(event) {
   1.346 +    iQ("#search").fadeOut();
   1.347 +    iQ("#searchshade").fadeOut();
   1.348 +    TabHandlers._mouseDownLocation = {x:event.clientX, y:event.clientY};
   1.349 +  },
   1.350 +
   1.351 +  // ---------
   1.352 +  // Function: _showHandler
   1.353 +  // Performs when mouse up on a canvas of tab item.
   1.354 +  _showHandler: function TabHandlers_showHandler(event) {
   1.355 +    // If the user clicks on a tab without moving the mouse then
   1.356 +    // they are zooming into the tab and we need to exit search
   1.357 +    // mode.
   1.358 +    if (TabHandlers._mouseDownLocation.x == event.clientX &&
   1.359 +        TabHandlers._mouseDownLocation.y == event.clientY) {
   1.360 +      Search.hide();
   1.361 +      return;
   1.362 +    }
   1.363 +
   1.364 +    iQ("#searchshade").show();
   1.365 +    iQ("#search").show();
   1.366 +    iQ("#searchbox")[0].focus();
   1.367 +    // Marshal the search.
   1.368 +    setTimeout(Search.perform, 0);
   1.369 +  }
   1.370 +};
   1.371 +
   1.372 +// ##########
   1.373 +// Class: Search
   1.374 +// 
   1.375 +// A object that handles the search feature.
   1.376 +let Search = {
   1.377 +  _initiatedBy: "",
   1.378 +  _blockClick: false,
   1.379 +  _currentHandler: null,
   1.380 +
   1.381 +  // ----------
   1.382 +  // Function: toString
   1.383 +  // Prints [Search] for debug use.
   1.384 +  toString: function Search_toString() {
   1.385 +    return "[Search]";
   1.386 +  },
   1.387 +
   1.388 +  // ----------
   1.389 +  // Function: init
   1.390 +  // Initializes the searchbox to be focused, and everything else to be hidden,
   1.391 +  // and to have everything have the appropriate event handlers.
   1.392 +  init: function Search_init() {
   1.393 +    let self = this;
   1.394 +
   1.395 +    iQ("#search").hide();
   1.396 +    iQ("#searchshade").hide().mousedown(function Search_init_shade_mousedown(event) {
   1.397 +      if (event.target.id != "searchbox" && !self._blockClick)
   1.398 +        self.hide();
   1.399 +    });
   1.400 +
   1.401 +    iQ("#searchbox").keyup(function Search_init_box_keyup() {
   1.402 +      self.perform();
   1.403 +    })
   1.404 +    .attr("title", tabviewString("button.searchTabs"));
   1.405 +
   1.406 +    iQ("#searchbutton").mousedown(function Search_init_button_mousedown() {
   1.407 +      self._initiatedBy = "buttonclick";
   1.408 +      self.ensureShown();
   1.409 +      self.switchToInMode();
   1.410 +    })
   1.411 +    .attr("title", tabviewString("button.searchTabs"));
   1.412 +
   1.413 +    window.addEventListener("focus", function Search_init_window_focus() {
   1.414 +      if (self.isEnabled()) {
   1.415 +        self._blockClick = true;
   1.416 +        setTimeout(function() {
   1.417 +          self._blockClick = false;
   1.418 +        }, 0);
   1.419 +      }
   1.420 +    }, false);
   1.421 +
   1.422 +    this.switchToBeforeMode();
   1.423 +  },
   1.424 +
   1.425 +  // ----------
   1.426 +  // Function: _beforeSearchKeyHandler
   1.427 +  // Handles all keydown before the search interface is brought up.
   1.428 +  _beforeSearchKeyHandler: function Search__beforeSearchKeyHandler(event) {
   1.429 +    // Only match reasonable text-like characters for quick search.
   1.430 +    if (event.altKey || event.ctrlKey || event.metaKey)
   1.431 +      return;
   1.432 +
   1.433 +    if ((event.keyCode > 0 && event.keyCode <= event.DOM_VK_DELETE) ||
   1.434 +        event.keyCode == event.DOM_VK_CONTEXT_MENU ||
   1.435 +        event.keyCode == event.DOM_VK_SLEEP ||
   1.436 +        (event.keyCode >= event.DOM_VK_F1 &&
   1.437 +         event.keyCode <= event.DOM_VK_SCROLL_LOCK) ||
   1.438 +        event.keyCode == event.DOM_VK_META ||
   1.439 +        event.keyCode == 91 || // 91 = left windows key
   1.440 +        event.keyCode == 92 || // 92 = right windows key
   1.441 +        (!event.keyCode && !event.charCode)) {
   1.442 +      return;
   1.443 +    }
   1.444 +
   1.445 +    // If we are already in an input field, allow typing as normal.
   1.446 +    if (event.target.nodeName == "INPUT")
   1.447 +      return;
   1.448 +
   1.449 +    // / is used to activate the search feature so the key shouldn't be entered 
   1.450 +    // into the search box.
   1.451 +    if (event.keyCode == KeyEvent.DOM_VK_SLASH) {
   1.452 +      event.stopPropagation();
   1.453 +      event.preventDefault();
   1.454 +    }
   1.455 +
   1.456 +    this.switchToInMode();
   1.457 +    this._initiatedBy = "keydown";
   1.458 +    this.ensureShown(true);
   1.459 +  },
   1.460 +
   1.461 +  // ----------
   1.462 +  // Function: _inSearchKeyHandler
   1.463 +  // Handles all keydown while search mode.
   1.464 +  _inSearchKeyHandler: function Search__inSearchKeyHandler(event) {
   1.465 +    let term = iQ("#searchbox").val();
   1.466 +    if ((event.keyCode == event.DOM_VK_ESCAPE) ||
   1.467 +        (event.keyCode == event.DOM_VK_BACK_SPACE && term.length <= 1 &&
   1.468 +         this._initiatedBy == "keydown")) {
   1.469 +      this.hide(event);
   1.470 +      return;
   1.471 +    }
   1.472 +
   1.473 +    let matcher = this.createSearchTabMatcher();
   1.474 +    let matches = matcher.matched();
   1.475 +    let others =  matcher.matchedTabsFromOtherWindows();
   1.476 +    if (event.keyCode == event.DOM_VK_RETURN &&
   1.477 +        (matches.length > 0 || others.length > 0)) {
   1.478 +      this.hide(event);
   1.479 +      if (matches.length > 0) 
   1.480 +        matches[0].zoomIn();
   1.481 +      else
   1.482 +        TabUtils.focus(others[0]);
   1.483 +    }
   1.484 +  },
   1.485 +
   1.486 +  // ----------
   1.487 +  // Function: switchToBeforeMode
   1.488 +  // Make sure the event handlers are appropriate for the before-search mode.
   1.489 +  switchToBeforeMode: function Search_switchToBeforeMode() {
   1.490 +    let self = this;
   1.491 +    if (this._currentHandler)
   1.492 +      iQ(window).unbind("keydown", this._currentHandler);
   1.493 +    this._currentHandler = function Search_switchToBeforeMode_handler(event) {
   1.494 +      self._beforeSearchKeyHandler(event);
   1.495 +    }
   1.496 +    iQ(window).keydown(this._currentHandler);
   1.497 +  },
   1.498 +
   1.499 +  // ----------
   1.500 +  // Function: switchToInMode
   1.501 +  // Make sure the event handlers are appropriate for the in-search mode.
   1.502 +  switchToInMode: function Search_switchToInMode() {
   1.503 +    let self = this;
   1.504 +    if (this._currentHandler)
   1.505 +      iQ(window).unbind("keydown", this._currentHandler);
   1.506 +    this._currentHandler = function Search_switchToInMode_handler(event) {
   1.507 +      self._inSearchKeyHandler(event);
   1.508 +    }
   1.509 +    iQ(window).keydown(this._currentHandler);
   1.510 +  },
   1.511 +
   1.512 +  createSearchTabMatcher: function Search_createSearchTabMatcher() {
   1.513 +    return new TabMatcher(iQ("#searchbox").val());
   1.514 +  },
   1.515 +
   1.516 +  // ----------
   1.517 +  // Function: isEnabled
   1.518 +  // Checks whether search mode is enabled or not.
   1.519 +  isEnabled: function Search_isEnabled() {
   1.520 +    return iQ("#search").css("display") != "none";
   1.521 +  },
   1.522 +
   1.523 +  // ----------
   1.524 +  // Function: hide
   1.525 +  // Hides search mode.
   1.526 +  hide: function Search_hide(event) {
   1.527 +    if (!this.isEnabled())
   1.528 +      return;
   1.529 +
   1.530 +    iQ("#searchbox").val("");
   1.531 +    iQ("#searchshade").hide();
   1.532 +    iQ("#search").hide();
   1.533 +
   1.534 +    iQ("#searchbutton").css({ opacity:.8 });
   1.535 +
   1.536 +#ifdef XP_MACOSX
   1.537 +    UI.setTitlebarColors(true);
   1.538 +#endif
   1.539 +
   1.540 +    this.perform();
   1.541 +    this.switchToBeforeMode();
   1.542 +
   1.543 +    if (event) {
   1.544 +      // when hiding the search mode, we need to prevent the keypress handler
   1.545 +      // in UI__setTabViewFrameKeyHandlers to handle the key press again. e.g. Esc
   1.546 +      // which is already handled by the key down in this class.
   1.547 +      if (event.type == "keydown")
   1.548 +        UI.ignoreKeypressForSearch = true;
   1.549 +      event.preventDefault();
   1.550 +      event.stopPropagation();
   1.551 +    }
   1.552 +
   1.553 +    // Return focus to the tab window
   1.554 +    UI.blurAll();
   1.555 +    gTabViewFrame.contentWindow.focus();
   1.556 +
   1.557 +    let newEvent = document.createEvent("Events");
   1.558 +    newEvent.initEvent("tabviewsearchdisabled", false, false);
   1.559 +    dispatchEvent(newEvent);
   1.560 +  },
   1.561 +
   1.562 +  // ----------
   1.563 +  // Function: perform
   1.564 +  // Performs a search.
   1.565 +  perform: function Search_perform() {
   1.566 +    let matcher =  this.createSearchTabMatcher();
   1.567 +
   1.568 +    // Remove any previous other-window search results and
   1.569 +    // hide the display area.
   1.570 +    iQ("#results").empty();
   1.571 +    iQ("#otherresults").hide();
   1.572 +    iQ("#otherresults>.label").text(tabviewString("search.otherWindowTabs"));
   1.573 +
   1.574 +    matcher.doSearch(TabHandlers.onMatch, TabHandlers.onUnmatch, TabHandlers.onOther);
   1.575 +  },
   1.576 +
   1.577 +  // ----------
   1.578 +  // Function: ensureShown
   1.579 +  // Ensures the search feature is displayed.  If not, display it.
   1.580 +  // Parameters:
   1.581 +  //  - a boolean indicates whether this is triggered by a keypress or not
   1.582 +  ensureShown: function Search_ensureShown(activatedByKeypress) {
   1.583 +    let $search = iQ("#search");
   1.584 +    let $searchShade = iQ("#searchshade");
   1.585 +    let $searchbox = iQ("#searchbox");
   1.586 +    iQ("#searchbutton").css({ opacity: 1 });
   1.587 +
   1.588 +    // NOTE: when this function is called by keydown handler, next keypress
   1.589 +    // event or composition events of IME will be fired on the focused editor.
   1.590 +    function dispatchTabViewSearchEnabledEvent() {
   1.591 +      let newEvent = document.createEvent("Events");
   1.592 +      newEvent.initEvent("tabviewsearchenabled", false, false);
   1.593 +      dispatchEvent(newEvent);
   1.594 +    };
   1.595 +
   1.596 +    if (!this.isEnabled()) {
   1.597 +      $searchShade.show();
   1.598 +      $search.show();
   1.599 +
   1.600 +#ifdef XP_MACOSX
   1.601 +      UI.setTitlebarColors({active: "#717171", inactive: "#EDEDED"});
   1.602 +#endif
   1.603 +
   1.604 +      if (activatedByKeypress) {
   1.605 +        // set the focus so key strokes are entered into the textbox.
   1.606 +        $searchbox[0].focus();
   1.607 +        dispatchTabViewSearchEnabledEvent();
   1.608 +      } else {
   1.609 +        // marshal the focusing, otherwise it ends up with searchbox[0].focus gets
   1.610 +        // called before the search button gets the focus after being pressed.
   1.611 +        setTimeout(function setFocusAndDispatchSearchEnabledEvent() {
   1.612 +          $searchbox[0].focus();
   1.613 +          dispatchTabViewSearchEnabledEvent();
   1.614 +        }, 0);
   1.615 +      }
   1.616 +    }
   1.617 +  }
   1.618 +};
   1.619 +

mercurial