From f037e83763b1b62e048e1fc433d52b9564ce3ba0 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 23 Aug 2014 11:40:25 +0100 Subject: Allow passing of keys to the underlying page. --- background_scripts/main.coffee | 37 +++++++++++++++++++++++++--------- background_scripts/settings.coffee | 4 ++++ content_scripts/vimium_frontend.coffee | 19 +++++++++++++++++ pages/options.html | 25 ++++++++++++++++++----- 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 431d9a31..e32027b6 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -69,17 +69,29 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) -> getCurrentTabUrl = (request, sender) -> sender.tab.url # -# Checks the user's preferences in local storage to determine if Vimium is enabled for the given URL. +# Checks the user's preferences in local storage to determine if Vimium is enabled for the given URL, and +# whether any keys should be passed through to the underlying page. # isEnabledForUrl = (request) -> - # excludedUrls are stored as a series of URL expressions separated by newlines. - excludedUrls = Settings.get("excludedUrls").split("\n") - isEnabled = true - for url in excludedUrls - # The user can add "*" to the URL which means ".*" - regexp = new RegExp("^" + url.replace(/\*/g, ".*") + "$") - isEnabled = false if request.url.match(regexp) - { isEnabledForUrl: isEnabled } + # Excluded URLs are stored as a series of URL expressions and optional passKeys, separated by newlines. + # Lines for which the first non-blank character is "#" are comments. + excludedLines = (line.trim() for line in Settings.get("excludedUrls").split("\n")) + excludedUrls = (line for line in excludedLines when line and line.indexOf("#") != 0) + for spec in excludedUrls + parse = spec.split(/\s+/) + if parse.length + url = parse[0] + # The user can add "*" to the URL which means ".*" + regexp = new RegExp("^" + url.replace(/\*/g, ".*") + "$") + if request.url.match(regexp) + passKeys = parse[1..].join("") + if passKeys + # Enabled, but only for these keys. + return { isEnabledForUrl: true, passKeys: passKeys } + # Disabled. + return { isEnabledForUrl: false } + # Enabled (the default). + { isEnabledForUrl: true } # Called by the popup UI. Strips leading/trailing whitespace and ignores empty strings. root.addExcludedUrl = (url) -> @@ -500,6 +512,13 @@ handleKeyDown = (request, port) -> console.log("checking keyQueue: [", keyQueue + key, "]") keyQueue = checkKeyQueue(keyQueue + key, port.sender.tab.id, request.frameId) console.log("new KeyQueue: " + keyQueue) + # Tell the front end whether there are keys in the queue. If there are, then subsequent keys in passKeys will be + # handled by vimium. + # FIXME: There is a race condition here. The behaviour depends upon whether this message gets back + # to the front end before the next keystroke or not. + chrome.tabs.sendMessage(port.sender.tab.id, + name: "currentKeyQueue", + keyQueue: keyQueue) checkKeyQueue = (keysToCheck, tabId, frameId) -> refreshedCompletionKeys = false diff --git a/background_scripts/settings.coffee b/background_scripts/settings.coffee index 175f3262..34d6e879 100644 --- a/background_scripts/settings.coffee +++ b/background_scripts/settings.coffee @@ -83,7 +83,11 @@ root.Settings = Settings = """ excludedUrls: """ + # Disable Vimium on Gmail: http*://mail.google.com/* + + # Use Facebook's own j/k bindings: + http*://www.facebook.com/* jk """ # NOTE: If a page contains both a single angle-bracket link and a double angle-bracket link, then in # most cases the single bracket link will be "prev/next page" and the double bracket link will be diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 565c9e61..70cc5cbb 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -15,6 +15,8 @@ isShowingHelpDialog = false keyPort = null # Users can disable Vimium on URL patterns via the settings page. isEnabledForUrl = true +passKeys = null +keyQueue = null # The user's operating system. currentCompletionKeys = null validFirstKeys = null @@ -115,6 +117,7 @@ initializePreDomReady = -> getScrollPosition: -> scrollX: window.scrollX, scrollY: window.scrollY setScrollPosition: (request) -> setScrollPosition request.scrollX, request.scrollY executePageCommand: executePageCommand + currentKeyQueue: (request) -> keyQueue = request.keyQueue getActiveState: -> { enabled: isEnabledForUrl } disableVimium: disableVimium @@ -321,6 +324,15 @@ extend window, false +# Should this keyChar be passed to the underlying page? +# Keystrokes are *never* considered passKeys if the keyQueue is not empty. So, for example, if 't' is a +# passKey, then 'gt' and '99t' will neverthless be handled by vimium. +# TODO: This currently only works for unmodified keys (so not for '', or the like). It's not clear if +# this is a problem or not. I don't recall coming across a web page with modifier key bindings. Such +# bindings might be too likely to conflict with browser bindings. +isPassKey = ( keyChar ) -> + !keyQueue and passKeys and 0 <= passKeys.indexOf keyChar + handledKeydownEvents = [] # @@ -349,6 +361,9 @@ onKeypress = (event) -> handleKeyCharForFindMode(keyChar) DomUtils.suppressEvent(event) else if (!isInsertMode() && !findMode) + # Is this keyChar is to be passed to the underlying page? + if (isPassKey keyChar) + return undefined if (currentCompletionKeys.indexOf(keyChar) != -1) DomUtils.suppressEvent(event) @@ -431,6 +446,9 @@ onKeydown = (event) -> else if (KeyboardUtils.isEscape(event)) keyPort.postMessage({ keyChar:"", frameId:frameId }) + else if isPassKey KeyboardUtils.getKeyChar(event) + return undefined + # Added to prevent propagating this event to other listeners if it's one that'll trigger a Vimium command. # The goal is to avoid the scenario where Google Instant Search uses every keydown event to dump us # back into the search box. As a side effect, this should also prevent overriding by other sites. @@ -467,6 +485,7 @@ checkIfEnabledForUrl = -> isEnabledForUrl = response.isEnabledForUrl if (isEnabledForUrl) initializeWhenEnabled() + passKeys = response.passKeys else if (HUD.isReady()) # Quickly hide any HUD we might already be showing, e.g. if we entered insert mode on page load. HUD.hide() diff --git a/pages/options.html b/pages/options.html index b71625e8..fc0f189d 100644 --- a/pages/options.html +++ b/pages/options.html @@ -197,15 +197,30 @@ - Excluded URLs
+ Excluded URLs and keys
- e.g. http*://mail.google.com/*
- This will disable Vimium on Gmail.

- Enter one URL per line.
+

+ To disable Vimium on a site, use:
+ http*://mail.google.com/*
+ This will wholly disable Vimium on Gmail.

+ To use Vimium together with a website's own
+ key bindings, use:
+ http*://mail.google.com/* jknpc
+ This will enable Vimium on Gmail, but pass
+ the five listed keys through to Gmail itself.

+ One entry per line.
+

- + + + -- cgit v1.2.3 From 3581b585acf996fb8515c11d30682269557301c2 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 23 Aug 2014 15:01:03 +0100 Subject: Allow passing of keys to the underlying page (minor code review). --- background_scripts/main.coffee | 4 ++-- pages/options.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index e32027b6..6d986187 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -512,10 +512,10 @@ handleKeyDown = (request, port) -> console.log("checking keyQueue: [", keyQueue + key, "]") keyQueue = checkKeyQueue(keyQueue + key, port.sender.tab.id, request.frameId) console.log("new KeyQueue: " + keyQueue) - # Tell the front end whether there are keys in the queue. If there are, then subsequent keys in passKeys will be + # Tell the content script whether there are keys in the queue. If there are, then subsequent keys in passKeys will be # handled by vimium. # FIXME: There is a race condition here. The behaviour depends upon whether this message gets back - # to the front end before the next keystroke or not. + # to the content script before the next keystroke or not. chrome.tabs.sendMessage(port.sender.tab.id, name: "currentKeyQueue", keyQueue: keyQueue) diff --git a/pages/options.html b/pages/options.html index fc0f189d..07dcab1d 100644 --- a/pages/options.html +++ b/pages/options.html @@ -217,7 +217,7 @@ not too much smaller than its help text (above). --> -- cgit v1.2.3 From 951f8839d02a8d85747d86ccd09efc0ee3a72501 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 23 Aug 2014 15:26:03 +0100 Subject: Allow passing of keys to the underlying page (more minor code review). --- background_scripts/main.coffee | 34 +++++++++++++++++----------------- content_scripts/vimium_frontend.coffee | 3 ++- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 6d986187..46a6a695 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -76,20 +76,18 @@ isEnabledForUrl = (request) -> # Excluded URLs are stored as a series of URL expressions and optional passKeys, separated by newlines. # Lines for which the first non-blank character is "#" are comments. excludedLines = (line.trim() for line in Settings.get("excludedUrls").split("\n")) - excludedUrls = (line for line in excludedLines when line and line.indexOf("#") != 0) - for spec in excludedUrls - parse = spec.split(/\s+/) - if parse.length - url = parse[0] - # The user can add "*" to the URL which means ".*" - regexp = new RegExp("^" + url.replace(/\*/g, ".*") + "$") - if request.url.match(regexp) - passKeys = parse[1..].join("") - if passKeys - # Enabled, but only for these keys. - return { isEnabledForUrl: true, passKeys: passKeys } - # Disabled. - return { isEnabledForUrl: false } + excludedSpecs = (line.split(/\s+/) for line in excludedLines when line and line.indexOf("#") != 0) + for spec in excludedSpecs + url = spec[0] + # The user can add "*" to the URL which means ".*" + regexp = new RegExp("^" + url.replace(/\*/g, ".*") + "$") + if request.url.match(regexp) + passKeys = spec[1..].join("") + if passKeys + # Enabled, but not for these keys. + return { isEnabledForUrl: true, passKeys: passKeys } + # Wholly disabled. + return { isEnabledForUrl: false } # Enabled (the default). { isEnabledForUrl: true } @@ -513,9 +511,11 @@ handleKeyDown = (request, port) -> keyQueue = checkKeyQueue(keyQueue + key, port.sender.tab.id, request.frameId) console.log("new KeyQueue: " + keyQueue) # Tell the content script whether there are keys in the queue. If there are, then subsequent keys in passKeys will be - # handled by vimium. - # FIXME: There is a race condition here. The behaviour depends upon whether this message gets back - # to the content script before the next keystroke or not. + # handled by vimium. So, if 't' is a passKey, then 'gt' and '99t' will nevertheless be handled by Vimium. + # FIXME: There is a race condition here. The behaviour in the content script depends upon whether this message gets + # back there before or after the next keystroke. + # That being said, I suspect there are other similar race conditions here, for example in checkKeyQueue(). + # Steve (23 Aug, 14). chrome.tabs.sendMessage(port.sender.tab.id, name: "currentKeyQueue", keyQueue: keyQueue) diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 70cc5cbb..feebda07 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -117,6 +117,7 @@ initializePreDomReady = -> getScrollPosition: -> scrollX: window.scrollX, scrollY: window.scrollY setScrollPosition: (request) -> setScrollPosition request.scrollX, request.scrollY executePageCommand: executePageCommand + # FIXME: currentKeyQueue(), below, does not respect the frameId. Should it? currentKeyQueue: (request) -> keyQueue = request.keyQueue getActiveState: -> { enabled: isEnabledForUrl } disableVimium: disableVimium @@ -329,7 +330,7 @@ extend window, # passKey, then 'gt' and '99t' will neverthless be handled by vimium. # TODO: This currently only works for unmodified keys (so not for '', or the like). It's not clear if # this is a problem or not. I don't recall coming across a web page with modifier key bindings. Such -# bindings might be too likely to conflict with browser bindings. +# bindings might be too likely to conflict with browsers' native bindings. isPassKey = ( keyChar ) -> !keyQueue and passKeys and 0 <= passKeys.indexOf keyChar -- cgit v1.2.3 From eeda751f54fd67bb895541264fcf2b5eb91b2556 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 23 Aug 2014 17:28:55 +0100 Subject: Allow passing of keys to the underlying page (populate page popup with existing rule). --- background_scripts/main.coffee | 36 +++++++++++++++++++++++++++--------- pages/popup.coffee | 17 +++++++++++++---- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 46a6a695..f3df2943 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -72,7 +72,7 @@ getCurrentTabUrl = (request, sender) -> sender.tab.url # Checks the user's preferences in local storage to determine if Vimium is enabled for the given URL, and # whether any keys should be passed through to the underlying page. # -isEnabledForUrl = (request) -> +root.isEnabledForUrl = isEnabledForUrl = (request) -> # Excluded URLs are stored as a series of URL expressions and optional passKeys, separated by newlines. # Lines for which the first non-blank character is "#" are comments. excludedLines = (line.trim() for line in Settings.get("excludedUrls").split("\n")) @@ -85,21 +85,39 @@ isEnabledForUrl = (request) -> passKeys = spec[1..].join("") if passKeys # Enabled, but not for these keys. - return { isEnabledForUrl: true, passKeys: passKeys } + return { isEnabledForUrl: true, passKeys: passKeys, matchingUrl: url } # Wholly disabled. - return { isEnabledForUrl: false } + return { isEnabledForUrl: false, passKeys: "", matchingUrl: url } # Enabled (the default). - { isEnabledForUrl: true } + { isEnabledForUrl: true, passKeys: undefined, matchingUrl: undefined } # Called by the popup UI. Strips leading/trailing whitespace and ignores empty strings. +# Also eliminates duplicates based on same URL/regexp. Of duplicates, the last is kept. +# Excluded URL specifications are kept in the same order as they were originally, with the new exclusion at +# the end. Passkeys, if any, are simply compied through within spec. root.addExcludedUrl = (url) -> return unless url = url.trim() - excludedUrls = Settings.get("excludedUrls") - return if excludedUrls.indexOf(url) >= 0 - - excludedUrls += "\n" + url - Settings.set("excludedUrls", excludedUrls) + excludedUrls = Settings.get("excludedUrls").split("\n") + excludedUrls.push(url) + + # Eliminate duplicates: reverse list, filter out duplicates based only on the URL/regexp, then reverse again + # and install the new excludedUrls. parse[0] is the URL/regexp. + seen = {} + newExcludedUrls = [] + for spec in excludedUrls.reverse() + spec = spec.trim() + parse = spec.split(/\s+/) + if parse.length == 0 or spec.indexOf("#") == 0 + # Keep comments and empty lines. + newExcludedUrls.push(spec) + else if !seen[parse[0]] + seen[parse[0]] = true + newExcludedUrls.push(spec) + else + console.log "addExcludedUrl: removing " + spec + + Settings.set("excludedUrls", newExcludedUrls.reverse().join("\n")) chrome.tabs.query({ windowId: chrome.windows.WINDOW_ID_CURRENT, active: true }, (tabs) -> updateActiveState(tabs[0].id)) diff --git a/pages/popup.coffee b/pages/popup.coffee index 6d7afafc..5332a596 100644 --- a/pages/popup.coffee +++ b/pages/popup.coffee @@ -1,10 +1,19 @@ onLoad = -> document.getElementById("optionsLink").setAttribute "href", chrome.runtime.getURL("pages/options.html") chrome.tabs.getSelected null, (tab) -> - # The common use case is to disable Vimium at the domain level. - # This regexp will match "http://www.example.com/" from "http://www.example.com/path/to/page.html". - domain = tab.url.match(/[^\/]*\/\/[^\/]*\//) or tab.url - document.getElementById("popupInput").value = domain + "*" + # Check if we have an existing exclusing rule for this page. + isEnabled = chrome.extension.getBackgroundPage().isEnabledForUrl(url: tab.url) + if isEnabled.matchingUrl + # There is an existing rule for this page. + pattern = isEnabled.matchingUrl + pattern += " " + isEnabled.passKeys if isEnabled.passKeys + document.getElementById("popupInput").value = pattern + else + # No existing exclusion rule. + # The common use case is to disable Vimium at the domain level. + # This regexp will match "http://www.example.com/" from "http://www.example.com/path/to/page.html". + domain = tab.url.match(/[^\/]*\/\/[^\/]*\//) or tab.url + document.getElementById("popupInput").value = domain + "*" onExcludeUrl = (e) -> url = document.getElementById("popupInput").value -- cgit v1.2.3 From 4dd09f77141b01d2ce99866cb52efc985f4f18a4 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 23 Aug 2014 18:32:41 +0100 Subject: Allow passing of keys to the underlying page (fix/maintain order of exclusion list). --- background_scripts/main.coffee | 41 +++++++++++++++++----------------- content_scripts/vimium_frontend.coffee | 3 +-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index f3df2943..816e4e45 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -91,33 +91,35 @@ root.isEnabledForUrl = isEnabledForUrl = (request) -> # Enabled (the default). { isEnabledForUrl: true, passKeys: undefined, matchingUrl: undefined } -# Called by the popup UI. Strips leading/trailing whitespace and ignores empty strings. -# Also eliminates duplicates based on same URL/regexp. Of duplicates, the last is kept. -# Excluded URL specifications are kept in the same order as they were originally, with the new exclusion at -# the end. Passkeys, if any, are simply compied through within spec. +# Called by the popup UI. Strips leading/trailing whitespace and ignores new empty strings. root.addExcludedUrl = (url) -> return unless url = url.trim() + parse = url.split(/\s+/) + url = parse[0] + passKeys = parse[1..].join(" ") + newSpec = (if passKeys then url + " " + passKeys else url) + excludedUrls = Settings.get("excludedUrls").split("\n") - excludedUrls.push(url) + excludedUrls.push(newSpec) - # Eliminate duplicates: reverse list, filter out duplicates based only on the URL/regexp, then reverse again - # and install the new excludedUrls. parse[0] is the URL/regexp. - seen = {} + # Update excludedUrls. + # Try to keep the list as unchanged as possible: same order, same comments, same blank lines. + seenNew = false newExcludedUrls = [] - for spec in excludedUrls.reverse() + for spec in excludedUrls spec = spec.trim() parse = spec.split(/\s+/) - if parse.length == 0 or spec.indexOf("#") == 0 - # Keep comments and empty lines. - newExcludedUrls.push(spec) - else if !seen[parse[0]] - seen[parse[0]] = true - newExcludedUrls.push(spec) - else - console.log "addExcludedUrl: removing " + spec + # Keep just one copy of the new exclusion rule. + if parse.length and parse[0] == url + if !seenNew + newExcludedUrls.push(newSpec) + seenNew = true + continue + # And just keep everything else. + newExcludedUrls.push(spec) - Settings.set("excludedUrls", newExcludedUrls.reverse().join("\n")) + Settings.set("excludedUrls", newExcludedUrls.join("\n")) chrome.tabs.query({ windowId: chrome.windows.WINDOW_ID_CURRENT, active: true }, (tabs) -> updateActiveState(tabs[0].id)) @@ -528,8 +530,7 @@ handleKeyDown = (request, port) -> console.log("checking keyQueue: [", keyQueue + key, "]") keyQueue = checkKeyQueue(keyQueue + key, port.sender.tab.id, request.frameId) console.log("new KeyQueue: " + keyQueue) - # Tell the content script whether there are keys in the queue. If there are, then subsequent keys in passKeys will be - # handled by vimium. So, if 't' is a passKey, then 'gt' and '99t' will nevertheless be handled by Vimium. + # Tell the content script whether there are keys in the queue. # FIXME: There is a race condition here. The behaviour in the content script depends upon whether this message gets # back there before or after the next keystroke. # That being said, I suspect there are other similar race conditions here, for example in checkKeyQueue(). diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index feebda07..34473e96 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -117,10 +117,9 @@ initializePreDomReady = -> getScrollPosition: -> scrollX: window.scrollX, scrollY: window.scrollY setScrollPosition: (request) -> setScrollPosition request.scrollX, request.scrollY executePageCommand: executePageCommand - # FIXME: currentKeyQueue(), below, does not respect the frameId. Should it? - currentKeyQueue: (request) -> keyQueue = request.keyQueue getActiveState: -> { enabled: isEnabledForUrl } disableVimium: disableVimium + currentKeyQueue: (request) -> keyQueue = request.keyQueue chrome.runtime.onMessage.addListener (request, sender, sendResponse) -> # in the options page, we will receive requests from both content and background scripts. ignore those -- cgit v1.2.3 From 2f786a62ad36631c4dc1a75595b41d61281727dc Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 23 Aug 2014 19:02:23 +0100 Subject: Allow passing of keys to the underlying page (slight reformatting of page popup). --- pages/popup.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/popup.html b/pages/popup.html index 8ccf7126..89f1f02a 100644 --- a/pages/popup.html +++ b/pages/popup.html @@ -6,14 +6,14 @@ padding: 0px; } - #vimiumPopup { width: 300px; } + #vimiumPopup { width: 500px; } #excludeControls { padding: 10px; } #popupInput { - width: 160px; + width: 330px; } #excludeConfirm { @@ -54,7 +54,7 @@
- Saved exclude pattern. + Saved.
-- cgit v1.2.3 From 7a0943b549986a060282e8047f14a58fbdb5acc3 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 23 Aug 2014 20:52:56 +0100 Subject: Allow passing of keys to the underlying page (comments can start with " too). --- background_scripts/main.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 816e4e45..b99d5307 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -74,9 +74,9 @@ getCurrentTabUrl = (request, sender) -> sender.tab.url # root.isEnabledForUrl = isEnabledForUrl = (request) -> # Excluded URLs are stored as a series of URL expressions and optional passKeys, separated by newlines. - # Lines for which the first non-blank character is "#" are comments. + # Lines for which the first non-blank character is "#" or '"' are comments. excludedLines = (line.trim() for line in Settings.get("excludedUrls").split("\n")) - excludedSpecs = (line.split(/\s+/) for line in excludedLines when line and line.indexOf("#") != 0) + excludedSpecs = (line.split(/\s+/) for line in excludedLines when line and line.indexOf("#") != 0 and line.indexOf('"') != 0) for spec in excludedSpecs url = spec[0] # The user can add "*" to the URL which means ".*" -- cgit v1.2.3 From 12b889d9cbfaa2a47834fbf69c4f7788cbc8c361 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 24 Aug 2014 08:20:15 +0100 Subject: New icons for partially enabled, for passkeys. --- background_scripts/main.coffee | 12 +++++++++--- icons/browser_action_partial.png | Bin 0 -> 35698 bytes icons/icon48partial.png | Bin 0 -> 3815 bytes 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 icons/browser_action_partial.png create mode 100644 icons/icon48partial.png diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index b99d5307..68d1236b 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -91,7 +91,8 @@ root.isEnabledForUrl = isEnabledForUrl = (request) -> # Enabled (the default). { isEnabledForUrl: true, passKeys: undefined, matchingUrl: undefined } -# Called by the popup UI. Strips leading/trailing whitespace and ignores new empty strings. +# Called by the popup UI. Strips leading/trailing whitespace and ignores new empty strings. If an existing +# exclusion rule has been changed, then the existing rule is updated. Otherwise, the new rule is added. root.addExcludedUrl = (url) -> return unless url = url.trim() @@ -387,15 +388,20 @@ updateOpenTabs = (tab) -> updateActiveState = (tabId) -> enabledIcon = "icons/browser_action_enabled.png" disabledIcon = "icons/browser_action_disabled.png" + partialIcon = "icons/browser_action_partial.png" chrome.tabs.get(tabId, (tab) -> # Default to disabled state in case we can't connect to Vimium, primarily for the "New Tab" page. chrome.browserAction.setIcon({ path: disabledIcon }) chrome.tabs.sendMessage(tabId, { name: "getActiveState" }, (response) -> isCurrentlyEnabled = (response? && response.enabled) - shouldBeEnabled = isEnabledForUrl({url: tab.url}).isEnabledForUrl + enabledConfig = isEnabledForUrl({url: tab.url}) + shouldBeEnabled = enabledConfig.isEnabledForUrl + shouldHavePassKeys = enabledConfig.passKeys if (isCurrentlyEnabled) - if (shouldBeEnabled) + if (shouldBeEnabled and shouldHavePassKeys) + chrome.browserAction.setIcon({ path: partialIcon }) + else if (shouldBeEnabled) chrome.browserAction.setIcon({ path: enabledIcon }) else chrome.browserAction.setIcon({ path: disabledIcon }) diff --git a/icons/browser_action_partial.png b/icons/browser_action_partial.png new file mode 100644 index 00000000..5e0457af Binary files /dev/null and b/icons/browser_action_partial.png differ diff --git a/icons/icon48partial.png b/icons/icon48partial.png new file mode 100644 index 00000000..088099b1 Binary files /dev/null and b/icons/icon48partial.png differ -- cgit v1.2.3 From d230f3037e259f0bf8c3710f20a7403731caecc0 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 24 Aug 2014 10:04:03 +0100 Subject: Better state management for passkeys. --- background_scripts/main.coffee | 45 ++++++++++++++------------- content_scripts/vimium_frontend.coffee | 56 ++++++++++++++++++---------------- pages/popup.coffee | 4 ++- 3 files changed, 55 insertions(+), 50 deletions(-) diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 68d1236b..5412ead5 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -377,37 +377,38 @@ updateOpenTabs = (tab) -> # Frames are recreated on refresh delete framesForTab[tab.id] -# Updates the browserAction icon to indicated whether Vimium is enabled or disabled on the current page. -# Also disables Vimium if it is currently enabled but should be disabled according to the url blacklist. +setBrowserActionIcon = (tabId,path) -> + chrome.browserAction.setIcon({ tabId: tabId, path: path }) + +# Updates the browserAction icon to indicate whether Vimium is enabled or disabled on the current page. +# Also propagates new enabled/disabled/passkeys state to active window, if necessary. # This lets you disable Vimium on a page without needing to reload. -# -# Three situations are considered: -# 1. Active tab is disabled -> disable icon -# 2. Active tab is enabled and should be enabled -> enable icon -# 3. Active tab is enabled but should be disabled -> disable icon and disable vimium updateActiveState = (tabId) -> enabledIcon = "icons/browser_action_enabled.png" disabledIcon = "icons/browser_action_disabled.png" partialIcon = "icons/browser_action_partial.png" - chrome.tabs.get(tabId, (tab) -> - # Default to disabled state in case we can't connect to Vimium, primarily for the "New Tab" page. - chrome.browserAction.setIcon({ path: disabledIcon }) - chrome.tabs.sendMessage(tabId, { name: "getActiveState" }, (response) -> - isCurrentlyEnabled = (response? && response.enabled) - enabledConfig = isEnabledForUrl({url: tab.url}) - shouldBeEnabled = enabledConfig.isEnabledForUrl - shouldHavePassKeys = enabledConfig.passKeys - - if (isCurrentlyEnabled) + chrome.tabs.get tabId, (tab) -> + chrome.tabs.sendMessage tabId, { name: "getActiveState" }, (response) -> + if response + isCurrentlyEnabled = response.enabled + currentPasskeys = response.passKeys + # TODO: + # isEnabledForUrl is quite expensive to run each time we change tab. Perhaps memoize it? + shouldHaveConfig = isEnabledForUrl({url: tab.url}) + shouldBeEnabled = shouldHaveConfig.isEnabledForUrl + shouldHavePassKeys = shouldHaveConfig.passKeys if (shouldBeEnabled and shouldHavePassKeys) - chrome.browserAction.setIcon({ path: partialIcon }) + setBrowserActionIcon(tabId,partialIcon) else if (shouldBeEnabled) - chrome.browserAction.setIcon({ path: enabledIcon }) + setBrowserActionIcon(tabId,enabledIcon) else - chrome.browserAction.setIcon({ path: disabledIcon }) - chrome.tabs.sendMessage(tabId, { name: "disableVimium" }) + setBrowserActionIcon(tabId,disabledIcon) + # Propagate the new state only if it has changed. + if (isCurrentlyEnabled != shouldBeEnabled || currentPasskeys != shouldHavePassKeys) + chrome.tabs.sendMessage(tabId, { name: "setState", enabled: shouldBeEnabled, passKeys: shouldHavePassKeys }) else - chrome.browserAction.setIcon({ path: disabledIcon }))) + # We didn't get a response from the front end, so Vimium isn't running. + setBrowserActionIcon(tabId,disabledIcon) handleUpdateScrollPosition = (request, sender) -> updateScrollPosition(sender.tab, request.scrollX, request.scrollY) diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 34473e96..d34ea761 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -13,7 +13,8 @@ findModeQueryHasResults = false findModeAnchorNode = null isShowingHelpDialog = false keyPort = null -# Users can disable Vimium on URL patterns via the settings page. +# Users can disable Vimium on URL patterns via the settings page. The following two variables +# (isEnabledForUrl and passKeys) control Vimium's enabled/disabled behaviour. isEnabledForUrl = true passKeys = null keyQueue = null @@ -117,43 +118,45 @@ initializePreDomReady = -> getScrollPosition: -> scrollX: window.scrollX, scrollY: window.scrollY setScrollPosition: (request) -> setScrollPosition request.scrollX, request.scrollY executePageCommand: executePageCommand - getActiveState: -> { enabled: isEnabledForUrl } - disableVimium: disableVimium + getActiveState: -> { enabled: isEnabledForUrl, passKeys: passKeys } + setState: setState currentKeyQueue: (request) -> keyQueue = request.keyQueue chrome.runtime.onMessage.addListener (request, sender, sendResponse) -> # in the options page, we will receive requests from both content and background scripts. ignore those # from the former. return if sender.tab and not sender.tab.url.startsWith 'chrome-extension://' - return unless isEnabledForUrl or request.name == 'getActiveState' + return unless isEnabledForUrl or request.name == 'getActiveState' or request.name == 'setState' sendResponse requestHandlers[request.name](request, sender) # Ensure the sendResponse callback is freed. false -# -# This is called once the background page has told us that Vimium should be enabled for the current URL. -# -initializeWhenEnabled = -> - document.addEventListener("keydown", onKeydown, true) - document.addEventListener("keypress", onKeypress, true) - document.addEventListener("keyup", onKeyup, true) - document.addEventListener("focus", onFocusCapturePhase, true) - document.addEventListener("blur", onBlurCapturePhase, true) - document.addEventListener("DOMActivate", onDOMActivate, true) - enterInsertModeIfElementIsFocused() +# Wrapper to install event listeners. Syntactic sugar. +installListener = (event, callback) -> document.addEventListener(event, callback, true) # -# Used to disable Vimium without needing to reload the page. -# This is called if the current page's url is blacklisted using the popup UI. +# This is called once the background page has told us that Vimium should be enabled for the current URL. +# We enable/disable Vimium by toggling isEnabledForUrl. The alternative, installing or uninstalling +# listeners, is error prone. It's more difficult to keep track of the state. # -disableVimium = -> - document.removeEventListener("keydown", onKeydown, true) - document.removeEventListener("keypress", onKeypress, true) - document.removeEventListener("keyup", onKeyup, true) - document.removeEventListener("focus", onFocusCapturePhase, true) - document.removeEventListener("blur", onBlurCapturePhase, true) - document.removeEventListener("DOMActivate", onDOMActivate, true) - isEnabledForUrl = false +installedListeners = false +initializeWhenEnabled = (newPassKeys=undefined) -> + isEnabledForUrl = true + passKeys = passKeys if typeof(newPassKeys) != 'undefined' + if (!installedListeners) + installListener "keydown", (event) -> if isEnabledForUrl then onKeydown(event) else true + installListener "keypress", (event) -> if isEnabledForUrl then onKeypress(event) else true + installListener "keyup", (event) -> if isEnabledForUrl then onKeyup(event) else true + installListener "focus", (event) -> if isEnabledForUrl then onFocusCapturePhase(event) else true + installListener "blur", (event) -> if isEnabledForUrl then onBlurCapturePhase(event) + installListener "DOMActivate", (event) -> if isEnabledForUrl then onDOMActivate(event) + enterInsertModeIfElementIsFocused() + installedListeners = true + +setState = (request) -> + isEnabledForUrl = request.enabled + passKeys = request.passKeys + initializeWhenEnabled(passKeys) if isEnabledForUrl and !installedListeners # # The backend needs to know which frame has focus. @@ -484,8 +487,7 @@ checkIfEnabledForUrl = -> chrome.runtime.sendMessage { handler: "isEnabledForUrl", url: url }, (response) -> isEnabledForUrl = response.isEnabledForUrl if (isEnabledForUrl) - initializeWhenEnabled() - passKeys = response.passKeys + initializeWhenEnabled(response.passKeys) else if (HUD.isReady()) # Quickly hide any HUD we might already be showing, e.g. if we entered insert mode on page load. HUD.hide() diff --git a/pages/popup.coffee b/pages/popup.coffee index 5332a596..41fc17a9 100644 --- a/pages/popup.coffee +++ b/pages/popup.coffee @@ -4,9 +4,11 @@ onLoad = -> # Check if we have an existing exclusing rule for this page. isEnabled = chrome.extension.getBackgroundPage().isEnabledForUrl(url: tab.url) if isEnabled.matchingUrl + console.log isEnabled # There is an existing rule for this page. pattern = isEnabled.matchingUrl - pattern += " " + isEnabled.passKeys if isEnabled.passKeys + if isEnabled.passKeys + pattern += " " + isEnabled.passKeys document.getElementById("popupInput").value = pattern else # No existing exclusion rule. -- cgit v1.2.3 From b520b5e2dac316e92802d6c5e0347b2d138ce1b8 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 24 Aug 2014 11:32:22 +0100 Subject: Better icon for passkeys. --- icons/browser_action_partial.png | Bin 35698 -> 34384 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/browser_action_partial.png b/icons/browser_action_partial.png index 5e0457af..e713f005 100644 Binary files a/icons/browser_action_partial.png and b/icons/browser_action_partial.png differ -- cgit v1.2.3 From d57748f902d9647c3dcf2e3c4d60411766c1bd25 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 24 Aug 2014 11:49:01 +0100 Subject: Do not call handlers which do not exist. --- content_scripts/vimium_frontend.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index d34ea761..3543f69c 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -127,7 +127,7 @@ initializePreDomReady = -> # from the former. return if sender.tab and not sender.tab.url.startsWith 'chrome-extension://' return unless isEnabledForUrl or request.name == 'getActiveState' or request.name == 'setState' - sendResponse requestHandlers[request.name](request, sender) + sendResponse(if requestHandlers[request.name] then requestHandlers[request.name](request, sender) else undefined) # Ensure the sendResponse callback is freed. false -- cgit v1.2.3 From 700d35a1c4ef85f97f3176aa87807f9a586bf441 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 24 Aug 2014 12:03:41 +0100 Subject: Do not call handlers which do not exist (fixed). --- content_scripts/vimium_frontend.coffee | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 3543f69c..5731838c 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -127,8 +127,9 @@ initializePreDomReady = -> # from the former. return if sender.tab and not sender.tab.url.startsWith 'chrome-extension://' return unless isEnabledForUrl or request.name == 'getActiveState' or request.name == 'setState' - sendResponse(if requestHandlers[request.name] then requestHandlers[request.name](request, sender) else undefined) - # Ensure the sendResponse callback is freed. + # registerFrame requests are delivered here, but there's no handler. + return if request.handler == "registerFrame" + requestHandlers[request.name](request, sender) false # Wrapper to install event listeners. Syntactic sugar. -- cgit v1.2.3 From 5cdeb4d4ac2bc3171b47043f0275d0f8331486a6 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 24 Aug 2014 12:10:28 +0100 Subject: Do not call handlers which do not exist (fixed, again). --- content_scripts/vimium_frontend.coffee | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 5731838c..2bdd85a8 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -123,12 +123,13 @@ initializePreDomReady = -> currentKeyQueue: (request) -> keyQueue = request.keyQueue chrome.runtime.onMessage.addListener (request, sender, sendResponse) -> - # in the options page, we will receive requests from both content and background scripts. ignore those + # In the options page, we will receive requests from both content and background scripts. ignore those # from the former. + console.log request.name, request return if sender.tab and not sender.tab.url.startsWith 'chrome-extension://' return unless isEnabledForUrl or request.name == 'getActiveState' or request.name == 'setState' - # registerFrame requests are delivered here, but there's no handler. - return if request.handler == "registerFrame" + # These requests are delivered to the options page, but there are no handlers there. + return if request.handler == "registerFrame" or request.handler == "frameFocused" requestHandlers[request.name](request, sender) false -- cgit v1.2.3 From fb25935bec26066a457468ae5402f71de4694b37 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 24 Aug 2014 15:24:54 +0100 Subject: PassKey tests (1) --- tests/unit_tests/exclusion_test.coffee | 71 +++++++++++++++++++++++++++++++ tests/unit_tests/test_chrome_stubs.coffee | 43 ++++++++++++++++--- 2 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 tests/unit_tests/exclusion_test.coffee diff --git a/tests/unit_tests/exclusion_test.coffee b/tests/unit_tests/exclusion_test.coffee new file mode 100644 index 00000000..33dbccd3 --- /dev/null +++ b/tests/unit_tests/exclusion_test.coffee @@ -0,0 +1,71 @@ + +require "./test_helper.js" +require "./test_chrome_stubs.js" + +# FIXME: +# Would like to do: +# extend(global, require "../../background_scripts/marks.js") +# But it looks like marks.coffee has never been included in a test before! +# Temporary fix... +root.Marks = + create: () -> true + goto: + bind: () -> true + +extend(global, require "../../lib/utils.js") +Utils.getCurrentVersion = -> '1.44' +extend(global,require "../../background_scripts/sync.js") +extend(global,require "../../background_scripts/settings.js") +Sync.init() +extend(global, require "../../background_scripts/commands.js") +extend(global, require "../../background_scripts/main.js") + +# These tests cover only the most basic aspects of excluded URLs and passKeys. +# +context "Excluded URLs and pass keys", + setup -> + Settings.set 'excludedUrls', 'http://mail.google.com/*\nhttp://www.facebook.com/* jk' + + should "be disabled for excluded sites", -> + rule = isEnabledForUrl({ url: 'http://mail.google.com/u/0/inbox' }) + assert.isFalse rule.isEnableForUrl + assert.isTrue rule.matchingUrl + + should "be enabled, but with pass keys", -> + rule = isEnabledForUrl({ url: 'http://www.facebook.com/pages' }) + assert.isTrue rule.isEnabledForUrl + assert.equal rule.passKeys, 'jk' + assert.isTrue rule.matchingUrl + + should "be enabled", -> + rule = isEnabledForUrl({ url: 'http://www.twitter.com/pages' }) + assert.isTrue rule.isEnabledForUrl + assert.isFalse rule.passKeys + + should "add a new excluded URL", -> + rule = isEnabledForUrl({ url: 'http://www.example.com/page' }) + assert.isTrue rule.isEnabledForUrl + addExcludedUrl("http://www.example.com*") + rule = isEnabledForUrl({ url: 'http://www.example.com/page' }) + assert.isFalse rule.isEnabledForUrl + assert.isFalse rule.passKeys + assert.isTrue rule.matchingUrl + + should "add a new excluded URL with passkeys", -> + rule = isEnabledForUrl({ url: 'http://www.example.com/page' }) + assert.isTrue rule.isEnabledForUrl + addExcludedUrl("http://www.example.com/* jk") + rule = isEnabledForUrl({ url: 'http://www.example.com/page' }) + assert.isTrue rule.isEnabledForUrl + assert.equal rule.passKeys, 'jk' + assert.isTrue rule.matchingUrl + + should "update an existing excluded URL with passkeys", -> + rule = isEnabledForUrl({ url: 'http://www.facebook.com/page' }) + assert.isTrue rule.isEnabledForUrl + addExcludedUrl("http://www.facebook.com/* jknp") + rule = isEnabledForUrl({ url: 'http://www.facebook.com/page' }) + assert.isTrue rule.isEnabledForUrl + assert.equal rule.passKeys, 'jknp' + assert.isTrue rule.matchingUrl + diff --git a/tests/unit_tests/test_chrome_stubs.coffee b/tests/unit_tests/test_chrome_stubs.coffee index e9c48f31..9622f85f 100644 --- a/tests/unit_tests/test_chrome_stubs.coffee +++ b/tests/unit_tests/test_chrome_stubs.coffee @@ -2,26 +2,55 @@ # # This is a stub for chrome.strorage.sync for testing. # It does what chrome.storage.sync should do (roughly), but does so synchronously. +# It also provides stubs for a number of other chrome APIs. # +global.window = {} +global.localStorage = {} + global.chrome = - runtime: {} + runtime: + getManifest: () -> + version: "1.2.3" + onConnect: + addListener: () -> true + onMessage: + addListener: () -> true - storage: + tabs: + onSelectionChanged: + addListener: () -> true + onUpdated: + addListener: () -> true + onAttached: + addListener: () -> true + onMoved: + addListener: () -> true + onRemoved: + addListener: () -> true + onActiveChanged: + addListener: () -> true + query: () -> true + windows: + onRemoved: + addListener: () -> true + getAll: () -> true + + storage: # chrome.storage.onChanged onChanged: addListener: (func) -> @func = func # Fake a callback from chrome.storage.sync. call: (key, value) -> - chrome.runtime = { lastError: undefined } + chrome.runtime.lastError = undefined key_value = {} key_value[key] = { newValue: value } @func(key_value,'synced storage stub') if @func callEmpty: (key) -> - chrome.runtime = { lastError: undefined } + chrome.runtime.lastError = undefined if @func items = {} items[key] = {} @@ -32,7 +61,7 @@ global.chrome = store: {} set: (items, callback) -> - chrome.runtime = { lastError: undefined } + chrome.runtime.lastError = undefined for own key, value of items @store[key] = value callback() if callback @@ -41,7 +70,7 @@ global.chrome = global.chrome.storage.onChanged.call(key,value) get: (keys, callback) -> - chrome.runtime = { lastError: undefined } + chrome.runtime.lastError = undefined if keys == null keys = [] for own key, value of @store @@ -53,7 +82,7 @@ global.chrome = callback items if callback remove: (key, callback) -> - chrome. runtime = { lastError: undefined } + chrome.runtime.lastError = undefined if key of @store delete @store[key] callback() if callback -- cgit v1.2.3 From b599492ea27c8c6bab38e87ef343968f5fcf58e7 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 24 Aug 2014 17:05:05 +0100 Subject: Fix passKeys bug introduced in commit 700d35a --- background_scripts/main.coffee | 1 + content_scripts/vimium_frontend.coffee | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 5412ead5..dda1beae 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -389,6 +389,7 @@ updateActiveState = (tabId) -> partialIcon = "icons/browser_action_partial.png" chrome.tabs.get tabId, (tab) -> chrome.tabs.sendMessage tabId, { name: "getActiveState" }, (response) -> + console.log response if response isCurrentlyEnabled = response.enabled currentPasskeys = response.passKeys diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 2bdd85a8..137b9d1a 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -125,12 +125,11 @@ initializePreDomReady = -> chrome.runtime.onMessage.addListener (request, sender, sendResponse) -> # In the options page, we will receive requests from both content and background scripts. ignore those # from the former. - console.log request.name, request return if sender.tab and not sender.tab.url.startsWith 'chrome-extension://' return unless isEnabledForUrl or request.name == 'getActiveState' or request.name == 'setState' # These requests are delivered to the options page, but there are no handlers there. return if request.handler == "registerFrame" or request.handler == "frameFocused" - requestHandlers[request.name](request, sender) + sendResponse requestHandlers[request.name](request, sender) false # Wrapper to install event listeners. Syntactic sugar. -- cgit v1.2.3