aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--background_scripts/main.coffee3
-rw-r--r--content_scripts/mode.coffee70
-rw-r--r--content_scripts/mode_insert.coffee25
-rw-r--r--content_scripts/vimium_frontend.coffee5
-rw-r--r--lib/dom_utils.coffee18
-rw-r--r--lib/handler_stack.coffee6
6 files changed, 66 insertions, 61 deletions
diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee
index 008eb89f..e8f39326 100644
--- a/background_scripts/main.coffee
+++ b/background_scripts/main.coffee
@@ -340,7 +340,8 @@ setBrowserActionIcon = (tabId,path) ->
chrome.browserAction.setIcon({ tabId: tabId, path: path })
# This color should match the blue of the Vimium browser popup (although it looks a little darker, to me?).
-chrome.browserAction.setBadgeBackgroundColor {color: [102, 176, 226, 255]}
+chrome.browserAction.setBadgeBackgroundColor
+ color: [102, 176, 226, 255]
setBadge = do ->
current = ""
diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee
index 632d3e99..8e37ee36 100644
--- a/content_scripts/mode.coffee
+++ b/content_scripts/mode.coffee
@@ -8,8 +8,10 @@
#
# badge:
# A badge (to appear on the browser popup).
-# Optional. Define a badge if the badge is constant. Otherwise, do not define a badge, but override
-# instead the chooseBadge method. Or, if the mode *never* shows a badge, then do neither.
+# Optional. Define a badge if the badge is constant; for example, in insert mode the badge is always "I".
+# Otherwise, do not define a badge, but instead override the chooseBadge method; for example, in passkeys
+# mode, the badge may be "P" or "", depending on the configuration state. Or, if the mode *never* shows a
+# badge, then do neither.
#
# keydown:
# keypress:
@@ -40,6 +42,7 @@
count = 0
class Mode
+ # If this is true, then we generate a trace of modes being activated and deactivated on the console.
@debug = true
# Constants; readable shortcuts for event-handler return values.
@@ -48,32 +51,31 @@ class Mode
stopBubblingAndTrue: handlerStack.stopBubblingAndTrue
stopBubblingAndFalse: handlerStack.stopBubblingAndFalse
- constructor: (options={}) ->
- @options = options
+ constructor: (@options={}) ->
@handlers = []
@exitHandlers = []
@modeIsActive = true
- @badge = options.badge || ""
- @name = options.name || "anonymous"
+ @badge = @options.badge || ""
+ @name = @options.name || "anonymous"
@count = ++count
console.log @count, "create:", @name if Mode.debug
@push
- keydown: options.keydown || null
- keypress: options.keypress || null
- keyup: options.keyup || null
+ keydown: @options.keydown || null
+ keypress: @options.keypress || null
+ keyup: @options.keyup || null
updateBadge: (badge) => @alwaysContinueBubbling => @chooseBadge badge
# Some modes are singletons: there may be at most one instance active at any one time. A mode is a
- # singleton if options.singleton is truthy. The value of options.singleton should be the key which is
+ # singleton if @options.singleton is truthy. The value of @options.singleton should be the key which is
# required to be unique. See PostFindMode for an example.
# New instances deactivate existing instances as they themselves are activated.
- @registerSingleton options.singleton if options.singleton
+ @registerSingleton @options.singleton if @options.singleton
- # If options.exitOnEscape is truthy, then the mode will exit when the escape key is pressed. The
+ # If @options.exitOnEscape is truthy, then the mode will exit when the escape key is pressed. The
# triggering keyboard event will be passed to the mode's @exit() method.
- if options.exitOnEscape
+ if @options.exitOnEscape
# Note. This handler ends up above the mode's own key handlers on the handler stack, so it takes
# priority.
@push
@@ -83,36 +85,36 @@ class Mode
DomUtils.suppressKeyupAfterEscape handlerStack
@suppressEvent
- # If options.exitOnBlur is truthy, then it should be an element. The mode will exit when that element
+ # If @options.exitOnBlur is truthy, then it should be an element. The mode will exit when that element
# loses the focus.
- if options.exitOnBlur
+ if @options.exitOnBlur
@push
- "blur": (event) => @alwaysContinueBubbling => @exit() if event.srcElement == options.exitOnBlur
+ "blur": (event) => @alwaysContinueBubbling => @exit() if event.srcElement == @options.exitOnBlur
- # If options.trackState is truthy, then the mode mainatins the current state in @enabled and @passKeys,
+ # If @options.trackState is truthy, then the mode mainatins the current state in @enabled and @passKeys,
# and calls @registerStateChange() (if defined) whenever the state changes.
- if options.trackState
+ if @options.trackState
@enabled = false
@passKeys = ""
@push
- "registerStateChange": ({enabled: enabled, passKeys: passKeys}) =>
+ "registerStateChange": ({ enabled: enabled, passKeys: passKeys }) =>
@alwaysContinueBubbling =>
if enabled != @enabled or passKeys != @passKeys
@enabled = enabled
@passKeys = passKeys
@registerStateChange?()
- # If options.trapAllKeyboardEvents is truthy, then it should be an element. All keyboard events on that
+ # If @options.trapAllKeyboardEvents is truthy, then it should be an element. All keyboard events on that
# element are suppressed *after* bubbling the event down the handler stack. This prevents such events
# from propagating to other extensions or the host page.
- if options.trapAllKeyboardEvents
+ if @options.trapAllKeyboardEvents
@unshift
- keydown: (event) => @alwaysContinueBubbling ->
- DomUtils.suppressPropagation event if event.srcElement == options.trapAllKeyboardEvents
- keypress: (event) => @alwaysContinueBubbling ->
- DomUtils.suppressEvent event if event.srcElement == options.trapAllKeyboardEvents
- keyup: (event) => @alwaysContinueBubbling ->
- DomUtils.suppressPropagation event if event.srcElement == options.trapAllKeyboardEvents
+ keydown: (event) => @alwaysContinueBubbling =>
+ DomUtils.suppressPropagation event if event.srcElement == @options.trapAllKeyboardEvents
+ keypress: (event) => @alwaysContinueBubbling =>
+ DomUtils.suppressEvent event if event.srcElement == @options.trapAllKeyboardEvents
+ keyup: (event) => @alwaysContinueBubbling =>
+ DomUtils.suppressPropagation event if event.srcElement == @options.trapAllKeyboardEvents
Mode.updateBadge() if @badge
# End of Mode.constructor().
@@ -139,9 +141,11 @@ class Mode
chooseBadge: (badge) ->
badge.badge ||= @badge
- # Shorthand for an otherwise long name. This allow us to write handlers which always yield the same value,
- # without having to be concerned with the result of the handler itself.
- alwaysContinueBubbling: (func) -> handlerStack.alwaysContinueBubbling func
+ # Shorthand for an otherwise long name. This wraps a handler with an arbitrary return value, and always
+ # yields @continueBubbling instead. This simplifies handlers if they always continue bubbling (a common
+ # case), because they do not need to be concerned with their return value (which helps keep code concise and
+ # clear).
+ alwaysContinueBubbling: handlerStack.alwaysContinueBubbling
# User for sometimes suppressing badge updates.
@badgeSuppressor: new Utils.Suppressor()
@@ -152,13 +156,13 @@ class Mode
@updateBadge: ->
@badgeSuppressor.unlessSuppressed ->
if document.hasFocus()
- handlerStack.bubbleEvent "updateBadge", badge = {badge: ""}
+ handlerStack.bubbleEvent "updateBadge", badge = { badge: "" }
chrome.runtime.sendMessage
handler: "setBadge"
badge: badge.badge
# Temporarily install a mode to protect a function call, then exit the mode. For example, temporarily
- # install an InsertModeBlocker.
+ # install an InsertModeBlocker, so that focus events don't unintentionally drop us into insert mode.
@runIn: (mode, func) ->
mode = new mode()
func()
@@ -181,7 +185,7 @@ class Mode
# badge choice of the other active modes.
# Note. We also create the the one-and-only instance, here.
new class BadgeMode extends Mode
- constructor: (options) ->
+ constructor: () ->
super
name: "badge"
trackState: true
diff --git a/content_scripts/mode_insert.coffee b/content_scripts/mode_insert.coffee
index 5280aada..7f1d5ddc 100644
--- a/content_scripts/mode_insert.coffee
+++ b/content_scripts/mode_insert.coffee
@@ -1,22 +1,4 @@
-# Input or text elements are considered focusable and able to receieve their own keyboard events, and will
-# enter insert mode if focused. Also note that the "contentEditable" attribute can be set on any element
-# which makes it a rich text editor, like the notes on jjot.com.
-isEditable =(element) ->
- return true if element.isContentEditable
- nodeName = element.nodeName?.toLowerCase()
- # Use a blacklist instead of a whitelist because new form controls are still being implemented for html5.
- if nodeName == "input" and element.type not in ["radio", "checkbox"]
- return true
- nodeName in ["textarea", "select"]
-
-# Embedded elements like Flash and quicktime players can obtain focus.
-isEmbed =(element) ->
- element.nodeName?.toLowerCase() in ["embed", "object"]
-
-isFocusable =(element) ->
- isEditable(element) or isEmbed element
-
# This mode is installed when insert mode is active.
class InsertMode extends Mode
constructor: (@insertModeLock = null) ->
@@ -33,7 +15,7 @@ class InsertMode extends Mode
exit: (event = null) ->
super()
element = event?.srcElement
- if element and isFocusable element
+ if element and DomUtils.isFocusable element
# Remove the focus so the user can't just get himself back into insert mode by typing in the same
# input box.
# NOTE(smblott, 2014/12/22) Including embeds for .blur() here is experimental. It appears to be the
@@ -63,11 +45,12 @@ class InsertModeTrigger extends Mode
@push
focus: (event) =>
triggerSuppressor.unlessSuppressed =>
- return if not isFocusable event.target
+ return unless DomUtils.isFocusable event.target
new InsertMode event.target
# We may already have focussed an input, so check.
- new InsertMode document.activeElement if document.activeElement and isEditable document.activeElement
+ if document.activeElement and DomUtils.isEditable document.activeElement
+ new InsertMode document.activeElement
# Used by InsertModeBlocker to suppress InsertModeTrigger; see below.
triggerSuppressor = new Utils.Suppressor true
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index e8248c0a..b1fc3c6f 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -123,15 +123,14 @@ initializePreDomReady = ->
settings.addEventListener("load", LinkHints.init.bind(LinkHints))
settings.load()
- # Install normal mode. This is at the bottom of both the mode stack and the handler stack, and is never
- # deactivated.
+ # Install normal mode. This is near the bottom of the handler stack, and is never deactivated.
new NormalMode()
# Initialize the scroller. The scroller installs a key handler, and this is next on the handler stack,
# immediately above normal mode.
Scroller.init settings
- # Install passKeys and insert modes. These too are permanently on the stack (although not always active).
+ # Install passKeys mode and the insert-mode trigger. These too are permanently on the stack.
passKeysMode = new PassKeysMode()
new InsertModeTrigger()
Mode.updateBadge()
diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee
index 1a992b43..9d7ca867 100644
--- a/lib/dom_utils.coffee
+++ b/lib/dom_utils.coffee
@@ -141,6 +141,24 @@ DomUtils =
(element.nodeName.toLowerCase() == "input" && unselectableTypes.indexOf(element.type) == -1) ||
element.nodeName.toLowerCase() == "textarea"
+ # Input or text elements are considered focusable and able to receieve their own keyboard events, and will
+ # enter insert mode if focused. Also note that the "contentEditable" attribute can be set on any element
+ # which makes it a rich text editor, like the notes on jjot.com.
+ isEditable: (element) ->
+ return true if element.isContentEditable
+ nodeName = element.nodeName?.toLowerCase()
+ # Use a blacklist instead of a whitelist because new form controls are still being implemented for html5.
+ if nodeName == "input" and element.type not in ["radio", "checkbox"]
+ return true
+ nodeName in ["textarea", "select"]
+
+ # Embedded elements like Flash and quicktime players can obtain focus.
+ isEmbed: (element) ->
+ element.nodeName?.toLowerCase() in ["embed", "object"]
+
+ isFocusable: (element) ->
+ @isEditable(element) or @isEmbed element
+
isDOMDescendant: (parent, child) ->
node = child
while (node != null)
diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee
index 718bee9d..97e189c5 100644
--- a/lib/handler_stack.coffee
+++ b/lib/handler_stack.coffee
@@ -58,10 +58,10 @@ class HandlerStack
@stack.splice(i, 1)
break
- # The handler stack handles chrome events (which may need to be suppressed) and internal (fake) events.
- # This checks whether that the event at hand is a chrome event.
+ # The handler stack handles chrome events (which may need to be suppressed) and internal (pseudo) events.
+ # This checks whether the event at hand is a chrome event.
isChromeEvent: (event) ->
- event?.preventDefault? and event?.stopImmediatePropagation?
+ event?.preventDefault? or event?.stopImmediatePropagation?
# Convenience wrappers.
alwaysContinueBubbling: (handler) ->