aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Blott2016-03-28 05:51:46 +0100
committerStephen Blott2016-03-28 05:51:46 +0100
commit2a62e4811fc2360257dd99066b4caa3e95025cbf (patch)
tree1a9a59629600a849aa096d19e7db3681828dcd1d
parent3539ad89382c02bf2f89d890b86e49409059391f (diff)
parent00c9e9e1a0c26fc8ece99318bf5eec85a090ecc2 (diff)
downloadvimium-2a62e4811fc2360257dd99066b4caa3e95025cbf.tar.bz2
Merge pull request #2048 from smblott-github/global-link-hints
Global link hints
-rw-r--r--background_scripts/main.coffee26
-rw-r--r--content_scripts/link_hints.coffee282
-rw-r--r--content_scripts/vimium_frontend.coffee5
-rw-r--r--lib/dom_utils.coffee19
-rw-r--r--tests/dom_tests/dom_tests.coffee28
5 files changed, 211 insertions, 149 deletions
diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee
index 4ce8c03a..2fe2fa99 100644
--- a/background_scripts/main.coffee
+++ b/background_scripts/main.coffee
@@ -319,6 +319,31 @@ cycleToFrame = (frames, frameId, count = 0) ->
count = (count + Math.max 0, frames.indexOf frameId) % frames.length
[frames[count..]..., frames[0...count]...]
+HintCoordinator =
+ tabState: {}
+
+ onMessage: (request, {tab: {id: tabId}}) ->
+ if request.messageType of this
+ this[request.messageType] tabId, request
+ else
+ # If there's no handler here, then the message is bounced to all frames in the sender's tab.
+ @sendMessage request.messageType, tabId, request
+
+ sendMessage: (messageType, tabId, request = {}) ->
+ chrome.tabs.sendMessage tabId, extend request, {name: "linkHintsMessage", messageType}
+
+ prepareToActivateMode: (tabId, {frameId: originatingFrameId, modeIndex}) ->
+ @tabState[tabId] = {frameIds: frameIdsForTab[tabId], hintDescriptors: [], originatingFrameId, modeIndex}
+ @sendMessage "getHintDescriptors", tabId
+
+ # Receive hint descriptors from all frames and activate link-hints mode when we have them all.
+ postHintDescriptors: (tabId, {frameId, hintDescriptors}) ->
+ @tabState[tabId].hintDescriptors.push hintDescriptors...
+ @tabState[tabId].frameIds = @tabState[tabId].frameIds.filter (fId) -> fId != frameId
+ if @tabState[tabId].frameIds.length == 0
+ @sendMessage "activateMode", tabId, @tabState[tabId]
+ delete @tabState[tabId] # We won't be needing this any more.
+
# Port handler mapping
portHandlers =
completions: handleCompletions
@@ -344,6 +369,7 @@ sendRequestHandlers =
# Send a message to all frames in the current tab.
sendMessageToFrames: (request, sender) -> chrome.tabs.sendMessage sender.tab.id, request.message
fetchFileContents: (request, sender) -> fetchFileContents request.fileName
+ linkHintsMessage: HintCoordinator.onMessage.bind HintCoordinator
# For debugging only. This allows content scripts to log messages to the extension's logging page.
log: ({frameId, message}, sender) -> BgUtils.log "#{frameId} #{message}", sender
diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee
index 6f99d970..3088812b 100644
--- a/content_scripts/link_hints.coffee
+++ b/content_scripts/link_hints.coffee
@@ -46,14 +46,50 @@ DOWNLOAD_LINK_URL =
indicator: "Download link URL."
clickModifiers: altKey: true, ctrlKey: false, metaKey: false
+availableModes = [OPEN_IN_CURRENT_TAB, OPEN_IN_NEW_BG_TAB, OPEN_IN_NEW_FG_TAB, OPEN_WITH_QUEUE, COPY_LINK_URL,
+ OPEN_INCOGNITO, DOWNLOAD_LINK_URL]
+
+HintCoordinator =
+ onExit: []
+
+ sendMessage: (messageType, request = {}) ->
+ chrome.runtime.sendMessage extend request, {handler: "linkHintsMessage", messageType, frameId}
+
+ prepareToActivateMode: (mode, onExit) ->
+ @onExit = [onExit]
+ @sendMessage "prepareToActivateMode", modeIndex: availableModes.indexOf mode
+
+ getHintDescriptors: ->
+ @localHints = LocalHints.getLocalHints()
+ @sendMessage "postHintDescriptors", hintDescriptors:
+ @localHints.map ({rect, linkText, showLinkText, hasHref}, localIndex) ->
+ {rect, linkText, showLinkText, hasHref, frameId, localIndex}
+
+ # We activate LinkHintsMode() in every frame and provide every frame with exactly the same hint descriptors.
+ # We also propagate the key state between frames. Therefore, the hint-selection process proceeds in lock
+ # step in every frame, and @linkHintsMode is in the same state in every frame.
+ activateMode: ({hintDescriptors, modeIndex, originatingFrameId}) ->
+ @onExit = [] unless frameId == originatingFrameId
+ @linkHintsMode = new LinkHintsMode hintDescriptors, availableModes[modeIndex]
+
+ # The following messages are exchanged between frames while link-hints mode is active.
+ updateKeyState: (request) -> @linkHintsMode.updateKeyState request
+ setOpenLinkMode: ({modeIndex}) -> @linkHintsMode.setOpenLinkMode availableModes[modeIndex], false
+ activateActiveHintMarker: -> @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker
+ getLocalHintMarker: (hint) -> if hint.frameId == frameId then @localHints[hint.localIndex] else null
+
+ exit: ->
+ @onExit.pop()() while 0 < @onExit.length
+ @linkHintsMode = @localHints = null
+
+ deactivate: ->
+ @onExit = [=> @linkHintsMode.deactivateMode()]
+ @exit()
+
LinkHints =
activateMode: (count = 1, mode = OPEN_IN_CURRENT_TAB) ->
- if 0 < count
- new LinkHintsMode mode, (event = null) ->
- # This is called which LinkHintsMode exits. Escape and Backspace are the two ways in which hints mode
- # can exit following which we do not restart hints mode.
- return if event?.type == "keydown" and KeyboardUtils.isEscape event
- return if event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]
+ if 0 < count or mode is OPEN_WITH_QUEUE
+ HintCoordinator.prepareToActivateMode mode, ->
# Wait for the next tick to allow the previous mode to exit. It might yet generate a click event,
# which would cause our new mode to exit immediately.
Utils.nextTick -> LinkHints.activateMode count-1, mode
@@ -65,7 +101,7 @@ LinkHints =
activateModeToOpenIncognito: (count) -> @activateMode count, OPEN_INCOGNITO
activateModeToDownloadLink: (count) -> @activateMode count, DOWNLOAD_LINK_URL
-class LinkHintsMode
+class LinkHintsModeBase # This is temporary, because the "visible hints" code is embedded in the hints class.
hintMarkerContainingDiv: null
# One of the enums listed at the top of this file.
mode: undefined
@@ -76,20 +112,13 @@ class LinkHintsMode
# A count of the number of Tab presses since the last non-Tab keyboard event.
tabCount: 0
- constructor: (mode = OPEN_IN_CURRENT_TAB, onExit = (->)) ->
+ constructor: (elements, mode = OPEN_IN_CURRENT_TAB) ->
# we need documentElement to be ready in order to append links
return unless document.documentElement
- elements = @getVisibleClickableElements()
# For these modes, we filter out those elements which don't have an HREF (since there's nothing we can do
# with them).
- elements = (el for el in elements when el.element.href?) if mode in [ COPY_LINK_URL, OPEN_INCOGNITO ]
- if Settings.get "filterLinkHints"
- # When using text filtering, we sort the elements such that we visit descendants before their ancestors.
- # This allows us to exclude the text used for matching descendants from that used for matching their
- # ancestors.
- length = (el) -> el.element.innerHTML?.length ? 0
- elements.sort (a,b) -> length(a) - length b
+ elements = (el for el in elements when el.hasHref) if mode in [ COPY_LINK_URL, OPEN_INCOGNITO ]
if elements.length == 0
HUD.showForDuration "No links to select.", 2000
@@ -110,21 +139,23 @@ class LinkHintsMode
keydown: @onKeyDownInMode.bind this, hintMarkers
keypress: @onKeyPressInMode.bind this, hintMarkers
- @hintMode.onExit =>
- @deactivateMode()
- @hintMode.onExit onExit
+ @hintMode.onExit (event) =>
+ HintCoordinator.sendMessage "deactivate" if event?.type == "click" or (event?.type == "keydown" and
+ (KeyboardUtils.isEscape(event) or event.keyCode in [keyCodes.backspace, keyCodes.deleteKey]))
- @setOpenLinkMode mode
+ @setOpenLinkMode mode, false
# Note(philc): Append these markers as top level children instead of as child nodes to the link itself,
# because some clickable elements cannot contain children, e.g. submit buttons.
@hintMarkerContainingDiv = DomUtils.addElementList hintMarkers,
id: "vimiumHintMarkerContainer", className: "vimiumReset"
+ @hideMarker hintMarker for hintMarker in hintMarkers when hintMarker.hintDescriptor.frameId != frameId
+ @updateKeyState = @updateKeyState.bind this, hintMarkers # TODO(smblott): This can be refactored out.
- setOpenLinkMode: (@mode) ->
- clickActivator = (modifiers) -> (link) -> DomUtils.simulateClick link, modifiers
- @linkActivator = @mode.linkActivator ? clickActivator @mode.clickModifiers
- @hintMode.setIndicator @mode.indicator
+ setOpenLinkMode: (@mode, shouldPropagateToOtherFrames = true) ->
+ @hintMode.setIndicator @mode.indicator if windowIsFocused()
+ if shouldPropagateToOtherFrames
+ HintCoordinator.sendMessage "setOpenLinkMode", modeIndex: availableModes.indexOf @mode
#
# Creates a link marker for the given link.
@@ -135,17 +166,20 @@ class LinkHintsMode
(link) ->
marker = DomUtils.createElement "div"
marker.className = "vimiumReset internalVimiumHintMarker vimiumHintMarker"
- marker.clickableItem = link.element
marker.stableSortCount = ++stableSortCount
+ # Extract other relevant fields from the hint descriptor. TODO(smblott) "link" here is misleading.
+ extend marker,
+ {hintDescriptor: link, linkText: link.linkText, showLinkText: link.showLinkText, rect: link.rect}
clientRect = link.rect
marker.style.left = clientRect.left + window.scrollX + "px"
marker.style.top = clientRect.top + window.scrollY + "px"
- marker.rect = link.rect
-
marker
+# TODO(smblott) This is temporary. Unfortunately, this code is embedded in the "old" link-hints mode class.
+# It should be moved, but it's left here for the moment to help keep the diff clearer.
+LocalHints =
#
# Determine whether the element is visible and clickable. If it is, find the rect bounding the element in
# the viewport. There may be more than one part of element which is clickable (for example, if it's an
@@ -219,6 +253,9 @@ class LinkHintsMode
isClickable ||= not element.disabled
when "label"
isClickable ||= element.control? and (@getVisibleClickable element.control).length == 0
+ when "body"
+ isClickable ||= element == document.body and not document.hasFocus() and
+ window.innerWidth > 3 and window.innerHeight > 3
# Elements with tabindex are sometimes useful, but usually not. We can treat them as second class
# citizens when it improves UX, so take special note of them.
@@ -241,7 +278,7 @@ class LinkHintsMode
# Because of this, the rects returned will frequently *NOT* be equivalent to the rects for the whole
# element.
#
- getVisibleClickableElements: ->
+ getLocalHints: ->
elements = document.documentElement.getElementsByTagName "*"
visibleElements = []
@@ -267,7 +304,7 @@ class LinkHintsMode
# + the ancestor of (1) appears later in the DOM than the ancestor of (2).
#
# Remove rects from elements where another clickable element lies above it.
- nonOverlappingElements = []
+ localHints = nonOverlappingElements = []
# Traverse the DOM from first to last, since later elements show above earlier elements.
visibleElements = visibleElements.reverse()
while visibleElement = visibleElements.pop()
@@ -285,7 +322,57 @@ class LinkHintsMode
# click some elements that we could click before.
nonOverlappingElements.push visibleElement unless visibleElement.secondClassCitizen
- nonOverlappingElements
+ hint.hasHref = hint.element.href? for hint in localHints
+ if Settings.get "filterLinkHints"
+ @withLabelMap (labelMap) =>
+ extend hint, @generateLinkText labelMap, hint.element for hint in localHints
+ localHints
+
+ # Generate a map of input element => label text, call a callback with it.
+ withLabelMap: (callback) ->
+ labelMap = {}
+ labels = document.querySelectorAll "label"
+ for label in labels
+ forElement = label.getAttribute "for"
+ if forElement
+ labelText = label.textContent.trim()
+ # Remove trailing ":" commonly found in labels.
+ if labelText[labelText.length-1] == ":"
+ labelText = labelText.substr 0, labelText.length-1
+ labelMap[forElement] = labelText
+ callback labelMap
+
+ generateLinkText: (labelMap, element) ->
+ linkText = ""
+ showLinkText = false
+ # toLowerCase is necessary as html documents return "IMG" and xhtml documents return "img"
+ nodeName = element.nodeName.toLowerCase()
+
+ if nodeName == "input"
+ if labelMap[element.id]
+ linkText = labelMap[element.id]
+ showLinkText = true
+ else if element.type != "password"
+ linkText = element.value
+ if not linkText and 'placeholder' of element
+ linkText = element.placeholder
+ # Check if there is an image embedded in the <a> tag.
+ else if nodeName == "a" and not element.textContent.trim() and
+ element.firstElementChild and
+ element.firstElementChild.nodeName.toLowerCase() == "img"
+ linkText = element.firstElementChild.alt || element.firstElementChild.title
+ showLinkText = true if linkText
+ else if element == document.body
+ linkText = "Frame."
+ showLinkText = true
+ else
+ linkText = (element.textContent.trim() || element.innerHTML.trim())[...512]
+
+ {linkText, showLinkText}
+
+# TODO(smblott) Again, this is temporary. We need to move the code above out of the "old" link-hints class.
+class LinkHintsMode extends LinkHintsModeBase
+ constructor: (args...) -> super args...
# Handles <Shift> and <Ctrl>.
onKeyDownInMode: (hintMarkers, event) ->
@@ -329,7 +416,7 @@ class LinkHintsMode
else if event.keyCode == keyCodes.enter
# Activate the active hint, if there is one. Only FilterHints uses an active hint.
- @activateLink @markerMatcher.activeHintMarker if @markerMatcher.activeHintMarker
+ HintCoordinator.sendMessage "activateActiveHintMarker" if @markerMatcher.activeHintMarker
else if event.keyCode == keyCodes.tab
@tabCount = previousTabCount + (if event.shiftKey then -1 else 1)
@@ -354,6 +441,12 @@ class LinkHintsMode
DomUtils.suppressEvent event
updateVisibleMarkers: (hintMarkers, tabCount = 0) ->
+ {hintKeystrokeQueue, linkTextKeystrokeQueue} = @markerMatcher
+ HintCoordinator.sendMessage "updateKeyState", {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}
+
+ updateKeyState: (hintMarkers, {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) ->
+ extend @markerMatcher, {hintKeystrokeQueue, linkTextKeystrokeQueue}
+
{linksMatched, userMightOverType} = @markerMatcher.getMatchingHints hintMarkers, tabCount
if linksMatched.length == 0
@deactivateMode()
@@ -363,38 +456,52 @@ class LinkHintsMode
@hideMarker marker for marker in hintMarkers
@showMarker matched, @markerMatcher.hintKeystrokeQueue.length for matched in linksMatched
- #
- # When only one link hint remains, this function activates it in the appropriate way.
- #
+ # When only one hint remains, activate it in the appropriate way. The current frame may or may not contain
+ # the matched link, and may or may not have the focus. The resulting four cases are accounted for here by
+ # selectively pushing the appropriate HintCoordinator.onExit handlers.
activateLink: (linkMatched, userMightOverType=false) ->
@removeHintMarkers()
- clickEl = linkMatched.clickableItem
-
- linkActivator = =>
- @deactivateMode()
- if DomUtils.isSelectable clickEl
- DomUtils.simulateSelect clickEl
- else
- # TODO: Are there any other input elements which should not receive focus?
- if clickEl.nodeName.toLowerCase() == "input" and clickEl.type not in ["button", "submit"]
- clickEl.focus()
- @linkActivator clickEl
- LinkHints.activateModeWithQueue() if @mode is OPEN_WITH_QUEUE
-
+ clickEl = HintCoordinator.getLocalHintMarker(linkMatched.hintDescriptor)?.element
+
+ if clickEl?
+ HintCoordinator.onExit.push =>
+ if clickEl == document.body
+ Utils.nextTick -> focusThisFrame highlight: true
+ else if DomUtils.isSelectable clickEl
+ window.focus()
+ DomUtils.simulateSelect clickEl
+ else
+ clickActivator = (modifiers) -> (link) -> DomUtils.simulateClick link, modifiers
+ linkActivator = @mode.linkActivator ? clickActivator @mode.clickModifiers
+ # TODO: Are there any other input elements which should not receive focus?
+ if clickEl.nodeName.toLowerCase() == "input" and clickEl.type not in ["button", "submit"]
+ clickEl.focus()
+ linkActivator clickEl
+
+ installKeyBoardBlocker = (startKeyboardBlocker) ->
+ if linkMatched.hintDescriptor.frameId == frameId
+ flashEl = DomUtils.addFlashRect linkMatched.hintDescriptor.rect
+ HintCoordinator.onExit.push -> DomUtils.removeElement flashEl
+
+ if document.hasFocus()
+ startKeyboardBlocker -> HintCoordinator.sendMessage "exit"
+
+ HintCoordinator.onExit.push => @deactivateMode()
+ # If we're using a keyboard blocker, then the frame with the focus sends the "exit" message, otherwise the
+ # frame containing the matched link does.
if userMightOverType and Settings.get "waitForEnterForFilteredHints"
- new WaitForEnter linkMatched.rect, linkActivator
+ installKeyBoardBlocker (callback) -> new WaitForEnter callback
else if userMightOverType
- # Block keyboard events while the user is still typing. The intention is to prevent the user from
- # inadvertently launching Vimium commands when (over-)typing the link text.
- new TypingProtector 200, linkMatched.rect, linkActivator
- else
- DomUtils.flashRect linkMatched.rect
- linkActivator()
+ installKeyBoardBlocker (callback) -> new TypingProtector 200, callback
+ else if linkMatched.hintDescriptor.frameId == frameId
+ DomUtils.flashRect linkMatched.hintDescriptor.rect
+ HintCoordinator.sendMessage "exit"
#
# Shows the marker, highlighting matchingCharCount characters.
#
showMarker: (linkMarker, matchingCharCount) ->
+ return unless linkMarker.hintDescriptor.frameId == frameId
linkMarker.style.display = ""
for j in [0...linkMarker.childNodes.length]
if (j < matchingCharCount)
@@ -461,26 +568,11 @@ class FilterHints
@linkHintNumbers = Settings.get "linkHintNumbers"
@hintKeystrokeQueue = []
@linkTextKeystrokeQueue = []
- @labelMap = {}
@activeHintMarker = null
# The regexp for splitting typed text and link texts. We split on sequences of non-word characters and
# link-hint numbers.
@splitRegexp = new RegExp "[\\W#{Utils.escapeRegexSpecialCharacters @linkHintNumbers}]+"
- #
- # Generate a map of input element => label
- #
- generateLabelMap: ->
- labels = document.querySelectorAll("label")
- for label in labels
- forElement = label.getAttribute("for")
- if (forElement)
- labelText = label.textContent.trim()
- # remove trailing : commonly found in labels
- if (labelText[labelText.length-1] == ":")
- labelText = labelText.substr(0, labelText.length-1)
- @labelMap[forElement] = labelText
-
generateHintString: (linkHintNumber) ->
base = @linkHintNumbers.length
hint = []
@@ -489,46 +581,12 @@ class FilterHints
linkHintNumber = Math.floor linkHintNumber / base
hint.reverse().join ""
- generateLinkText: (element) ->
- linkText = ""
- showLinkText = false
- # toLowerCase is necessary as html documents return "IMG" and xhtml documents return "img"
- nodeName = element.nodeName.toLowerCase()
-
- if (nodeName == "input")
- if (@labelMap[element.id])
- linkText = @labelMap[element.id]
- showLinkText = true
- else if (element.type != "password")
- linkText = element.value
- if not linkText and 'placeholder' of element
- linkText = element.placeholder
- # check if there is an image embedded in the <a> tag
- else if (nodeName == "a" && !element.textContent.trim() &&
- element.firstElementChild &&
- element.firstElementChild.nodeName.toLowerCase() == "img")
- linkText = element.firstElementChild.alt || element.firstElementChild.title
- showLinkText = true if (linkText)
- else
- linkText = DomUtils.textContent.get element
-
- { text: linkText, show: showLinkText }
-
renderMarker: (marker) ->
marker.innerHTML = spanWrap(marker.hintString +
(if marker.showLinkText then ": " + marker.linkText else ""))
fillInMarkers: (hintMarkers) ->
- @generateLabelMap()
- DomUtils.textContent.reset()
- for marker in hintMarkers
- linkTextObject = @generateLinkText(marker.clickableItem)
- marker.linkText = linkTextObject.text
- marker.showLinkText = linkTextObject.show
- @renderMarker(marker)
-
- @activeHintMarker = hintMarkers[0]
- @activeHintMarker?.classList.add "vimiumActiveHintMarker"
+ @renderMarker marker for marker in hintMarkers
# We use @filterLinkHints() here (although we know that all of the hints will match) to fill in the hint
# strings. This ensures that we always get hint strings in the same order.
@@ -622,7 +680,7 @@ spanWrap = (hintString) ->
# Suppress all keyboard events until the user stops typing for sufficiently long.
class TypingProtector extends Mode
- constructor: (delay, rect, callback) ->
+ constructor: (delay, callback) ->
@timer = Utils.setTimeout delay, => @exit()
resetExitTimer = (event) =>
@@ -637,17 +695,11 @@ class TypingProtector extends Mode
@onExit callback
- # We keep a "flash" overlay active while the user is typing; this provides visual feeback that something
- # has been selected.
- flashEl = DomUtils.addFlashRect rect
- @onExit -> DomUtils.removeElement flashEl
-
class WaitForEnter extends Mode
- constructor: (rect, callback) ->
+ constructor: (callback) ->
super
name: "hint/wait-for-enter"
suppressAllKeyboardEvents: true
- exitOnEscape: true
indicator: "Hit <Enter> to proceed..."
@push
@@ -659,10 +711,8 @@ class WaitForEnter extends Mode
else
true
- flashEl = DomUtils.addFlashRect rect
- @onExit -> DomUtils.removeElement flashEl
-
root = exports ? window
root.LinkHints = LinkHints
+root.HintCoordinator = HintCoordinator
# For tests:
-root.AlphabetHints = AlphabetHints
+extend root, {LinkHintsMode, LocalHints, AlphabetHints}
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index 564966bf..93ab440a 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -154,6 +154,7 @@ initializePreDomReady = ->
checkEnabledAfterURLChange: checkEnabledAfterURLChange
runInTopFrame: ({sourceFrameId, registryEntry}) ->
Utils.invokeCommandString registryEntry.command, sourceFrameId, registryEntry if DomUtils.isTopFrame()
+ linkHintsMessage: (request) -> HintCoordinator[request.messageType] request
chrome.runtime.onMessage.addListener (request, sender, sendResponse) ->
# These requests are intended for the background page, but they're delivered to the options page too.
@@ -637,8 +638,8 @@ root.frameId = frameId
root.Frame = Frame
root.windowIsFocused = windowIsFocused
root.bgLog = bgLog
-# These are exported for find mode.
+# These are exported for find mode and link-hints mode.
extend root, {handleEscapeForFindMode, handleEnterForFindMode, performFind, performBackwardsFind,
- enterFindMode}
+ enterFindMode, focusThisFrame}
# These are exported only for the tests.
extend root, {installModes, installListeners}
diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee
index 39e1fecd..014a1f50 100644
--- a/lib/dom_utils.coffee
+++ b/lib/dom_utils.coffee
@@ -316,25 +316,6 @@ DomUtils =
else
sel.focusNode
- # Get the text content of an element (and its descendents), but omit the text content of previously-visited
- # nodes. See #1514.
- # NOTE(smblott). This is currently O(N^2) (when called on N elements). An alternative would be to mark
- # each node visited, and then clear the marks when we're done.
- textContent: do ->
- visitedNodes = null
- reset: -> visitedNodes = []
- get: (element) ->
- nodes = document.createTreeWalker element, NodeFilter.SHOW_TEXT
- texts =
- while node = nodes.nextNode()
- continue unless node.nodeType == 3
- continue if node in visitedNodes
- text = node.data.trim()
- continue unless 0 < text.length
- visitedNodes.push node
- text
- texts.join " "
-
# Get the element in the DOM hierachy that contains `element`.
# If the element is rendered in a shadow DOM via a <content> element, the <content> element will be
# returned, so the shadow DOM is traversed rather than passed over.
diff --git a/tests/dom_tests/dom_tests.coffee b/tests/dom_tests/dom_tests.coffee
index 2311b768..bc137a56 100644
--- a/tests/dom_tests/dom_tests.coffee
+++ b/tests/dom_tests/dom_tests.coffee
@@ -61,6 +61,9 @@ getHintMarkers = ->
stubSettings = (key, value) -> stub Settings.cache, key, JSON.stringify value
+HintCoordinator.sendMessage = (name, request = {}) -> HintCoordinator[name]? request; request
+activateLinkHintsMode = -> HintCoordinator.activateMode HintCoordinator.getHintDescriptors()
+
#
# Generate tests that are common to both default and filtered
# link hinting modes.
@@ -81,7 +84,7 @@ createGeneralHintTests = (isFilteredMode) ->
document.getElementById("test-div").innerHTML = ""
should "create hints when activated, discard them when deactivated", ->
- linkHints = LinkHints.activateMode()
+ linkHints = activateLinkHintsMode()
assert.isFalse not linkHints.hintMarkerContainingDiv?
linkHints.deactivateMode()
assert.isTrue not linkHints.hintMarkerContainingDiv?
@@ -91,13 +94,13 @@ createGeneralHintTests = (isFilteredMode) ->
assert.equal element1.getClientRects()[0].left, element2.getClientRects()[0].left
assert.equal element1.getClientRects()[0].top, element2.getClientRects()[0].top
stub document.body, "style", "static"
- linkHints = LinkHints.activateMode()
+ linkHints = activateLinkHintsMode()
hintMarkers = getHintMarkers()
assertStartPosition document.getElementsByTagName("a")[0], hintMarkers[0]
assertStartPosition document.getElementsByTagName("a")[1], hintMarkers[1]
linkHints.deactivateMode()
stub document.body.style, "position", "relative"
- linkHints = LinkHints.activateMode()
+ linkHints = activateLinkHintsMode()
hintMarkers = getHintMarkers()
assertStartPosition document.getElementsByTagName("a")[0], hintMarkers[0]
assertStartPosition document.getElementsByTagName("a")[1], hintMarkers[1]
@@ -143,8 +146,9 @@ context "Test link hints for focusing input elements correctly",
input.addEventListener "focus", activeListener, false
input.addEventListener "click", activeListener, false
- LinkHints.activateMode()
- [hint] = getHintMarkers().filter (hint) -> input == hint.clickableItem
+ activateLinkHintsMode()
+ [hint] = getHintMarkers().filter (hint) ->
+ input == HintCoordinator.getLocalHintMarker(hint.hintDescriptor).element
sendKeyboardEvent char for char in hint.hintString
input.removeEventListener "focus", activeListener, false
@@ -156,7 +160,7 @@ context "Test link hints for changing mode",
initializeModeState()
testDiv = document.getElementById("test-div")
testDiv.innerHTML = "<a>link</a>"
- @linkHints = LinkHints.activateMode()
+ @linkHints = activateLinkHintsMode()
tearDown ->
document.getElementById("test-div").innerHTML = ""
@@ -185,7 +189,7 @@ context "Alphabetical link hints",
# Three hints will trigger double hint chars.
createLinks 3
- @linkHints = LinkHints.activateMode()
+ @linkHints = activateLinkHintsMode()
tearDown ->
@linkHints.deactivateMode()
@@ -233,7 +237,7 @@ context "Filtered link hints",
initializeModeState()
testContent = "<a>test</a>" + "<a>tress</a>" + "<a>trait</a>" + "<a>track<img alt='alt text'/></a>"
document.getElementById("test-div").innerHTML = testContent
- @linkHints = LinkHints.activateMode()
+ @linkHints = activateLinkHintsMode()
tearDown ->
document.getElementById("test-div").innerHTML = ""
@@ -264,7 +268,7 @@ context "Filtered link hints",
testContent = "<a><img alt='alt text'/></a><a><img alt='alt text' title='some title'/></a>
<a><img title='some title'/></a>" + "<a><img src='' width='320px' height='100px'/></a>"
document.getElementById("test-div").innerHTML = testContent
- @linkHints = LinkHints.activateMode()
+ @linkHints = activateLinkHintsMode()
tearDown ->
document.getElementById("test-div").innerHTML = ""
@@ -289,7 +293,7 @@ context "Filtered link hints",
<input type='text' id='test-input' value='some value'/>
<label for='test-input-2'/>a label: </label><input type='text' id='test-input-2' value='some value'/>"
document.getElementById("test-div").innerHTML = testContent
- @linkHints = LinkHints.activateMode()
+ @linkHints = activateLinkHintsMode()
tearDown ->
document.getElementById("test-div").innerHTML = ""
@@ -324,9 +328,9 @@ context "Filtered link hints",
{id: 9, text: "test abc one - longer still"} # For tab test - 3.
].map(({id,text}) -> "<a id=\"#{id}\">#{text}</a>").join " "
document.getElementById("test-div").innerHTML = testContent
- @linkHints = LinkHints.activateMode()
+ @linkHints = activateLinkHintsMode()
@getActiveHintMarker = ->
- @linkHints.markerMatcher.activeHintMarker.clickableItem.id
+ HintCoordinator.getLocalHintMarker(@linkHints.markerMatcher.activeHintMarker.hintDescriptor).element.id
tearDown ->
document.getElementById("test-div").innerHTML = ""