aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/mode.coffee
diff options
context:
space:
mode:
authorStephen Blott2015-01-04 09:29:36 +0000
committerStephen Blott2015-01-04 13:00:50 +0000
commit9ae4b6c10d53153929d905f28bc7de57c0ba6dfe (patch)
treee6243e08f2f4e0925c3960dd68381d917d46a510 /content_scripts/mode.coffee
parent615f8a79f91f1d868465a6dae903c6710103515f (diff)
downloadvimium-9ae4b6c10d53153929d905f28bc7de57c0ba6dfe.tar.bz2
Modes; various improvements.
- Add StateMode. - PasskeysMode is a StateMode. - BadgeUpdateMode is a StateMode. - Improve badge handling. - Add push method to Mode. - Document how modes work. - Cache badge on background page to reduce the number of updates. - Remove badge restriction on document.body?.tagName.toLowerCase() == "frameset". - Add ExitOnEscape mode, use it for ConstrainedMode and FindMode. - Move PostFindMode to its own file.
Diffstat (limited to 'content_scripts/mode.coffee')
-rw-r--r--content_scripts/mode.coffee155
1 files changed, 117 insertions, 38 deletions
diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee
index 64001eaa..10b7bb2a 100644
--- a/content_scripts/mode.coffee
+++ b/content_scripts/mode.coffee
@@ -1,4 +1,43 @@
-
+# Modes.
+#
+# A mode implements a number of event handlers which are pushed onto the handler stack when the mode starts,
+# and poped when the mode exits. The Mode base takes as single argument options which can defined:
+#
+# name:
+# A name for this mode.
+#
+# badge:
+# A badge (to appear on the browser popup) for this mode.
+# Optional. Define a badge is the badge is constant. Otherwise, do not set a badge and override the
+# chooseBadge method instead. Or, if the mode *never* shows a bade, then do neither.
+#
+# keydown:
+# keypress:
+# keyup:
+# Key handlers. Optional: provide these as required. The default is to continue bubbling all key events.
+#
+# Additional handlers associated with the mode can be added by using the push method. For example, if a mode
+# responds to "focus" events, then push an additional handler:
+# @push
+# "focus": (event) => ....
+# Any such additional handlers are removed when the mode exits.
+#
+# New mode types are created by inheriting from Mode or one of its sub-classes. Some generic cub-classes are
+# provided below:
+# SingletonMode: ensures that at most one instance of the mode should be active at any time.
+# ConstrainedMode: exits the mode if the user clicks outside of the given element.
+# ExitOnEscapeMode: exits the mode if the user types Esc.
+# StateMode: tracks the current Vimium state in @enabled and @passKeys.
+#
+# To install and existing mode, use:
+# myMode = new MyMode()
+#
+# To remove a mode, use:
+# myMode.exit() # externally triggered.
+# @exit() # internally triggered (more common).
+#
+
+# Debug only; to be stripped out.
count = 0
class Mode
@@ -15,23 +54,26 @@ class Mode
# Default values.
name: ""
badge: ""
- keydown: (event) => @continueBubbling
- keypress: (event) => @continueBubbling
- keyup: (event) => @continueBubbling
+ keydown: null
+ keypress: null
+ keyup: null
- constructor: (options) ->
+ constructor: (options={}) ->
Mode.modes.unshift @
extend @, options
@count = ++count
console.log @count, "create:", @name
@handlers = []
- @handlers.push handlerStack.push
+ @push
keydown: @keydown
keypress: @keypress
keyup: @keyup
updateBadge: (badge) => handlerStack.alwaysContinueBubbling => @chooseBadge badge
+ push: (handlers) ->
+ @handlers.push handlerStack.push handlers
+
exit: ->
console.log @count, "exit:", @name
handlerStack.remove handlerId for handlerId in @handlers
@@ -45,14 +87,13 @@ class Mode
badge.badge ||= @badge
# Static method. Used externally and internally to initiate bubbling of an updateBadge event and to send
- # the resulting badge to the background page. We only update the badge if this document has the focus, and
- # the document's body isn't a frameset.
+ # the resulting badge to the background page. We only update the badge if this document has the focus.
@updateBadge: ->
if document.hasFocus()
- unless document.body?.tagName.toLowerCase() == "frameset"
- badge = {badge: ""}
- handlerStack.bubbleEvent "updateBadge", badge
- chrome.runtime.sendMessage({ handler: "setBadge", badge: badge.badge })
+ handlerStack.bubbleEvent "updateBadge", badge = {badge: ""}
+ chrome.runtime.sendMessage
+ handler: "setBadge"
+ badge: badge.badge
# Temporarily install a mode.
@runIn: (mode, func) ->
@@ -60,10 +101,6 @@ class Mode
func()
mode.exit()
-# We need to detect when the focused frame/tab changes, and update the badge.
-handlerStack.push
- "focus": -> handlerStack.alwaysContinueBubbling -> Mode.updateBadge()
-
# A SingletonMode is a Mode of which there may be at most one instance (of @singleton) active at any one time.
# New instances cancel previous instances on startup.
class SingletonMode extends Mode
@@ -74,26 +111,35 @@ class SingletonMode extends Mode
super()
constructor: (@singleton, options={}) ->
- SingletonMode.instances[@singleton].exit() if SingletonMode.instances[@singleton]
+ SingletonMode.kill @singleton
SingletonMode.instances[@singleton] = @
super options
-# # MultiMode is a collection of modes which are installed or uninstalled together.
-# class MultiMode extends Mode
-# constructor: (modes...) ->
-# @modes = (new mode() for mode in modes)
-# super {name: "multimode"}
-#
-# exit: ->
-# mode.exit() for mode in modes
+ # Static method. If there's a singleton instance running, then kill it.
+ @kill: (singleton) ->
+ SingletonMode.instances[singleton].exit() if SingletonMode.instances[singleton]
+
+# The mode exits when the user hits Esc.
+class ExitOnEscapeMode extends Mode
+ constructor: (options) ->
+ super options
+
+ # This handler ends up above the mode's own key handlers on the handler stack, so it takes priority.
+ @push
+ "keydown": (event) =>
+ return @continueBubbling unless KeyboardUtils.isEscape event
+ @exit
+ source: ExitOnEscapeMode
+ event: event
+ @suppressEvent
# When the user clicks anywhere outside of the given element, the mode is exited.
-class ConstrainedMode extends Mode
+class ConstrainedMode extends ExitOnEscapeMode
constructor: (@element, options) ->
options.name = if options.name? then "constrained-#{options.name}" else "constrained"
super options
- @handlers.push handlerStack.push
+ @push
"click": (event) =>
@exit() unless @isDOMDescendant @element, event.srcElement
@continueBubbling
@@ -105,19 +151,52 @@ class ConstrainedMode extends Mode
node = node.parentNode
false
-# # The mode exits when the user hits Esc.
-# class ExitOnEscapeMode extends Mode
-# constructor: (options) ->
-# super options
-#
-# # This handler ends up above the mode's own handlers on the handler stack, so it takes priority.
-# @handlers.push handlerStack.push
-# "keydown": (event) =>
-# return @continueBubbling unless KeyboardUtils.isEscape event
-# @exit()
-# @suppressEvent
+# The state mode tracks the enabled state in @enabled and @passKeys, and its initialized state in
+# @initialized. It calls @registerStateChange() whenever the state changes.
+class StateMode extends Mode
+ constructor: (options) ->
+ @stateInitialized = false
+ @enabled = false
+ @passKeys = ""
+ super options
+
+ @push
+ "registerStateChange": ({enabled: enabled, passKeys: passKeys}) =>
+ handlerStack.alwaysContinueBubbling =>
+ if enabled != @enabled or passKeys != @passKeys or not @stateInitialized
+ @stateInitialized = true
+ @enabled = enabled
+ @passKeys = passKeys
+ @registerStateChange()
+
+ # Overridden by sub-classes.
+ registerStateChange: ->
+
+# BadgeMode is a psuedo mode for managing 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.
+class BadgeMode extends StateMode
+ constructor: (options) ->
+ options.name ||= "badge"
+ super options
+
+ @push
+ "focus": =>
+ handlerStack.alwaysContinueBubbling =>
+ Mode.updateBadge()
+
+ chooseBadge: (badge) ->
+ # If we're not enabled, then post an empty badge (so, no badge at all).
+ badge.badge = "" unless @enabled
+
+ registerStateChange: ->
+ Mode.updateBadge()
+
+# Install a single BadgeMode instance.
+new BadgeMode {}
root = exports ? window
root.Mode = Mode
root.SingletonMode = SingletonMode
root.ConstrainedMode = ConstrainedMode
+root.StateMode = StateMode
+root.ExitOnEscapeMode = ExitOnEscapeMode