aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Blott2015-01-11 15:01:12 +0000
committerStephen Blott2015-01-11 15:54:47 +0000
commit8066a3838ef44b010f6dfb46cea8b47c6bdfc087 (patch)
tree209fe896b61e34c6d622584d9e0d7773b288828e
parentf76c15c6ae6565c0c08569a127974dfd3383ed88 (diff)
downloadvimium-8066a3838ef44b010f6dfb46cea8b47c6bdfc087.tar.bz2
Modes; yet more tweaks, yet more tests.
-rw-r--r--content_scripts/mode.coffee14
-rw-r--r--content_scripts/vimium_frontend.coffee42
-rw-r--r--lib/handler_stack.coffee2
-rw-r--r--tests/dom_tests/dom_tests.coffee128
4 files changed, 135 insertions, 51 deletions
diff --git a/content_scripts/mode.coffee b/content_scripts/mode.coffee
index dd797c46..1c6cddb1 100644
--- a/content_scripts/mode.coffee
+++ b/content_scripts/mode.coffee
@@ -44,7 +44,7 @@ count = 0
class Mode
# If Mode.debug is true, then we generate a trace of modes being activated and deactivated on the console, along
# with a list of the currently active modes.
- debug: false
+ debug: true
@modes: []
# Constants; short, readable names for handlerStack event-handler return values.
@@ -205,10 +205,14 @@ class Mode
log: (args...) ->
console.log args...
+ # Return the name of the must-recently activated mode.
+ @top: ->
+ @modes[@modes.length-1]?.name
+
# BadgeMode is a pseudo mode for triggering badge updates on focus changes and state updates. It sits at the
# bottom of the handler stack, and so it receives state changes *after* all other modes, and can override the
# badge choice of the other active modes.
-# Note. We create the the one-and-only instance, here.
+# Note. We create the the one-and-only instance here.
new class BadgeMode extends Mode
constructor: () ->
super
@@ -223,16 +227,18 @@ new class BadgeMode extends Mode
"focus": => @alwaysContinueBubbling -> Mode.updateBadge()
chooseBadge: (badge) ->
- # If we're not enabled, then post an empty badge.
+ # If we're not enabled, then post an empty badge. BadgeMode is last, so this takes priority.
badge.badge = "" unless @enabled
+ # When the registerStateChange event bubbles to the bottom of the stack, all modes have been notified. So
+ # it's now time to update the badge.
registerStateChange: ->
Mode.updateBadge()
# KeySuppressor is a pseudo mode (near the bottom of the stack) which suppresses keyboard events tagged with
# the "vimium_suppress_event" property. This allows modes higher up in the stack to tag events for
# suppression, but only after verifying that no other mode (notably, normal mode) wants to handle the event.
-# Note. We create the the one-and-only instance, here.
+# Note. We create the the one-and-only instance here.
new class KeySuppressor extends Mode
constructor: ->
super
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index ed5844dc..09e19486 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -108,9 +108,9 @@ class NormalMode extends Mode
super
name: "normal"
badge: "N"
- keydown: onKeydown
- keypress: onKeypress
- keyup: onKeyup
+ keydown: (event) => onKeydown.call @, event
+ keypress: (event) => onKeypress.call @, event
+ keyup: (event) => onKeyup.call @, event
chooseBadge: (badge) ->
super badge
@@ -460,7 +460,7 @@ KeydownEvents =
#
# Note that some keys will only register keydown events and not keystroke events, e.g. ESC.
#
-
+# @/this, here, is the the normal-mode Mode object.
onKeypress = (event) ->
keyChar = ""
@@ -471,25 +471,26 @@ onKeypress = (event) ->
# Enter insert mode when the user enables the native find interface.
if (keyChar == "f" && KeyboardUtils.isPrimaryModifierKey(event))
enterInsertModeWithoutShowingIndicator()
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
if (keyChar)
if (findMode)
handleKeyCharForFindMode(keyChar)
DomUtils.suppressEvent(event)
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
else if (!isInsertMode() && !findMode)
if (isPassKey keyChar)
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
if currentCompletionKeys.indexOf(keyChar) != -1 or isValidFirstKey(keyChar)
DomUtils.suppressEvent(event)
keyPort.postMessage({ keyChar:keyChar, frameId:frameId })
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
keyPort.postMessage({ keyChar:keyChar, frameId:frameId })
- return true
+ return @continueBubbling
+# @/this, here, is the the normal-mode Mode object.
onKeydown = (event) ->
keyChar = ""
@@ -529,37 +530,37 @@ onKeydown = (event) ->
exitInsertMode()
DomUtils.suppressEvent event
KeydownEvents.push event
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
else if (findMode)
if (KeyboardUtils.isEscape(event))
handleEscapeForFindMode()
DomUtils.suppressEvent event
KeydownEvents.push event
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
else if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey)
handleDeleteForFindMode()
DomUtils.suppressEvent event
KeydownEvents.push event
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
else if (event.keyCode == keyCodes.enter)
handleEnterForFindMode()
DomUtils.suppressEvent event
KeydownEvents.push event
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
else if (!modifiers)
DomUtils.suppressPropagation(event)
KeydownEvents.push event
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
else if (isShowingHelpDialog && KeyboardUtils.isEscape(event))
hideHelpDialog()
DomUtils.suppressEvent event
KeydownEvents.push event
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
else if (!isInsertMode() && !findMode)
if (keyChar)
@@ -567,7 +568,7 @@ onKeydown = (event) ->
DomUtils.suppressEvent event
KeydownEvents.push event
keyPort.postMessage({ keyChar:keyChar, frameId:frameId })
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
keyPort.postMessage({ keyChar:keyChar, frameId:frameId })
@@ -589,14 +590,15 @@ onKeydown = (event) ->
isValidFirstKey(KeyboardUtils.getKeyChar(event))))
DomUtils.suppressPropagation(event)
KeydownEvents.push event
- return handlerStack.stopBubblingAndTrue
+ return @stopBubblingAndTrue
- return true
+ return @continueBubbling
+# @/this, here, is the the normal-mode Mode object.
onKeyup = (event) ->
- return true unless KeydownEvents.pop event
+ return @continueBubbling unless KeydownEvents.pop event
DomUtils.suppressPropagation(event)
- handlerStack.stopBubblingAndTrue
+ @stopBubblingAndTrue
checkIfEnabledForUrl = ->
url = window.location.toString()
diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee
index d671fb3a..fddc45db 100644
--- a/lib/handler_stack.coffee
+++ b/lib/handler_stack.coffee
@@ -49,7 +49,7 @@ class HandlerStack
result = handler[type].call @, event
@logResult type, event, handler, result if @debug
if not result
- DomUtils.suppressEvent(event) if @isChromeEvent event
+ DomUtils.suppressEvent event if @isChromeEvent event
return false
return true if result == @stopBubblingAndTrue
return false if result == @stopBubblingAndFalse
diff --git a/tests/dom_tests/dom_tests.coffee b/tests/dom_tests/dom_tests.coffee
index d9e7775f..eacd0aed 100644
--- a/tests/dom_tests/dom_tests.coffee
+++ b/tests/dom_tests/dom_tests.coffee
@@ -12,8 +12,8 @@ mockKeyboardEvent = (keyChar) ->
event.preventDefault = -> @suppressed = true
event
-# Some of these tests have side effects on the handler stack and mode. Therefore, we take backups and restore
-# them on tear down.
+# Some of these tests have side effects on the handler stack and active mode. Therefore, we take backups and
+# restore them on tear down.
backupStackState = ->
Mode.backup = Mode.modes[..]
handlerStack.backup = handlerStack.stack[..]
@@ -202,15 +202,15 @@ context "Input focus",
focusInput 1
assert.equal "first", document.activeElement.id
# deactivate the tabbing mode and its overlays
- assert.isTrue Mode.modes[Mode.modes.length-1].name != "insert"
+ assert.isTrue Mode.top() != "insert"
handlerStack.bubbleEvent 'keydown', mockKeyboardEvent("A")
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "insert"
+ assert.isTrue Mode.top() == "insert"
focusInput 100
assert.equal "third", document.activeElement.id
- assert.isTrue Mode.modes[Mode.modes.length-1].name != "insert"
+ assert.isTrue Mode.top() != "insert"
handlerStack.bubbleEvent 'keydown', mockKeyboardEvent("A")
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "insert"
+ assert.isTrue Mode.top() == "insert"
# TODO: these find prev/next link tests could be refactored into unit tests which invoke a function which has
# a tighter contract than goNext(), since they test minor aspects of goNext()'s link matching behavior, and we
@@ -393,19 +393,19 @@ context "Insert-mode trigger",
target:
isContentEditable: true
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "insert"
+ assert.isTrue Mode.top() == "insert"
should "trigger insert mode on focus of text input", ->
document.getElementById("first").focus()
handlerStack.bubbleEvent "focus", { target: document.activeElement }
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "insert"
+ assert.isTrue Mode.top() == "insert"
should "trigger insert mode on focus of password input", ->
document.getElementById("third").focus()
handlerStack.bubbleEvent "focus", { target: document.activeElement }
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "insert"
+ assert.isTrue Mode.top() == "insert"
should "not trigger insert mode on focus of contentEditable elements", ->
new InsertModeBlocker()
@@ -413,21 +413,21 @@ context "Insert-mode trigger",
target:
isContentEditable: true
- assert.isTrue Mode.modes[Mode.modes.length-1].name != "insert"
+ assert.isTrue Mode.top() != "insert"
should "not trigger insert mode on focus of text input", ->
new InsertModeBlocker()
document.getElementById("first").focus()
handlerStack.bubbleEvent "focus", { target: document.activeElement }
- assert.isTrue Mode.modes[Mode.modes.length-1].name != "insert"
+ assert.isTrue Mode.top() != "insert"
should "not trigger insert mode on focus of password input", ->
new InsertModeBlocker()
document.getElementById("third").focus()
handlerStack.bubbleEvent "focus", { target: document.activeElement }
- assert.isTrue Mode.modes[Mode.modes.length-1].name != "insert"
+ assert.isTrue Mode.top() != "insert"
context "Mode utilities",
setup ->
@@ -458,10 +458,11 @@ context "Mode utilities",
super()
assert.isTrue count == 0
- new Test()
- assert.isTrue count == 1
- new Test()
- assert.isTrue count == 1
+ for [1..10]
+ mode = new Test(); assert.isTrue count == 1
+
+ mode.exit()
+ assert.isTrue count == 0
should "exit on escape", ->
escape =
@@ -471,20 +472,47 @@ context "Mode utilities",
exitOnEscape: true
name: "test"
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "test"
+ assert.isTrue Mode.top() == "test"
+ handlerStack.bubbleEvent "keydown", escape
+ assert.isTrue Mode.top() != "test"
+
+ should "not exit on escape if not enabled", ->
+ escape =
+ keyCode: 27
+ keyIdentifier: ""
+ stopImmediatePropagation: ->
+
+ new Mode
+ exitOnEscape: false
+ name: "test"
+
+ assert.isTrue Mode.top() == "test"
handlerStack.bubbleEvent "keydown", escape
- assert.isTrue Mode.modes[Mode.modes.length-1].name != "test"
+ assert.isTrue Mode.top() == "test"
should "exit on blur", ->
element = document.getElementById("first")
+ element.focus()
new Mode
exitOnBlur: element
name: "test"
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "test"
+ assert.isTrue Mode.top() == "test"
handlerStack.bubbleEvent "blur", { srcElement: element }
- assert.isTrue Mode.modes[Mode.modes.length-1].name != "test"
+ assert.isTrue Mode.top() != "test"
+
+ should "not exit on blur if not enabled", ->
+ element = document.getElementById("first")
+ element.focus()
+
+ new Mode
+ exitOnBlur: null
+ name: "test"
+
+ assert.isTrue Mode.top() == "test"
+ handlerStack.bubbleEvent "blur", { srcElement: element }
+ assert.isTrue Mode.top() == "test"
should "register state change", ->
enabled = null
@@ -623,21 +651,69 @@ context "PostFindMode",
should "be clickable to focus", ->
new PostFindMode @element
- assert.isTrue Mode.modes[Mode.modes.length-1].name != "insert"
+ assert.isTrue Mode.top() != "insert"
handlerStack.bubbleEvent "click", { target: document.activeElement }
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "insert"
+ assert.isTrue Mode.top() == "insert"
should "enter insert mode on immediate escape", ->
new PostFindMode @element
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "post-find"
+ assert.isTrue Mode.top() == "post-find"
handlerStack.bubbleEvent "keydown", @escape
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "insert"
+ assert.isTrue Mode.top() == "insert"
should "not enter insert mode on subsequent escape", ->
new PostFindMode @element
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "post-find"
+ assert.isTrue Mode.top() == "post-find"
handlerStack.bubbleEvent "keydown", mockKeyboardEvent "u"
handlerStack.bubbleEvent "keydown", @escape
- assert.isTrue Mode.modes[Mode.modes.length-1].name == "post-find"
+ assert.isTrue Mode.top() == "post-find"
+
+context "Mode badges",
+ setup ->
+ backupStackState()
+
+ tearDown ->
+ restoreStackState()
+
+ should "have an N badge without passKeys", ->
+ handlerStack.bubbleEvent "registerStateChange",
+ enabled: true
+ passKeys: ""
+
+ handlerStack.bubbleEvent "updateBadge", badge = { badge: "" }
+ assert.isTrue badge.badge == "N"
+
+ should "have an P badge with passKeys", ->
+ handlerStack.bubbleEvent "registerStateChange",
+ enabled: true
+ passKeys: "p"
+
+ handlerStack.bubbleEvent "updateBadge", badge = { badge: "" }
+ assert.isTrue badge.badge == "P"
+
+ should "have an I badge in insert mode", ->
+ handlerStack.bubbleEvent "registerStateChange",
+ enabled: true
+ passKeys: ""
+
+ handlerStack.bubbleEvent "updateBadge", badge = { badge: "" }
+ assert.isTrue badge.badge == "N"
+
+ insertMode = new InsertMode()
+ handlerStack.bubbleEvent "updateBadge", badge = { badge: "" }
+ assert.isTrue badge.badge == "I"
+
+ insertMode.exit()
+ handlerStack.bubbleEvent "updateBadge", badge = { badge: "" }
+ assert.isTrue badge.badge == "N"
+
+ should "have no badge when disabled", ->
+ handlerStack.bubbleEvent "registerStateChange",
+ enabled: false
+ passKeys: ""
+
+ new InsertMode()
+ handlerStack.bubbleEvent "updateBadge", badge = { badge: "" }
+ assert.isTrue badge.badge == ""