diff options
| -rw-r--r-- | README.md | 9 | ||||
| -rw-r--r-- | background_scripts/commands.coffee | 3 | ||||
| -rw-r--r-- | content_scripts/link_hints.coffee | 21 | ||||
| -rw-r--r-- | content_scripts/mode_visual_edit.coffee | 361 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 35 | ||||
| -rw-r--r-- | lib/dom_utils.coffee | 20 | ||||
| -rw-r--r-- | lib/keyboard_utils.coffee | 2 | ||||
| -rw-r--r-- | manifest.json | 2 |
8 files changed, 97 insertions, 356 deletions
@@ -145,7 +145,9 @@ Shifts are automatically detected so, for example, `<c-&>` corresponds to ctrl+s More documentation ------------------ -Many of the more advanced or involved features are documented on [Vimium's github wiki](https://github.com/philc/vimium/wiki). +Many of the more advanced or involved features are documented on +[Vimium's github wiki](https://github.com/philc/vimium/wiki). Also +see the [FAQ](https://github.com/philc/vimium/wiki/FAQ). Contributing ------------ @@ -155,8 +157,9 @@ Release Notes ------------- Next version (not yet released) -- Bug fixes: - - Fix endless scrolling (#1911). +1.54 (2016-01-30) + +- Fix occasional endless scrolling (#1911). 1.53 (2015-09-25) diff --git a/background_scripts/commands.coffee b/background_scripts/commands.coffee index 50306c35..0a296c3e 100644 --- a/background_scripts/commands.coffee +++ b/background_scripts/commands.coffee @@ -98,7 +98,6 @@ Commands = "enterInsertMode", "enterVisualMode", "enterVisualLineMode", - # "enterEditMode", "focusInput", "LinkHints.activateMode", "LinkHints.activateModeToOpenInNewTab", @@ -187,7 +186,6 @@ defaultKeyMappings = "i": "enterInsertMode" "v": "enterVisualMode" "V": "enterVisualLineMode" - # "gv": "enterEditMode" "H": "goBack" "L": "goForward" @@ -279,7 +277,6 @@ commandDescriptions = enterInsertMode: ["Enter insert mode", { noRepeat: true }] enterVisualMode: ["Enter visual mode", { noRepeat: true }] enterVisualLineMode: ["Enter visual line mode", { noRepeat: true }] - # enterEditMode: ["Enter vim-like edit mode (not yet implemented)", { noRepeat: true }] focusInput: ["Focus the first text box on the page. Cycle between them using tab", { passCountToFunction: true }] diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index 07deaf61..54476935 100644 --- a/content_scripts/link_hints.coffee +++ b/content_scripts/link_hints.coffee @@ -58,6 +58,11 @@ class LinkHintsMode # 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) @markerMatcher = new (if Settings.get "filterLinkHints" then FilterHints else AlphabetHints) @markerMatcher.fillInMarkers hintMarkers @@ -70,7 +75,6 @@ class LinkHintsMode suppressTrailingKeyEvents: true exitOnEscape: true exitOnClick: true - exitOnScroll: true keydown: @onKeyDownInMode.bind this, hintMarkers keypress: @onKeyPressInMode.bind this, hintMarkers @@ -120,7 +124,7 @@ class LinkHintsMode DomUtils.simulateClick link, altKey: true, ctrlKey: false, metaKey: false else # OPEN_IN_CURRENT_TAB @hintMode.setIndicator "Open link in current tab." - @linkActivator = (link) -> DomUtils.simulateClick.bind(DomUtils, link)() + @linkActivator = DomUtils.simulateClick.bind DomUtils # # Creates a link marker for the given link. @@ -590,7 +594,8 @@ class FilterHints scoreLinkHint: (linkSearchString) -> searchWords = linkSearchString.trim().split /\s+/ (linkMarker) -> - linkWords = linkMarker.linkWords ?= linkMarker.linkText.trim().toLowerCase().split /\s+/ + text = linkMarker.linkText.trim() + linkWords = linkMarker.linkWords ?= text.toLowerCase().split /\s+/ searchWordScores = for searchWord in searchWords @@ -606,8 +611,14 @@ class FilterHints 0 Math.max linkWordScores... - addFunc = (a,b) -> a + b - if 0 in searchWordScores then 0 else searchWordScores.reduce addFunc, 0 + if 0 in searchWordScores + 0 + else + addFunc = (a,b) -> a + b + score = searchWordScores.reduce addFunc, 0 + # Prefer matches in shorter texts. To keep things balanced for links without any text, we just weight + # them as if their length was 50. + score / Math.log(text.length || 50) # # Make each hint character a span, so that we can highlight the typed characters as you type them. diff --git a/content_scripts/mode_visual_edit.coffee b/content_scripts/mode_visual_edit.coffee index 8bcde6cb..ae8897ca 100644 --- a/content_scripts/mode_visual_edit.coffee +++ b/content_scripts/mode_visual_edit.coffee @@ -4,13 +4,12 @@ # - VisualMode # - VisualLineMode # - CaretMode -# - EditMode (experimental) # # SuppressPrintable and CountPrefix are shared utility base classes. # Movement is a shared vim-like movement base class. # # The class inheritance hierarchy is: -# - Mode, SuppressPrintable, CountPrefix, Movement, [ VisualMode | CaretMode | EditMode ] +# - Mode, SuppressPrintable, CountPrefix, Movement, [ VisualMode | CaretMode ] # - Mode, SuppressPrintable, CountPrefix, Movement, VisualMode, VisualLineMode # # The possible mode states are: @@ -20,10 +19,6 @@ # - ..., VisualMode, FindMode # - ..., VisualLineMode, FindMode # - ..., CaretMode, FindMode -# - ..., EditMode -# - ..., EditMode, InsertMode -# - ..., EditMode, VisualMode -# - ..., EditMode, VisualLineMode # # This prevents printable characters from being passed through to underlying modes or the underlying page. @@ -44,8 +39,7 @@ class SuppressPrintable extends Mode class CountPrefix extends SuppressPrintable constructor: (options) -> @countPrefix = "" - # This is an initial multiplier for the first count. It allows edit mode to implement both "d3w" and - # "3dw". Also, "3d2w" deletes six words. + # This is an initial multiplier for the first count. @countPrefixFactor = options.initialCountPrefix || 1 super options @@ -93,10 +87,9 @@ class Movement extends CountPrefix # disable copy so that subsequent copies do not propagate. @copy = (->) if isFinalUserCopy - # This s used whenever manipulating the selection may, as a side effect, change the clipboard's contents. + # This is used whenever manipulating the selection may, as a side effect, change the clipboard's contents. # We restore the original clipboard contents when we're done. May be asynchronous. We use a lock so that - # calls can be nested. We do this primarily for edit mode, where the user does not expect caret movements - # to change the clipboard contents. + # calls can be nested. protectClipboard: do -> locked = false @@ -111,10 +104,7 @@ class Movement extends CountPrefix # mode with visual-line mode. changeMode: (mode, options = {}) -> @exit() - if @options.parentMode - @options.parentMode.launchSubMode mode, options - else - new mode options + new mode options # Return the character following (to the right of) the focus, and leave the selection unchanged. Returns # undefined if no such character exists. @@ -287,20 +277,6 @@ class Movement extends CountPrefix when "function" then @movements[keyChar].call @, count @scrollIntoView() - # The bahavior of Movement can be tweaked by setting the following options: - # - options.parentMode (a mode) - # This instance is a sub-mode of another mode (currently, only edit mode). - # - options.oneMovementOnly (truthy/falsy) - # This instance is created for one movement only, after which it yanks and exits. - # - options.immediateMovement (a keyChar string) - # This instance is created for one movement only, and this options specifies the movement (e.g. "j"). - # - options.deleteFromDocument (truthy/falsy) - # When yanking text, also delete it from the document. - # - options.onYank (a function) - # When yanking text, also call this function, passing the yanked text as an argument. - # - options.noCopyToClipboard (truthy/falsy) - # If truthy, then do not copy the yanked text to the clipboard when yanking. - # constructor: (options) -> @selection = window.getSelection() @movements = extend {}, @movements @@ -312,13 +288,8 @@ class Movement extends CountPrefix @movements.B = @movements.b @movements.W = @movements.w - if @options.immediateMovement - # This instance has been created to execute a single, given movement. - @runMovementKeyChar @options.immediateMovement, @getCountPrefix() - return - # This is the main keyboard-event handler for movements and commands for all user modes (visual, - # visual-line, caret and edit). + # visual-line and caret). @push _name: "#{@id}/keypress" keypress: (event) => @@ -346,27 +317,24 @@ class Movement extends CountPrefix @continueBubbling - # Install basic bindings for find mode, "n" and "N". We do not install these bindings if this is a - # sub-mode of edit mode (because we cannot guarantee that the selection will remain within the active - # element), or if this instance has been created to execute only a single movement. - unless @options.parentMode or options.oneMovementOnly - do => - doFind = (count, backwards) => - initialRange = @selection.getRangeAt(0).cloneRange() - for [0...count] by 1 - unless FindMode.execute null, {colorSelection: false, backwards} - @setSelectionRange initialRange - HUD.showForDuration("No matches for '#{FindMode.query.rawQuery}'", 1000) - return - # The find was successfull. If we're in caret mode, then we should now have a selection, so we can - # drop back into visual mode. - @changeMode VisualMode if @name == "caret" and 0 < @selection.toString().length - - @movements.n = (count) -> doFind count, false - @movements.N = (count) -> doFind count, true - @movements["/"] = -> - @findMode = new FindMode returnToViewport: true - @findMode.onExit => @changeMode VisualMode + # Install basic bindings for find mode, "n" and "N". + do => + doFind = (count, backwards) => + initialRange = @selection.getRangeAt(0).cloneRange() + for [0...count] by 1 + unless FindMode.execute null, {colorSelection: false, backwards} + @setSelectionRange initialRange + HUD.showForDuration("No matches for '#{FindMode.query.rawQuery}'", 1000) + return + # The find was successfull. If we're in caret mode, then we should now have a selection, so we can + # drop back into visual mode. + @changeMode VisualMode if @name == "caret" and 0 < @selection.toString().length + + @movements.n = (count) -> doFind count, false + @movements.N = (count) -> doFind count, true + @movements["/"] = -> + @findMode = new FindMode returnToViewport: true + @findMode.onExit => @changeMode VisualMode # # End of Movement constructor. @@ -374,41 +342,18 @@ class Movement extends CountPrefix # it. yank: (args = {}) -> @yankedText = @selection.toString() - if @options.deleteFromDocument or args.deleteFromDocument - @selection.deleteFromDocument() - else - @selection.collapseToStart() + @selection.collapseToStart() message = @yankedText.replace /\s+/g, " " message = message[...12] + "..." if 15 < @yankedText.length plural = if @yankedText.length == 1 then "" else "s" - @options.onYank?.call @, @yankedText @exit() HUD.showForDuration "Yanked #{@yankedText.length} character#{plural}: \"#{message}\".", 2500 @yankedText exit: (event, target) -> - unless @options.parentMode or @options.oneMovementOnly - @selection.collapseToStart() if event?.type == "keydown" and KeyboardUtils.isEscape event - - # Disabled, pending discussion of fine-tuning the UX. Simpler alternative is implemented above. - # # If we're exiting on escape and there is a range selection, then we leave it in place. However, an - # # immediately-following Escape clears the selection. See #1441. - # if @selection.type == "Range" and event?.type == "keydown" and KeyboardUtils.isEscape event - # handlerStack.push - # _name: "visual/range/escape" - # click: -> handlerStack.remove(); @continueBubbling - # focus: -> handlerStack.remove(); @continueBubbling - # keydown: (event) => - # handlerStack.remove() - # if @selection.type == "Range" and event.type == "keydown" and KeyboardUtils.isEscape event - # @collapseSelectionToFocus() - # DomUtils.suppressKeyupAfterEscape handlerStack - # @suppressEvent - # else - # @continueBubbling - + @selection.collapseToStart() if event?.type == "keydown" and KeyboardUtils.isEscape event super event, target # For "daw", "das", and so on. We select a lexical entity (a word, a sentence or a paragraph). @@ -450,11 +395,7 @@ class Movement extends CountPrefix if @element and DomUtils.isEditable @element if @element.clientHeight < @element.scrollHeight if @element.isContentEditable - # WIP (edit mode only)... elementWithFocus = DomUtils.getElementWithFocus @selection, @getDirection() == backward - # position = @element.getClientRects()[0].top - elementWithFocus.getClientRects()[0].top - # console.log "top", position - # Scroller.scrollToPosition @element, position, 0 position = elementWithFocus.getClientRects()[0].bottom - @element.getClientRects()[0].top - @element.clientHeight + @element.scrollTop Scroller.scrollToPosition @element, position, 0 else @@ -478,29 +419,24 @@ class VisualMode extends Movement super extend defaults, options # Establish or use the initial selection. If that's not possible, then enter caret mode. - unless @options.oneMovementOnly or options.immediateMovement - if @options.parentMode and @selection.type == "Caret" - # We're being called from edit mode, so establish an intial visible selection. - @extendByOneCharacter(forward) or @extendByOneCharacter backward + if @selection.type in [ "Caret", "Range" ] + selectionRect = @selection.getRangeAt(0).getBoundingClientRect() + selectionRect = Rect.intersect selectionRect, (Rect.create 0, 0, window.innerWidth, + window.innerHeight) + if selectionRect.height >= 0 and selectionRect.width >= 0 + # The selection is visible in the current viewport. + if @selection.type == "Caret" + # The caret is in the viewport. Make make it visible. + @extendByOneCharacter(forward) or @extendByOneCharacter backward else - if @selection.type in [ "Caret", "Range" ] - selectionRect = @selection.getRangeAt(0).getBoundingClientRect() - selectionRect = Rect.intersect selectionRect, (Rect.create 0, 0, window.innerWidth, - window.innerHeight) - if selectionRect.height >= 0 and selectionRect.width >= 0 - # The selection is visible in the current viewport. - if @selection.type == "Caret" - # The caret is in the viewport. Make make it visible. - @extendByOneCharacter(forward) or @extendByOneCharacter backward - else - # The selection is outside of the viewport: clear it. We guess that the user has moved on, and is - # more likely to be interested in visible content. - @selection.removeAllRanges() + # The selection is outside of the viewport: clear it. We guess that the user has moved on, and is + # more likely to be interested in visible content. + @selection.removeAllRanges() - if @selection.type != "Range" - @changeMode CaretMode - HUD.showForDuration "No usable selection, entering caret mode...", 2500 - return + if @selection.type != "Range" + @changeMode CaretMode + HUD.showForDuration "No usable selection, entering caret mode...", 2500 + return @push _name: "#{@id}/enter/click" @@ -514,60 +450,29 @@ class VisualMode extends Movement # Click in a focusable element exits. click: (event) => @alwaysContinueBubbling => - unless @options.parentMode - @exit event, event.target if DomUtils.isFocusable event.target + @exit event, event.target if DomUtils.isFocusable event.target # Visual-mode commands. - unless @options.oneMovementOnly - @commands.y = -> @yank() - @commands.p = -> chrome.runtime.sendMessage handler: "openUrlInCurrentTab", url: @yank() - @commands.P = -> chrome.runtime.sendMessage handler: "openUrlInNewTab", url: @yank() - @commands.V = -> @changeMode VisualLineMode - @commands.c = -> @collapseSelectionToFocus(); @changeMode CaretMode - @commands.o = -> @reverseSelection() - - # Additional commands when run under edit mode. - if @options.parentMode - @commands.x = -> @yank deleteFromDocument: true - @commands.d = -> @yank deleteFromDocument: true - @commands.c = -> @yank deleteFromDocument: true; @options.parentMode.enterInsertMode() - - # For edit mode's "yy" and "dd". - if @options.yankLineCharacter - @commands[@options.yankLineCharacter] = (count) -> - @selectLine count; @yank() - - # For edit mode's "daw", "cas", and so on. - if @options.oneMovementOnly - @commands.a = (count) -> - for entity in [ word, sentence, paragraph ] - do (entity) => - @commands[entity.charAt 0] = -> - @selectLexicalEntity entity, count; @yank() + @commands.y = -> @yank() + @commands.p = -> chrome.runtime.sendMessage handler: "openUrlInCurrentTab", url: @yank() + @commands.P = -> chrome.runtime.sendMessage handler: "openUrlInNewTab", url: @yank() + @commands.V = -> @changeMode VisualLineMode + @commands.c = -> @collapseSelectionToFocus(); @changeMode CaretMode + @commands.o = -> @reverseSelection() + # # End of VisualMode constructor. exit: (event, target) -> - unless @options.parentMode - # Don't leave the user in insert mode just because they happen to have selected text within an input - # element. - if document.activeElement and DomUtils.isEditable document.activeElement - document.activeElement.blur() unless event?.type == "click" - - if @options.parentMode - # E.g. when exiting visual mode under edit mode, we no longer want the selection. - @collapseSelectionToFocus() + # Don't leave the user in insert mode just because they happen to have selected text within an input + # element. + if document.activeElement and DomUtils.isEditable document.activeElement + document.activeElement.blur() unless event?.type == "click" super event, target if @yankedText? - unless @options.noCopyToClipboard - console.log "yank:", @yankedText if @debug - @copy @yankedText, true - - # Call sub-class; then yank, if we've only been created for a single movement. - handleMovementKeyChar: (args...) -> - super args... - @yank() if @options.oneMovementOnly or @options.immediateMovement + console.log "yank:", @yankedText if @debug + @copy @yankedText, true selectLine: (count) -> @reverseSelection() if @getDirection() == forward @@ -657,160 +562,6 @@ class CaretMode extends Movement return true false -class EditMode extends Movement - constructor: (options = {}) -> - @alterMethod = "move" - @element = document.activeElement - return unless @element and DomUtils.isEditable @element - - options.indicator = "Edit mode" - defaults = - name: "edit" - exitOnEscape: true - exitOnBlur: @element - super extend defaults, options - - # Edit mode commands. - extend @commands, - i: -> @enterInsertMode() - a: -> @enterInsertMode() - I: -> @runMovement backward, lineboundary; @enterInsertMode() - A: -> @runMovement forward, lineboundary; @enterInsertMode() - o: -> @openLine forward - O: -> @openLine backward - p: -> @pasteClipboard forward - P: -> @pasteClipboard backward - v: -> @launchSubMode VisualMode - V: -> @launchSubMode VisualLineMode - - Y: (count) -> @enterVisualModeForMovement count, immediateMovement: "Y" - x: (count) -> @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true, noCopyToClipboard: true - X: (count) -> @enterVisualModeForMovement count, immediateMovement: "h", deleteFromDocument: true, noCopyToClipboard: true - y: (count) -> @enterVisualModeForMovement count, yankLineCharacter: "y" - d: (count) -> @enterVisualModeForMovement count, yankLineCharacter: "d", deleteFromDocument: true - c: (count) -> @enterVisualModeForMovement count, deleteFromDocument: true, onYank: => @enterInsertMode() - - D: (count) -> @enterVisualModeForMovement 1, immediateMovement: "$", deleteFromDocument: true - C: (count) -> @enterVisualModeForMovement 1, immediateMovement: "$", deleteFromDocument: true, onYank: => @enterInsertMode() - - '~': (count) -> @swapCase count, true - 'g~': (count) -> @swapCase count, false - - # Disabled. Doesn't work reliably. - # J: (count) -> - # for [0...count] - # @runMovement forward, lineboundary - # @enterVisualModeForMovement 1, immediateMovement: "w", deleteFromDocument: true, noCopyToClipboard: true - # DomUtils.simulateTextEntry @element, " " - - r: (count) -> - handlerStack.push - _name: "repeat-character" - keydown: (event) => DomUtils.suppressPropagation event; @stopBubblingAndFalse - keypress: (event) => - handlerStack.remove() - keyChar = String.fromCharCode event.charCode - if keyChar.length == 1 - @enterVisualModeForMovement count, immediateMovement: "l", deleteFromDocument: true, noCopyToClipboard: true - DomUtils.simulateTextEntry @element, [0...count].map(-> keyChar).join "" - @suppressEvent - - # Disabled: potentially confusing. - # # If the input is empty, then enter insert mode immediately. - # unless @element.isContentEditable - # if @element.value.trim() == "" - # @enterInsertMode() - # HUD.showForDuration "Input empty, entered insert mode directly.", 3500 - # - # End of edit-mode constructor. - - # For "~", "3~", "g~3w", "g~e", and so on. - swapCase: (count, immediate) -> - @enterVisualModeForMovement count, - immediateMovement: if immediate then "l" else null - deleteFromDocument: true - noCopyToClipboard: true - onYank: (text) => - chars = - for char in text.split "" - if char == char.toLowerCase() then char.toUpperCase() else char.toLowerCase() - DomUtils.simulateTextEntry @element, chars.join "" - - # For "p" and "P". - pasteClipboard: (direction) -> - @paste (text) => - if text - # We use the following heuristic: if the text ends with a newline character, then it's a line-oriented - # paste, and should be pasted in at a line break. - if /\n$/.test text - @runMovement backward, lineboundary - @runMovement forward, line if direction == forward - DomUtils.simulateTextEntry @element, text - @runMovement backward, line - else - DomUtils.simulateTextEntry @element, text - - # For "o" and "O". - openLine: (direction) -> - @runMovement direction, lineboundary - DomUtils.simulateTextEntry @element, "\n" - @runMovement backward, character if direction == backward - @enterInsertMode() - - # This lanches a visual-mode instance for one movement only, (usually) yanks the resulting selected text, - # and (possibly) deletes it. - enterVisualModeForMovement: (count, options = {}) -> - @launchSubMode VisualMode, extend options, - initialCountPrefix: count - oneMovementOnly: true - - enterInsertMode: () -> - @launchSubMode InsertMode, - exitOnEscape: true - targetElement: @options.targetElement - - launchSubMode: (mode, options = {}) -> - @activeSubMode?.instance.exit() - @activeSubMode = - mode: mode - options: options - instance: new mode extend options, parentMode: @ - @activeSubMode.instance.onExit => @activeSubMode = null - - exit: (event, target) -> - super event, target - - # Deactivate any active sub-mode. Any such mode will clear @activeSubMode on exit, so we grab a copy now. - activeSubMode = @activeSubMode - activeSubMode?.instance.exit() - - if event?.type == "keydown" and KeyboardUtils.isEscape event - if target? and DomUtils.isDOMDescendant @element, target - @element.blur() - - if event?.type == "blur" - # This instance of edit mode has now been entirely removed from the handler stack. It is inactive. - # However, the user hasn't asked to leave edit mode, and may return. For example, we get a blur event - # when we change tab. Or, the user may be copying text with the mouse. When the user does return, - # they expect to still be in edit mode. We leave behind a "suspended-edit" mode which watches for focus - # events and activates a new edit-mode instance if required. - # - # How does this get cleaned up? It's a bit tricky. The suspended-edit mode remains active on the - # current input element indefinitely. However, the only way to enter edit mode is via focusInput. And - # all modes launched by focusInput on a particular input element share a singleton (the element itself). - # In addition, the new mode below shares the same singleton. So any new insert-mode or edit-mode - # instance on this target element (the singleton) displaces any previously-active mode (including any - # suspended-edit mode). PostFindMode shares the same singleton. - # - (new Mode name: "#{@id}-suspended", singleton: @options.singleton).push - _name: "suspended-edit/#{@id}/focus" - focus: (event) => - @alwaysContinueBubbling => - if event?.target == @options.targetElement - editMode = new EditMode Utils.copyObjectOmittingProperties @options, "keydown", "keypress", "keyup" - editMode.launchSubMode activeSubMode.mode, activeSubMode.options if activeSubMode - root = exports ? window root.VisualMode = VisualMode root.VisualLineMode = VisualLineMode -root.EditMode = EditMode diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee index 781223b1..e041245d 100644 --- a/content_scripts/vimium_frontend.coffee +++ b/content_scripts/vimium_frontend.coffee @@ -217,7 +217,6 @@ window.addEventListener "hashchange", onFocus initializeOnDomReady = -> # Tell the background page we're in the dom ready state. chrome.runtime.connect({ name: "domReady" }) - CursorHider.init() # We only initialize the vomnibar in the tab's main frame, because it's only ever opened there. Vomnibar.init() if DomUtils.isTopFrame() HUD.init() @@ -836,40 +835,6 @@ toggleHelpDialog = (html, fid) -> else showHelpDialog(html, fid) -CursorHider = - # - # Hide the cursor when the browser scrolls, and prevent mouse from hovering while invisible. - # - cursorHideStyle: null - isScrolling: false - - onScroll: (event) -> - CursorHider.isScrolling = true - unless CursorHider.cursorHideStyle.parentElement - document.head.appendChild CursorHider.cursorHideStyle - - onMouseMove: (event) -> - if CursorHider.cursorHideStyle.parentElement and not CursorHider.isScrolling - CursorHider.cursorHideStyle.remove() - CursorHider.isScrolling = false - - init: -> - # Temporarily disabled pending consideration of #1359 (in particular, whether cursor hiding is too fragile - # as to provide a consistent UX). - return - - # Disable cursor hiding for Chrome versions less than 39.0.2171.71 due to a suspected browser error. - # See #1345 and #1348. - return unless Utils.haveChromeVersion "39.0.2171.71" - - @cursorHideStyle = DomUtils.createElement "style" - @cursorHideStyle.innerHTML = """ - body * {pointer-events: none !important; cursor: none !important;} - body, html {cursor: none !important;} - """ - window.addEventListener "mousemove", @onMouseMove - window.addEventListener "scroll", @onScroll - initializePreDomReady() DomUtils.documentReady initializeOnDomReady DomUtils.documentReady registerFrame diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index 027188bf..7473df17 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -234,13 +234,27 @@ DomUtils = if element.selectionStart == 0 and element.selectionEnd == 0 element.setSelectionRange element.value.length, element.value.length - + simulateUnhover: (element, modifiers) -> @simulateMouseEvent "mouseout", element, modifiers simulateClick: (element, modifiers) -> - modifiers ||= {} - eventSequence = ["mouseover", "mousedown", "mouseup", "click"] for event in eventSequence + @simulateMouseEvent event, element, modifiers + + simulateMouseEvent: do -> + lastHoveredElement = undefined + (event, element, modifiers = {}) -> + + if event == "mouseout" + element ?= lastHoveredElement # Allow unhovering the last hovered element by passing undefined. + lastHoveredElement = undefined + return unless element? + + else if event == "mouseover" + # Simulate moving the mouse off the previous element first, as if we were a real mouse. + @simulateMouseEvent "mouseout", undefined, modifiers + lastHoveredElement = element + mouseEvent = document.createEvent("MouseEvents") mouseEvent.initMouseEvent(event, true, true, window, 1, 0, 0, 0, 0, modifiers.ctrlKey, modifiers.altKey, modifiers.shiftKey, modifiers.metaKey, 0, null) diff --git a/lib/keyboard_utils.coffee b/lib/keyboard_utils.coffee index 5c95680c..f123f75a 100644 --- a/lib/keyboard_utils.coffee +++ b/lib/keyboard_utils.coffee @@ -53,7 +53,7 @@ KeyboardUtils = isEscape: (event) -> # c-[ is mapped to ESC in Vim by default. - (event.keyCode == @keyCodes.ESC) || (event.ctrlKey && @getKeyChar(event) == '[') + (event.keyCode == @keyCodes.ESC) || (event.ctrlKey && @getKeyChar(event) == '[' and not event.metaKey) # TODO. This is probably a poor way of detecting printable characters. However, it shouldn't incorrectly # identify any of chrome's own keyboard shortcuts as printable. diff --git a/manifest.json b/manifest.json index 63d96e6f..7fe13b13 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Vimium", - "version": "1.53", + "version": "1.54", "description": "The Hacker's Browser. Vimium provides keyboard shortcuts for navigation and control in the spirit of Vim.", "icons": { "16": "icons/icon16.png", "48": "icons/icon48.png", |
