aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--background_scripts/completion.coffee97
-rw-r--r--background_scripts/main.coffee15
-rw-r--r--background_scripts/search_engines.coffee3
-rw-r--r--lib/utils.coffee6
-rw-r--r--pages/vomnibar.coffee2
5 files changed, 77 insertions, 46 deletions
diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee
index c8650f45..fee4778a 100644
--- a/background_scripts/completion.coffee
+++ b/background_scripts/completion.coffee
@@ -346,29 +346,31 @@ class SearchEngineCompleter
suggestions[0].autoSelect = true
queryTerms = queryTerms[1..]
- # For custom search-engine queries, this adds suggestions only if we have a completer. For other queries,
- # this adds suggestions for the default search engine (if we have a completer for that).
- SearchEngines.complete searchUrl, queryTerms, (searchSuggestions = []) =>
-
- # Scoring:
- # - The score does not depend upon the actual suggestion (so, it does not depend upon word relevancy).
- # We assume that the completion engine has already factored that in.
- # - The score is higher if the query is longer. The idea is that search suggestions are more likely
- # to be relevant if, after typing quite some number of characters, the user hasn't yet found a
- # useful suggestion from another completer.
- # - Scores are weighted such that they retain the ordering provided by the completion engine.
- characterCount = query.length - queryTerms.length + 1
- score = 0.8 * (Math.min(characterCount, 12.0)/12.0)
-
- for suggestion in searchSuggestions
- suggestions.push @mkSuggestion true, queryTerms, type, mkUrl(suggestion), suggestion, @computeRelevancy, score
- score *= 0.9
-
- if custom
- for suggestion in suggestions
- suggestion.reinsertPrefix = "#{keyword} " if suggestion.insertText
-
- onComplete suggestions
+ onComplete suggestions, (onComplete) =>
+ suggestions = []
+ # For custom search-engine queries, this adds suggestions only if we have a completer. For other queries,
+ # this adds suggestions for the default search engine (if we have a completer for that).
+ SearchEngines.complete searchUrl, queryTerms, (searchSuggestions = []) =>
+
+ # Scoring:
+ # - The score does not depend upon the actual suggestion (so, it does not depend upon word relevancy).
+ # We assume that the completion engine has already factored that in.
+ # - The score is higher if the query is longer. The idea is that search suggestions are more likely
+ # to be relevant if, after typing quite some number of characters, the user hasn't yet found a
+ # useful suggestion from another completer.
+ # - Scores are weighted such that they retain the ordering provided by the completion engine.
+ characterCount = query.length - queryTerms.length + 1
+ score = 0.8 * (Math.min(characterCount, 12.0)/12.0)
+
+ for suggestion in searchSuggestions
+ suggestions.push @mkSuggestion true, queryTerms, type, mkUrl(suggestion), suggestion, @computeRelevancy, score
+ score *= 0.9
+
+ if custom
+ for suggestion in suggestions
+ suggestion.reinsertPrefix = "#{keyword} " if suggestion.insertText
+
+ onComplete suggestions
mkSuggestion: (insertText, args...) ->
suggestion = new Suggestion args...
@@ -410,9 +412,11 @@ class SearchEngineCompleter
# A completer which calls filter() on many completers, aggregates the results, ranks them, and returns the top
# 10. Queries from the vomnibar frontend script come through a multi completer.
class MultiCompleter
- constructor: (@completers) -> @maxResults = 10
+ constructor: (@completers) ->
+ @maxResults = 10
- refresh: -> completer.refresh() for completer in @completers when completer.refresh
+ refresh: ->
+ completer.refresh?() for completer in @completers
filter: (queryTerms, onComplete) ->
# Allow only one query to run at a time.
@@ -424,21 +428,40 @@ class MultiCompleter
@filterInProgress = true
suggestions = []
completersFinished = 0
+ continuation = null
for completer in @completers
# Call filter() on every source completer and wait for them all to finish before returning results.
- completer.filter queryTerms, (newSuggestions) =>
- suggestions = suggestions.concat(newSuggestions)
- completersFinished += 1
- if completersFinished >= @completers.length
- results = @sortSuggestions(suggestions)[0...@maxResults]
- result.generateHtml() for result in results
- onComplete(results)
- @filterInProgress = false
- @filter(@mostRecentQuery.queryTerms, @mostRecentQuery.onComplete) if @mostRecentQuery
-
- sortSuggestions: (suggestions) ->
- suggestion.computeRelevancy(@queryTerms) for suggestion in suggestions
+ # At most one of the completers (SearchEngineCompleter) may pass a continuation function, which will be
+ # called asynchronously after the results of all of the other completers have been posted. Any
+ # additional results from this continuation will be added to the existing results and posted. We don't
+ # call the continuation if another query is already waiting.
+ completer.filter queryTerms, (newSuggestions, cont = null) =>
+ # Allow completers to execute concurrently.
+ Utils.nextTick =>
+ suggestions = suggestions.concat newSuggestions
+ continuation = cont if cont?
+ completersFinished += 1
+ if completersFinished >= @completers.length
+ onComplete @prepareSuggestions(suggestions), keepAlive: continuation?
+ onDone = =>
+ @filterInProgress = false
+ @filter @mostRecentQuery.queryTerms, @mostRecentQuery.onComplete if @mostRecentQuery
+ # We add a very short delay. It is possible for all of this processing to have been handled
+ # pretty-much synchronously, which would have prevented any newly-arriving queries from
+ # registering.
+ Utils.setTimeout 10, =>
+ if continuation? and not @mostRecentQuery
+ continuation (newSuggestions) =>
+ onComplete @prepareSuggestions suggestions.concat(newSuggestions)
+ onDone()
+ else
+ onDone()
+
+ prepareSuggestions: (suggestions) ->
+ suggestion.computeRelevancy @queryTerms for suggestion in suggestions
suggestions.sort (a, b) -> b.relevancy - a.relevancy
+ suggestions = suggestions[0...@maxResults]
+ suggestion.generateHtml() for suggestion in suggestions
suggestions
# Utilities which help us compute a relevancy score for a given item.
diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee
index 4d2546fc..45619023 100644
--- a/background_scripts/main.coffee
+++ b/background_scripts/main.coffee
@@ -50,11 +50,13 @@ completionSources =
searchEngines: new SearchEngineCompleter()
completers =
- omni: new MultiCompleter([
- completionSources.searchEngines,
- completionSources.bookmarks,
- completionSources.history,
- completionSources.domains])
+ omni: new MultiCompleter [
+ completionSources.bookmarks
+ completionSources.history
+ completionSources.domains
+ # This comes last, because it delivers additional, asynchronous results.
+ completionSources.searchEngines
+ ]
bookmarks: new MultiCompleter([completionSources.bookmarks])
tabs: new MultiCompleter([completionSources.tabs])
@@ -220,7 +222,8 @@ refreshCompleter = (request) -> completers[request.name].refresh()
whitespaceRegexp = /\s+/
filterCompleter = (args, port) ->
queryTerms = if (args.query == "") then [] else args.query.split(whitespaceRegexp)
- completers[args.name].filter(queryTerms, (results) -> port.postMessage({ id: args.id, results: results }))
+ completers[args.name].filter queryTerms, (results, extra = {}) ->
+ port.postMessage extend extra, id: args.id, results: results
chrome.tabs.onSelectionChanged.addListener (tabId, selectionInfo) ->
if (selectionChangedHandlers.length > 0)
diff --git a/background_scripts/search_engines.coffee b/background_scripts/search_engines.coffee
index a80c218c..abf8c86e 100644
--- a/background_scripts/search_engines.coffee
+++ b/background_scripts/search_engines.coffee
@@ -134,8 +134,7 @@ SearchEngines =
@requests[searchUrl] = xhr = new XMLHttpRequest()
xhr.open "GET", url, true
- # We set a fairly short timeout. If we block for too long, then we block *all* completers.
- xhr.timeout = 500
+ xhr.timeout = 750
xhr.ontimeout = => @cancel searchUrl, callback
xhr.onerror = => @cancel searchUrl, callback
xhr.send()
diff --git a/lib/utils.coffee b/lib/utils.coffee
index 5d9696e1..07528714 100644
--- a/lib/utils.coffee
+++ b/lib/utils.coffee
@@ -183,6 +183,12 @@ Utils =
return true if re.test string
false
+ # Convenience wrapper for setTimeout (with the arguments around the other way).
+ setTimeout: (ms, func) -> setTimeout func, ms
+
+ # Like Nodejs's nextTick.
+ nextTick: (func) -> @setTimeout 0, func
+
# This creates a new function out of an existing function, where the new function takes fewer arguments. This
# allows us to pass around functions instead of functions + a partial list of arguments.
diff --git a/pages/vomnibar.coffee b/pages/vomnibar.coffee
index 2076fea6..3fb63177 100644
--- a/pages/vomnibar.coffee
+++ b/pages/vomnibar.coffee
@@ -239,7 +239,7 @@ class BackgroundCompleter
id = BackgroundCompleter.messageId += 1
@filterPort.onMessage.addListener handler = (msg) =>
if msg.id == id
- @filterPort.onMessage.removeListener handler
+ @filterPort.onMessage.removeListener handler unless msg.keepAlive and id == BackgroundCompleter.messageId
if id == BackgroundCompleter.messageId
# The result objects coming from the background page will be of the form:
# { html: "", type: "", url: "" }