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 | |
| 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')
| -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, }; -})(); +})() |
