From 8b19dfcf3bc4fbc0c426f057a4481bd699c09f72 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Thu, 10 Mar 2016 09:14:20 +0000 Subject: Separate ClickableElements functions from LinkHintsMode. Really, all of the code related to finding clickable elements is unrelated to LinkHintsMode. Unfortunately, it's embedded in the middle of the class. So, we can't use it from outside of the class. This is a temporary restructuring of the link-hints code. The intention is to move the lines around eventually. For now, however, we do it like this to keep the diff smaller and clearer. --- content_scripts/link_hints.coffee | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 6f99d970..ec57f74a 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -65,7 +65,7 @@ LinkHints = activateModeToOpenIncognito: (count) -> @activateMode count, OPEN_INCOGNITO activateModeToDownloadLink: (count) -> @activateMode count, DOWNLOAD_LINK_URL -class LinkHintsMode +class LinkHintsModeBase hintMarkerContainingDiv: null # One of the enums listed at the top of this file. mode: undefined @@ -80,7 +80,7 @@ class LinkHintsMode # we need documentElement to be ready in order to append links return unless document.documentElement - elements = @getVisibleClickableElements() + elements = ClickableElements.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 ] @@ -146,6 +146,9 @@ class LinkHintsMode marker +# TODO(smblott) It is not intended that the code remain structured this way. This a temporary in order to +# keep the diff smaller and clearer. Basically, we need to move a lot of lines around. +ClickableElements = # # 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 @@ -287,6 +290,11 @@ class LinkHintsMode nonOverlappingElements +# TODO(smblott) It is not intended that the code remain structured this way. This a temporary in order to +# keep the diff smaller and clearer. Basically, we need to move a lot of lines around. +class LinkHintsMode extends LinkHintsModeBase + constructor: (args...) -> super args... + # Handles and . onKeyDownInMode: (hintMarkers, event) -> return if event.repeat -- cgit v1.2.3 From 09519db103b1cef197e183435e864287ab67e7e3 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Thu, 10 Mar 2016 09:32:15 +0000 Subject: Add HintCorrdinator stub. This adds a shim between launching a link-hints mode and creating the LinkHintsMode object. The shim is responsibly for for finding the clickable elements. This is preparatory to implementing global hints. --- content_scripts/link_hints.coffee | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index ec57f74a..57f7cc2b 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -46,17 +46,25 @@ DOWNLOAD_LINK_URL = indicator: "Download link URL." clickModifiers: altKey: true, ctrlKey: false, metaKey: false +HintCoordinator = + 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] + + activateMode: (activateModeCallback) -> + activateModeCallback ClickableElements.getVisibleClickableElements() + 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 ] - # 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 + HintCoordinator.activateMode (elements) -> + new LinkHintsMode mode, elements, (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 ] + # 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 activateModeToOpenInNewTab: (count) -> @activateMode count, OPEN_IN_NEW_BG_TAB activateModeToOpenInNewForegroundTab: (count) -> @activateMode count, OPEN_IN_NEW_FG_TAB @@ -76,11 +84,10 @@ class LinkHintsModeBase # 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: (mode = OPEN_IN_CURRENT_TAB, elements, onExit = (->)) -> # we need documentElement to be ready in order to append links return unless document.documentElement - elements = ClickableElements.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 ] -- cgit v1.2.3 From 17379d86faefdeb158b30ccdfb5c3814008bfea3 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Thu, 10 Mar 2016 12:03:17 +0000 Subject: Move link activation to HintCoordinator. This moves the link activation logic out of LinkHintsMode and into the hint coordinator. At this point, there is (almost) no DOM-specific logic left in LinksHintMode. It only depends an a list of "elements", each of which has a rect property. The main exception to this is filtered hints. In the following commits, we're going to leave filtered hints behind - mainly to keep the diff shorter and cleaner. --- content_scripts/link_hints.coffee | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 57f7cc2b..b3c7412d 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -53,6 +53,21 @@ HintCoordinator = activateMode: (activateModeCallback) -> activateModeCallback ClickableElements.getVisibleClickableElements() + activateLink: (mode, linkMatched) -> + clickEl = linkMatched.clickableItem + + clickActivator = (modifiers) -> (link) -> DomUtils.simulateClick link, modifiers + linkActivator = mode.linkActivator ? clickActivator mode.clickModifiers + + 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 + LinkHints = activateMode: (count = 1, mode = OPEN_IN_CURRENT_TAB) -> if 0 < count @@ -129,8 +144,6 @@ class LinkHintsModeBase id: "vimiumHintMarkerContainer", className: "vimiumReset" setOpenLinkMode: (@mode) -> - clickActivator = (modifiers) -> (link) -> DomUtils.simulateClick link, modifiers - @linkActivator = @mode.linkActivator ? clickActivator @mode.clickModifiers @hintMode.setIndicator @mode.indicator # @@ -383,18 +396,11 @@ class LinkHintsMode extends LinkHintsModeBase # activateLink: (linkMatched, userMightOverType=false) -> @removeHintMarkers() - clickEl = linkMatched.clickableItem + mode = @mode 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 + HintCoordinator.activateLink mode, linkMatched if userMightOverType and Settings.get "waitForEnterForFilteredHints" new WaitForEnter linkMatched.rect, linkActivator -- cgit v1.2.3 From 20fa0828cbb0b71159cf0a519341d120b78c5466 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Fri, 11 Mar 2016 09:57:52 +0000 Subject: Global link hints... TODO: - fix tests --- content_scripts/link_hints.coffee | 267 +++++++++++++++++++-------------- content_scripts/vimium_frontend.coffee | 1 + 2 files changed, 152 insertions(+), 116 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index b3c7412d..b2f74dc7 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -46,40 +46,55 @@ 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 = - 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] + onExit: [] - activateMode: (activateModeCallback) -> - activateModeCallback ClickableElements.getVisibleClickableElements() + sendMessage: (name, request = {}) -> + request = extend request, {handler: "linkHintsMessage", name, frameId} + chrome.runtime.sendMessage request - activateLink: (mode, linkMatched) -> - clickEl = linkMatched.clickableItem + activateMode: (@mode, onExit) -> + @onExit = [onExit] + @sendMessage "activateMode", modeIndex: availableModes.indexOf @mode - clickActivator = (modifiers) -> (link) -> DomUtils.simulateClick link, modifiers - linkActivator = mode.linkActivator ? clickActivator mode.clickModifiers + getHints: -> + @localHints = ClickableElements.getVisibleClickableElements() + @sendMessage "postHints", hints: for hint, localIndex in @localHints + {rect: hint.rect, linkText: hint.linkText, showLinkText: hint.showLinkText, localIndex, frameId} - 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 + activateLinkHintsMode: ({hints, modeIndex, frameId: activateModeFrameId}) -> + @onExit = [] unless frameId == activateModeFrameId + mode = availableModes[modeIndex] + @linkHintsMode = new LinkHintsMode hints, mode + + postKeyState: (request) -> + @linkHintsMode.updateVisibleMarkersFromCoordinator request + + activateActiveHintMarker: -> + @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker + + exit: -> + @onExit.pop()() while 0 < @onExit.length + @linkHintsMode = null + @localHints = null + + exitFailure: -> + @onExit = [=> @linkHintsMode.deactivateMode()] + @exit() + + getLocalHintMarker: (hint) -> + if hint.frameId == frameId then @localHints[hint.localIndex] else null LinkHints = activateMode: (count = 1, mode = OPEN_IN_CURRENT_TAB) -> - if 0 < count - HintCoordinator.activateMode (elements) -> - new LinkHintsMode mode, elements, (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 ] - # 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 + if 0 < count or mode is OPEN_WITH_QUEUE + HintCoordinator.activateMode 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 activateModeToOpenInNewTab: (count) -> @activateMode count, OPEN_IN_NEW_BG_TAB activateModeToOpenInNewForegroundTab: (count) -> @activateMode count, OPEN_IN_NEW_FG_TAB @@ -99,27 +114,21 @@ class LinkHintsModeBase # A count of the number of Tab presses since the last non-Tab keyboard event. tabCount: 0 - constructor: (mode = OPEN_IN_CURRENT_TAB, elements, onExit = (->)) -> + constructor: (elements, mode = OPEN_IN_CURRENT_TAB) -> # we need documentElement to be ready in order to append links return unless document.documentElement # 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 if elements.length == 0 HUD.showForDuration "No links to select.", 2000 return - hintMarkers = (@createMarkerFor(el) for el in elements) + @hintMarkers = (@createMarkerFor(el) for el in elements) @markerMatcher = new (if Settings.get "filterLinkHints" then FilterHints else AlphabetHints) - @markerMatcher.fillInMarkers hintMarkers + @markerMatcher.fillInMarkers @hintMarkers @hintMode = new Mode name: "hint/#{mode.name}" @@ -129,22 +138,27 @@ class LinkHintsModeBase suppressTrailingKeyEvents: true exitOnEscape: true exitOnClick: true - keydown: @onKeyDownInMode.bind this, hintMarkers - keypress: @onKeyPressInMode.bind this, hintMarkers - - @hintMode.onExit => - @deactivateMode() - @hintMode.onExit onExit + keydown: @onKeyDownInMode.bind this, @hintMarkers + keypress: @onKeyPressInMode.bind this, @hintMarkers @setOpenLinkMode mode + @hintMode.onExit (event) => + HintCoordinator.sendMessage "exitFailure" if @isExitFailureEvent event # 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, + @hintMarkerContainingDiv = DomUtils.addElementList @hintMarkers, id: "vimiumHintMarkerContainer", className: "vimiumReset" + # Hide markers from other frames. + @hideMarker marker for marker in @hintMarkers when marker.hint.frameId != frameId setOpenLinkMode: (@mode) -> - @hintMode.setIndicator @mode.indicator + @hintMode.setIndicator @mode.indicator if DomUtils.isTopFrame() + + # This tests whether this event is an event which indicates that we're exiting wthout selecting a link. + isExitFailureEvent: (event) -> + (event?.type == "keydown" and KeyboardUtils.isEscape event) or + (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]) # # Creates a link marker for the given link. @@ -157,6 +171,11 @@ class LinkHintsModeBase marker.className = "vimiumReset internalVimiumHintMarker vimiumHintMarker" marker.clickableItem = link.element marker.stableSortCount = ++stableSortCount + # Keep track of the original hint. We'll need this to decide which markers to display, and to activate + # hints in other frames. + marker.hint = link + marker.linkText = link.linkText + marker.showLinkText = link.showLinkText clientRect = link.rect marker.style.left = clientRect.left + window.scrollX + "px" @@ -166,8 +185,9 @@ class LinkHintsModeBase marker -# TODO(smblott) It is not intended that the code remain structured this way. This a temporary in order to -# keep the diff smaller and clearer. Basically, we need to move a lot of lines around. +# TODO(smblott) It is not intended that the code remain structured this way. This temporary in order to keep +# the diff smaller and clearer. Basically, we need to move the whole of ClickableElements, here, out of the +# LinkHintsMode class. ClickableElements = # # Determine whether the element is visible and clickable. If it is, find the rect bounding the element in @@ -308,8 +328,50 @@ ClickableElements = # click some elements that we could click before. nonOverlappingElements.push visibleElement unless visibleElement.secondClassCitizen + if Settings.get "filterLinkHints" + @generateLabelMap() + DomUtils.textContent.reset() + extend hint, @generateLinkText hint.element for hint in nonOverlappingElements + nonOverlappingElements + # 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 + + 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 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 + + {linkText, showLinkText} + # TODO(smblott) It is not intended that the code remain structured this way. This a temporary in order to # keep the diff smaller and clearer. Basically, we need to move a lot of lines around. class LinkHintsMode extends LinkHintsModeBase @@ -349,7 +411,7 @@ class LinkHintsMode extends LinkHintsModeBase else if event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ] if @markerMatcher.popKeyChar() - @updateVisibleMarkers hintMarkers + @updateVisibleMarkers() else # Exit via @hintMode.exit(), so that the LinkHints.activate() "onExit" callback sees the key event and # knows not to restart hints mode. @@ -357,11 +419,11 @@ class LinkHintsMode extends LinkHintsModeBase 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) - @updateVisibleMarkers hintMarkers, @tabCount + @updateVisibleMarkers @tabCount else return @@ -376,19 +438,25 @@ class LinkHintsMode extends LinkHintsModeBase keyChar = String.fromCharCode(event.charCode).toLowerCase() if keyChar @markerMatcher.pushKeyChar keyChar, @keydownKeyChar - @updateVisibleMarkers hintMarkers + @updateVisibleMarkers() # We've handled the event, so suppress it. DomUtils.suppressEvent event - updateVisibleMarkers: (hintMarkers, tabCount = 0) -> - {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints hintMarkers, tabCount + updateVisibleMarkers: (tabCount = 0) -> + {hintKeystrokeQueue, linkTextKeystrokeQueue} = @markerMatcher + HintCoordinator.sendMessage "postKeyState", {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount} + + updateVisibleMarkersFromCoordinator: ({hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) -> + extend @markerMatcher, {hintKeystrokeQueue, linkTextKeystrokeQueue} + + {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints @hintMarkers, tabCount if linksMatched.length == 0 @deactivateMode() else if linksMatched.length == 1 @activateLink linksMatched[0], userMightOverType ? false else - @hideMarker marker for marker in hintMarkers + @hideMarker marker for marker in @hintMarkers @showMarker matched, @markerMatcher.hintKeystrokeQueue.length for matched in linksMatched # @@ -396,26 +464,45 @@ class LinkHintsMode extends LinkHintsModeBase # activateLink: (linkMatched, userMightOverType=false) -> @removeHintMarkers() + HintCoordinator.onExit.push => @deactivateMode() + clickEl = HintCoordinator.getLocalHintMarker(linkMatched.hint)?.element - mode = @mode - linkActivator = => - @deactivateMode() - HintCoordinator.activateLink mode, linkMatched + if clickEl? + HintCoordinator.onExit.push => + if DomUtils.isSelectable clickEl + 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.hint.frameId == frameId + flashEl = DomUtils.addFlashRect linkMatched.hint.rect + HintCoordinator.onExit.push -> DomUtils.removeElement flashEl + + if document.hasFocus() + startKeyboardBlocker -> HintCoordinator.sendMessage "exit" 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 + installKeyBoardBlocker (callback) -> new TypingProtector 200, callback else - DomUtils.flashRect linkMatched.rect - linkActivator() + DomUtils.flashRect linkMatched.rect if linkMatched.hint.frameId == frameId + HintCoordinator.sendMessage "exit" # # Shows the marker, highlighting matchingCharCount characters. # showMarker: (linkMarker, matchingCharCount) -> + # Never show markers from other frames + return unless linkMarker.hint.frameId == frameId linkMarker.style.display = "" for j in [0...linkMarker.childNodes.length] if (j < matchingCharCount) @@ -488,20 +575,6 @@ class FilterHints # 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 = [] @@ -510,43 +583,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 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) + @renderMarker marker for marker in hintMarkers @activeHintMarker = hintMarkers[0] @activeHintMarker?.classList.add "vimiumActiveHintMarker" @@ -643,7 +685,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) => @@ -658,13 +700,8 @@ 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 @@ -680,10 +717,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 diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 564966bf..bd7da625 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. -- cgit v1.2.3 From 5c230c79933f583e792342a60dffab5f6cc04f79 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 12 Mar 2016 07:45:44 +0000 Subject: Simplify diff. --- content_scripts/link_hints.coffee | 55 ++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 29 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index b2f74dc7..5442bf25 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -71,7 +71,7 @@ HintCoordinator = @linkHintsMode = new LinkHintsMode hints, mode postKeyState: (request) -> - @linkHintsMode.updateVisibleMarkersFromCoordinator request + @linkHintsMode.postKeyState request activateActiveHintMarker: -> @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker @@ -126,9 +126,9 @@ class LinkHintsModeBase HUD.showForDuration "No links to select.", 2000 return - @hintMarkers = (@createMarkerFor(el) for el in elements) + hintMarkers = (@createMarkerFor(el) for el in elements) @markerMatcher = new (if Settings.get "filterLinkHints" then FilterHints else AlphabetHints) - @markerMatcher.fillInMarkers @hintMarkers + @markerMatcher.fillInMarkers hintMarkers @hintMode = new Mode name: "hint/#{mode.name}" @@ -138,28 +138,27 @@ class LinkHintsModeBase suppressTrailingKeyEvents: true exitOnEscape: true exitOnClick: true - keydown: @onKeyDownInMode.bind this, @hintMarkers - keypress: @onKeyPressInMode.bind this, @hintMarkers + keydown: @onKeyDownInMode.bind this, hintMarkers + keypress: @onKeyPressInMode.bind this, hintMarkers - @setOpenLinkMode mode @hintMode.onExit (event) => - HintCoordinator.sendMessage "exitFailure" if @isExitFailureEvent event + if event?.type == "click" or (event?.type == "keydown" and KeyboardUtils.isEscape event) or + (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]) + HintCoordinator.sendMessage "exitFailure" + + @setOpenLinkMode mode # 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, + @hintMarkerContainingDiv = DomUtils.addElementList hintMarkers, id: "vimiumHintMarkerContainer", className: "vimiumReset" # Hide markers from other frames. - @hideMarker marker for marker in @hintMarkers when marker.hint.frameId != frameId + @hideMarker marker for marker in hintMarkers when marker.hint.frameId != frameId + @postKeyState = @postKeyState.bind this, hintMarkers setOpenLinkMode: (@mode) -> @hintMode.setIndicator @mode.indicator if DomUtils.isTopFrame() - # This tests whether this event is an event which indicates that we're exiting wthout selecting a link. - isExitFailureEvent: (event) -> - (event?.type == "keydown" and KeyboardUtils.isEscape event) or - (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]) - # # Creates a link marker for the given link. # @@ -171,8 +170,7 @@ class LinkHintsModeBase marker.className = "vimiumReset internalVimiumHintMarker vimiumHintMarker" marker.clickableItem = link.element marker.stableSortCount = ++stableSortCount - # Keep track of the original hint. We'll need this to decide which markers to display, and to activate - # hints in other frames. + # Keep track of the original hint descriptor. We'll need this to decide which markers to display. marker.hint = link marker.linkText = link.linkText marker.showLinkText = link.showLinkText @@ -185,9 +183,8 @@ class LinkHintsModeBase marker -# TODO(smblott) It is not intended that the code remain structured this way. This temporary in order to keep -# the diff smaller and clearer. Basically, we need to move the whole of ClickableElements, here, out of the -# LinkHintsMode class. +# TODO(smblott) It is not intended that this remain structured this way. This is temporary, just to keep the +# diff smaller and clearer. We need to move the whole of ClickableElements out of the LinkHintsMode class. ClickableElements = # # Determine whether the element is visible and clickable. If it is, find the rect bounding the element in @@ -372,8 +369,8 @@ ClickableElements = {linkText, showLinkText} -# TODO(smblott) It is not intended that the code remain structured this way. This a temporary in order to -# keep the diff smaller and clearer. Basically, we need to move a lot of lines around. +# TODO(smblott) It is not intended that this remain structured this way. This is temporary, just to keep the +# diff smaller and clearer. We need to move the whole of ClickableElements out of the LinkHintsMode class. class LinkHintsMode extends LinkHintsModeBase constructor: (args...) -> super args... @@ -411,7 +408,7 @@ class LinkHintsMode extends LinkHintsModeBase else if event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ] if @markerMatcher.popKeyChar() - @updateVisibleMarkers() + @updateVisibleMarkers hintMarkers else # Exit via @hintMode.exit(), so that the LinkHints.activate() "onExit" callback sees the key event and # knows not to restart hints mode. @@ -423,7 +420,7 @@ class LinkHintsMode extends LinkHintsModeBase else if event.keyCode == keyCodes.tab @tabCount = previousTabCount + (if event.shiftKey then -1 else 1) - @updateVisibleMarkers @tabCount + @updateVisibleMarkers hintMarkers, @tabCount else return @@ -438,25 +435,25 @@ class LinkHintsMode extends LinkHintsModeBase keyChar = String.fromCharCode(event.charCode).toLowerCase() if keyChar @markerMatcher.pushKeyChar keyChar, @keydownKeyChar - @updateVisibleMarkers() + @updateVisibleMarkers hintMarkers # We've handled the event, so suppress it. DomUtils.suppressEvent event - updateVisibleMarkers: (tabCount = 0) -> + updateVisibleMarkers: (hintMarkers, tabCount = 0) -> {hintKeystrokeQueue, linkTextKeystrokeQueue} = @markerMatcher HintCoordinator.sendMessage "postKeyState", {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount} - updateVisibleMarkersFromCoordinator: ({hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) -> + postKeyState: (hintMarkers, {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) -> extend @markerMatcher, {hintKeystrokeQueue, linkTextKeystrokeQueue} - {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints @hintMarkers, tabCount + {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints hintMarkers, tabCount if linksMatched.length == 0 @deactivateMode() else if linksMatched.length == 1 @activateLink linksMatched[0], userMightOverType ? false else - @hideMarker marker for marker in @hintMarkers + @hideMarker marker for marker in hintMarkers @showMarker matched, @markerMatcher.hintKeystrokeQueue.length for matched in linksMatched # @@ -464,7 +461,6 @@ class LinkHintsMode extends LinkHintsModeBase # activateLink: (linkMatched, userMightOverType=false) -> @removeHintMarkers() - HintCoordinator.onExit.push => @deactivateMode() clickEl = HintCoordinator.getLocalHintMarker(linkMatched.hint)?.element if clickEl? @@ -487,6 +483,7 @@ class LinkHintsMode extends LinkHintsModeBase if document.hasFocus() startKeyboardBlocker -> HintCoordinator.sendMessage "exit" + HintCoordinator.onExit.push => @deactivateMode() if userMightOverType and Settings.get "waitForEnterForFilteredHints" installKeyBoardBlocker (callback) -> new WaitForEnter callback else if userMightOverType -- cgit v1.2.3 From 060e2397d18de00e7ccfc68af52db87b0a4cbbae Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 12 Mar 2016 07:55:40 +0000 Subject: Simplify diff (again). --- content_scripts/link_hints.coffee | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 5442bf25..12c29dc2 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -67,8 +67,7 @@ HintCoordinator = activateLinkHintsMode: ({hints, modeIndex, frameId: activateModeFrameId}) -> @onExit = [] unless frameId == activateModeFrameId - mode = availableModes[modeIndex] - @linkHintsMode = new LinkHintsMode hints, mode + @linkHintsMode = new LinkHintsMode hints, availableModes[modeIndex] postKeyState: (request) -> @linkHintsMode.postKeyState request @@ -78,8 +77,7 @@ HintCoordinator = exit: -> @onExit.pop()() while 0 < @onExit.length - @linkHintsMode = null - @localHints = null + @linkHintsMode = @localHints = null exitFailure: -> @onExit = [=> @linkHintsMode.deactivateMode()] @@ -170,7 +168,7 @@ class LinkHintsModeBase marker.className = "vimiumReset internalVimiumHintMarker vimiumHintMarker" marker.clickableItem = link.element marker.stableSortCount = ++stableSortCount - # Keep track of the original hint descriptor. We'll need this to decide which markers to display. + # Keep track of the original hint descriptor, we'll need it to decide which markers to display. marker.hint = link marker.linkText = link.linkText marker.showLinkText = link.showLinkText -- cgit v1.2.3 From fe2e958ebbb4157d06a963ee8479ece7f2685c94 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 12 Mar 2016 08:36:18 +0000 Subject: Golbal link hints; fic tests. --- content_scripts/link_hints.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 12c29dc2..7424b3f5 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -332,6 +332,7 @@ ClickableElements = # Generate a map of input element => label generateLabelMap: -> + @labelMap = {} labels = document.querySelectorAll("label") for label in labels forElement = label.getAttribute("for") @@ -564,7 +565,6 @@ 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. @@ -716,4 +716,4 @@ root = exports ? window root.LinkHints = LinkHints root.HintCoordinator = HintCoordinator # For tests: -root.AlphabetHints = AlphabetHints +extend root, {LinkHintsMode, ClickableElements, AlphabetHints} -- cgit v1.2.3 From bdf1469075c8e5cca8cd328ca326ac8df145a141 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 12 Mar 2016 10:00:25 +0000 Subject: Global link hints; reinstate href-based modes. --- content_scripts/link_hints.coffee | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 7424b3f5..25f57316 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -53,17 +53,17 @@ HintCoordinator = onExit: [] sendMessage: (name, request = {}) -> - request = extend request, {handler: "linkHintsMessage", name, frameId} - chrome.runtime.sendMessage request + chrome.runtime.sendMessage extend request, {handler: "linkHintsMessage", name, frameId} - activateMode: (@mode, onExit) -> + activateMode: (mode, onExit) -> @onExit = [onExit] - @sendMessage "activateMode", modeIndex: availableModes.indexOf @mode + @sendMessage "activateMode", modeIndex: availableModes.indexOf mode getHints: -> @localHints = ClickableElements.getVisibleClickableElements() @sendMessage "postHints", hints: for hint, localIndex in @localHints - {rect: hint.rect, linkText: hint.linkText, showLinkText: hint.showLinkText, localIndex, frameId} + {rect: hint.rect, linkText: hint.linkText, showLinkText: hint.showLinkText, hasHref: hint.hasHref, + localIndex, frameId} activateLinkHintsMode: ({hints, modeIndex, frameId: activateModeFrameId}) -> @onExit = [] unless frameId == activateModeFrameId @@ -118,7 +118,7 @@ class LinkHintsModeBase # 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 ] + 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 @@ -328,6 +328,7 @@ ClickableElements = DomUtils.textContent.reset() extend hint, @generateLinkText hint.element for hint in nonOverlappingElements + hint.hasHref = hint.element.href? for hint in nonOverlappingElements nonOverlappingElements # Generate a map of input element => label -- cgit v1.2.3 From 409d1c69acb61ba6ac7ff7981fd8eb0354ba548f Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 12 Mar 2016 10:14:10 +0000 Subject: Global link hints; reinstate mode toggling. --- content_scripts/link_hints.coffee | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 25f57316..764944ee 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -69,11 +69,10 @@ HintCoordinator = @onExit = [] unless frameId == activateModeFrameId @linkHintsMode = new LinkHintsMode hints, availableModes[modeIndex] - postKeyState: (request) -> - @linkHintsMode.postKeyState request - - activateActiveHintMarker: -> - @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker + postKeyState: (request) -> @linkHintsMode.postKeyState request + activateActiveHintMarker: -> @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker + setMode: ({modeIndex}) -> @linkHintsMode.setOpenLinkMode availableModes[modeIndex], true + getLocalHintMarker: (hint) -> if hint.frameId == frameId then @localHints[hint.localIndex] else null exit: -> @onExit.pop()() while 0 < @onExit.length @@ -83,9 +82,6 @@ HintCoordinator = @onExit = [=> @linkHintsMode.deactivateMode()] @exit() - getLocalHintMarker: (hint) -> - if hint.frameId == frameId then @localHints[hint.localIndex] else null - LinkHints = activateMode: (count = 1, mode = OPEN_IN_CURRENT_TAB) -> if 0 < count or mode is OPEN_WITH_QUEUE @@ -144,7 +140,7 @@ class LinkHintsModeBase (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]) HintCoordinator.sendMessage "exitFailure" - @setOpenLinkMode mode + @setOpenLinkMode mode, true # 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. @@ -154,8 +150,10 @@ class LinkHintsModeBase @hideMarker marker for marker in hintMarkers when marker.hint.frameId != frameId @postKeyState = @postKeyState.bind this, hintMarkers - setOpenLinkMode: (@mode) -> + setOpenLinkMode: (@mode, doNotPropagate = false) -> @hintMode.setIndicator @mode.indicator if DomUtils.isTopFrame() + unless doNotPropagate + HintCoordinator.sendMessage "setMode", modeIndex: availableModes.indexOf @mode # # Creates a link marker for the given link. -- cgit v1.2.3 From c8414d92886c7705dc0892cdedf3939f75336889 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 12 Mar 2016 12:00:03 +0000 Subject: Global link hints; rename message name. We cannot use "request" and "name" to describe a link-hints message. The message is then accepted (and fails) on the options page where there is no handler. So, here, use "messageType" instead of "name". --- content_scripts/link_hints.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 764944ee..e695e869 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -52,8 +52,8 @@ availableModes = [OPEN_IN_CURRENT_TAB, OPEN_IN_NEW_BG_TAB, OPEN_IN_NEW_FG_TAB, O HintCoordinator = onExit: [] - sendMessage: (name, request = {}) -> - chrome.runtime.sendMessage extend request, {handler: "linkHintsMessage", name, frameId} + sendMessage: (messageType, request = {}) -> + chrome.runtime.sendMessage extend request, {handler: "linkHintsMessage", messageType, frameId} activateMode: (mode, onExit) -> @onExit = [onExit] -- cgit v1.2.3 From 52ce015c04b22ae8c519fcd5b420788f8825c800 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 12 Mar 2016 12:53:03 +0000 Subject: Global link hints; fix focus issue for options page. Here's the issue (and we may have to address this ouside of this PR). If we put the HUD in the top frame, then the top frame grabs the focus when the HUD is displayed. If we open link hints with the help dialog open, then the help dialog loses the focus, and we can't `Esc` out of it. --- content_scripts/link_hints.coffee | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index e695e869..36ab1c97 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -71,7 +71,7 @@ HintCoordinator = postKeyState: (request) -> @linkHintsMode.postKeyState request activateActiveHintMarker: -> @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker - setMode: ({modeIndex}) -> @linkHintsMode.setOpenLinkMode availableModes[modeIndex], true + setMode: ({modeIndex}) -> @linkHintsMode.setOpenLinkMode availableModes[modeIndex], false getLocalHintMarker: (hint) -> if hint.frameId == frameId then @localHints[hint.localIndex] else null exit: -> @@ -140,7 +140,7 @@ class LinkHintsModeBase (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]) HintCoordinator.sendMessage "exitFailure" - @setOpenLinkMode mode, true + @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. @@ -150,9 +150,9 @@ class LinkHintsModeBase @hideMarker marker for marker in hintMarkers when marker.hint.frameId != frameId @postKeyState = @postKeyState.bind this, hintMarkers - setOpenLinkMode: (@mode, doNotPropagate = false) -> - @hintMode.setIndicator @mode.indicator if DomUtils.isTopFrame() - unless doNotPropagate + setOpenLinkMode: (@mode, shouldPropagtetoOtherFrames = true) -> + @hintMode.setIndicator @mode.indicator if windowIsFocused() + if shouldPropagtetoOtherFrames HintCoordinator.sendMessage "setMode", modeIndex: availableModes.indexOf @mode # -- cgit v1.2.3 From 9ca85dde948e1e4983f24c2cc6c7ba3d200bcc42 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 12 Mar 2016 14:45:01 +0000 Subject: Global link hints; do not exit in Esc here. --- content_scripts/link_hints.coffee | 1 - 1 file changed, 1 deletion(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 36ab1c97..1a15ded9 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -699,7 +699,6 @@ class WaitForEnter extends Mode super name: "hint/wait-for-enter" suppressAllKeyboardEvents: true - exitOnEscape: true indicator: "Hit to proceed..." @push -- cgit v1.2.3 From 9a83ccc49e30af30f39fc3fa15208993c28fa520 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 12 Mar 2016 14:52:31 +0000 Subject: Global link hints; focus window for selectable elements. Because we're running hint modes in multiple frames, we need to ensure the right frame has the focus for selectable elements (inputs). --- content_scripts/link_hints.coffee | 1 + 1 file changed, 1 insertion(+) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 1a15ded9..8e983ebe 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -464,6 +464,7 @@ class LinkHintsMode extends LinkHintsModeBase if clickEl? HintCoordinator.onExit.push => if DomUtils.isSelectable clickEl + window.focus() DomUtils.simulateSelect clickEl else clickActivator = (modifiers) -> (link) -> DomUtils.simulateClick link, modifiers -- cgit v1.2.3 From 5e7536145be34f4415396a45cc584c480d76465c Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sat, 12 Mar 2016 17:08:31 +0000 Subject: Global link hints; better comments, exit() fix. --- content_scripts/link_hints.coffee | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 8e983ebe..e14ddfde 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -65,6 +65,9 @@ HintCoordinator = {rect: hint.rect, linkText: hint.linkText, showLinkText: hint.showLinkText, hasHref: hint.hasHref, localIndex, frameId} + # We activate LinkHintsMode() in every frame, and provide every frame with exactly the same hints. We also + # propagate the key state between frames. Therefore, the hint-selection process proceeds in lock step in + # every frame, the linkHintsMode arrives in the samme state in every frame. activateLinkHintsMode: ({hints, modeIndex, frameId: activateModeFrameId}) -> @onExit = [] unless frameId == activateModeFrameId @linkHintsMode = new LinkHintsMode hints, availableModes[modeIndex] @@ -454,9 +457,10 @@ class LinkHintsMode extends LinkHintsModeBase @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 link hint remains, this function activates it in the appropriate way. The current frame may + # or may not contain the matched link, and it may or may not have the focus. These two things are + # independent, so there are four possible cases. All four cases are accounted for here by selectively + # pushing the appropriate onExit() handlers. activateLink: (linkMatched, userMightOverType=false) -> @removeHintMarkers() clickEl = HintCoordinator.getLocalHintMarker(linkMatched.hint)?.element @@ -482,6 +486,8 @@ class LinkHintsMode extends LinkHintsModeBase if document.hasFocus() startKeyboardBlocker -> HintCoordinator.sendMessage "exit" + # If we're using a keyboard blocker, then the frame with the focus invokes "exit", otherwise the frame + # containing the selected link invokes "exit". HintCoordinator.onExit.push => @deactivateMode() if userMightOverType and Settings.get "waitForEnterForFilteredHints" installKeyBoardBlocker (callback) -> new WaitForEnter callback @@ -489,8 +495,8 @@ class LinkHintsMode extends LinkHintsModeBase # 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. installKeyBoardBlocker (callback) -> new TypingProtector 200, callback - else - DomUtils.flashRect linkMatched.rect if linkMatched.hint.frameId == frameId + else if linkMatched.hint.frameId == frameId + DomUtils.flashRect linkMatched.rect HintCoordinator.sendMessage "exit" # -- cgit v1.2.3 From 1cc4ab0bd5f3a7b1292ea70e06755fef6f9c7750 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 13 Mar 2016 05:16:11 +0000 Subject: Global link hints; self code review. - Better comments in places. - Better variable and message names in some places. --- content_scripts/link_hints.coffee | 118 ++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 63 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index e14ddfde..f10e7bea 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -55,40 +55,41 @@ HintCoordinator = sendMessage: (messageType, request = {}) -> chrome.runtime.sendMessage extend request, {handler: "linkHintsMessage", messageType, frameId} - activateMode: (mode, onExit) -> + prepareToActivateMode: (mode, onExit) -> @onExit = [onExit] - @sendMessage "activateMode", modeIndex: availableModes.indexOf mode - - getHints: -> - @localHints = ClickableElements.getVisibleClickableElements() - @sendMessage "postHints", hints: for hint, localIndex in @localHints - {rect: hint.rect, linkText: hint.linkText, showLinkText: hint.showLinkText, hasHref: hint.hasHref, - localIndex, frameId} - - # We activate LinkHintsMode() in every frame, and provide every frame with exactly the same hints. We also - # propagate the key state between frames. Therefore, the hint-selection process proceeds in lock step in - # every frame, the linkHintsMode arrives in the samme state in every frame. - activateLinkHintsMode: ({hints, modeIndex, frameId: activateModeFrameId}) -> - @onExit = [] unless frameId == activateModeFrameId - @linkHintsMode = new LinkHintsMode hints, availableModes[modeIndex] - - postKeyState: (request) -> @linkHintsMode.postKeyState request + @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 - setMode: ({modeIndex}) -> @linkHintsMode.setOpenLinkMode availableModes[modeIndex], false getLocalHintMarker: (hint) -> if hint.frameId == frameId then @localHints[hint.localIndex] else null exit: -> @onExit.pop()() while 0 < @onExit.length @linkHintsMode = @localHints = null - exitFailure: -> + deactivate: -> @onExit = [=> @linkHintsMode.deactivateMode()] @exit() LinkHints = activateMode: (count = 1, mode = OPEN_IN_CURRENT_TAB) -> if 0 < count or mode is OPEN_WITH_QUEUE - HintCoordinator.activateMode mode, -> + 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 @@ -100,7 +101,7 @@ LinkHints = activateModeToOpenIncognito: (count) -> @activateMode count, OPEN_INCOGNITO activateModeToDownloadLink: (count) -> @activateMode count, DOWNLOAD_LINK_URL -class LinkHintsModeBase +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 @@ -141,7 +142,7 @@ class LinkHintsModeBase @hintMode.onExit (event) => if event?.type == "click" or (event?.type == "keydown" and KeyboardUtils.isEscape event) or (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]) - HintCoordinator.sendMessage "exitFailure" + HintCoordinator.sendMessage "deactivate" @setOpenLinkMode mode, false @@ -150,13 +151,13 @@ class LinkHintsModeBase @hintMarkerContainingDiv = DomUtils.addElementList hintMarkers, id: "vimiumHintMarkerContainer", className: "vimiumReset" # Hide markers from other frames. - @hideMarker marker for marker in hintMarkers when marker.hint.frameId != frameId - @postKeyState = @postKeyState.bind this, hintMarkers + @hideMarker marker for marker in hintMarkers when marker.hintDescriptor.frameId != frameId + @updateKeyState = @updateKeyState.bind this, hintMarkers # TODO(smblott): This can be refactored out. - setOpenLinkMode: (@mode, shouldPropagtetoOtherFrames = true) -> + setOpenLinkMode: (@mode, shouldPropagateToOtherFrames = true) -> @hintMode.setIndicator @mode.indicator if windowIsFocused() - if shouldPropagtetoOtherFrames - HintCoordinator.sendMessage "setMode", modeIndex: availableModes.indexOf @mode + if shouldPropagateToOtherFrames + HintCoordinator.sendMessage "setOpenLinkMode", modeIndex: availableModes.indexOf @mode # # Creates a link marker for the given link. @@ -167,24 +168,20 @@ class LinkHintsModeBase (link) -> marker = DomUtils.createElement "div" marker.className = "vimiumReset internalVimiumHintMarker vimiumHintMarker" - marker.clickableItem = link.element marker.stableSortCount = ++stableSortCount - # Keep track of the original hint descriptor, we'll need it to decide which markers to display. - marker.hint = link - marker.linkText = link.linkText - marker.showLinkText = link.showLinkText + # Extract other relevant fields from the hint descriptor. TODO(smblott) The name "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) It is not intended that this remain structured this way. This is temporary, just to keep the -# diff smaller and clearer. We need to move the whole of ClickableElements out of the LinkHintsMode class. -ClickableElements = +# 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 make 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 @@ -280,7 +277,7 @@ ClickableElements = # 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 = [] @@ -306,7 +303,7 @@ ClickableElements = # + 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() @@ -325,12 +322,12 @@ ClickableElements = nonOverlappingElements.push visibleElement unless visibleElement.secondClassCitizen if Settings.get "filterLinkHints" - @generateLabelMap() DomUtils.textContent.reset() - extend hint, @generateLinkText hint.element for hint in nonOverlappingElements + @generateLabelMap() + extend hint, @generateLinkText hint.element for hint in localHints - hint.hasHref = hint.element.href? for hint in nonOverlappingElements - nonOverlappingElements + hint.hasHref = hint.element.href? for hint in localHints + localHints # Generate a map of input element => label generateLabelMap: -> @@ -370,8 +367,7 @@ ClickableElements = {linkText, showLinkText} -# TODO(smblott) It is not intended that this remain structured this way. This is temporary, just to keep the -# diff smaller and clearer. We need to move the whole of ClickableElements out of the LinkHintsMode class. +# 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... @@ -443,9 +439,9 @@ class LinkHintsMode extends LinkHintsModeBase updateVisibleMarkers: (hintMarkers, tabCount = 0) -> {hintKeystrokeQueue, linkTextKeystrokeQueue} = @markerMatcher - HintCoordinator.sendMessage "postKeyState", {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount} + HintCoordinator.sendMessage "updateKeyState", {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount} - postKeyState: (hintMarkers, {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) -> + updateKeyState: (hintMarkers, {hintKeystrokeQueue, linkTextKeystrokeQueue, tabCount}) -> extend @markerMatcher, {hintKeystrokeQueue, linkTextKeystrokeQueue} {linksMatched, userMightOverType} = @markerMatcher.getMatchingHints hintMarkers, tabCount @@ -457,13 +453,12 @@ class LinkHintsMode extends LinkHintsModeBase @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. The current frame may - # or may not contain the matched link, and it may or may not have the focus. These two things are - # independent, so there are four possible cases. All four cases are accounted for here by selectively - # pushing the appropriate onExit() handlers. + # When only one link 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 = HintCoordinator.getLocalHintMarker(linkMatched.hint)?.element + clickEl = HintCoordinator.getLocalHintMarker(linkMatched.hintDescriptor)?.element if clickEl? HintCoordinator.onExit.push => @@ -479,32 +474,29 @@ class LinkHintsMode extends LinkHintsModeBase linkActivator clickEl installKeyBoardBlocker = (startKeyboardBlocker) -> - if linkMatched.hint.frameId == frameId - flashEl = DomUtils.addFlashRect linkMatched.hint.rect + if linkMatched.hintDescriptor.frameId == frameId + flashEl = DomUtils.addFlashRect linkMatched.hintDescriptor.rect HintCoordinator.onExit.push -> DomUtils.removeElement flashEl if document.hasFocus() startKeyboardBlocker -> HintCoordinator.sendMessage "exit" - # If we're using a keyboard blocker, then the frame with the focus invokes "exit", otherwise the frame - # containing the selected link invokes "exit". HintCoordinator.onExit.push => @deactivateMode() + # If we're using a keyboard blocker, then the frame with the focus invokes "exit", otherwise the frame + # containing the matched link invokes "exit". if userMightOverType and Settings.get "waitForEnterForFilteredHints" 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. installKeyBoardBlocker (callback) -> new TypingProtector 200, callback - else if linkMatched.hint.frameId == frameId - DomUtils.flashRect linkMatched.rect + else if linkMatched.hintDescriptor.frameId == frameId + DomUtils.flashRect linkMatched.hintDescriptor.rect HintCoordinator.sendMessage "exit" # # Shows the marker, highlighting matchingCharCount characters. # showMarker: (linkMarker, matchingCharCount) -> - # Never show markers from other frames - return unless linkMarker.hint.frameId == frameId + return unless linkMarker.hintDescriptor.frameId == frameId linkMarker.style.display = "" for j in [0...linkMarker.childNodes.length] if (j < matchingCharCount) @@ -721,4 +713,4 @@ root = exports ? window root.LinkHints = LinkHints root.HintCoordinator = HintCoordinator # For tests: -extend root, {LinkHintsMode, ClickableElements, AlphabetHints} +extend root, {LinkHintsMode, LocalHints, AlphabetHints} -- cgit v1.2.3 From 6271ce2ba4e73c7506f638113660a5fb1e66e999 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 13 Mar 2016 07:32:46 +0000 Subject: Global link hints; self code review (minor tweaks). Just tweaks. --- content_scripts/link_hints.coffee | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index f10e7bea..32d67ab9 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -140,9 +140,8 @@ class LinkHintsModeBase # This is temporary, because the "visible hints" code is keypress: @onKeyPressInMode.bind this, hintMarkers @hintMode.onExit (event) => - if event?.type == "click" or (event?.type == "keydown" and KeyboardUtils.isEscape event) or - (event?.type == "keydown" and event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ]) - HintCoordinator.sendMessage "deactivate" + 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, false @@ -150,8 +149,7 @@ class LinkHintsModeBase # This is temporary, because the "visible hints" code is # because some clickable elements cannot contain children, e.g. submit buttons. @hintMarkerContainingDiv = DomUtils.addElementList hintMarkers, id: "vimiumHintMarkerContainer", className: "vimiumReset" - # Hide markers from other frames. - @hideMarker marker for marker in hintMarkers when marker.hintDescriptor.frameId != frameId + @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, shouldPropagateToOtherFrames = true) -> @@ -169,7 +167,7 @@ class LinkHintsModeBase # This is temporary, because the "visible hints" code is marker = DomUtils.createElement "div" marker.className = "vimiumReset internalVimiumHintMarker vimiumHintMarker" marker.stableSortCount = ++stableSortCount - # Extract other relevant fields from the hint descriptor. TODO(smblott) The name "link" here is misleading. + # 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} @@ -180,7 +178,7 @@ class LinkHintsModeBase # This is temporary, because the "visible hints" code is 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 make the diff clearer. +# 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 @@ -321,12 +319,12 @@ LocalHints = # click some elements that we could click before. nonOverlappingElements.push visibleElement unless visibleElement.secondClassCitizen + hint.hasHref = hint.element.href? for hint in localHints if Settings.get "filterLinkHints" DomUtils.textContent.reset() @generateLabelMap() extend hint, @generateLinkText hint.element for hint in localHints - hint.hasHref = hint.element.href? for hint in localHints localHints # Generate a map of input element => label @@ -453,9 +451,9 @@ class LinkHintsMode extends LinkHintsModeBase @hideMarker marker for marker in hintMarkers @showMarker matched, @markerMatcher.hintKeystrokeQueue.length for matched in linksMatched - # When only one link 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. + # 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 = HintCoordinator.getLocalHintMarker(linkMatched.hintDescriptor)?.element -- cgit v1.2.3 From b7f0487d32dd50b73cf7729bbb4a900db9b786c8 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 13 Mar 2016 08:10:23 +0000 Subject: Global link hints; revert b7535a604954b5873d825eb66bfecd08f1f2c99b. Here's why: - b7535a604954b5873d825eb66bfecd08f1f2c99b is complicated and complex (O(n^2)). - With experience, it is not obviously better than what was there before, and in some cases it's worse. - b7535a604954b5873d825eb66bfecd08f1f2c99b selects way too much text in some cases; e.g. on Google Inbox, it selects text lengths in the tens of thousands of characters. That cannot be useful. - With global hints, this extra cost (resulting from passing large objects between frames) is significant and noticable. - The simpler approach of accounting for text length when scoring filtered hints (tweaked here: 5cbc5ad702a01a81b98f8c82edb3b6d227c2c7b5) works better in practice. --- content_scripts/link_hints.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 32d67ab9..fae255c6 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -321,7 +321,6 @@ LocalHints = hint.hasHref = hint.element.href? for hint in localHints if Settings.get "filterLinkHints" - DomUtils.textContent.reset() @generateLabelMap() extend hint, @generateLinkText hint.element for hint in localHints @@ -354,14 +353,14 @@ LocalHints = linkText = element.value if not linkText and 'placeholder' of element linkText = element.placeholder - # check if there is an image embedded in the tag + # Check if there is an image embedded in the 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 + linkText = (element.textContent.trim() || element.innerHTML.trim())[...512] {linkText, showLinkText} -- cgit v1.2.3 From 742dcfcca95918996a27930bfdd86b19307d8450 Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Sun, 13 Mar 2016 09:01:55 +0000 Subject: Global link hints; self code review (3)... ... Also, do not set an active hint marker initially (because it's not predicatble which hint will be selected). --- content_scripts/link_hints.coffee | 53 ++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 28 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index fae255c6..cb5b0858 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -321,50 +321,50 @@ LocalHints = hint.hasHref = hint.element.href? for hint in localHints if Settings.get "filterLinkHints" - @generateLabelMap() - extend hint, @generateLinkText hint.element for hint in localHints - + @withLabelMap (labelMap) => + extend hint, @generateLinkText labelMap, hint.element for hint in localHints localHints - # Generate a map of input element => label - generateLabelMap: -> - @labelMap = {} - labels = document.querySelectorAll("label") + # 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) + 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 + # Remove trailing ":" commonly found in labels. + if labelText[labelText.length-1] == ":" + labelText = labelText.substr 0, labelText.length-1 + labelMap[forElement] = labelText + callback labelMap - generateLinkText: (element) -> + 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] + if nodeName == "input" + if labelMap[element.id] + linkText = labelMap[element.id] showLinkText = true - else if (element.type != "password") + 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 tag. - else if (nodeName == "a" && !element.textContent.trim() && - element.firstElementChild && - element.firstElementChild.nodeName.toLowerCase() == "img") + 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) + showLinkText = true if linkText 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, +# 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... @@ -479,8 +479,8 @@ class LinkHintsMode extends LinkHintsModeBase startKeyboardBlocker -> HintCoordinator.sendMessage "exit" HintCoordinator.onExit.push => @deactivateMode() - # If we're using a keyboard blocker, then the frame with the focus invokes "exit", otherwise the frame - # containing the matched link invokes "exit". + # 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" installKeyBoardBlocker (callback) -> new WaitForEnter callback else if userMightOverType @@ -580,9 +580,6 @@ class FilterHints fillInMarkers: (hintMarkers) -> @renderMarker marker for marker in hintMarkers - @activeHintMarker = hintMarkers[0] - @activeHintMarker?.classList.add "vimiumActiveHintMarker" - # 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. @filterLinkHints hintMarkers -- cgit v1.2.3 From 7532d1c1dda67cb233e488afafbbf29308dc65bf Mon Sep 17 00:00:00 2001 From: Stephen Blott Date: Wed, 16 Mar 2016 06:35:38 +0000 Subject: Global link hints; make frames selectable. --- content_scripts/link_hints.coffee | 10 +++++++++- content_scripts/vimium_frontend.coffee | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'content_scripts') diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index cb5b0858..3088812b 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -253,6 +253,9 @@ LocalHints = 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. @@ -359,6 +362,9 @@ LocalHints = 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] @@ -459,7 +465,9 @@ class LinkHintsMode extends LinkHintsModeBase if clickEl? HintCoordinator.onExit.push => - if DomUtils.isSelectable clickEl + if clickEl == document.body + Utils.nextTick -> focusThisFrame highlight: true + else if DomUtils.isSelectable clickEl window.focus() DomUtils.simulateSelect clickEl else diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index bd7da625..93ab440a 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -638,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} -- cgit v1.2.3