aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--content_scripts/vimium_frontend.coffee3
-rw-r--r--lib/dom_utils.coffee31
-rw-r--r--tests/dom_tests/dom_tests.coffee41
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 ->