diff options
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 3 | ||||
| -rw-r--r-- | lib/dom_utils.coffee | 31 | ||||
| -rw-r--r-- | tests/dom_tests/dom_tests.coffee | 41 | 
3 files changed, 63 insertions, 12 deletions
| diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 8342abb8..836acecd 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -797,8 +797,7 @@ executeFind = (query, options) ->    # previous find landed in an editable element, then that element may still be activated.  In this case, we    # don't want to leave it behind (see #1412).    if document.activeElement and DomUtils.isEditable document.activeElement -    if not DomUtils.isSelected document.activeElement -      document.activeElement.blur() +    document.activeElement.blur() unless DomUtils.isSelected document.activeElement    # we need to save the anchor node here because <esc> seems to nullify it, regardless of whether we do    # preventDefault() diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index f53e28d1..0231f994 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -167,15 +167,20 @@ DomUtils =        node = node.parentNode      false -  # True if element contains the active selection range. +  # True if element is editable and contains the active selection range.    isSelected: (element) -> +    selection = document.getSelection()      if element.isContentEditable -      node = document.getSelection()?.anchorNode +      node = selection.anchorNode        node and @isDOMDescendant element, node      else -      # Note.  This makes the wrong decision if the user has placed the caret at the start of element.  We -      # cannot distinguish that case from the user having made no selection. -      element.selectionStart? and element.selectionEnd? and element.selectionEnd != 0 +      if selection.type == "Range" and selection.isCollapsed +	      # The selection is inside the Shadow DOM of a node. We can check the node it registers as being +	      # before, since this represents the node whose Shadow DOM it's inside. +        containerNode = selection.anchorNode.childNodes[selection.anchorOffset] +        element == containerNode # True if the selection is inside the Shadow DOM of our element. +      else +        false    simulateSelect: (element) ->      # If element is already active, then we don't move the selection.  However, we also won't get a new focus @@ -185,11 +190,17 @@ DomUtils =        handlerStack.bubbleEvent "click", target: element      else        element.focus() -      unless @isSelected element -        # When focusing a textbox (without an existing selection), put the selection caret at the end of the -        # textbox's contents.  For some HTML5 input types (eg. date) we can't position the caret, so we wrap -        # this with a try. -        try element.setSelectionRange(element.value.length, element.value.length) +      # If the cursor is at the start of the element's contents, send it to the end. Motivation: +      # * the end is a more useful place to focus than the start, +      # * this way preserves the last used position (except when it's at the beginning), so the user can +      #   'resume where they left off'. +      # NOTE(mrmr1993): Some elements throw an error when we try to access their selection properties, so +      # wrap this with a try. +      try +        if element.selectionStart == 0 and element.selectionEnd == 0 +          element.setSelectionRange element.value.length, element.value.length + +    simulateClick: (element, modifiers) ->      modifiers ||= {} diff --git a/tests/dom_tests/dom_tests.coffee b/tests/dom_tests/dom_tests.coffee index a1ca8723..600616a9 100644 --- a/tests/dom_tests/dom_tests.coffee +++ b/tests/dom_tests/dom_tests.coffee @@ -84,6 +84,47 @@ createGeneralHintTests = (isFilteredMode) ->  createGeneralHintTests false  createGeneralHintTests true +inputs = [] +context "Test link hints for focusing input elements correctly", + +  setup -> +    initializeModeState() +    testDiv = document.getElementById("test-div") +    testDiv.innerHTML = "" + +    stub settings.values, "filterLinkHints", false +    stub settings.values, "linkHintCharacters", "ab" + +    # Every HTML5 input type except for hidden. We should be able to activate all of them with link hints. +    inputTypes = ["button", "checkbox", "color", "date", "datetime", "datetime-local", "email", "file", +      "image", "month", "number", "password", "radio", "range", "reset", "search", "submit", "tel", "text", +      "time", "url", "week"] + +    for type in inputTypes +      input = document.createElement "input" +      input.type = type +      testDiv.appendChild input +      inputs.push input + +  tearDown -> +    document.getElementById("test-div").innerHTML = "" + +  should "Focus each input when its hint text is typed", -> +    for input in inputs +      input.scrollIntoView() # Ensure the element is visible so we create a link hint for it. + +      activeListener = ensureCalled (event) -> +        input.blur() if event.type == "focus" +      input.addEventListener "focus", activeListener, false +      input.addEventListener "click", activeListener, false + +      LinkHints.activateMode() +      [hint] = getHintMarkers().filter (hint) -> input == hint.clickableItem +      sendKeyboardEvent char for char in hint.hintString + +      input.removeEventListener "focus", activeListener, false +      input.removeEventListener "click", activeListener, false +  context "Alphabetical link hints",    setup -> | 
