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 | |
| 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
| -rw-r--r-- | background_page.html | 94 | ||||
| -rw-r--r-- | fuzzyMode.js | 75 | ||||
| -rw-r--r-- | lib/completion.js | 204 |
3 files changed, 189 insertions, 184 deletions
diff --git a/background_page.html b/background_page.html index a56028bc..f760ea70 100644 --- a/background_page.html +++ b/background_page.html @@ -4,6 +4,7 @@ <script type="text/javascript" src="lib/clipboard.js"></script> <script type="text/javascript" src="lib/utils.js"></script> <script type="text/javascript" src="background/settings.js"></script> +<script type="text/javascript" src="lib/completion.js"></script> <script type="text/javascript" charset="utf-8"> var currentVersion = utils.getCurrentVersion(); @@ -27,9 +28,7 @@ getCurrentTabUrl: getCurrentTabUrl, settings: handleSettings, getBookmarks: getBookmarks, - getAllBookmarks: getAllBookmarks, - getHistory: getHistory, - getTabsInCurrentWindow: getTabsInCurrentWindow, + filterCompleter: filterCompleter, }; var sendRequestHandlers = { @@ -46,6 +45,7 @@ isEnabledForUrl: isEnabledForUrl, saveHelpDialogSettings: saveHelpDialogSettings, selectSpecificTab: selectSpecificTab, + refreshCompleter: refreshCompleter, }; // Event handlers @@ -53,6 +53,28 @@ var getScrollPositionHandlers = {}; // tabId -> function(tab, scrollX, scrollY); var tabLoadedHandlers = {}; // tabId -> function() + var completionSources = { + smart: new completion.SmartCompletionSource({ + 'wiki ': [ 'Wikipedia (en)', 'http://en.wikipedia.org/wiki/%s' ], + 'luck ': [ 'Google Lucky (en)', 'http://www.google.com/search?q=%s&btnI=I%27m+Feeling+Lucky' ], + 'cc ' : [ 'dict.cc', 'http://www.dict.cc/?s=%s' ], + ';' : [ 'goto', '%s' ], + '?' : [ 'search', function(query) { return utils.createSearchUrl(query) } ], + }), + bookmarks: new completion.FuzzyBookmarkCompletionSource(), + history: new completion.FuzzyHistoryCompletionSource(20000), + tabs: new completion.FuzzyTabCompletionSource(), + } + var completers = { + omni: new completion.MultiCompleter([ + completionSources.smart, + completionSources.bookmarks, + completionSources.history, + completionSources.tabs, + ], 1), + tabs: new completion.MultiCompleter([ completionSources.tabs ], 0), + }; + chrome.extension.onConnect.addListener(function(port, name) { var senderTabId = port.sender.tab ? port.sender.tab.id : null; // If this is a tab we've been waiting to open, execute any "tab loaded" handlers, e.g. to restore @@ -74,9 +96,6 @@ if (portHandlers[port.name]) port.onMessage.addListener(portHandlers[port.name]); - - // prefetch history - useHistory(function() {}); }); chrome.extension.onRequest.addListener(function (request, sender, sendResponse) { @@ -289,66 +308,15 @@ }) } - function getAllBookmarks(args, port) { - function traverseTree(bookmarks, callback) { - for (var i = 0; i < bookmarks.length; ++i) { - callback(bookmarks[i]); - if (typeof bookmarks[i].children === "undefined") - continue; - traverseTree(bookmarks[i].children, callback); - } - }; - - chrome.bookmarks.getTree(function(bookmarks) { - var results = []; - traverseTree(bookmarks, function(bookmark) { - results.push(bookmark); - }); - port.postMessage({bookmarks:results}); - }); - }; - - var cachedHistory = null; - function useHistory(callback) { - if (cachedHistory !== null) { - callback(cachedHistory); - return; - } - - 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); - }); - cachedHistory = history; - callback(cachedHistory); - }); + function refreshCompleter(request) { + completers[request.name].refresh(); } - chrome.history.onVisited.addListener(function(item) { - if (cachedHistory === null) return; - // only cache newly visited sites - if (item.visitCount === 1) - cachedHistory.push(item); - }); - - function getHistory(args, port) { - useHistory(function(history) { - port.postMessage({ - history: history.slice(Math.max(history.length - args.maxResults, 0)) - }); + function filterCompleter(args, port) { + completers[args.name].filter(args.query, args.maxResults, function(results) { + port.postMessage({ id: args.id, results: results }); }); - }; - - function getTabsInCurrentWindow(args, port) { - chrome.tabs.getAllInWindow(null, function(tabs) { - port.postMessage({tabs:tabs}); - }); - }; + } /* * Used by everyone to get settings from local storage. diff --git a/fuzzyMode.js b/fuzzyMode.js index 2a9b4601..8dbad5cc 100644 --- a/fuzzyMode.js +++ b/fuzzyMode.js @@ -1,35 +1,10 @@ var fuzzyMode = (function() { var fuzzyBox = null; // the dialog instance for this window - var completers = {}; // completer cache - - function createCompleter(name) { - if (name === 'smart') - return new completion.SmartCompleter({ - 'wiki ': [ 'Wikipedia (en)', 'http://en.wikipedia.org/wiki/%s' ], - 'luck ': [ 'Google Lucky (en)', 'http://www.google.com/search?q=%s&btnI=I%27m+Feeling+Lucky' ], - 'cc ' : [ 'dict.cc', 'http://www.dict.cc/?s=%s' ], - ';' : [ 'goto', '%s' ], - '?' : [ 'search', function(query) { return utils.createSearchUrl(query) } ], - }); - else if (name === 'history') - return new completion.FuzzyHistoryCompleter(8000); - else if (name === 'bookmarks') - return new completion.FuzzyBookmarkCompleter(); - else if (name === 'tabs') - return new completion.FuzzyTabCompleter(); - else if (name === 'tabsSorted') - return new completion.MergingCompleter([getCompleter('tabs')], 0); - else if (name === 'all') - return new completion.MergingCompleter([ - getCompleter('smart'), - getCompleter('bookmarks'), - getCompleter('history'), - getCompleter('tabs'), - ], 1); - } + var completers = { }; + function getCompleter(name) { if (!(name in completers)) - completers[name] = createCompleter(name); + completers[name] = new completion.BackgroundCompleter(name); return completers[name]; } @@ -70,6 +45,7 @@ var fuzzyMode = (function() { hide: function() { this.box.style.display = 'none'; + this.completionList.style.display = 'none'; handlerStack.pop(); }, @@ -78,7 +54,6 @@ var fuzzyMode = (function() { this.updateTimer = null; this.completions = []; this.selection = 0; - // force synchronous updating so that the old results will not be flash up shortly this.update(true); }, @@ -90,6 +65,7 @@ var fuzzyMode = (function() { }, onKeydown: function(event) { + var self = this; var keyChar = getKeyChar(event); if (isEscape(event)) { @@ -120,20 +96,19 @@ var fuzzyMode = (function() { // refresh with F5 else if (keyChar == 'f5') { this.completer.refresh(); - this.update(true); // force synchronous update + this.update(true); // force immediate update } // use primary action with Enter. Holding down Shift/Ctrl uses the alternative action // (opening in new tab) else if (event.keyCode == keyCodes.enter) { - this.update(true); // force synchronous update - - var alternative = (event.shiftKey || isPrimaryModifierKey(event)); - if (this.reverseAction) - alternative = !alternative; - this.completions[this.selection].action[alternative ? 1 : 0](); - this.hide(); - this.reset(); + this.update(true, function() { + var alternative = (event.shiftKey || isPrimaryModifierKey(event)); + if (self.reverseAction) + alternative = !alternative; + self.completions[self.selection].action[alternative ? 1 : 0](); + self.hide(); + }); } else if (keyChar.length == 1) { @@ -146,34 +121,34 @@ var fuzzyMode = (function() { return true; }, - updateCompletions: function() { + updateCompletions: function(callback) { var self = this; - var start = Date.now(); + //var start = Date.now(); this.completer.filter(this.query, this.maxResults, function(completions) { self.completions = completions; - // clear completions + // update completion list with the new data self.completionList.innerHTML = completions.map(function(completion) { return '<li>' + completion.html + '</li>'; }).join(''); - console.log("total update time: " + (Date.now() - start)); self.completionList.style.display = self.completions.length > 0 ? 'block' : 'none'; self.updateSelection(); + if (callback) callback(); }); }, - update: function(sync) { - sync = sync || false; // explicitely default to asynchronous updating + update: function(force, callback) { + force = force || false; // explicitely default to asynchronous updating this.query = this.query.replace(/^\s*/, ''); this.input.textContent = this.query; - if (sync) { + if (force) { // cancel scheduled update if (this.updateTimer !== null) window.clearTimeout(this.updateTimer); - this.updateCompletions(); + this.updateCompletions(callback); } else if (this.updateTimer !== null) { // an update is already scheduled, don't do anything return; @@ -182,7 +157,7 @@ var fuzzyMode = (function() { // always update asynchronously for better user experience and to take some load off the CPU // (not every keystroke will cause a dedicated update) this.updateTimer = setTimeout(function() { - self.updateCompletions(); + self.updateCompletions(callback); self.updateTimer = null; }, this.refreshInterval); } @@ -205,9 +180,9 @@ var fuzzyMode = (function() { // public interface return { - activateAll: function() { start('all', false, 100); }, - activateAllNewTab: function() { start('all', true, 100); }, - activateTabs: function() { start('tabsSorted', false, 0); }, + activateAll: function() { start('omni', false, 200); }, + activateAllNewTab: function() { start('omni', true, 200); }, + activateTabs: function() { start('tabs', false, 200); }, } })(); 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, }; -})(); +})() |
