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) -> | 
