aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts
diff options
context:
space:
mode:
Diffstat (limited to 'content_scripts')
-rw-r--r--content_scripts/mode.coffee55
-rw-r--r--content_scripts/mode_find.coffee1
-rw-r--r--content_scripts/mode_insert.coffee20
-rw-r--r--content_scripts/scroller.coffee15
-rw-r--r--content_scripts/vimium_frontend.coffee14
5 files changed, 73 insertions, 32 deletions
diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee
index 37f3a8c2..a33197b0 100644
--- a/content_scripts/mode.coffee
+++ b/content_scripts/mode.coffee
@@ -38,12 +38,14 @@
# myMode.exit() # externally triggered.
#
-# For debug only; to be stripped out.
+# For debug only.
count = 0
class Mode
- # If this is true, then we generate a trace of modes being activated and deactivated on the console.
- @debug = true
+ # If Mode.debug is true, then we generate a trace of modes being activated and deactivated on the console, along
+ # with a list of the currently active modes.
+ debug: true
+ @modes: []
# Constants; short, readable names for handlerStack event-handler return values.
continueBubbling: true
@@ -60,7 +62,8 @@ class Mode
@name = @options.name || "anonymous"
@count = ++count
- console.log @count, "create:", @name if Mode.debug
+ @id = "#{@name}-#{@count}"
+ @logger "activate:", @id if @debug
@push
keydown: @options.keydown || null
@@ -80,6 +83,7 @@ class Mode
# Note. This handler ends up above the mode's own key handlers on the handler stack, so it takes
# priority.
@push
+ _name: "mode-#{@id}/exitOnEscape"
"keydown": (event) =>
return @continueBubbling unless KeyboardUtils.isEscape event
@exit event
@@ -90,6 +94,7 @@ class Mode
# loses the focus.
if @options.exitOnBlur
@push
+ _name: "mode-#{@id}/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,
@@ -98,6 +103,7 @@ class Mode
@enabled = false
@passKeys = ""
@push
+ _name: "mode-#{@id}/registerStateChange"
"registerStateChange": ({ enabled: enabled, passKeys: passKeys }) =>
@alwaysContinueBubbling =>
if enabled != @enabled or passKeys != @passKeys
@@ -110,30 +116,38 @@ class Mode
# from propagating to other extensions or the host page.
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
+ _name: "mode-#{@id}/trapAllKeyboardEvents"
+ keydown: (event) =>
+ if event.srcElement == @options.trapAllKeyboardEvents then @suppressEvent else @continueBubbling
+ keypress: (event) =>
+ if event.srcElement == @options.trapAllKeyboardEvents then @suppressEvent else @continueBubbling
+ keyup: (event) =>
+ if event.srcElement == @options.trapAllKeyboardEvents then @suppressEvent else @continueBubbling
Mode.updateBadge() if @badge
- # End of Mode.constructor().
+ Mode.modes.push @
+ @log() if @debug
+ handlerStack.debugOn()
+ # End of Mode constructor.
push: (handlers) ->
+ handlers._name ||= "mode-#{@id}"
@handlers.push handlerStack.push handlers
unshift: (handlers) ->
- @handlers.unshift handlerStack.push handlers
+ handlers._name ||= "mode-#{@id}"
+ handlers._name += "/unshifted"
+ @handlers.push handlerStack.unshift handlers
onExit: (handler) ->
@exitHandlers.push handler
exit: ->
if @modeIsActive
- console.log @count, "exit:", @name if Mode.debug
+ @logger "deactivate:", @id if @debug
handler() for handler in @exitHandlers
handlerStack.remove handlerId for handlerId in @handlers
+ Mode.modes = Mode.modes.filter (mode) => mode != @
Mode.updateBadge()
@modeIsActive = false
@@ -177,12 +191,24 @@ class Mode
# flickering in some cases.
Mode.badgeSuppressor.runSuppresed =>
if singletons[key]
- console.log singletons[key].count, "singleton:", @name, "(deactivating)"
+ @logger "singleton:", "deactivating #{singletons[key].id}" if @debug
singletons[key].exit()
singletons[key] = @
@onExit => delete singletons[key] if singletons[key] == @
+ # Debugging routines.
+ log: ->
+ if Mode.modes.length == 0
+ @logger "It looks like debugging is not enabled in modes.coffee."
+ else
+ @logger "active modes (top to bottom), current: #{@id}"
+ for mode in Mode.modes[..].reverse()
+ @logger " ", mode.id
+
+ logger: (args...) ->
+ handlerStack.log args...
+
# BadgeMode is a pseudo mode for triggering badge updates on focus changes and state updates. It sits at the
# bottom of the handler stack, and so it receives state changes *after* all other modes, and can override the
# badge choice of the other active modes.
@@ -194,6 +220,7 @@ new class BadgeMode extends Mode
trackState: true
@push
+ _name: "mode-#{@id}/focus"
"focus": => @alwaysContinueBubbling -> Mode.updateBadge()
chooseBadge: (badge) ->
diff --git a/content_scripts/mode_find.coffee b/content_scripts/mode_find.coffee
index d63b3319..0ce03af6 100644
--- a/content_scripts/mode_find.coffee
+++ b/content_scripts/mode_find.coffee
@@ -33,6 +33,7 @@ class PostFindMode extends InsertModeBlocker
self = @
@push
+ _name: "mode-#{@id}/handle-escape"
keydown: (event) ->
if element == document.activeElement and KeyboardUtils.isEscape event
self.exit()
diff --git a/content_scripts/mode_insert.coffee b/content_scripts/mode_insert.coffee
index 144b0be6..7668d794 100644
--- a/content_scripts/mode_insert.coffee
+++ b/content_scripts/mode_insert.coffee
@@ -51,6 +51,7 @@ class InsertModeTrigger extends Mode
@stopBubblingAndTrue
@push
+ _name: "mode-#{@id}/activate-on-focus"
focus: (event) =>
triggerSuppressor.unlessSuppressed =>
@alwaysContinueBubbling =>
@@ -78,6 +79,7 @@ class InsertModeBlocker extends Mode
@onExit -> triggerSuppressor.unsuppress()
@push
+ _name: "mode-#{@id}/bail-on-click"
"click": (event) =>
@alwaysContinueBubbling =>
# The user knows best; so, if the user clicks on something, the insert-mode blocker gets out of the
@@ -92,18 +94,18 @@ class InsertModeBlocker extends Mode
new @options.onClickMode
targetElement: document.activeElement
-# There's some unfortunate feature interaction with chrome's content editable handling. If the selection is
-# content editable and a descendant of the active element, then chrome focuses it on any unsuppressed keyboard
-# event. This has the unfortunate effect of dropping us unintentally into insert mode. See #1415.
-# A single instance of this mode sits near the bottom of the handler stack and suppresses keyboard events if:
-# - they haven't been handled by any other mode (so not by normal mode, passkeys mode, insert mode, and so
-# on),
+# There's some unfortunate feature interaction with chrome's contentEditable handling. If the selection is
+# contentEditable and a descendant of the active element, then chrome focuses it on any unsuppressed keyboard
+# event. This has the unfortunate effect of dropping us unintentally into insert mode. See #1415. A single
+# instance of this mode sits near the bottom of the handler stack and suppresses keyboard events if:
+# - they haven't been handled by any other mode (so not by normal mode, passkeys, insert, ...),
# - the selection is content editable, and
# - the selection is a descendant of the active element.
# This should rarely fire, typically only on fudged keypresses in normal mode. And, even then, only in the
# circumstances outlined above. So, we shouldn't usually be blocking keyboard events for other extensions or
# the page itself.
-# handling keyboard events.
+# There's some controversy as to whether this is the right thing to do. See discussion in #1415. This
+# implements Option 2 from there, although Option 3 would be a reasonable alternative.
new class ContentEditableTrap extends Mode
constructor: ->
super
@@ -112,10 +114,10 @@ new class ContentEditableTrap extends Mode
keypress: (event) => @handle => @suppressEvent
keyup: (event) => @handle => @suppressEvent
- handle: (func) -> if @isContentEditableFocused() then func() else @continueBubbling
+ handle: (func) -> if @wouldTriggerInsert() then func() else @continueBubbling
# True if the selection is content editable and a descendant of the active element.
- isContentEditableFocused: ->
+ wouldTriggerInsert: ->
element = document.getSelection()?.anchorNode?.parentElement
return element?.isContentEditable and
document.activeElement and
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 889dc042..f70d3aed 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -124,12 +124,15 @@ CoreScroller =
@keyIsDown = false
handlerStack.push
+ _name: 'scroller/track-key-down/up'
keydown: (event) =>
- @keyIsDown = true
- @lastEvent = event
+ handlerStack.alwaysContinueBubbling =>
+ @keyIsDown = true
+ @lastEvent = event
keyup: =>
- @keyIsDown = false
- @time += 1
+ handlerStack.alwaysContinueBubbling =>
+ @keyIsDown = false
+ @time += 1
# Return true if CoreScroller would not initiate a new scroll right now.
wouldNotInitiateScroll: -> @lastEvent?.repeat and @settings.get "smoothScroll"
@@ -205,7 +208,9 @@ CoreScroller =
# Scroller contains the two main scroll functions (scrollBy and scrollTo) which are exported to clients.
Scroller =
init: (frontendSettings) ->
- handlerStack.push DOMActivate: -> activatedElement = event.target
+ handlerStack.push
+ _name: 'scroller/active-element'
+ DOMActivate: (event) -> handlerStack.alwaysContinueBubbling -> activatedElement = event.target
CoreScroller.init frontendSettings
# scroll the active element in :direction by :amount * :factor.
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index a9bf30a3..1406b1e7 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -396,10 +396,16 @@ extend window,
visibleInputs[selectedInputIndex].element.focus()
@suppressEvent
else unless event.keyCode == KeyboardUtils.keyCodes.shiftKey
- @exit event
- # In @exit(), we just pushed a new mode (usually insert mode). Restart bubbling, so that the
- # new mode can now see the event too.
- @restartBubbling
+ mode = @exit event
+ if mode
+ # In @exit(), we just pushed a new mode (usually insert mode). Restart bubbling, so that the
+ # new mode can now see the event too.
+ # Exception: If the new mode exits on Escape, and this key event is Escape, then rebubbling the
+ # event will just cause the mode to exit immediately. So we suppress Escapes.
+ if mode.options.exitOnEscape and KeyboardUtils.isEscape event
+ @suppressEvent
+ else
+ @restartBubbling
visibleInputs[selectedInputIndex].element.focus()
return @exit() if visibleInputs.length == 1