aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--background_scripts/completion.coffee37
-rw-r--r--lib/utils.coffee19
-rw-r--r--tests/unit_tests/completion_test.coffee54
-rw-r--r--tests/unit_tests/test_chrome_stubs.coffee4
-rw-r--r--tests/unit_tests/utils_test.coffee7
5 files changed, 101 insertions, 20 deletions
diff --git a/background_scripts/completion.coffee b/background_scripts/completion.coffee
index 23696185..b6a52a15 100644
--- a/background_scripts/completion.coffee
+++ b/background_scripts/completion.coffee
@@ -238,7 +238,7 @@ class DomainCompleter
onComplete()
onPageVisited: (newPage) ->
- domain = @parseDomain(newPage.url)
+ domain = @parseDomainAndScheme newPage.url
if domain
slot = @domains[domain] ||= { entry: newPage, referenceCount: 0 }
# We want each entry in our domains hash to point to the most recent History entry for that domain.
@@ -250,15 +250,40 @@ class DomainCompleter
@domains = {}
else
toRemove.urls.forEach (url) =>
- domain = @parseDomain(url)
+ domain = @parseDomainAndScheme url
if domain and @domains[domain] and ( @domains[domain].referenceCount -= 1 ) == 0
delete @domains[domain]
- parseDomain: (url) -> url.split("/")[2] || ""
+ # Return something like "http://www.example.com" or false.
+ parseDomainAndScheme: (url) ->
+ Utils.hasFullUrlPrefix(url) and not Utils.hasChromePrefix(url) and url.split("/",3).join "/"
# Suggestions from the Domain completer have the maximum relevancy. They should be shown first in the list.
computeRelevancy: -> 1
+# TabRecency associates a logical timestamp with each tab id.
+class TabRecency
+ constructor: ->
+ @timestamp = 1
+ @cache = {}
+
+ chrome.tabs.onActivated.addListener (activeInfo) => @add activeInfo.tabId
+ chrome.tabs.onRemoved.addListener (tabId) => @remove tabId
+
+ chrome.tabs.onReplaced.addListener (addedTabId, removedTabId) =>
+ @remove removedTabId
+ @add addedTabId
+
+ add: (tabId) -> @cache[tabId] = ++@timestamp
+ remove: (tabId) -> delete @cache[tabId]
+
+ # Recently-visited tabs get a higher score (except the current tab, which gets a low score).
+ recencyScore: (tabId) ->
+ @cache[tabId] ||= 1
+ if @cache[tabId] == @timestamp then 0.0 else @cache[tabId] / @timestamp
+
+tabRecency = new TabRecency()
+
# Searches through all open tabs, matching on title and URL.
class TabCompleter
filter: (queryTerms, onComplete) ->
@@ -274,7 +299,10 @@ class TabCompleter
onComplete(suggestions)
computeRelevancy: (suggestion) ->
- RankingUtils.wordRelevancy(suggestion.queryTerms, suggestion.url, suggestion.title)
+ if suggestion.queryTerms.length
+ RankingUtils.wordRelevancy(suggestion.queryTerms, suggestion.url, suggestion.title)
+ else
+ tabRecency.recencyScore(suggestion.tabId)
# A completer which will return your search engines
class SearchEngineCompleter
@@ -547,3 +575,4 @@ root.SearchEngineCompleter = SearchEngineCompleter
root.HistoryCache = HistoryCache
root.RankingUtils = RankingUtils
root.RegexpCache = RegexpCache
+root.TabRecency = TabRecency
diff --git a/lib/utils.coffee b/lib/utils.coffee
index bbcee1a0..b7f8731a 100644
--- a/lib/utils.coffee
+++ b/lib/utils.coffee
@@ -26,28 +26,29 @@ Utils =
-> id += 1
hasChromePrefix: do ->
- chromePrefixes = [ "about:", "view-source:", "chrome-extension:", "data:" ]
+ chromePrefixes = [ "about:", "view-source:", "extension:", "chrome-extension:", "data:" ]
(url) ->
if 0 < url.indexOf ":"
for prefix in chromePrefixes
return true if url.startsWith prefix
false
+ hasFullUrlPrefix: do ->
+ urlPrefix = new RegExp "^[a-z]{3,}://."
+ (url) -> urlPrefix.test url
+
# Completes a partial URL (without scheme)
createFullUrl: (partialUrl) ->
- unless /^[a-z]{3,}:\/\//.test partialUrl
- "http://" + partialUrl
- else
- partialUrl
+ if @hasFullUrlPrefix(partialUrl) then partialUrl else ("http://" + partialUrl)
# Tries to detect if :str is a valid URL.
isUrl: (str) ->
- # Starts with a scheme: URL
- return true if /^[a-z]{3,}:\/\//.test str
-
# Must not contain spaces
return false if ' ' in str
+ # Starts with a scheme: URL
+ return true if @hasFullUrlPrefix str
+
# More or less RFC compliant URL host part parsing. This should be sufficient for our needs
urlRegex = new RegExp(
'^(?:([^:]+)(?::([^:]+))?@)?' + # user:password (optional) => \1, \2
@@ -98,7 +99,7 @@ Utils =
convertToUrl: (string) ->
string = string.trim()
- # Special-case about:[url] and view-source:[url]
+ # Special-case about:[url], view-source:[url] and the like
if Utils.hasChromePrefix string
string
else if Utils.isUrl string
diff --git a/tests/unit_tests/completion_test.coffee b/tests/unit_tests/completion_test.coffee
index 811436a9..88f59b7e 100644
--- a/tests/unit_tests/completion_test.coffee
+++ b/tests/unit_tests/completion_test.coffee
@@ -1,8 +1,8 @@
require "./test_helper.js"
extend(global, require "../../lib/utils.js")
extend(global, require "../../background_scripts/completion.js")
+extend global, require "./test_chrome_stubs.js"
-global.chrome = {}
global.document =
createElement: -> {}
@@ -163,13 +163,13 @@ context "domain completer",
should "return only a single matching domain", ->
results = filterCompleter(@completer, ["story"])
- assert.arrayEqual ["history1.com"], results.map (result) -> result.url
+ assert.arrayEqual ["http://history1.com"], results.map (result) -> result.url
should "pick domains which are more recent", ->
# These domains are the same except for their last visited time.
- assert.equal "history1.com", filterCompleter(@completer, ["story"])[0].url
+ assert.equal "http://history1.com", filterCompleter(@completer, ["story"])[0].url
@history2.lastVisitTime = hours(3)
- assert.equal "history2.com", filterCompleter(@completer, ["story"])[0].url
+ assert.equal "http://history2.com", filterCompleter(@completer, ["story"])[0].url
should "returns no results when there's more than one query term, because clearly it's not a domain", ->
assert.arrayEqual [], filterCompleter(@completer, ["his", "tory"])
@@ -194,15 +194,15 @@ context "domain completer (removing entries)",
should "remove 1 entry for domain with reference count of 1", ->
@onVisitRemovedListener { allHistory: false, urls: [@history1.url] }
- assert.equal "history2.com", filterCompleter(@completer, ["story"])[0].url
+ assert.equal "http://history2.com", filterCompleter(@completer, ["story"])[0].url
assert.equal 0, filterCompleter(@completer, ["story1"]).length
should "remove 2 entries for domain with reference count of 2", ->
@onVisitRemovedListener { allHistory: false, urls: [@history2.url] }
- assert.equal "history2.com", filterCompleter(@completer, ["story2"])[0].url
+ assert.equal "http://history2.com", filterCompleter(@completer, ["story2"])[0].url
@onVisitRemovedListener { allHistory: false, urls: [@history3.url] }
assert.equal 0, filterCompleter(@completer, ["story2"]).length
- assert.equal "history1.com", filterCompleter(@completer, ["story"])[0].url
+ assert.equal "http://history1.com", filterCompleter(@completer, ["story"])[0].url
should "remove 3 (all) matching domain entries", ->
@onVisitRemovedListener { allHistory: false, urls: [@history2.url] }
@@ -399,6 +399,46 @@ context "RegexpCache",
should "search for a string with a prefix/suffix (negative case)", ->
assert.isTrue "hound dog".search(RegexpCache.get("do", "\\b", "\\b")) == -1
+context "TabRecency",
+ setup ->
+ @tabRecency = new TabRecency()
+ @tabRecency.add 3
+ @tabRecency.add 2
+ @tabRecency.add 9
+ @tabRecency.add 1
+ @tabRecency.remove 9
+ @tabRecency.add 4
+
+ should "have entries for active tabs", ->
+ assert.isTrue @tabRecency.cache[1]
+ assert.isTrue @tabRecency.cache[2]
+ assert.isTrue @tabRecency.cache[3]
+ assert.isTrue @tabRecency.cache[4]
+
+ should "not have entries for removed tabs", ->
+ assert.isFalse @tabRecency.cache[9]
+
+ should "give a high score to the most recent tab", ->
+ assert.isTrue @tabRecency.recencyScore(4) < @tabRecency.recencyScore 1
+ assert.isTrue @tabRecency.recencyScore(3) < @tabRecency.recencyScore 1
+ assert.isTrue @tabRecency.recencyScore(2) < @tabRecency.recencyScore 1
+
+ should "give a low score to the current tab", ->
+ assert.isTrue @tabRecency.recencyScore(1) > @tabRecency.recencyScore 4
+ assert.isTrue @tabRecency.recencyScore(2) > @tabRecency.recencyScore 4
+ assert.isTrue @tabRecency.recencyScore(3) > @tabRecency.recencyScore 4
+
+ should "rank tabs by recency", ->
+ assert.isTrue @tabRecency.recencyScore(3) < @tabRecency.recencyScore 2
+ assert.isTrue @tabRecency.recencyScore(2) < @tabRecency.recencyScore 1
+ @tabRecency.add 3
+ @tabRecency.add 4 # Making 3 the most recent tab which isn't the current tab.
+ assert.isTrue @tabRecency.recencyScore(1) < @tabRecency.recencyScore 3
+ assert.isTrue @tabRecency.recencyScore(2) < @tabRecency.recencyScore 3
+ assert.isTrue @tabRecency.recencyScore(4) < @tabRecency.recencyScore 3
+ assert.isTrue @tabRecency.recencyScore(4) < @tabRecency.recencyScore 1
+ assert.isTrue @tabRecency.recencyScore(4) < @tabRecency.recencyScore 2
+
# A convenience wrapper around completer.filter() so it can be called synchronously in tests.
filterCompleter = (completer, queryTerms) ->
results = []
diff --git a/tests/unit_tests/test_chrome_stubs.coffee b/tests/unit_tests/test_chrome_stubs.coffee
index 2abd26c9..80750337 100644
--- a/tests/unit_tests/test_chrome_stubs.coffee
+++ b/tests/unit_tests/test_chrome_stubs.coffee
@@ -30,6 +30,10 @@ exports.chrome =
addListener: () -> true
onActiveChanged:
addListener: () -> true
+ onActivated:
+ addListener: () -> true
+ onReplaced:
+ addListener: () -> true
query: () -> true
windows:
diff --git a/tests/unit_tests/utils_test.coffee b/tests/unit_tests/utils_test.coffee
index b2d656ab..556f5b7a 100644
--- a/tests/unit_tests/utils_test.coffee
+++ b/tests/unit_tests/utils_test.coffee
@@ -61,6 +61,13 @@ context "hasChromePrefix",
assert.isFalse Utils.hasChromePrefix "data"
assert.isFalse Utils.hasChromePrefix "data :foobar"
+context "isUrl",
+ should "identify URLs as URLs", ->
+ assert.isTrue Utils.isUrl "http://www.example.com/blah"
+
+ should "identify non-URLs and non-URLs", ->
+ assert.isFalse Utils.isUrl "http://www.example.com/ blah"
+
context "Function currying",
should "Curry correctly", ->
foo = (a, b) -> "#{a},#{b}"