diff options
| author | Stephen Blott | 2015-01-11 15:01:12 +0000 | 
|---|---|---|
| committer | Stephen Blott | 2015-01-11 15:54:47 +0000 | 
| commit | 8066a3838ef44b010f6dfb46cea8b47c6bdfc087 (patch) | |
| tree | 209fe896b61e34c6d622584d9e0d7773b288828e | |
| parent | f76c15c6ae6565c0c08569a127974dfd3383ed88 (diff) | |
| download | vimium-8066a3838ef44b010f6dfb46cea8b47c6bdfc087.tar.bz2 | |
Modes; yet more tweaks, yet more tests.
| -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 == "" | 
