aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNiklas Baumstark2012-01-27 19:21:54 +0100
committerNiklas Baumstark2012-04-10 23:59:54 +0200
commit3449af0461782c24c8577fe4a5938f35f417cbb1 (patch)
treefbf4f352f1679599c4fa8f7d84cafd50e05870e6
parent0ab64a092def03ccd462802f040d83ce2911ea1e (diff)
downloadvimium-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.html94
-rw-r--r--fuzzyMode.js75
-rw-r--r--lib/completion.js204
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,
};
-})();
+})()