aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Blott2014-09-01 12:07:24 +0100
committerStephen Blott2014-09-02 08:43:36 +0100
commit41bdac83d2fd450569013dd5cfdb78239143ba24 (patch)
tree58c06e564cb5641102fbc6583071e84e094818f7
parent1685640ccabe265c9f182a0175d8ce823db35b4b (diff)
downloadvimium-41bdac83d2fd450569013dd5cfdb78239143ba24.tar.bz2
Structured passkeys, internally and on the options and popup pages.
-rw-r--r--background_scripts/exclusions.coffee77
-rw-r--r--background_scripts/main.coffee87
-rw-r--r--background_scripts/settings.coffee17
-rw-r--r--content_scripts/vimium_frontend.coffee15
-rw-r--r--lib/exclusion_rule.coffee46
-rw-r--r--manifest.json3
-rw-r--r--pages/options.coffee23
-rw-r--r--pages/options.html68
-rw-r--r--pages/popup.coffee91
-rw-r--r--pages/popup.html22
-rw-r--r--tests/unit_tests/exclusion_test.coffee31
11 files changed, 331 insertions, 149 deletions
diff --git a/background_scripts/exclusions.coffee b/background_scripts/exclusions.coffee
new file mode 100644
index 00000000..326989bf
--- /dev/null
+++ b/background_scripts/exclusions.coffee
@@ -0,0 +1,77 @@
+root = exports ? window
+
+RegexpCache =
+ cache: {}
+ get: (pattern) ->
+ if regexp = @cache[pattern]
+ regexp
+ else
+ @cache[pattern] = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$")
+
+# The Exclusions class manages the exclusion rule setting.
+# An exclusion is an object with two attributes: pattern and passKeys.
+# The exclusions are an array of such objects (because the order matters).
+
+root.Exclusions = Exclusions =
+
+ rules: Settings.get("exclusionRules")
+
+ # Return the first exclusion rule matching the URL, or null.
+ getRule: (url) ->
+ for rule in @rules
+ return rule if url.match(RegexpCache.get(rule.pattern))
+ return null
+
+ setRules: (rules) ->
+ @rules = rules.filter (rule) -> rule and rule.pattern
+ Settings.set("exclusionRules",@rules)
+
+ postUpdateHook: (rules) ->
+ @rules = rules
+
+ # Update an existing rule or add a new rule.
+ updateOrAdd: (newRule) ->
+ seen = false
+ @rules.push(newRule)
+ @setRules(@rules.map (rule) -> if rule.pattern == newRule.pattern then (if seen then null else seen = newRule) else rule)
+
+ remove: (pattern) ->
+ @setRules(@rules.filter((rule) -> rule.pattern != pattern))
+
+ # DOM handling for the options page; populate the exclusionRules option.
+ populateOption: (exclusionRulesElement,enableSaveButton) ->
+ populate = =>
+ while exclusionRulesElement.firstChild
+ exclusionRulesElement.removeChild(exclusionRulesElement.firstChild)
+ for rule in @rules
+ exclusionRulesElement.appendChild(ExclusionRule.buildRuleElement(rule,enableSaveButton))
+ exclusionRulesElement.appendChild(ExclusionRule.buildRuleElement({pattern: "", passKeys: ""},enableSaveButton))
+ populate()
+ return {
+ saveOption: =>
+ @setRules(ExclusionRule.extractRule(element) for element in exclusionRulesElement.getElementsByClassName('exclusionRow'))
+ populate()
+ restoreToDefault: =>
+ Settings.clear("exclusionRules")
+ populate()
+ }
+
+# Development and debug only.
+# Enable this (temporarily) to restore legacy exclusion rules from backup.
+if false and Settings.has("excludedUrlsBackup")
+ Settings.clear("exclusionRules")
+ Settings.set("excludedUrls",Settings.get("excludedUrlsBackup"))
+
+if not Settings.has("exclusionRules") and Settings.has("excludedUrls")
+ # Migration from the legacy exclusion rules (settings: "excludedUrls" -> "exclusionRules").
+
+ parseLegacyRules = (lines) ->
+ for line in lines.trim().split("\n").map((line) -> line.trim())
+ if line.length and line.indexOf("#") != 0 and line.indexOf('"') != 0
+ parse = line.split(/\s+/)
+ { pattern: parse[0], passKeys: parse[1..].join("") }
+
+ Exclusions.setRules(parseLegacyRules(Settings.get("excludedUrls")))
+ # We'll keep a backup of the excludedUrls setting, just in case (and for testing).
+ Settings.set("excludedUrlsBackup",Settings.get("excludedUrls")) if not Settings.has("excludedUrlsBackup")
+ Settings.clear("excludedUrls")
diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee
index dda1beae..4111ac06 100644
--- a/background_scripts/main.coffee
+++ b/background_scripts/main.coffee
@@ -73,57 +73,25 @@ getCurrentTabUrl = (request, sender) -> sender.tab.url
# whether any keys should be passed through to the underlying page.
#
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 "#" 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 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, matchingUrl: url }
- # Wholly disabled.
- return { isEnabledForUrl: false, passKeys: "", matchingUrl: url }
- # Enabled (the default).
- { isEnabledForUrl: true, passKeys: undefined, matchingUrl: undefined }
-
-# 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()
-
- 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(newSpec)
-
- # 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
- spec = spec.trim()
- parse = spec.split(/\s+/)
- # 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.join("\n"))
-
- chrome.tabs.query({ windowId: chrome.windows.WINDOW_ID_CURRENT, active: true },
- (tabs) -> updateActiveState(tabs[0].id))
+ rule = Exclusions.getRule(request.url)
+ return { rule: rule, isEnabledForUrl: true, passKeys: rule.passKeys } if rule and rule.passKeys
+ return { rule: rule, isEnabledForUrl: false, passKeys: "" } if rule
+ return { rule: rule, isEnabledForUrl: true, passKeys: "" }
+
+# Called by the popup UI. If an existing exclusion rule has been changed, then the existing rule is updated.
+# Otherwise, the new rule is added.
+root.addExclusionRule = (pattern,passKeys) ->
+ if pattern = pattern.trim()
+ Exclusions.updateOrAdd({ pattern: pattern, passKeys: passKeys })
+ chrome.tabs.query({ windowId: chrome.windows.WINDOW_ID_CURRENT, active: true },
+ (tabs) -> updateActiveState(tabs[0].id))
+
+# Called by the popup UI. Remove all existing exclusion rules with this pattern.
+root.removeExclusionRule = (pattern) ->
+ if pattern = pattern.trim()
+ Exclusions.remove(pattern)
+ chrome.tabs.query({ windowId: chrome.windows.WINDOW_ID_CURRENT, active: true },
+ (tabs) -> updateActiveState(tabs[0].id))
saveHelpDialogSettings = (request) ->
Settings.set("helpDialog_showAdvancedCommands", request.showAdvancedCommands)
@@ -389,24 +357,21 @@ 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
- # 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)
+ config = isEnabledForUrl({url: tab.url})
+ enabled = config.isEnabledForUrl
+ passKeys = config.passKeys
+ if (enabled and passKeys)
setBrowserActionIcon(tabId,partialIcon)
- else if (shouldBeEnabled)
+ else if (enabled)
setBrowserActionIcon(tabId,enabledIcon)
else
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 })
+ if (isCurrentlyEnabled != enabled || currentPasskeys != passKeys)
+ chrome.tabs.sendMessage(tabId, { name: "setState", enabled: enabled, passKeys: passKeys })
else
# We didn't get a response from the front end, so Vimium isn't running.
setBrowserActionIcon(tabId,disabledIcon)
diff --git a/background_scripts/settings.coffee b/background_scripts/settings.coffee
index 34d6e879..63dd851b 100644
--- a/background_scripts/settings.coffee
+++ b/background_scripts/settings.coffee
@@ -35,6 +35,9 @@ root.Settings = Settings =
searchEngines: (value) ->
root.Settings.parseSearchEngines value
+ exclusionRules: (value) ->
+ root.Exclusions.postUpdateHook value
+
# postUpdateHooks convenience wrapper
performPostUpdateHook: (key, value) ->
@postUpdateHooks[key] value if @postUpdateHooks[key]
@@ -81,14 +84,14 @@ root.Settings = Settings =
div > .vimiumHintMarker > .matchingCharacter {
}
"""
- excludedUrls:
- """
- # Disable Vimium on Gmail:
- http*://mail.google.com/*
+ # Default exclusion rules.
+ exclusionRules:
+ [
+ # Disable Vimium on Google Reader, and use Gmail's own j/k bindings.
+ { pattern: "http*://www.google.com/reader/*", passKeys: "" }
+ { pattern: "http*://mail.google.com/*", passKeys: "jk" }
+ ]
- # 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
# "first/last page", so we put the single bracket first in the pattern string so that it gets searched
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index 137b9d1a..e2ffa7f0 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. The following two variables
# (isEnabledForUrl and passKeys) control Vimium's enabled/disabled behaviour.
+# "passKeys" are keys which would normally be handled by Vimium, but are disabled on this tab, and therefore
+# are passed through to the underlying page.
isEnabledForUrl = true
passKeys = null
keyQueue = null
@@ -130,6 +132,7 @@ initializePreDomReady = ->
# These requests are delivered to the options page, but there are no handlers there.
return if request.handler == "registerFrame" or request.handler == "frameFocused"
sendResponse requestHandlers[request.name](request, sender)
+ # Ensure the sendResponse callback is freed.
false
# Wrapper to install event listeners. Syntactic sugar.
@@ -141,9 +144,9 @@ installListener = (event, callback) -> document.addEventListener(event, callback
# listeners, is error prone. It's more difficult to keep track of the state.
#
installedListeners = false
-initializeWhenEnabled = (newPassKeys=undefined) ->
+initializeWhenEnabled = (newPassKeys) ->
isEnabledForUrl = true
- passKeys = passKeys if typeof(newPassKeys) != 'undefined'
+ passKeys = newPassKeys
if (!installedListeners)
installListener "keydown", (event) -> if isEnabledForUrl then onKeydown(event) else true
installListener "keypress", (event) -> if isEnabledForUrl then onKeypress(event) else true
@@ -328,14 +331,11 @@ extend window,
false
-# Should this keyChar be passed to the underlying page?
+# Decide whether 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 '<c-a>', 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 browsers' native bindings.
isPassKey = ( keyChar ) ->
- !keyQueue and passKeys and 0 <= passKeys.indexOf keyChar
+ return !keyQueue and 0 <= passKeys.indexOf(keyChar)
handledKeydownEvents = []
@@ -365,7 +365,6 @@ 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)
diff --git a/lib/exclusion_rule.coffee b/lib/exclusion_rule.coffee
new file mode 100644
index 00000000..0942e7cf
--- /dev/null
+++ b/lib/exclusion_rule.coffee
@@ -0,0 +1,46 @@
+root = exports ? window
+
+# Operations to build the DOM on the options page for a single exclusion rule.
+
+root.ExclusionRule =
+
+ # Build a DOM table row (a "tr") for this rule.
+ buildRuleElement: (rule,enableSaveButton) ->
+ pattern = @buildInput(enableSaveButton,rule.pattern,"URL pattern","pattern")
+ passKeys = @buildInput(enableSaveButton,rule.passKeys,"Excluded keys","passKeys")
+ row = document.createElement("tr")
+ row.className = "exclusionRow"
+ remove = document.createElement("input")
+ remove.type = "button"
+ remove.value = "\u2716" # A cross.
+ remove.className = "exclusionRemoveButton"
+ remove.addEventListener "click", ->
+ row.parentNode.removeChild(row)
+ enableSaveButton()
+ row.appendChild(pattern)
+ row.appendChild(passKeys)
+ row.appendChild(remove)
+ # NOTE: Since the order of exclusions matters, it would be nice to have "Move Up" and "Move Down" buttons,
+ # too. But this option is pretty cluttered already.
+ row
+
+ # Build DOM (a "td" containing an "input") for a single input element.
+ buildInput: (enableSaveButton,value,placeholder,cls) ->
+ input = document.createElement("input")
+ input.setAttribute("placeholder",placeholder)
+ input.type = "text"
+ input.value = value
+ input.className = cls
+ input.addEventListener "keyup", enableSaveButton, false
+ input.addEventListener "change", enableSaveButton, false
+ container = document.createElement("td")
+ container.appendChild(input)
+ container
+
+ # Build a new exclusion rule from the given element. This is the reverse of the two methods above.
+ extractRule: (element) ->
+ patternElement = element.firstChild
+ passKeysElement = patternElement.nextSibling
+ pattern = patternElement.firstChild.value.trim()
+ passKeys = passKeysElement.firstChild.value.trim()
+ if pattern then { pattern: pattern, passKeys: passKeys } else null
diff --git a/manifest.json b/manifest.json
index 9a4e0167..3f4c877d 100644
--- a/manifest.json
+++ b/manifest.json
@@ -12,7 +12,9 @@
"background_scripts/commands.js",
"lib/clipboard.js",
"background_scripts/sync.js",
+ "lib/exclusion_rule.js",
"background_scripts/settings.js",
+ "background_scripts/exclusions.js",
"background_scripts/completion.js",
"background_scripts/marks.js",
"background_scripts/main.js"
@@ -35,6 +37,7 @@
"lib/dom_utils.js",
"lib/handler_stack.js",
"lib/clipboard.js",
+ "lib/exclusion_rule.js",
"content_scripts/link_hints.js",
"content_scripts/vomnibar.js",
"content_scripts/scroller.js",
diff --git a/pages/options.coffee b/pages/options.coffee
index d4767da6..be0eccfb 100644
--- a/pages/options.coffee
+++ b/pages/options.coffee
@@ -2,11 +2,17 @@ $ = (id) -> document.getElementById id
bgSettings = chrome.extension.getBackgroundPage().Settings
-editableFields = [ "scrollStepSize", "excludedUrls", "linkHintCharacters", "linkHintNumbers",
+editableFields = [ "scrollStepSize", "linkHintCharacters", "linkHintNumbers",
"userDefinedLinkHintCss", "keyMappings", "filterLinkHints", "previousPatterns",
"nextPatterns", "hideHud", "regexFindMode", "searchUrl", "searchEngines"]
-canBeEmptyFields = ["excludedUrls", "keyMappings", "userDefinedLinkHintCss", "searchEngines"]
+canBeEmptyFields = ["keyMappings", "userDefinedLinkHintCss", "searchEngines"]
+
+# Settings which handle their own DOM and callbacks for the options page.
+# See populateOption in ../background_scripts/exclusions.coffee for an example.
+selfHandlingFields =
+ exclusionRules: (args...) -> chrome.extension.getBackgroundPage().Exclusions.populateOption(args...)
+selfHandlingCallbacks = {}
document.addEventListener "DOMContentLoaded", ->
populateOptions()
@@ -68,6 +74,10 @@ saveOptions = ->
$(fieldName).value = fieldValue
$(fieldName).setAttribute "savedValue", fieldValue
bgSettings.performPostUpdateHook fieldName, fieldValue
+
+ # Self-handling options save themselves.
+ for field of selfHandlingFields
+ selfHandlingCallbacks[field].saveOption() if selfHandlingCallbacks[field].saveOption
$("saveOptions").disabled = true
@@ -76,14 +86,17 @@ populateOptions = ->
for field in editableFields
val = bgSettings.get(field) or ""
setFieldValue $(field), val
- onDataLoaded()
+ # Self-handling options build their own DOM, and provide callbacks for saveOptions and restoreToDefaults.
+ for field of selfHandlingFields
+ selfHandlingCallbacks[field] = selfHandlingFields[field]($(field),enableSaveButton)
restoreToDefaults = ->
- return unless confirm "Are you sure you want to return Vimium's settings to their defaults?"
-
for field in editableFields
val = bgSettings.defaults[field] or ""
setFieldValue $(field), val
+ # Self-handling options restore their own defaults.
+ for field of selfHandlingFields
+ selfHandlingCallbacks[field].restoreToDefault() if selfHandlingCallbacks[field].restoreToDefault
onDataLoaded()
enableSaveButton()
diff --git a/pages/options.html b/pages/options.html
index 07dcab1d..c9fc5a63 100644
--- a/pages/options.html
+++ b/pages/options.html
@@ -6,6 +6,7 @@
<script src="../lib/dom_utils.js"></script>
<script src="../lib/handler_stack.js"></script>
<script src="../lib/clipboard.js"></script>
+ <script src="../lib/exclusion_rule.js"></script>
<script src="../content_scripts/link_hints.js"></script>
<script src="../content_scripts/vomnibar.js"></script>
<script src="../content_scripts/scroller.js"></script>
@@ -109,11 +110,6 @@
width: 40px;
margin-right: 3px;
}
- textarea#excludedUrls {
- margin-top: 5px;
- width: 100%;
- min-height: 100px;
- }
textarea#userDefinedLinkHintCss {
width: 100%;;
min-height: 100px;
@@ -178,6 +174,30 @@
padding: 15px 0;
border-top: 1px solid #eee;
}
+ /* Ids and classes for rendering exclusionRules */
+ #exclusionScroll {
+ overflow: scroll;
+ overflow-x: hidden;
+ height: 225px;
+ border: 1px solid #bfbfbf;
+ border-radius: 2px;
+ color: #444;
+ }
+ .exclusionRemoveButton {
+ /* cursor: pointer; */
+ /* border: none; */
+ /* background: none; */
+ }
+ input.pattern, input.passKeys {
+ font-family: Consolas, "Liberation Mono", Courier, monospace;
+ font-size: 14px;
+ }
+ .pattern {
+ width: 250px;
+ }
+ .passKeys {
+ width: 120px;
+ }
</style>
<link rel="stylesheet" type="text/css" href="../content_scripts/vimium.css" />
@@ -196,31 +216,21 @@
</td>
</tr>
<tr>
- <td colspan="3">
- Excluded URLs and keys<br/>
- <div class="help">
- <div class="example">
- <p>
- To disable Vimium on a site, use:<br/>
- <tt>http*://mail.google.com/*</tt><br/>
- This will <i>wholly disable</i> Vimium on Gmail.<br/><br/>
- To use Vimium together with a website's own<br/>
- key bindings, use:<br/>
- <tt>http*://mail.google.com/* jknpc</tt><br/>
- This will <i>enable</i> Vimium on Gmail, but pass<br/>
- the five listed keys through to Gmail itself.<br/><br/>
- One entry per line.<br/>
- </p>
- </div>
+ <td>Excluded URLs<br/>and keys</td>
+ <td>
+ <div class="help">
+ <div class="example">
+ <p>
+ The left column contains URL patterns. Vimium will be wholly or partially disabled for URLs matching these patterns. Patterns are Javascript regular expressions. Additionally, the symbox "*" matches any zero or more characters.
+ </p>
+ <p>
+ The right column contains keys which Vimium would would normally handle, but should instead be passed through to the underlying web page (for pages matching the corresponding pattern). If empty, then Vimium is wholly disabled.
+ </p>
</div>
- <!-- Hack: fix a minimum size for the text area (below) so that it is
- not too much smaller than its help text (above). -->
- <!-- FIXME:
- This text area should really be broken out into an array
- of separate inputs. However, the whole options page really
- needs a workover, so I'm leaving it like this, for now
- (Steve, 23 Aug, 14). -->
- <textarea id="excludedUrls" style="min-height:180px"></textarea>
+ </div>
+ <div id="exclusionScroll">
+ <table id="exclusionRules"></table>
+ </div>
</td>
</tr>
<tbody id='advancedOptions'>
diff --git a/pages/popup.coffee b/pages/popup.coffee
index 41fc17a9..ff943f32 100644
--- a/pages/popup.coffee
+++ b/pages/popup.coffee
@@ -1,27 +1,86 @@
+
+originalRule = undefined
+originalPattern = undefined
+originalPassKeys = undefined
+
onLoad = ->
document.getElementById("optionsLink").setAttribute "href", chrome.runtime.getURL("pages/options.html")
chrome.tabs.getSelected null, (tab) ->
- # 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
- if isEnabled.passKeys
- pattern += " " + isEnabled.passKeys
- document.getElementById("popupInput").value = pattern
+ if isEnabled.rule
+ # There is an existing exclusion rule for this page.
+ originalRule = isEnabled.rule
+ originalPattern = originalRule.pattern
+ originalPassKeys = originalRule.passKeys
else
- # No existing exclusion rule.
+ # There is not an 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 + "*"
+ domain = (tab.url.match(/[^\/]*\/\/[^\/]*\//) or tab.url) + "*"
+ originalRule = null
+ originalPattern = domain
+ originalPassKeys = ""
+ document.getElementById("popupPattern").value = originalPattern
+ document.getElementById("popupPassKeys").value = originalPassKeys
+ onChange()
+
+onChange = ->
+ pattern = document.getElementById("popupPattern").value.trim()
+ passKeys = document.getElementById("popupPassKeys").value.trim()
+
+ document.getElementById("popupRemove").disabled =
+ not (originalRule and pattern == originalPattern)
+
+ if originalRule and pattern == originalPattern and passKeys == originalPassKeys
+ document.getElementById("popupExclude").disabled = true
+ document.getElementById("popupExclude").value = "Update Rule"
+
+ else if originalRule and pattern == originalPattern
+ document.getElementById("popupExclude").disabled = false
+ document.getElementById("popupExclude").value = "Update Rule"
+
+ else if originalRule
+ document.getElementById("popupExclude").disabled = false
+ document.getElementById("popupExclude").value = "Add Rule"
+
+ else if pattern
+ document.getElementById("popupExclude").disabled = false
+ document.getElementById("popupExclude").value = "Add Rule"
-onExcludeUrl = (e) ->
- url = document.getElementById("popupInput").value
- chrome.extension.getBackgroundPage().addExcludedUrl url
- document.getElementById("excludeConfirm").setAttribute "style", "display: inline-block"
+ else
+ document.getElementById("popupExclude").disabled = true
+ document.getElementById("popupExclude").value = "Add Rule"
+
+showMessage = do ->
+ timer = null
+
+ hideConfirmationMessage = ->
+ document.getElementById("confirmationMessage").setAttribute "style", "display: none"
+ timer = null
+
+ (message) ->
+ document.getElementById("confirmationMessage").setAttribute "style", "display: inline-block"
+ document.getElementById("confirmationMessage").innerHTML = message
+ clearTimeout(timer) if timer
+ timer = setTimeout(hideConfirmationMessage,2000)
+
+addExclusionRule = ->
+ pattern = document.getElementById("popupPattern").value.trim()
+ passKeys = document.getElementById("popupPassKeys").value.trim()
+ chrome.extension.getBackgroundPage().addExclusionRule pattern, passKeys
+ showMessage("Updated.")
+ onLoad()
+
+removeExclusionRule = ->
+ pattern = document.getElementById("popupPattern").value.trim()
+ chrome.extension.getBackgroundPage().removeExclusionRule pattern
+ showMessage("Removed.")
+ onLoad()
document.addEventListener "DOMContentLoaded", ->
- document.getElementById("popupButton").addEventListener "click", onExcludeUrl, false
+ document.getElementById("popupExclude").addEventListener "click", addExclusionRule, false
+ document.getElementById("popupRemove").addEventListener "click", removeExclusionRule, false
+ for field in ["popupPattern", "popupPassKeys"]
+ for event in ["keyup", "change"]
+ document.getElementById(field).addEventListener event, onChange, false
onLoad()
diff --git a/pages/popup.html b/pages/popup.html
index 89f1f02a..86982eae 100644
--- a/pages/popup.html
+++ b/pages/popup.html
@@ -6,17 +6,22 @@
padding: 0px;
}
- #vimiumPopup { width: 500px; }
+ #vimiumPopup { width: 400px; }
#excludeControls {
padding: 10px;
}
- #popupInput {
+ #popupPattern, #popupPassKeys {
+ margin: 5px;
width: 330px;
+ /* Match the corresponding font and font size used on the options page. */
+ /* TODO (smblott): Match other styles from the options page. */
+ font-family: Consolas, "Liberation Mono", Courier, monospace;
+ font-size: 14px;
}
- #excludeConfirm {
+ #confirmationMessage {
display: inline-block;
width: 18px;
height: 13px;
@@ -24,7 +29,8 @@
display: none;
}
- #popupButton { margin-left: 10px; }
+ #popupRemove { margin: 5px; }
+ #popupExclude { margin: 5px; }
#popupMenu ul {
list-style: none;
@@ -52,9 +58,11 @@
<body>
<div id="vimiumPopup">
<div id="excludeControls">
- <input id="popupInput" type="text" />
- <input id="popupButton" type="button" value="Exclude URL" />
- <span id="excludeConfirm">Saved.</span>
+ <input id="popupPattern" placeholder="Pattern against which to match URLs..." type="text" /><br/>
+ <input id="popupPassKeys" placeholder="Only exclude these keys..." type="text" /><br/>
+ <input id="popupRemove" type="button" value="Remove Rule" />
+ <input id="popupExclude" type="button" value="Add or Update Rule" />
+ <span id="confirmationMessage">Text is added in popup.coffee.</span>
</div>
<div id="popupMenu">
diff --git a/tests/unit_tests/exclusion_test.coffee b/tests/unit_tests/exclusion_test.coffee
index 33dbccd3..7fb63df9 100644
--- a/tests/unit_tests/exclusion_test.coffee
+++ b/tests/unit_tests/exclusion_test.coffee
@@ -15,27 +15,28 @@ root.Marks =
extend(global, require "../../lib/utils.js")
Utils.getCurrentVersion = -> '1.44'
extend(global,require "../../background_scripts/sync.js")
+extend(global, require "../../lib/exclusion_rule.js")
extend(global,require "../../background_scripts/settings.js")
Sync.init()
+extend(global, require "../../background_scripts/exclusions.js")
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'
+
+ # These tests have no setup, they use the default values from settings.coffee.
should "be disabled for excluded sites", ->
- rule = isEnabledForUrl({ url: 'http://mail.google.com/u/0/inbox' })
+ rule = isEnabledForUrl({ url: 'http://www.google.com/calendar/page' })
assert.isFalse rule.isEnableForUrl
- assert.isTrue rule.matchingUrl
+ assert.isFalse rule.passKeys
should "be enabled, but with pass keys", ->
- rule = isEnabledForUrl({ url: 'http://www.facebook.com/pages' })
+ rule = isEnabledForUrl({ url: 'https://mail.google.com/mail/u/0/#inbox' })
assert.isTrue rule.isEnabledForUrl
assert.equal rule.passKeys, 'jk'
- assert.isTrue rule.matchingUrl
should "be enabled", ->
rule = isEnabledForUrl({ url: 'http://www.twitter.com/pages' })
@@ -45,27 +46,25 @@ context "Excluded URLs and pass keys",
should "add a new excluded URL", ->
rule = isEnabledForUrl({ url: 'http://www.example.com/page' })
assert.isTrue rule.isEnabledForUrl
- addExcludedUrl("http://www.example.com*")
+ addExclusionRule("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' })
+ rule = isEnabledForUrl({ url: 'http://www.anotherexample.com/page' })
assert.isTrue rule.isEnabledForUrl
- addExcludedUrl("http://www.example.com/* jk")
- rule = isEnabledForUrl({ url: 'http://www.example.com/page' })
+ addExclusionRule("http://www.anotherexample.com/*","jk")
+ rule = isEnabledForUrl({ url: 'http://www.anotherexample.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' })
+ rule = isEnabledForUrl({ url: 'http://mail.google.com/page' })
assert.isTrue rule.isEnabledForUrl
- addExcludedUrl("http://www.facebook.com/* jknp")
- rule = isEnabledForUrl({ url: 'http://www.facebook.com/page' })
+ assert.equal rule.passKeys, 'jk'
+ addExclusionRule("http*://mail.google.com/*","jknp")
+ rule = isEnabledForUrl({ url: 'http://mail.google.com/page' })
assert.isTrue rule.isEnabledForUrl
assert.equal rule.passKeys, 'jknp'
- assert.isTrue rule.matchingUrl