diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | content_scripts/link_hints.coffee | 6 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 45 | ||||
| -rw-r--r-- | content_scripts/vomnibar.coffee | 4 | ||||
| -rw-r--r-- | lib/dom_utils.coffee | 4 | ||||
| -rw-r--r-- | lib/handler_stack.coffee | 37 | ||||
| -rw-r--r-- | manifest.json | 1 | ||||
| -rw-r--r-- | test_harnesses/vomnibar.html | 2 | ||||
| -rw-r--r-- | tests/dom_tests/dom_tests.coffee | 4 | ||||
| -rw-r--r-- | tests/dom_tests/dom_tests.html | 1 | ||||
| -rw-r--r-- | tests/unit_tests/handler_stack_test.coffee | 52 |
11 files changed, 119 insertions, 38 deletions
@@ -3,3 +3,4 @@ *.swp *.crx *.js +node_modules/* diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index a47f9263..7faa1a65 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -63,7 +63,7 @@ LinkHints = { id: "vimiumHintMarkerContainer", className: "vimiumReset" }) # handlerStack is declared by vimiumFrontend.js - handlerStack.push({ + @handlerId = handlerStack.push({ keydown: @onKeyDownInMode.bind(this, hintMarkers), # trap all key events keypress: -> false @@ -163,7 +163,7 @@ LinkHints = keyup: (event) -> return if (event.keyCode != keyCodes.shiftKey) LinkHints.setOpenLinkMode(!LinkHints.shouldOpenInNewTab, LinkHints.shouldOpenWithQueue, false) - handlerStack.pop() + @remove() }) # TODO(philc): Ignore keys that have modifiers. @@ -231,7 +231,7 @@ LinkHints = if (LinkHints.hintMarkerContainingDiv) DomUtils.removeElement LinkHints.hintMarkerContainingDiv LinkHints.hintMarkerContainingDiv = null - handlerStack.pop() + handlerStack.remove @handlerId HUD.hide() @isActive = false diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 29161ec6..5e5b4958 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -10,7 +10,7 @@ findModeQuery = { rawQuery: "" } findModeQueryHasResults = false findModeAnchorNode = null isShowingHelpDialog = false -handlerStack = [] +handlerStack = new HandlerStack keyPort = null # Users can disable Vimium on URL patterns via the settings page. isEnabledForUrl = true @@ -355,7 +355,9 @@ extend window, visibleInputs[selectedInputIndex].element.focus() else unless event.keyCode == KeyboardUtils.keyCodes.shiftKey DomUtils.removeElement hintContainingDiv - handlerStack.pop() + @remove() + + false # # Sends everything except i & ESC to the handler in background_page. i & ESC are special because they control @@ -365,7 +367,7 @@ extend window, # Note that some keys will only register keydown events and not keystroke events, e.g. ESC. # onKeypress = (event) -> - return unless bubbleEvent('keypress', event) + return unless handlerStack.bubbleEvent('keypress', event) keyChar = "" @@ -381,32 +383,15 @@ onKeypress = (event) -> if (keyChar) if (findMode) handleKeyCharForFindMode(keyChar) - suppressEvent(event) + DomUtils.suppressEvent(event) else if (!isInsertMode() && !findMode) if (currentCompletionKeys.indexOf(keyChar) != -1) - suppressEvent(event) + DomUtils.suppressEvent(event) keyPort.postMessage({ keyChar:keyChar, frameId:frameId }) -# -# Called whenever we receive a key event. Each individual handler has the option to stop the event's -# propagation by returning a falsy value. -# -bubbleEvent = (type, event) -> - for i in [(handlerStack.length - 1)..0] - # We need to check for existence of handler because the last function call may have caused the release of - # more than one handler. - if (handlerStack[i] && handlerStack[i][type] && !handlerStack[i][type](event)) - suppressEvent(event) - return false - true - -suppressEvent = (event) -> - event.preventDefault() - event.stopPropagation() - onKeydown = (event) -> - return unless bubbleEvent('keydown', event) + return unless handlerStack.bubbleEvent('keydown', event) keyChar = "" @@ -442,20 +427,20 @@ onKeydown = (event) -> if (isEditable(event.srcElement)) event.srcElement.blur() exitInsertMode() - suppressEvent(event) + DomUtils.suppressEvent(event) else if (findMode) if (KeyboardUtils.isEscape(event)) handleEscapeForFindMode() - suppressEvent(event) + DomUtils.suppressEvent(event) else if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) handleDeleteForFindMode() - suppressEvent(event) + DomUtils.suppressEvent(event) else if (event.keyCode == keyCodes.enter) handleEnterForFindMode() - suppressEvent(event) + DomUtils.suppressEvent(event) else if (!modifiers) event.stopPropagation() @@ -466,7 +451,7 @@ onKeydown = (event) -> else if (!isInsertMode() && !findMode) if (keyChar) if (currentCompletionKeys.indexOf(keyChar) != -1) - suppressEvent(event) + DomUtils.suppressEvent(event) keyPort.postMessage({ keyChar:keyChar, frameId:frameId }) @@ -485,7 +470,7 @@ onKeydown = (event) -> isValidFirstKey(KeyboardUtils.getKeyChar(event)))) event.stopPropagation() -onKeyup = () -> return unless bubbleEvent('keyup', event) +onKeyup = (event) -> return unless handlerStack.bubbleEvent('keyup', event) checkIfEnabledForUrl = -> url = window.location.toString() @@ -750,7 +735,7 @@ findAndFocus = (backwards) -> if (elementCanTakeInput) handlerStack.push({ keydown: (event) -> - handlerStack.pop() + @remove() if (KeyboardUtils.isEscape(event)) DomUtils.simulateSelect(document.activeElement) enterInsertModeWithoutShowingIndicator(document.activeElement) diff --git a/content_scripts/vomnibar.coffee b/content_scripts/vomnibar.coffee index 32d5dbac..724f4874 100644 --- a/content_scripts/vomnibar.coffee +++ b/content_scripts/vomnibar.coffee @@ -52,13 +52,13 @@ class VomnibarUI show: -> @box.style.display = "block" @input.focus() - handlerStack.push({ keydown: @onKeydown.bind(this) }) + @handlerId = handlerStack.push keydown: @onKeydown.bind @ hide: -> @box.style.display = "none" @completionList.style.display = "none" @input.blur() - handlerStack.pop() + handlerStack.remove @handlerId reset: -> @input.value = "" diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 30530e0d..501e43a5 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -131,5 +131,9 @@ DomUtils = document.documentElement.appendChild(flashEl) setTimeout((-> DomUtils.removeElement flashEl), 400) + suppressEvent: (event) -> + event.preventDefault() + event.stopPropagation() + root = exports ? window root.DomUtils = DomUtils diff --git a/lib/handler_stack.coffee b/lib/handler_stack.coffee new file mode 100644 index 00000000..858f2ec9 --- /dev/null +++ b/lib/handler_stack.coffee @@ -0,0 +1,37 @@ +root = exports ? window + +class root.HandlerStack + + constructor: -> + @stack = [] + @counter = 0 + + genId: -> @counter = ++@counter & 0xffff + + # Adds a handler to the stack. Returns a unique ID for that handler that can be used to remove it later. + push: (handler) -> + handler.id = @genId() + @stack.push handler + handler.id + + # Called whenever we receive a key event. Each individual handler has the option to stop the event's + # propagation by returning a falsy value. + bubbleEvent: (type, event) -> + for i in [(@stack.length - 1)..0] by -1 + handler = @stack[i] + # We need to check for existence of handler because the last function call may have caused the release + # of more than one handler. + if handler && handler[type] + @currentId = handler.id + passThrough = handler[type].call(@, event) + if not passThrough + DomUtils.suppressEvent(event) + return false + true + + remove: (id = @currentId) -> + for i in [(@stack.length - 1)..0] by -1 + handler = @stack[i] + if handler.id == id + @stack.splice(i, 1) + break diff --git a/manifest.json b/manifest.json index 8ec9d400..bfccb920 100644 --- a/manifest.json +++ b/manifest.json @@ -30,6 +30,7 @@ "js": ["lib/utils.js", "lib/keyboard_utils.js", "lib/dom_utils.js", + "lib/handler_stack.js", "lib/clipboard.js", "content_scripts/link_hints.js", "content_scripts/vomnibar.js", diff --git a/test_harnesses/vomnibar.html b/test_harnesses/vomnibar.html index af1d0a19..f9528548 100644 --- a/test_harnesses/vomnibar.html +++ b/test_harnesses/vomnibar.html @@ -15,7 +15,7 @@ <link rel="stylesheet" type="text/css" href="../vimium.css" /> <script> function setup() { - window.handlerStack = []; + window.handlerStack = new HandlerStack(); // This itemHtml was obtained just by copying and pasting what was generated when using it in practice. var itemHtml = '<span class="source">history</span> http://<span class="fuzzyMatch">n</span><span class="fuzzyMatch">i</span><span class="fuzzyMatch">n</span><span class="fuzzyMatch">j</span><span class="fuzzyMatch">a</span>words.com/info/about <span class="title">Ninjawords - a really fast dictionary</span>'; var results = [{ html: itemHtml }, { html: itemHtml }]; diff --git a/tests/dom_tests/dom_tests.coffee b/tests/dom_tests/dom_tests.coffee index 3d981ee5..a0254acf 100644 --- a/tests/dom_tests/dom_tests.coffee +++ b/tests/dom_tests/dom_tests.coffee @@ -177,11 +177,11 @@ context "Input focus", focusInput 1 assert.equal "first", document.activeElement.id # deactivate the tabbing mode and its overlays - handlerStack[handlerStack.length - 1].keydown mockKeyboardEvent("A") + handlerStack.bubbleEvent 'keydown', mockKeyboardEvent("A") focusInput 100 assert.equal "third", document.activeElement.id - handlerStack[handlerStack.length - 1].keydown mockKeyboardEvent("A") + handlerStack.bubbleEvent 'keydown', mockKeyboardEvent("A") Tests.outputMethod = (args...) -> newOutput = args.join "\n" diff --git a/tests/dom_tests/dom_tests.html b/tests/dom_tests/dom_tests.html index 1dc45782..c658235b 100644 --- a/tests/dom_tests/dom_tests.html +++ b/tests/dom_tests/dom_tests.html @@ -32,6 +32,7 @@ <script type="text/javascript" src="../../lib/utils.js"></script> <script type="text/javascript" src="../../lib/keyboard_utils.js"></script> <script type="text/javascript" src="../../lib/dom_utils.js"></script> + <script type="text/javascript" src="../../lib/handler_stack.js"></script> <script type="text/javascript" src="../../lib/clipboard.js"></script> <script type="text/javascript" src="../../content_scripts/link_hints.js"></script> <script type="text/javascript" src="../../content_scripts/vomnibar.js"></script> diff --git a/tests/unit_tests/handler_stack_test.coffee b/tests/unit_tests/handler_stack_test.coffee new file mode 100644 index 00000000..0ed8f4c0 --- /dev/null +++ b/tests/unit_tests/handler_stack_test.coffee @@ -0,0 +1,52 @@ +require "./test_helper.js" +extend(global, require "../../lib/handler_stack.js") + +context "handlerStack", + setup -> + stub global, "DomUtils", {} + stub DomUtils, "suppressEvent", -> + @handlerStack = new HandlerStack + @handler1Called = false + @handler2Called = false + + should "bubble events", -> + @handlerStack.push { keydown: => @handler1Called = true } + @handlerStack.push { keydown: => @handler2Called = true } + @handlerStack.bubbleEvent 'keydown', {} + assert.isTrue @handler2Called + assert.isTrue @handler1Called + + should "terminate bubbling on falsy return value", -> + @handlerStack.push { keydown: => @handler1Called = true } + @handlerStack.push { keydown: => @handler2Called = true; false } + @handlerStack.bubbleEvent 'keydown', {} + assert.isTrue @handler2Called + assert.isFalse @handler1Called + + should "remove handlers correctly", -> + @handlerStack.push { keydown: => @handler1Called = true } + handlerId = @handlerStack.push { keydown: => @handler2Called = true } + @handlerStack.remove handlerId + @handlerStack.bubbleEvent 'keydown', {} + assert.isFalse @handler2Called + assert.isTrue @handler1Called + + should "remove handlers correctly", -> + handlerId = @handlerStack.push { keydown: => @handler1Called = true } + @handlerStack.push { keydown: => @handler2Called = true } + @handlerStack.remove handlerId + @handlerStack.bubbleEvent 'keydown', {} + assert.isTrue @handler2Called + assert.isFalse @handler1Called + + should "handle self-removing handlers correctly", -> + ctx = @ + @handlerStack.push { keydown: => @handler1Called = true } + @handlerStack.push { keydown: -> + ctx.handler2Called = true + @remove() + } + @handlerStack.bubbleEvent 'keydown', {} + assert.isTrue @handler2Called + assert.isTrue @handler1Called + assert.equal @handlerStack.stack.length, 1 |
