diff options
| -rw-r--r-- | background_scripts/main.coffee | 7 | ||||
| -rw-r--r-- | content_scripts/mode.coffee | 30 | ||||
| -rw-r--r-- | content_scripts/mode_insert.coffee | 49 | ||||
| -rw-r--r-- | content_scripts/mode_passkeys.coffee | 25 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 16 | ||||
| -rw-r--r-- | lib/handler_stack.coffee | 7 | 
6 files changed, 87 insertions, 47 deletions
| diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 7d7359b8..3ce05a49 100644 --- a/background_scripts/main.coffee +++ b/background_scripts/main.coffee @@ -342,9 +342,8 @@ setBrowserActionIcon = (tabId,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]} -setBadge = (response) -> -  badge = response?.badge || "" -  chrome.browserAction.setBadgeText {text: badge} +setBadge = (request) -> +  chrome.browserAction.setBadgeText {text: request.badge || ""}  # Updates the browserAction icon to indicate whether Vimium is enabled or disabled on the current page.  # Also propagates new enabled/disabled/passkeys state to active window, if necessary. @@ -356,7 +355,6 @@ root.updateActiveState = updateActiveState = (tabId) ->    partialIcon = "icons/browser_action_partial.png"    chrome.tabs.get tabId, (tab) ->      chrome.tabs.sendMessage tabId, { name: "getActiveState" }, (response) -> -      setBadge response        if response          isCurrentlyEnabled = response.enabled          currentPasskeys = response.passKeys @@ -610,7 +608,6 @@ unregisterFrame = (request, sender) ->        frameIdsForTab[tabId] = frameIdsForTab[tabId].filter (id) -> id != request.frameId  handleFrameFocused = (request, sender) -> -  setBadge request    tabId = sender.tab.id    if frameIdsForTab[tabId]?      frameIdsForTab[tabId] = diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee index 82dbf74a..468f587a 100644 --- a/content_scripts/mode.coffee +++ b/content_scripts/mode.coffee @@ -18,13 +18,14 @@ class Mode    constructor: (options) ->      extend @, options -    @handlerId = handlerStack.push +    @handlers = [] +    @handlers.push handlerStack.push        keydown: @checkForBuiltInHandler "keydown", @keydown        keypress: @checkForBuiltInHandler "keypress", @keypress        keyup: @checkForBuiltInHandler "keyup", @keyup +      updateBadgeForMode: (badge) => @updateBadgeForMode badge      Mode.modes.unshift @ -    Mode.setBadge()    # Allow the strings "suppress" and "pass" to be used as proxies for the built-in handlers.    checkForBuiltInHandler: (type, handler) -> @@ -48,18 +49,25 @@ class Mode      (event) -> handler(event) and Mode.suppressPropagation # Always falsy.    exit: -> -    handlerStack.remove @handlerId +    handlerStack.remove handlerId for handlerId in @handlers      Mode.modes = Mode.modes.filter (mode) => mode != @ -    Mode.setBadge() +    Mode.updateBadge() -  # Set the badge on the browser popup to indicate the current mode; static method. -  @setBadge: -> -    chrome.runtime.sendMessage({ handler: "setBadge", badge: Mode.getBadge() }) +  # Default updateBadgeForMode handler.  This is overridden by sub-classes.  The default is to install the +  # current mode's badge, unless the bade is already set. +  updateBadgeForMode: (badge) -> +    badge.badge ||= @badge +    Mode.propagate -  # Static convenience methods. -  @is: (mode) -> Mode.current()?.name == mode -  @getBadge: -> Mode.current()?.badge || "" -  @isInsert: -> Mode.is "insert" +  # Static method.  Used externally and internally to initiate bubbling of an updateBadgeForMode event. +  @updateBadge: -> +    badge = {badge: ""} +    handlerStack.bubbleEvent "updateBadgeForMode", badge +    Mode.sendBadge badge.badge + +  # Static utility to update the browser-popup badge. +  @sendBadge: (badge) -> +    chrome.runtime.sendMessage({ handler: "setBadge", badge: badge })  root = exports ? window  root.Mode = Mode diff --git a/content_scripts/mode_insert.coffee b/content_scripts/mode_insert.coffee index 4ef490c9..e68bf6ab 100644 --- a/content_scripts/mode_insert.coffee +++ b/content_scripts/mode_insert.coffee @@ -1,6 +1,6 @@  class InsertMode extends Mode -  userActivated: false +  isInsertMode: false    # 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 @@ -11,7 +11,7 @@ class InsertMode extends Mode      # Use a blacklist instead of a whitelist because new form controls are still being implemented for html5.      if nodeName == "input" and element.type and not element.type in ["radio", "checkbox"]        return true -     nodeName in ["textarea", "select"] +    nodeName in ["textarea", "select"]    # Embedded elements like Flash and quicktime players can obtain focus but cannot be programmatically    # unfocused. @@ -21,8 +21,12 @@ class InsertMode extends Mode    canEditElement: (element) ->      element and (@isEditable(element) or @isEmbed element) +  # Check whether insert mode is active.  Also, activate insert mode if the current element is editable.    isActive: -> -    @userActivated or @canEditElement document.activeElement +    return true if @isInsertMode +    # FIXME(smblott).  Is there a way to (safely) cache the results of these @canEditElement() calls? +    @activate() if @canEditElement document.activeElement +    @isInsertMode    generateKeyHandler: (type) ->      (event) => @@ -36,35 +40,40 @@ class InsertMode extends Mode          # right thing to do for most common use cases.  However, it could also cripple flash-based sites and          # games.  See discussion in #1211 and #1194.          event.srcElement.blur() -      @userActivated = false -      @updateBadge() +      @isInsertMode = false +      Mode.updateBadge()        Mode.suppressPropagation -  pickBadge: -> -    if @isActive() then "I" else "" +  activate: -> +    @isInsertMode = true +    Mode.updateBadge() -  updateBadge: -> -    badge = @badge -    @badge = @pickBadge() -    Mode.setBadge() if badge != @badge -    Mode.propagate +  # Override (and re-use) updateBadgeForMode() from Mode.updateBadgeForMode().  Use insert-mode badge only if +  # we're active and no mode higher in stack has already inserted a badge. +  updateBadgeForMode: (badge) -> +    @badge = if @isActive() then "I" else "" +    super badge -  activate: -> -    @userActivated = true -    @updateBadge() +  checkModeState: -> +    previousState = @isInsertMode +    if @isActive() != previousState +      Mode.updateBadge()    constructor: ->      super        name: "insert" -      badge: @pickBadge() +      badge: "I"        keydown: @generateKeyHandler "keydown"        keypress: @generateKeyHandler "keypress"        keyup: @generateKeyHandler "keyup" -    handlerStack.push -      DOMActivate: => @updateBadge() -      focus: => @updateBadge() -      blur: => @updateBadge() +    @handlers.push handlerStack.push +      DOMActivate: => @checkModeState() +      focus: => @checkModeState() +      blur: => @checkModeState() + +    # We may already have been dropped into insert mode.  So check. +    Mode.updateBadge()  root = exports ? window  root.InsertMode = InsertMode diff --git a/content_scripts/mode_passkeys.coffee b/content_scripts/mode_passkeys.coffee index ce9f25d2..82f7596b 100644 --- a/content_scripts/mode_passkeys.coffee +++ b/content_scripts/mode_passkeys.coffee @@ -13,20 +13,31 @@ class PassKeysMode extends Mode      for keyChar in [KeyboardUtils.getKeyChar(event), String.fromCharCode(event.charCode)]        # A key is passed through to the underlying page by returning handlerStack.passDirectlyToPage.        return handlerStack.passDirectlyToPage if keyChar and @isPassKey keyChar -    true +    Mode.propagate -  setState: (response) -> -    if response.isEnabledForUrl? -      @passKeys = (response.isEnabledForUrl and response.passKeys) or "" -    if response.keyQueue? -      @keyQueue = response.keyQueue +  # This is called to set the pass-keys state with various types of request from various sources, so we handle +  # all of these. +  # TODO(smblott) Rationalize this. +  setState: (request) -> +    if request.isEnabledForUrl? +      @passKeys = (request.isEnabledForUrl and request.passKeys) or "" +    if request.enabled? +      @passKeys = (request.enabled and request.passKeys) or "" +    if request.keyQueue? +      @keyQueue = request.keyQueue +    Mode.updateBadge()    constructor: ->      super        name: "passkeys"        keydown: (event) => @handlePassKeyEvent event        keypress: (event) => @handlePassKeyEvent event -      keyup: -> true # Allow event to propagate. +      keyup: -> Mode.propagate + +  # Overriding and re-using updateBadgeForMode() from Mode.updateBadgeForMode(). +  updateBadgeForMode: (badge) -> +    @badge = if @passKeys and not @keyQueue then "P" else "" +    super badge  root = exports ? window  root.PassKeysMode = PassKeysMode diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 59404247..3ce4169d 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -115,15 +115,22 @@ initializePreDomReady = ->    # deactivated.    new Mode      name: "normal" +    badge: "N"      keydown: onKeydown      keypress: onKeypress      keyup: onKeyup +    # Overriding updateBadgeForMode() from Mode.updateBadgeForMode(). +    updateBadgeForMode: (badge) -> +      badge.badge ||= @badge +      badge.badge = "" unless isEnabledForUrl +    # Initialize the scroller. The scroller install 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). +  # Note.  There's no need to explicitly Mode.updateBadge().  The new InsertMode() updates the badge.    passKeysMode = new PassKeysMode()    insertMode = new InsertMode() @@ -151,7 +158,9 @@ initializePreDomReady = ->      getScrollPosition: -> scrollX: window.scrollX, scrollY: window.scrollY      setScrollPosition: (request) -> setScrollPosition request.scrollX, request.scrollY      executePageCommand: executePageCommand -    getActiveState: -> { enabled: isEnabledForUrl, passKeys: passKeys, badge: Mode.getBadge() } +    getActiveState: -> +      Mode.updateBadge() +      return { enabled: isEnabledForUrl, passKeys: passKeys }      setState: setState      currentKeyQueue: (request) -> passKeysMode.setState request @@ -206,7 +215,7 @@ setState = (request) ->  window.addEventListener "focus", ->    # settings may have changed since the frame last had focus    settings.load() -  chrome.runtime.sendMessage({ handler: "frameFocused", frameId: frameId, badge: Mode.getBadge() }) +  chrome.runtime.sendMessage({ handler: "frameFocused", frameId: frameId })  #  # Initialization tasks that must wait for the document to be ready. @@ -549,13 +558,14 @@ checkIfEnabledForUrl = ->    url = window.location.toString()    chrome.runtime.sendMessage { handler: "isEnabledForUrl", url: url }, (response) -> -    passKeysMode.setState response      isEnabledForUrl = response.isEnabledForUrl      if (isEnabledForUrl)        initializeWhenEnabled(response.passKeys)      else if (HUD.isReady())        # Quickly hide any HUD we might already be showing, e.g. if we entered insert mode on page load.        HUD.hide() +    passKeysMode.setState response +    Mode.updateBadge()  refreshCompletionKeys = (response) ->    if (response) diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee index 8929fa53..8de6ec12 100644 --- a/lib/handler_stack.coffee +++ b/lib/handler_stack.coffee @@ -26,7 +26,7 @@ class HandlerStack          @currentId = handler.id          passThrough = handler[type].call(@, event)          if not passThrough -          DomUtils.suppressEvent(event) +          DomUtils.suppressEvent(event) if @isChromeEvent event            return false          # If the constant @passDirectlyToPage is returned, then discontinue further bubbling and pass the          # event through to the underlying page.  The event is not suppresssed. @@ -41,5 +41,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. +  isChromeEvent: (event) -> +    event?.preventDefault? and event?.stopImmediatePropagation? +  root.HandlerStack = HandlerStack  root.handlerStack = new HandlerStack | 
