diff options
| author | Niklas Baumstark | 2012-01-27 19:21:54 +0100 | 
|---|---|---|
| committer | Niklas Baumstark | 2012-04-10 23:59:54 +0200 | 
| commit | 3449af0461782c24c8577fe4a5938f35f417cbb1 (patch) | |
| tree | fbf4f352f1679599c4fa8f7d84cafd50e05870e6 /lib/completion.js | |
| parent | 0ab64a092def03ccd462802f040d83ce2911ea1e (diff) | |
| download | vimium-3449af0461782c24c8577fe4a5938f35f417cbb1.tar.bz2 | |
move completion logic to background page
This has the following advantages:
* searching is done in the background, UI responsiveness is improved
* caches are no longer duplicated. This saves RAM and improves performance
Diffstat (limited to 'lib/completion.js')
| -rw-r--r-- | lib/completion.js | 204 | 
1 files changed, 133 insertions, 71 deletions
| diff --git a/lib/completion.js b/lib/completion.js index 1915a83a..f04eb356 100644 --- a/lib/completion.js +++ b/lib/completion.js @@ -195,6 +195,14 @@ var completion = (function() {        return [ open(false), open(true, true), open(true, false) ];    } +  /** Returns an action that switches to the tab with the given :id. */ +  function createActionSwitchToTab(id) { +    var open = function() { +      chrome.extension.sendRequest({ handler: 'selectSpecificTab', id: id }); +    } +    return [open, open, open]; +  } +    /** Creates an file-internal representation of a URL match with the given paramters */    function createCompletionHtml(type, str, title) {      title = title || ''; @@ -259,32 +267,17 @@ var completion = (function() {    /** Helper class to construct fuzzy completers for asynchronous data sources like history or bookmark     * matchers. */ -  var AsyncCompleter = function() { -    this.completions = null; +  var AsyncCompletionSource = function() {      this.id = utils.createUniqueId(); -    this.readyCallback = this.fallbackReadyCallback = function(results) { +    this.reset(); +    this.resultsReady = this.fallbackReadyCallback = function(results) {        this.completions = results;      }    } -  AsyncCompleter.prototype = { -    /** Convenience function to remove shared code in the completers. Clears the completion cache, sends -     * a message to an extension port and pipes the returned message through a callback before storing it into -     * the instance's completion cache. -     */ -    fetchFromPort: function(name, query, callback) { -      this.completions = null; // reset completions - -      // asynchronously fetch from a port -      var port = chrome.extension.connect({ name: name }) ; -      var self = this; -      port.onMessage.addListener(function(msg) { -        self.readyCallback(callback(msg)); -      }); -      port.postMessage(query); -    }, - -    resetCache: function() { +  AsyncCompletionSource.prototype = { +    reset: function() {        fuzzyMatcher.invalidateFilterCache(this.id); +      this.completions = null;      },      /** Convenience function to remove shared code in the completers. Creates an internal representation of @@ -294,7 +287,7 @@ var completion = (function() {        var url = item.url;        var parts = [type, url, item.title];        var str = parts.join(' '); -      action = action || createActionOpenUrl(url); +      action = action || {func: 'completion.createActionOpenUrl', args: [url]};        function createLazyCompletion(query) {          return new LazyCompletion(url.length / fuzzyMatcher.calculateRelevancy(query, str), function() { @@ -333,10 +326,10 @@ var completion = (function() {          handler(this.completions);        } else {          // no: register the handler as a callback -        this.readyCallback = function(results) { +        this.resultsReady = function(results) {            handler(results); -          self.readyCallback = self.fallbackReadyCallback; -          self.readyCallback(results); +          self.resultsReady = self.fallbackReadyCallback; +          self.resultsReady(results);          }        }      }, @@ -346,7 +339,7 @@ var completion = (function() {    /** A simple completer that suggests to open the input string as an URL or to trigger a web search for the     * given term, depending on whether it thinks the input is an URL or not. */ -  var SmartCompleter = function(commands) { +  var SmartCompletionSource = function(commands) {      commands = commands || {};      var commandKeys = Object.keys(commands); @@ -364,7 +357,7 @@ var completion = (function() {          return new LazyCompletion(-2, function() {            return {              html:   createCompletionHtml(desc, term), -            action: createActionOpenUrl(utils.createFullUrl(url)), +            action: {func: 'completion.createActionOpenUrl', args: [utils.createFullUrl(url)]},            }})        });      } @@ -379,8 +372,8 @@ var completion = (function() {        return new LazyCompletion(-1, function() {          return {            html: createCompletionHtml(isUrl ? 'goto' : 'search', query), -          action: createActionOpenUrl(isUrl ? utils.createFullUrl(query) -                                            : utils.createSearchUrl(query)), +          action: {func: 'completion.createActionOpenUrl', args: isUrl ? [utils.createFullUrl(query)] +                                                                       : [utils.createSearchUrl(query)]},          }});      } @@ -391,69 +384,134 @@ var completion = (function() {      }    } -  /** A fuzzy history completer */ -  var FuzzyHistoryCompleter = function(maxResults) { -    AsyncCompleter.call(this); -    this.maxResults = maxResults || 1000; +  /** A fuzzy bookmark completer */ +  var FuzzyBookmarkCompletionSource = function() { +    AsyncCompletionSource.call(this);    } -  utils.extend(AsyncCompleter, FuzzyHistoryCompleter); +  utils.extend(AsyncCompletionSource, FuzzyBookmarkCompletionSource); -  FuzzyHistoryCompleter.prototype.refresh = function() { +  FuzzyBookmarkCompletionSource.prototype.traverseTree = function(bookmarks, results) {      var self = this; -    self.resetCache(); -    self.fetchFromPort('getHistory', { maxResults: self.maxResults }, function(msg) { -      return msg.history.map(function(item) { -        return self.createInternalMatch('history', item); -      }); +    bookmarks.forEach(function(bookmark) { +      results.push(bookmark); +      if (bookmark.children === undefined) +        return; +      self.traverseTree(bookmark.children, results);      });    } -  /** A fuzzy bookmark completer */ -  var FuzzyBookmarkCompleter = function() { -    AsyncCompleter.call(this); +  FuzzyBookmarkCompletionSource.prototype.refresh = function() { +    var self = this; self.reset(); +    chrome.bookmarks.getTree(function(bookmarks) { +      var results = []; +      self.traverseTree(bookmarks, results); + +      self.resultsReady(results.filter(function(b) { return b.url !== undefined; }) +                              .map(function(bookmark) { +        return self.createInternalMatch('bookmark', bookmark); +      })); +    }); +  } + +  /** A fuzzy history completer */ +  var FuzzyHistoryCompletionSource = function(maxResults) { +    AsyncCompletionSource.call(this); +    this.cachedHistory = null; +    this.maxResults = maxResults;    } -  utils.extend(AsyncCompleter, FuzzyBookmarkCompleter); +  utils.extend(AsyncCompletionSource, FuzzyHistoryCompletionSource); -  FuzzyBookmarkCompleter.prototype.refresh = function() { +  FuzzyHistoryCompletionSource.prototype.useHistory = function(callback) {      var self = this; -    self.resetCache(); -    self.fetchFromPort('getAllBookmarks', {}, function(msg) { -      return msg.bookmarks.filter(function(bookmark) { return bookmark.url !== undefined }) -                          .map(function(bookmark) { -        return self.createInternalMatch('bookmark', bookmark); +    if (self.cachedHistory !== null) +      return callback(self.cachedHistory); + +    chrome.history.search({ +        text: '', +        maxResults: 20000, +        startTime: 0, +    }, function(history) { +      // sorting in ascending order, so we can push new items to the end later +      history.sort(function(a, b) { +        return (a.lastVisitTime|| 0) - (b.lastVisitTime || 0);        }); +      self.cachedHistory = history; +      callback(history); +    }); + +    chrome.history.onVisited.addListener(function(item) { +      // only cache newly visited sites +      if (item.visitCount === 1) +        self.cachedHistory.push(item); +    }); +  } + +  FuzzyHistoryCompletionSource.prototype.refresh = function() { +    var self = this; +    self.reset(); + +    self.useHistory(function(history) { +      self.resultsReady(history.slice(-self.maxResults).map(function(item) { +        return self.createInternalMatch('history', item); +      }))      });    }    /** A fuzzy tab completer */ -  var FuzzyTabCompleter = function() { -    AsyncCompleter.call(this); +  var FuzzyTabCompletionSource = function() { +    AsyncCompletionSource.call(this);    } -  utils.extend(AsyncCompleter, FuzzyTabCompleter); +  utils.extend(AsyncCompletionSource, FuzzyTabCompletionSource); -  FuzzyTabCompleter.prototype.refresh = function() { +  FuzzyTabCompletionSource.prototype.refresh = function() {      var self = this; -    self.resetCache(); -    self.fetchFromPort('getTabsInCurrentWindow', {}, function(msg) { -      return msg.tabs.map(function(tab) { -        var open = function() { -          chrome.extension.sendRequest({ handler: 'selectSpecificTab', id: tab.id }); -        } -        return self.createInternalMatch('tab', tab, [open, open, open]); -      }); +    self.reset(); + +    chrome.tabs.getAllInWindow(null, function(tabs) { +      self.resultsReady(tabs.map(function(tab) { +        return self.createInternalMatch('tab', tab, +                                        { func: 'completion.createActionSwitchToTab', +                                          args: [tab.id] }); +      }));      });    } +  var BackgroundCompleter = function(name) { +    this.name = name; +    this.filterPort = chrome.extension.connect({ name: 'filterCompleter' }); +  } +  BackgroundCompleter.prototype = { +    refresh: function() { +      chrome.extension.sendRequest({ handler: 'refreshCompleter', name: this.name }); +    }, + +    filter: function(query, maxResults, callback) { +      var id = utils.createUniqueId(); +      this.filterPort.onMessage.addListener(function(msg) { +        if (msg.id != id) return; +        callback(msg.results.map(function(result) { +          var action = result.action; +          result.action = eval(action.func).apply(null, action.args); +          return result; +        })); +      }); +      this.filterPort.postMessage({ id: id, +                                    name: this.name, +                                    query: query, +                                    maxResults: maxResults }); +    }, +  } +    /** A meta-completer that delegates queries and merges and sorts the results of a collection of other     * completer instances given in :sources. The optional argument :queryThreshold determines how long a -   * query has to be to trigger a refresh. */ -  var MergingCompleter = function(sources, queryThreshold) { +   * query has to be to trigger a search. */ +  var MultiCompleter = function(sources, queryThreshold) {      if (queryThreshold === undefined)        queryThreshold = 1; // default      this.sources = sources;      this.queryThreshold = queryThreshold;    } -  MergingCompleter.prototype = { +  MultiCompleter.prototype = {      refresh: function() {        this.sources.forEach(function(x) { x.refresh(); });      }, @@ -464,6 +522,7 @@ var completion = (function() {          return;        } +      var self = this;        var all = [];        var counter = this.sources.length; @@ -484,10 +543,13 @@ var completion = (function() {    // public interface    return { -    FuzzyHistoryCompleter: FuzzyHistoryCompleter, -    FuzzyBookmarkCompleter: FuzzyBookmarkCompleter, -    FuzzyTabCompleter: FuzzyTabCompleter, -    SmartCompleter: SmartCompleter, -    MergingCompleter: MergingCompleter, +    FuzzyBookmarkCompletionSource: FuzzyBookmarkCompletionSource, +    FuzzyHistoryCompletionSource: FuzzyHistoryCompletionSource, +    FuzzyTabCompletionSource: FuzzyTabCompletionSource, +    SmartCompletionSource: SmartCompletionSource, +    MultiCompleter: MultiCompleter, +    BackgroundCompleter: BackgroundCompleter, +    createActionOpenUrl: createActionOpenUrl, +    createActionSwitchToTab: createActionSwitchToTab,    }; -})(); +})() | 
