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>" |
