diff options
| author | Stephen Blott | 2015-05-10 15:19:25 +0100 | 
|---|---|---|
| committer | Stephen Blott | 2015-05-10 15:54:42 +0100 | 
| commit | 6b52c9e6397ac0a040c1bd46b7e6825a0a8415d2 (patch) | |
| tree | bfd7281a66e456f50892251c59c0a6c473c4fc72 /background_scripts/completion_engines.coffee | |
| parent | 8493811a4279950194cc8b1f5941cf9730cda1f0 (diff) | |
| download | vimium-6b52c9e6397ac0a040c1bd46b7e6825a0a8415d2.tar.bz2 | |
Search completion; move completion engines to their own file.
Diffstat (limited to 'background_scripts/completion_engines.coffee')
| -rw-r--r-- | background_scripts/completion_engines.coffee | 170 | 
1 files changed, 9 insertions, 161 deletions
| diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index 638d7f60..07ecfa26 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -4,18 +4,20 @@  #  # Each completion engine defines three functions:  # -#   1. "match" - This takes a searchUrl, and returns a boolean indicating whether this completion engine can +#   1. "match" - This takes a searchUrl and returns a boolean indicating whether this completion engine can  #      perform completion for the given search engine.  #  #   2. "getUrl" - This takes a list of query terms (queryTerms) and generates a completion URL, that is, a URL  #      which will provide completions for this completion engine.  #  #   3. "parse" - This takes a successful XMLHttpRequest object (the request has completed successfully), and -#      returns a list of suggestions (a list of strings). +#      returns a list of suggestions (a list of strings).  This method is always executed within the context +#      of a try/catch block, so errors do not propagate. +# +# Each new completion engine must be add to the list "CompletionEngines" at the bottom of this file. +# +# The lookup logic which uses these completion engines is in "./completion_search.coffee".  # -# The main completion entry point is CompletionEngines.complete().  This implements all lookup and caching -# logic.  It is possible to add new completion engines without changing the CompletionEngines infrastructure -# itself.  # A base class for common regexp-based matching engines.  class RegexpEngine @@ -106,7 +108,8 @@ class DummyCompletionEngine    getUrl: -> chrome.runtime.getURL "content_scripts/vimium.css"    parse: -> [] -completionEngines = [ +# Note: Order matters here. +CompletionEngines = [    Youtube    Google    DuckDuckGo @@ -116,160 +119,5 @@ completionEngines = [    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 requests before launching the HTTP request.  The intention is to cut -  # down on the number of HTTP requests we issue. -  # delay: 100 -  delay: 0 - -  get: (searchUrl, url, callback) -> -    xhr = new XMLHttpRequest() -    xhr.open "GET", url, true -    xhr.timeout = 1000 -    xhr.ontimeout = xhr.onerror = -> callback null -    xhr.send() - -    xhr.onreadystatechange = -> -      if xhr.readyState == 4 -        callback(if xhr.status == 200 then xhr else null) - -  # 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 query, 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 -      @engineCache.get searchUrl -    else -      for engine in completionEngines -        engine = new engine() -        return @engineCache.set searchUrl, engine if engine.match searchUrl - -  # True if we have a completion engine for this search URL, undefined otherwise. -  haveCompletionEngine: (searchUrl) -> -    not @lookupEngine(searchUrl).dummy - -  # 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 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 = null) -> -    @mostRecentHandler = null -    query = queryTerms.join "" - -    # If no callback is provided, then we're to provide suggestions only if we can do so synchronously (ie. -    # from a cache).  In this case we return the results and don't call callback.  Return null if we cannot -    # service the request synchronously. -    returnResultsOnlyFromCache = not callback? -    callback ?= (suggestions) -> suggestions - -    # We don't complete single characters: the results are usually useless. -    return callback [] unless 1 < query.length - -    # 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 might be possible, but -    # vanishingly unlikely. -    junk = "//Zi?ei5;o//" -    completionCacheKey = searchUrl + junk + queryTerms.map((s) -> s.toLowerCase()).join junk -    @completionCache ?= new SimpleCache 60 * 60 * 1000, 2000 # One hour, 2000 entries. -    if @completionCache.has completionCacheKey -      if returnResultsOnlyFromCache -        return @completionCache.get completionCacheKey -      else -        # We add a short delay, even for a cache hit.  This avoids an ugly flicker when the additional -        # suggestions are posted. -        Utils.setTimeout 50, => -          console.log "hit", completionCacheKey if @debug -          callback @completionCache.get completionCacheKey -        return - -    if @mostRecentQuery? and @mostRecentSuggestions? -      # If the user appears to be typing a continuation of the characters of the most recent query, then we -      # can re-use the previous suggestions. -      reusePreviousSuggestions = do (query) => -        query = queryTerms.join(" ").toLowerCase() -        # Verify that the previous query is a prefix of the current query. -        return false unless 0 == query.indexOf @mostRecentQuery.toLowerCase() -        # Ensure that every previous suggestion contains the text of the new query. -        for suggestion in (@mostRecentSuggestions.map (s) -> s.toLowerCase()) -          return false unless 0 <= suggestion.indexOf query -        # Ok. Re-use the suggestion. -        true - -      if reusePreviousSuggestions -        console.log "reuse previous query:", @mostRecentQuery if @debug -        @mostRecentQuery = queryTerms.join " " -        return callback @completionCache.set completionCacheKey, @mostRecentSuggestions - -    return null if returnResultsOnlyFromCache - -    fetchSuggestions = (engine, callback) => -      url = engine.getUrl queryTerms -      query = queryTerms.join(" ").toLowerCase() -      @get searchUrl, url, (xhr = null) => -        # Parsing the response may fail if we receive an unexpected or an unexpectedly-formatted response.  In -        # all cases, we fall back to the catch clause, below.  Therefore, we "fail safe" in the case of -        # incorrect or out-of-date completion engines. -        try -          suggestions = engine.parse xhr -          # Make sure we really do have an iterable of strings. -          suggestions = (suggestion for suggestion in suggestions when "string" == typeof suggestion) -          # Filter out the query itself. It's not adding anything. -          suggestions = (suggestion for suggestion in suggestions when suggestion.toLowerCase() != query) -          console.log "GET", url if @debug -        catch -          suggestions = [] -          # We allow failures to be cached, but remove them after just ten minutes.  This (it is hoped) avoids -          # repeated unnecessary XMLHttpRequest failures over a short period of time. -          removeCompletionCacheKey = => @completionCache.set completionCacheKey, null -          setTimeout removeCompletionCacheKey, 10 * 60 * 1000 # Ten minutes. -          console.log "fail", url if @debug - -        callback suggestions - -    # We pause in case the user is still typing. -    Utils.setTimeout @delay, handler = @mostRecentHandler = => -      if handler != @mostRecentHandler -        # Bail! Another completion has begun, or the user is typing. -        # NOTE: We do *not* call the callback (because we are not providing results, and we don't want allow -        # any higher-level component to cache the results -- specifically, the vomnibar itself, via -        # callerMayCacheResults). -        console.log "bail", completionCacheKey if @debug -        return -      @mostRecentHandler = null -      # Don't allow duplicate identical active requests.  This can happen, for example, when the user enters or -      # removes a space, or when they enter a character and immediately delete it. -      @inTransit ?= {} -      unless @inTransit[completionCacheKey]?.push callback -        queue = @inTransit[completionCacheKey] = [] -        engine = @lookupEngine searchUrl -        fetchSuggestions engine, (suggestions) => -          @mostRecentQuery = queryTerms.join " " -          @mostRecentSuggestions = suggestions -          @completionCache.set completionCacheKey, suggestions unless engine.doNotCache -          callback suggestions -          delete @inTransit[completionCacheKey] -          console.log "callbacks", queue.length, completionCacheKey if @debug and 0 < queue.length -          callback suggestions for callback in queue - -  # Cancel any pending (ie. blocked on @delay) queries.  Does not cancel in-flight queries. -  cancel: -> -    if @mostRecentHandler? -      @mostRecentHandler = null -      console.log "cancel (user is typing)" if @debug -  root = exports ? window  root.CompletionEngines = CompletionEngines | 
