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 |
