diff options
| author | Stephen Blott | 2015-05-06 11:50:28 +0100 |
|---|---|---|
| committer | Stephen Blott | 2015-05-06 11:50:28 +0100 |
| commit | f330e3243bc27f1a19040fb386fc877fe82fbefe (patch) | |
| tree | be6b6087103fc954d8b80558906ae2c1aa6e55bc | |
| parent | 5752c0ead0a65fc2329515509f66e00bd6ee2f60 (diff) | |
| download | vimium-f330e3243bc27f1a19040fb386fc877fe82fbefe.tar.bz2 | |
Search completion; yet more tweaks.
| -rw-r--r-- | background_scripts/completion.coffee | 11 | ||||
| -rw-r--r-- | background_scripts/completion_engines.coffee | 63 | ||||
| -rw-r--r-- | background_scripts/main.coffee | 4 | ||||
| -rw-r--r-- | pages/vomnibar.coffee | 19 |
4 files changed, 48 insertions, 49 deletions
diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee index bffb9700..18bfafd1 100644 --- a/background_scripts/completion.coffee +++ b/background_scripts/completion.coffee @@ -8,8 +8,7 @@ # A completer is a class which has three functions: # - filter(query, onComplete): "query" will be whatever the user typed into the Vomnibox. # - refresh(): (optional) refreshes the completer's data source (e.g. refetches the list of bookmarks). -# - userIsTyping(): (optional) informs the completer that the user is typing (and pending completions may no -# longer be needed). +# - cancel(): (optional) cancels any pending, cancelable action. class Suggestion showRelevancy: true # Set this to true to render relevancy when debugging the ranking scores. @@ -351,8 +350,8 @@ class TabCompleter class SearchEngineCompleter searchEngines: {} - userIsTyping: -> - CompletionEngines.userIsTyping() + cancel: -> + CompletionEngines.cancel() filter: (queryTerms, onComplete) -> suggestions = [] @@ -462,8 +461,8 @@ class MultiCompleter refresh: -> completer.refresh?() for completer in @completers - userIsTyping: -> - completer.userIsTyping?() for completer in @completers + cancel: -> + completer.cancel?() for completer in @completers filter: (queryTerms, onComplete) -> # Allow only one query to run at a time. diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index 1386256f..b5caadd7 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -22,8 +22,9 @@ class RegexpEngine constructor: (@regexps) -> match: (searchUrl) -> Utils.matchesAnyRegexp @regexps, searchUrl -# Several Google completion engines package responses in this way. +# Several Google completion engines package XML responses in this way. class GoogleXMLRegexpEngine extends RegexpEngine + doNotCache: true parse: (xhr) -> for suggestion in xhr.responseXML.getElementsByTagName "suggestion" continue unless suggestion = suggestion.getAttribute "data" @@ -50,6 +51,7 @@ class Youtube extends GoogleXMLRegexpEngine "http://suggestqueries.google.com/complete/search?client=youtube&ds=yt&xml=t&q=#{Utils.createSearchQuery queryTerms}" class Wikipedia extends RegexpEngine + doNotCache: true # Example search URL: http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s constructor: -> super [ new RegExp "^https?://[a-z]+\.wikipedia\.org/" ] @@ -96,8 +98,8 @@ class DuckDuckGo extends RegexpEngine suggestion.phrase for suggestion in JSON.parse xhr.responseText # A dummy search engine which is guaranteed to match any search URL, but never produces completions. This -# allows the rest of the logic to be written knowing that there will be a search engine match. -class DummySearchEngine +# allows the rest of the logic to be written knowing that there will always be a completion engine match. +class DummyCompletionEngine match: -> true # We return a useless URL which we know will succeed, but which won't generate any network traffic. getUrl: -> chrome.runtime.getURL "content_scripts/vimium.css" @@ -110,13 +112,19 @@ completionEngines = [ Wikipedia Bing Amazon - DummySearchEngine + DummyCompletionEngine ] +# A note on caching. +# Some completion engines allow caching, and Chrome serves up cached responses to requests (e.g. Google, +# Wikipedia, YouTube). Others do not (e.g. Bing, DuckDuckGo, Amazon). A completion engine can set +# @doNotCache to a truthy value to disable caching in cases where it is unnecessary. + CompletionEngines = debug: true - # The amount of time to wait for new completions before launching the HTTP request. + # The amount of time to wait for new requests before launching the HTTP request. The intention is to cut + # down on the number of HTTP requests we issue. delay: 250 get: (searchUrl, url, callback) -> @@ -128,11 +136,12 @@ CompletionEngines = xhr.onreadystatechange = -> if xhr.readyState == 4 + console.log xhr.getAllResponseHeaders() callback(if xhr.status == 200 then xhr else null) - # Look up the search-completion engine for this searchUrl. Because of DummySearchEngine, above, we know - # there will always be a match. Imagining that there may be many completion engines, and knowing that this - # is called for every input event in the vomnibar, we cache the result. + # Look up the completion engine for this searchUrl. Because of DummyCompletionEngine, above, we know there + # will always be a match. Imagining that there may be many completion engines, and knowing that this is + # called for every input event in the vomnibar, we cache the result. lookupEngine: (searchUrl) -> @engineCache ?= new SimpleCache 30 * 60 * 60 * 1000 # 30 hours (these are small, we can keep them longer). if @engineCache.has searchUrl @@ -145,29 +154,25 @@ CompletionEngines = # This is the main entry point. # - searchUrl is the search engine's URL, e.g. Settings.get("searchUrl"), or a custome search engine's URL. # This is only used as a key for determining the relevant completion engine. - # - queryTerms are the queryTerms. + # - queryTerms are the query terms. # - callback will be applied to a list of suggestion strings (which may be an empty list, if anything goes # wrong). complete: (searchUrl, queryTerms, callback) -> @mostRecentHandler = null + query = queryTerms.join "" - # We can't complete empty queries. - return callback [] unless 0 < queryTerms.length - - if 1 == queryTerms.length - # We don't complete URLs. - return callback [] if Utils.isUrl queryTerms[0] - # We don't complete less then three characters: the results are usually useless. This also prevents - # one- and two-character custom search engine keywords from being sent to the default completer (e.g. - # the initial "w" before typing "w something"). - return callback [] unless 2 < queryTerms[0].length + # We don't complete less then three characters: the results are usually useless. This also prevents + # one- and two-character custom search engine keywords from being sent to the default completer (e.g. + # the initial "w" before typing "w something" for Wikipedia). + return callback [] unless 3 <= query.length - # We don't complete Javascript URLs. - return callback [] if Utils.hasJavascriptPrefix queryTerms[0] + # We don't complete regular URLs or Javascript URLs. + return callback [] if 1 == queryTerms.length and Utils.isUrl query + return callback [] if Utils.hasJavascriptPrefix query # Cache completions. However, completions depend upon both the searchUrl and the query terms. So we need - # to generate a key. We mix in some junk generated by pwgen. A key clash is possible, but vanishingly - # unlikely. + # to generate a key. We mix in some junk generated by pwgen. A key clash might be possible, but + # vanishingly unlikely. junk = "//Zi?ei5;o//" completionCacheKey = searchUrl + junk + queryTerms.join junk @completionCache ?= new SimpleCache 60 * 60 * 1000, 2000 # One hour, 2000 entries. @@ -180,8 +185,7 @@ CompletionEngines = callback @completionCache.get completionCacheKey return - fetchSuggestions = (callback) => - engine = @lookupEngine searchUrl + fetchSuggestions = (engine, callback) => url = engine.getUrl queryTerms query = queryTerms.join(" ").toLowerCase() @get searchUrl, url, (xhr = null) => @@ -215,14 +219,15 @@ CompletionEngines = @inTransit ?= {} unless @inTransit[completionCacheKey]?.push callback queue = @inTransit[completionCacheKey] = [] - fetchSuggestions (suggestions) => - callback @completionCache.set completionCacheKey, suggestions + engine = @lookupEngine searchUrl + fetchSuggestions engine, (suggestions) => + callback @completionCache.set completionCacheKey, suggestions unless engine.doNotCache delete @inTransit[completionCacheKey] console.log "callbacks", queue.length, completionCacheKey if @debug and 0 < queue.length callback suggestions for callback in queue - userIsTyping: -> - console.log "reset (typing)" if @debug and @mostRecentHandler? + cancel: -> + console.log "cancel (user is typing)" if @debug and @mostRecentHandler? @mostRecentHandler = null root = exports ? window diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 066e4cb6..c1be9f8f 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -65,8 +65,8 @@ completionHandlers = queryTerms = args.query.split(/\s+/).filter (s) -> 0 < s.length completer.filter queryTerms, (results) -> port.postMessage id: args.id, results: results - refreshCompleter: (completer) -> completer.refresh() - userIsTyping: (completer) -> completer.userIsTyping() + refresh: (completer) -> completer.refresh() + cancel: (completer) -> completer.cancel() handleCompletions = (args, port) -> completionHandlers[args.handler] completers[args.name], args, port diff --git a/pages/vomnibar.coffee b/pages/vomnibar.coffee index a2d4df85..ff585a5e 100644 --- a/pages/vomnibar.coffee +++ b/pages/vomnibar.coffee @@ -8,11 +8,6 @@ Vomnibar = getUI: -> @vomnibarUI completers: {} - getCompleter: (name) -> - if (!(name of @completers)) - @completers[name] = new BackgroundCompleter(name) - @completers[name] - # # Activate the Vomnibox. # @@ -27,7 +22,8 @@ Vomnibar = options.refreshInterval = if options.completer == "omni" then 125 else 0 - completer = @getCompleter(options.completer) + name = options.completer + completer = @completers[name] ?= new BackgroundCompleter name @vomnibarUI ?= new VomnibarUI() completer.refresh() @vomnibarUI.setInitialSelectionValue(if options.selectFirst then 0 else -1) @@ -35,7 +31,7 @@ Vomnibar = @vomnibarUI.setRefreshInterval(options.refreshInterval) @vomnibarUI.setForceNewTab(options.newTab) @vomnibarUI.setQuery(options.query) - @vomnibarUI.update() + @vomnibarUI.update true hide: -> @vomnibarUI?.hide() onHidden: -> @vomnibarUI?.onHidden() @@ -54,7 +50,6 @@ class VomnibarUI setCompleter: (completer) -> @completer = completer @reset() - @update(true) setRefreshInterval: (refreshInterval) -> @refreshInterval = refreshInterval @@ -178,7 +173,7 @@ class VomnibarUI @updateSelection() updateOnInput: => - @completer.userIsTyping() + @completer.cancel() # If the user types, then don't reset any previous text, and re-enable auto-select. if @previousInputValue? @previousInputValue = null @@ -249,10 +244,10 @@ class BackgroundCompleter @port.postMessage name: @name, handler: "filter", id: @messageId, query: query refresh: -> - @port.postMessage name: @name, handler: "refreshCompleter" + @port.postMessage name: @name, handler: "refresh" - userIsTyping: -> - @port.postMessage name: @name, handler: "userIsTyping" + cancel: -> + @port.postMessage name: @name, handler: "cancel" # These are the actions we can perform when the user selects a result in the Vomnibox. completionActions: |
