diff options
| -rw-r--r-- | content_scripts/mode.coffee | 14 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 42 | ||||
| -rw-r--r-- | lib/handler_stack.coffee | 2 | ||||
| -rw-r--r-- | tests/dom_tests/dom_tests.coffee | 128 |
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 == "" |
