aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--background_scripts/main.coffee7
-rw-r--r--content_scripts/mode.coffee30
-rw-r--r--content_scripts/mode_insert.coffee49
-rw-r--r--content_scripts/mode_passkeys.coffee25
-rw-r--r--content_scripts/vimium_frontend.coffee16
-rw-r--r--lib/handler_stack.coffee7
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