diff options
| -rw-r--r-- | background_scripts/main.coffee | 3 | ||||
| -rw-r--r-- | content_scripts/mode.coffee | 70 | ||||
| -rw-r--r-- | content_scripts/mode_insert.coffee | 25 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 5 | ||||
| -rw-r--r-- | lib/dom_utils.coffee | 18 | ||||
| -rw-r--r-- | lib/handler_stack.coffee | 6 |
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) -> |
