diff options
| -rw-r--r-- | background_scripts/completion_engines.coffee | 210 | ||||
| -rw-r--r-- | background_scripts/completion_search.coffee | 7 | ||||
| -rw-r--r-- | pages/completion_engines.coffee | 6 | 
3 files changed, 118 insertions, 105 deletions
| diff --git a/background_scripts/completion_engines.coffee b/background_scripts/completion_engines.coffee index 18aa00bb..afbb2040 100644 --- a/background_scripts/completion_engines.coffee +++ b/background_scripts/completion_engines.coffee @@ -1,142 +1,160 @@ -# A completion engine provides search suggestions for a search engine.  A search engine is identified by a -# "searchUrl", e.g. Settings.get("searchUrl"), or a custom search engine URL. +# A completion engine provides search suggestions for a custom search engine.  A custom search engine is +# identified by a "searchUrl".  An "engineUrl" is used for fetching suggestions, whereas a "searchUrl" is used +# for the actual search itself.  # -# Each completion engine defines three functions: +# Each completion engine defines:  # -#   1. "match" - This takes a searchUrl and returns a boolean indicating whether this completion engine can -#      perform completion for the given search engine. +#   1. An "engineUrl". This is the URL to use for search completions and is passed as the option "engineUrl" +#      to the "BaseEngine" constructor.  # -#   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. +#   2. One or more regular expressions which define the custom search engine URLs for which the completion +#      engine will be used. This is passed as the "regexps" option to the "BaseEngine" constructor.  # -#   3. "parse" - This takes a successful XMLHttpRequest object (the request has completed successfully), and -#      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. +#   3. A "parse" function. This takes a successful XMLHttpRequest object (the request has completed +#      successfully), and 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. +# +#   4. For documentation only, each completion engine *must* and example custom search engine.  The example +#      must include an example "keyword" and and example "searchUrl", and may include and example +#      "description".  #  # Each new completion engine must be added to the list "CompletionEngines" at the bottom of this file.  #  # The lookup logic which uses these completion engines is in "./completion_search.coffee".  # -# A base class for common regexp-based matching engines. -class RegexpEngine -  constructor: (args...) -> @regexps = args.map (regexp) -> new RegExp regexp +# A base class for common regexp-based matching engines. "options" must define: +#   options.engineUrl: the URL to use for the completion engine. This must be a string. +#   options.regexps: one or regular expressions.  This may either a single string or a list of strings. +#   options.example: an example object containing at least "keyword" and "searchUrl", and optional "description". +class BaseEngine +  constructor: (options) -> +    extend this, options +    @regexps = [ @regexps ] if "string" == typeof @regexps +    @regexps = @regexps.map (regexp) -> new RegExp regexp +    match: (searchUrl) -> Utils.matchesAnyRegexp @regexps, searchUrl +  getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, @engineUrl -# Several Google completion engines package XML responses in this way. -class GoogleXMLRegexpEngine extends RegexpEngine +# Several Google completion engines package responses as XML. This parses such XML. +class GoogleXMLBaseEngine extends BaseEngine    parse: (xhr) ->      for suggestion in xhr.responseXML.getElementsByTagName "suggestion"        continue unless suggestion = suggestion.getAttribute "data"        suggestion -class Google extends GoogleXMLRegexpEngine +class Google extends GoogleXMLBaseEngine    constructor: (regexps = null) -> -    super regexps ? "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/" -    @exampleSearchUrl = "http://www.google.com/search?q=%s" -    @exampleKeyword = "m" - -  getUrl: (queryTerms) -> -    Utils.createSearchUrl queryTerms, -      "http://suggestqueries.google.com/complete/search?ss_protocol=legace&client=toolbar&q=%s" - -# A wrapper class for Google completions.  This adds prefix terms to the query, and strips those terms from -# the resulting suggestions.  For example, for Google Maps, we add "map of" as a prefix, then strip "map of" -# from the resulting suggestions. -class GoogleWithPrefix extends Google -  constructor: (prefix, args...) -> -    super args... -    prefix = prefix.trim() -    @prefix = "#{prefix} " -    @queryTerms = prefix.split /\s+/ -  getUrl: (queryTerms) -> super [ @queryTerms..., queryTerms... ] -  parse: (xhr) -> -    super(xhr) -      .filter (suggestion) => suggestion.startsWith @prefix -      .map (suggestion) => suggestion[@prefix.length..].ltrim() - -# For Google Maps, we add the prefix "map of" to the query, and send it to Google's general search engine, -# then strip "map of" from the resulting suggestions. -class GoogleMaps extends GoogleWithPrefix +    super +      engineUrl: "http://suggestqueries.google.com/complete/search?ss_protocol=legace&client=toolbar&q=%s" +      regexps: regexps ? "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/" +      example: +        searchUrl: "http://www.google.com/search?q=%s" +        keyword: "g" + +## # A wrapper class for Google completions.  This adds prefix terms to the query, and strips those terms from +## # the resulting suggestions.  For example, for Google Maps, we add "map of" as a prefix, then strip "map of" +## # from the resulting suggestions. +## class GoogleWithPrefix extends Google +##   constructor: (prefix, args...) -> +##     super args... +##     prefix = prefix.trim() +##     @prefix = "#{prefix} " +##     @queryTerms = prefix.split /\s+/ +##   getUrl: (queryTerms) -> super [ @queryTerms..., queryTerms... ] +##   parse: (xhr) -> +##     super(xhr) +##       .filter (suggestion) => suggestion.startsWith @prefix +##       .map (suggestion) => suggestion[@prefix.length..].ltrim() +## +## # For Google Maps, we add the prefix "map of" to the query, and send it to Google's general search engine, +## # then strip "map of" from the resulting suggestions. +## class GoogleMaps extends GoogleWithPrefix +##   constructor: -> +##     super "map of", "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/maps" +##     @exampleSearchUrl = "https://www.google.com/maps?q=%s" +##     @exampleKeyword = "m" +##     @exampleDescription = "Google maps" + +class Youtube extends GoogleXMLBaseEngine    constructor: -> -    super "map of", "^https?://[a-z]+\\.google\\.(com|ie|co\\.uk|ca|com\\.au)/maps" -    @exampleSearchUrl = "https://www.google.com/maps?q=%s" -    @exampleKeyword = "m" -    @exampleDescription = "Google maps" - -class Youtube extends GoogleXMLRegexpEngine +    super +      engineUrl: "http://suggestqueries.google.com/complete/search?client=youtube&ds=yt&xml=t&q=%s" +      regexps: "^https?://[a-z]+\\.youtube\\.com/results" +      example: +        searchUrl: "http://www.youtube.com/results?search_query=%s" +        keyword: "y" + +class Wikipedia extends BaseEngine    constructor: -> -    super "^https?://[a-z]+\\.youtube\\.com/results" -    @exampleSearchUrl = "http://www.youtube.com/results?search_query=%s" -    @exampleKeyword = "y" +    super +      engineUrl: "https://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=%s" +      regexps: "^https?://[a-z]+\\.wikipedia\\.org/" +      example: +        searchUrl: "http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s" +        keyword: "w" -  getUrl: (queryTerms) -> -    Utils.createSearchUrl queryTerms, -      "http://suggestqueries.google.com/complete/search?client=youtube&ds=yt&xml=t&q=%s" +  parse: (xhr) -> JSON.parse(xhr.responseText)[1] -class Wikipedia extends RegexpEngine +class Bing extends BaseEngine    constructor: -> -    super "^https?://[a-z]+\\.wikipedia\\.org/" -    @exampleSearchUrl = "http://www.wikipedia.org/w/index.php?title=Special:Search&search=%s" -    @exampleKeyword = "y" - -  getUrl: (queryTerms) -> -    Utils.createSearchUrl queryTerms, -      "https://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=%s" +    super +      engineUrl: "http://api.bing.com/osjson.aspx?query=%s" +      regexps: "^https?://www\\.bing\\.com/search" +      example: +        searchUrl: "https://www.bing.com/search?q=%s" +        keyword: "b" -  parse: (xhr) -> -    JSON.parse(xhr.responseText)[1] - -class Bing extends RegexpEngine -  constructor: -> -    super "^https?://www\\.bing\\.com/search" -    @exampleSearchUrl = "https://www.bing.com/search?q=%s" -    @exampleKeyword = "b" -  getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://api.bing.com/osjson.aspx?query=%s"    parse: (xhr) -> JSON.parse(xhr.responseText)[1] -class Amazon extends RegexpEngine +class Amazon extends BaseEngine    constructor: -> -    super "^https?://www\\.amazon\\.(com|co\\.uk|ca|com\\.au)/s/" -    @exampleSearchUrl = "http://www.amazon.com/s/?field-keywords=%s" -    @exampleKeyword = "a" -  getUrl: (queryTerms) -> -    Utils.createSearchUrl queryTerms, -      "https://completion.amazon.com/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=1&q=%s" +    super +      engineUrl: "https://completion.amazon.com/search/complete?method=completion&search-alias=aps&client=amazon-search-ui&mkt=1&q=%s" +      regexps: "^https?://www\\.amazon\\.(com|co\\.uk|ca|com\\.au)/s/" +      example: +        searchUrl: "http://www.amazon.com/s/?field-keywords=%s" +        keyword: "a" +    parse: (xhr) -> JSON.parse(xhr.responseText)[1] -class DuckDuckGo extends RegexpEngine +class DuckDuckGo extends BaseEngine    constructor: -> -    super "^https?://([a-z]+\\.)?duckduckgo\\.com/" -    @exampleSearchUrl = "https://duckduckgo.com/?q=%s" -    @exampleKeyword = "d" -  getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "https://duckduckgo.com/ac/?q=%s" +    super +      engineUrl: "https://duckduckgo.com/ac/?q=%s" +      regexps: "^https?://([a-z]+\\.)?duckduckgo\\.com/" +      example: +        searchUrl: "https://duckduckgo.com/?q=%s" +        keyword: "d" +    parse: (xhr) ->      suggestion.phrase for suggestion in JSON.parse xhr.responseText -class Webster extends RegexpEngine +class Webster extends BaseEngine    constructor: -> -    super "^https?://www.merriam-webster.com/dictionary/" -    @exampleSearchUrl = "http://www.merriam-webster.com/dictionary/%s" -    @exampleKeyword = "dw" -    @exampleDescription = "Dictionary" -  getUrl: (queryTerms) -> Utils.createSearchUrl queryTerms, "http://www.merriam-webster.com/autocomplete?query=%s" +    super +      engineUrl: "http://www.merriam-webster.com/autocomplete?query=%s" +      regexps: "^https?://www.merriam-webster.com/dictionary/" +      example: +        searchUrl: "http://www.merriam-webster.com/dictionary/%s" +        keyword: "dw" +        description: "Dictionary" +    parse: (xhr) -> JSON.parse(xhr.responseText).suggestions  # 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 always be a completion engine match. -class DummyCompletionEngine -  dummy: true -  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" -  parse: -> [] +class DummyCompletionEngine extends BaseEngine +  constructor: -> +    super +      regexps: "." +      dummy: true  # Note: Order matters here.  CompletionEngines = [    Youtube -  GoogleMaps +  # GoogleMaps    Google    DuckDuckGo    Wikipedia diff --git a/background_scripts/completion_search.coffee b/background_scripts/completion_search.coffee index d89eb278..cb819025 100644 --- a/background_scripts/completion_search.coffee +++ b/background_scripts/completion_search.coffee @@ -58,12 +58,7 @@ CompletionSearch =      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 -    # is vanishingly unlikely. -    junk = "//Zi?ei5;o//" -    completionCacheKey = searchUrl + junk + queryTerms.map((s) -> s.toLowerCase()).join junk - +    completionCacheKey = JSON.stringify [ searchUrl, queryTerms ]      if @completionCache.has completionCacheKey        console.log "hit", completionCacheKey if @debug        return callback @completionCache.get completionCacheKey diff --git a/pages/completion_engines.coffee b/pages/completion_engines.coffee index 94381fb5..d744b3b3 100644 --- a/pages/completion_engines.coffee +++ b/pages/completion_engines.coffee @@ -17,12 +17,12 @@ DomUtils.documentReady ->        html.push "</pre>"      if engine.prefix        html.push "<p>This uses the general Google completion engine, but adds the prefix \"<tt>#{engine.prefix.trim()}</tt>\" to the query.</p>" -    if engine.exampleSearchUrl and engine.exampleKeyword -      engine.exampleDescription ||= engine.constructor.name +    if engine.example.searchUrl and engine.example.keyword +      engine.example.description ||= engine.constructor.name        html.push "<p>"        html.push "Example:"        html.push "<pre>" -      html.push "#{engine.exampleKeyword}: #{engine.exampleSearchUrl} #{engine.exampleDescription}" +      html.push "#{engine.example.keyword}: #{engine.example.searchUrl} #{engine.example.description}"        html.push "</pre>"        html.push "</p>"      html.push "</div>" | 
