diff options
| author | Phil Crosby | 2012-05-05 20:15:27 -0700 |
|---|---|---|
| committer | Phil Crosby | 2012-05-05 20:24:33 -0700 |
| commit | 083ed4dc8282de961e1733e1d98a792d79befc5f (patch) | |
| tree | 30eb6fef963d3a70204c53c6a4c22c2893326a6c /content_scripts/vomnibar.js | |
| parent | 6cec158a79263067e14ba9a8efef7bd2626203ad (diff) | |
| download | vimium-083ed4dc8282de961e1733e1d98a792d79befc5f.tar.bz2 | |
Put content scripts and background scripts in separate directories, so the purpose and execution mode are more clear.
Sorry if you had patches in your local copies and this breaks them -- these renames were
a long time coming, and now is better than later.
Diffstat (limited to 'content_scripts/vomnibar.js')
| -rw-r--r-- | content_scripts/vomnibar.js | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/content_scripts/vomnibar.js b/content_scripts/vomnibar.js new file mode 100644 index 00000000..4f136635 --- /dev/null +++ b/content_scripts/vomnibar.js @@ -0,0 +1,232 @@ +var vomnibar = (function() { + var vomnibarUI = null; // the dialog instance for this window + var completers = { }; + + function getCompleter(name) { + if (!(name in completers)) + completers[name] = new BackgroundCompleter(name); + return completers[name]; + } + + /* + * Activate the Vomnibox. + */ + function activate(completerName, refreshInterval, initialQueryValue) { + var completer = getCompleter(completerName); + if (!vomnibarUI) + vomnibarUI = new VomnibarUI(10); + completer.refresh(); + vomnibarUI.setCompleter(completer); + vomnibarUI.setRefreshInterval(refreshInterval); + if (initialQueryValue) + vomnibarUI.setQuery(initialQueryValue); + vomnibarUI.show(); + } + + /** User interface for fuzzy completion */ + var VomnibarUI = Class.extend({ + init: function(maxResults) { + this.prompt = '>'; + this.maxResults = maxResults; + this.refreshInterval = 0; + this.initDom(); + }, + + setQuery: function(query) { this.input.value = query; }, + + setCompleter: function(completer) { + this.completer = completer; + this.reset(); + }, + + setRefreshInterval: function(refreshInterval) { this.refreshInterval = refreshInterval; }, + + show: function() { + this.box.style.display = "block"; + this.input.focus(); + handlerStack.push({ keydown: this.onKeydown.bind(this) }); + }, + + hide: function() { + this.box.style.display = "none"; + this.completionList.style.display = "none"; + this.input.blur(); + handlerStack.pop(); + }, + + reset: function() { + this.input.value = ""; + this.updateTimer = null; + this.completions = []; + this.selection = 0; + this.update(true); + }, + + updateSelection: function() { + if (this.completions.length > 0) + this.selection = Math.min(this.selection, this.completions.length - 1); + for (var i = 0; i < this.completionList.children.length; ++i) + this.completionList.children[i].className = (i == this.selection) ? "selected" : ""; + }, + + /* + * Returns the user's action ("up", "down", "enter", "dismiss" or null) based on their keypress. + * We support the arrow keys and other shortcuts for moving, so this method hides that complexity. + */ + actionFromKeyEvent: function(event) { + var key = getKeyChar(event); + if (isEscape(event)) + return "dismiss"; + else if (key == "up" || + (event.shiftKey && event.keyCode == keyCodes.tab) || + (event.ctrlKey && (key == "k" || key == "p"))) + return "up"; + else if (key == "down" || + (event.keyCode == keyCodes.tab && !event.shiftKey) || + (event.ctrlKey && (key == "j" || key == "n"))) + return "down"; + else if (event.keyCode == keyCodes.enter) + return "enter"; + }, + + onKeydown: function(event) { + var action = this.actionFromKeyEvent(event); + if (!action) return true; // pass through + + if (action == "dismiss") { + this.hide(); + } + else if (action == "up") { + if (this.selection > 0) + this.selection -= 1; + this.updateSelection(); + } + else if (action == "down") { + if (this.selection < this.completions.length - 1) + this.selection += 1; + this.updateSelection(); + } + else if (action == "enter") { + this.update(true, function() { + // Shift+Enter will open the result in a new tab instead of the current tab. + var openInNewTab = (event.shiftKey || isPrimaryModifierKey(event)); + this.completions[this.selection].performAction(openInNewTab); + this.hide(); + }.proxy(this)); + } + + // It seems like we have to manually supress the event here and still return true. + event.stopPropagation(); + event.preventDefault(); + return true; + }, + + updateCompletions: function(callback) { + query = this.input.value.replace(/^\s*/, ""); + + this.completer.filter(query, this.maxResults, function(completions) { + this.completions = completions; + + // update completion list with the new data + this.completionList.innerHTML = completions.map(function(completion) { + return "<li>" + completion.html + "</li>"; + }).join(''); + + this.completionList.style.display = this.completions.length > 0 ? "block" : "none"; + this.updateSelection(); + if (callback) callback(); + }.proxy(this)); + }, + + update: function(force, callback) { + force = force || false; // explicitely default to asynchronous updating + + if (force) { + // cancel scheduled update + if (this.updateTimer !== null) + window.clearTimeout(this.updateTimer); + this.updateCompletions(callback); + } else if (this.updateTimer !== null) { + // an update is already scheduled, don't do anything + return; + } else { + // 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() { + this.updateCompletions(callback); + this.updateTimer = null; + }.proxy(this), this.refreshInterval); + } + }, + + initDom: function() { + this.box = utils.createElementFromHtml( + '<div id="vomnibar" class="vimiumReset">'+ + '<div class="input">'+ + '<span class="prompt">' + utils.escapeHtml(this.prompt) + '</span> '+ + '<input type="text" class="query"></span></div>'+ + '<ul></ul></div>'); + this.box.style.display = 'none'; + document.body.appendChild(this.box); + + this.input = document.querySelector("#vomnibar .query"); + this.input.addEventListener("input", function() { this.update(); }.bind(this)); + this.completionList = document.querySelector("#vomnibar ul"); + this.completionList.style.display = "none"; + } + }); + + /* + * Sends filter and refresh requests to a Vomnibox completer on the background page. + */ + var BackgroundCompleter = Class.extend({ + /* - name: The background page completer that you want to interface with. Either "omni" or "tabs". */ + init: function(name) { + this.name = name; + this.filterPort = chrome.extension.connect({ name: "filterCompleter" }); + }, + + 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) { + // functionName will be either "navigateToUrl" or "switchToTab". args will be a URL or a tab ID. + var functionToCall = completionActions[result.action.functionName]; + result.performAction = functionToCall.curry(result.action.args); + return result; + })); + }); + this.filterPort.postMessage({ id: id, name: this.name, query: query, maxResults: maxResults }); + } + }); + + /* + * These are the actions we can perform when the user selects a result in the Vomnibox. + */ + var completionActions = { + navigateToUrl: function(url, openInNewTab) { + // If the URL is a bookmarklet prefixed with javascript:, we shouldn't open that in a new tab. + if (url.indexOf("javascript:") == 0) + openInNewTab = false; + chrome.extension.sendRequest({ + handler: openInNewTab ? "openUrlInNewTab" : "openUrlInCurrentTab", + url: url, + selected: openInNewTab + }); + }, + + switchToTab: function(tabId) { + chrome.extension.sendRequest({ handler: "selectSpecificTab", id: tabId }); + } + }; + + // public interface + return { + activate: function() { activate("omni", 100); }, + activateWithCurrentUrl: function() { activate("omni", 100, window.location.toString()); }, + activateTabSelection: function() { activate("tabs", 0); } + } +})(); |
