diff options
| -rw-r--r-- | CONTRIBUTING.md | 2 | ||||
| -rw-r--r-- | README.md | 10 | ||||
| -rw-r--r-- | background_scripts/commands.coffee | 4 | ||||
| -rw-r--r-- | content_scripts/link_hints.coffee | 88 | ||||
| -rw-r--r-- | content_scripts/mode_visual_edit.coffee | 4 | ||||
| -rw-r--r-- | content_scripts/vimium.css | 2 | ||||
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 35 | ||||
| -rw-r--r-- | icons/browser_action_disabled.png | bin | 32025 -> 5161 bytes | |||
| -rw-r--r-- | icons/browser_action_enabled.png | bin | 37455 -> 5370 bytes | |||
| -rw-r--r-- | icons/browser_action_partial.png | bin | 34384 -> 7118 bytes | |||
| -rw-r--r-- | icons/icon128.png | bin | 19884 -> 15172 bytes | |||
| -rw-r--r-- | icons/icon16.png | bin | 781 -> 737 bytes | |||
| -rw-r--r-- | icons/icon48.png | bin | 4254 -> 3915 bytes | |||
| -rw-r--r-- | icons/icon48disabled.png | bin | 3648 -> 1831 bytes | |||
| -rw-r--r-- | icons/icon48partial.png | bin | 3815 -> 3585 bytes | |||
| -rw-r--r-- | icons/vimium.png | bin | 81847 -> 44314 bytes | |||
| -rw-r--r-- | lib/dom_utils.coffee | 41 | ||||
| -rw-r--r-- | lib/utils.coffee | 1 | ||||
| -rw-r--r-- | manifest.json | 2 | ||||
| -rw-r--r-- | tests/dom_tests/dom_tests.coffee | 4 |
20 files changed, 75 insertions, 118 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 034a287e..27db315c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ Our tests use [shoulda.js](https://github.com/philc/shoulda.js) and [PhantomJS]( 1. `git submodule update --init --recursive` -- this pulls in shoulda.js. 1. Install [PhantomJS](http://phantomjs.org/download.html). - 1. `npm install path` to install the [Node.js Path module](http://nodejs.org/api/path.html), used by the test runner. + 1. `npm install path@0.11` to install the [Node.js Path module](http://nodejs.org/api/path.html), used by the test runner. 1. `npm install util` to install the [util module](https://www.npmjs.com/package/util), used by the tests. 1. `cake build` to compile `*.coffee` to `*.js` 1. `cake test` to run the tests. @@ -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 ------------ @@ -153,6 +155,12 @@ Please see [CONTRIBUTING.md](https://github.com/philc/vimium/blob/master/CONTRIB Release Notes ------------- +Next version (not yet released) + +1.54 (2016-01-30) + +- Fix occasional endless scrolling (#1911). + 1.53 (2015-09-25) - Vimium now works on the new-tab page for Chrome 47. diff --git a/background_scripts/commands.coffee b/background_scripts/commands.coffee index a0b17ebc..c8121a96 100644 --- a/background_scripts/commands.coffee +++ b/background_scripts/commands.coffee @@ -277,8 +277,8 @@ commandDescriptions = openCopiedUrlInNewTab: ["Open the clipboard's URL in a new tab", { background: true, repeatLimit: 20 }] enterInsertMode: ["Enter insert mode", { noRepeat: true }] - enterVisualMode: ["Enter visual mode (beta feature)", { noRepeat: true }] - enterVisualLineMode: ["Enter visual line mode (beta feature)", { 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", diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee index bf120629..84c9f7f9 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. @@ -304,11 +308,16 @@ class LinkHintsMode when keyCodes.ctrlKey @setOpenLinkMode(if @mode is OPEN_IN_NEW_FG_TAB then OPEN_IN_NEW_BG_TAB else OPEN_IN_NEW_FG_TAB) - handlerStack.push + handlerId = handlerStack.push keyup: (event) => if event.keyCode == keyCode handlerStack.remove() @setOpenLinkMode previousMode if @isActive + true # Continue bubbling the event. + + # For some (unknown) reason, we don't always receive the keyup event needed to remove this handler. + # Therefore, we ensure that it's always removed when hint mode exits. See #1911 and #1926. + @hintMode.onExit -> handlerStack.remove handlerId else if event.keyCode in [ keyCodes.backspace, keyCodes.deleteKey ] if @markerMatcher.popKeyChar() @@ -411,8 +420,6 @@ class LinkHintsMode # Use characters for hints, and do not filter links by their text. class AlphabetHints - logXOfBase: (x, base) -> Math.log(x) / Math.log(base) - constructor: -> @linkHintCharacters = Settings.get "linkHintCharacters" # We use the keyChar from keydown if the link-hint characters are all "a-z0-9". This is the default @@ -435,39 +442,16 @@ class AlphabetHints # may be of different lengths. # hintStrings: (linkCount) -> - # Determine how many digits the link hints will require in the worst case. Usually we do not need - # all of these digits for every link single hint, so we can show shorter hints for a few of the links. - digitsNeeded = Math.ceil(@logXOfBase(linkCount, @linkHintCharacters.length)) - # Short hints are the number of hints we can possibly show which are (digitsNeeded - 1) digits in length. - shortHintCount = Math.floor( - (Math.pow(@linkHintCharacters.length, digitsNeeded) - linkCount) / - @linkHintCharacters.length) - longHintCount = linkCount - shortHintCount - - hintStrings = [] - - if (digitsNeeded > 1) - for i in [0...shortHintCount] - hintStrings.push(numberToHintString(i, @linkHintCharacters, digitsNeeded - 1)) + hints = [""] + offset = 0 + while hints.length - offset < linkCount or hints.length == 1 + hint = hints[offset++] + hints.push ch + hint for ch in @linkHintCharacters + hints = hints[offset...offset+linkCount] - start = shortHintCount * @linkHintCharacters.length - for i in [start...(start + longHintCount)] - hintStrings.push(numberToHintString(i, @linkHintCharacters, digitsNeeded)) - - @shuffleHints(hintStrings, @linkHintCharacters.length) - - # - # This shuffles the given set of hints so that they're scattered -- hints starting with the same character - # will be spread evenly throughout the array. - # - shuffleHints: (hints, characterSetLength) -> - buckets = ([] for i in [0...characterSetLength] by 1) - for hint, i in hints - buckets[i % buckets.length].push(hint) - result = [] - for bucket in buckets - result = result.concat(bucket) - result + # Shuffle the hints so that they're scattered; hints starting with the same character and short hints are + # spread evenly throughout the array. + return hints.sort().map (str) -> str.reverse() getMatchingHints: (hintMarkers) -> matchString = @hintKeystrokeQueue.join "" @@ -501,7 +485,12 @@ class FilterHints @labelMap[forElement] = labelText generateHintString: (linkHintNumber) -> - numberToHintString linkHintNumber, @linkHintNumbers.toUpperCase() + base = @linkHintNumbers.length + hint = [] + while 0 < linkHintNumber + hint.push @linkHintNumbers[Math.floor linkHintNumber % base] + linkHintNumber = Math.floor linkHintNumber / base + hint.reverse().join "" generateLinkText: (element) -> linkText = "" @@ -633,29 +622,6 @@ spanWrap = (hintString) -> innerHTML.push("<span class='vimiumReset'>" + char + "</span>") innerHTML.join("") -# -# Converts a number like "8" into a hint string like "JK". This is used to sequentially generate all of the -# hint text. The hint string will be "padded with zeroes" to ensure its length is >= numHintDigits. -# -numberToHintString = (number, characterSet, numHintDigits = 0) -> - base = characterSet.length - hintString = [] - remainder = 0 - loop - remainder = number % base - hintString.unshift(characterSet[remainder]) - number -= remainder - number /= Math.floor(base) - break unless number > 0 - - # Pad the hint string we're returning so that it matches numHintDigits. - # Note: the loop body changes hintString.length, so the original length must be cached! - hintStringLength = hintString.length - for i in [0...(numHintDigits - hintStringLength)] by 1 - hintString.unshift(characterSet[0]) - - hintString.join("") - # Suppress all keyboard events until the user stops typing for sufficiently long. class TypingProtector extends Mode constructor: (delay, callback) -> diff --git a/content_scripts/mode_visual_edit.coffee b/content_scripts/mode_visual_edit.coffee index 9e597cca..8bcde6cb 100644 --- a/content_scripts/mode_visual_edit.coffee +++ b/content_scripts/mode_visual_edit.coffee @@ -554,6 +554,10 @@ class VisualMode extends Movement 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() + super event, target if @yankedText? unless @options.noCopyToClipboard diff --git a/content_scripts/vimium.css b/content_scripts/vimium.css index e02df7c2..f323cbd6 100644 --- a/content_scripts/vimium.css +++ b/content_scripts/vimium.css @@ -5,7 +5,7 @@ * * The z-indexes of Vimium elements are very large, because we always want them to show on top. Chrome may * support up to Number.MAX_VALUE, which is approximately 1.7976e+308. We're using 2^31, which is the max - * value of a singed 32 bit int. Let's try larger valeus if 2**31 empirically isn't large enough. + * value of a signed 32 bit int. Let's try larger values if 2**31 empirically isn't large enough. */ /* 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/icons/browser_action_disabled.png b/icons/browser_action_disabled.png Binary files differindex 458ffe72..1051b22e 100644 --- a/icons/browser_action_disabled.png +++ b/icons/browser_action_disabled.png diff --git a/icons/browser_action_enabled.png b/icons/browser_action_enabled.png Binary files differindex fe4c36bb..1f003ccb 100644 --- a/icons/browser_action_enabled.png +++ b/icons/browser_action_enabled.png diff --git a/icons/browser_action_partial.png b/icons/browser_action_partial.png Binary files differindex e713f005..95a53dde 100644 --- a/icons/browser_action_partial.png +++ b/icons/browser_action_partial.png diff --git a/icons/icon128.png b/icons/icon128.png Binary files differindex 312d8462..81fb9422 100644 --- a/icons/icon128.png +++ b/icons/icon128.png diff --git a/icons/icon16.png b/icons/icon16.png Binary files differindex b353889d..0f48503d 100644 --- a/icons/icon16.png +++ b/icons/icon16.png diff --git a/icons/icon48.png b/icons/icon48.png Binary files differindex 9eb94d4f..78cf8cc2 100644 --- a/icons/icon48.png +++ b/icons/icon48.png diff --git a/icons/icon48disabled.png b/icons/icon48disabled.png Binary files differindex fa6279d3..54963abe 100644 --- a/icons/icon48disabled.png +++ b/icons/icon48disabled.png diff --git a/icons/icon48partial.png b/icons/icon48partial.png Binary files differindex 088099b1..32e1a21c 100644 --- a/icons/icon48partial.png +++ b/icons/icon48partial.png diff --git a/icons/vimium.png b/icons/vimium.png Binary files differindex 2261f411..7009cac8 100644 --- a/icons/vimium.png +++ b/icons/vimium.png diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee index ee7d415f..7473df17 100644 --- a/lib/dom_utils.coffee +++ b/lib/dom_utils.coffee @@ -223,23 +223,38 @@ DomUtils = handlerStack.bubbleEvent "click", target: element else element.focus() - # If the cursor is at the start of the element's contents, send it to the end. Motivation: - # * the end is a more useful place to focus than the start, - # * this way preserves the last used position (except when it's at the beginning), so the user can - # 'resume where they left off'. - # NOTE(mrmr1993): Some elements throw an error when we try to access their selection properties, so - # wrap this with a try. - try - if element.selectionStart == 0 and element.selectionEnd == 0 - element.setSelectionRange element.value.length, element.value.length - - + if element.tagName.toLowerCase() != "textarea" + # If the cursor is at the start of the (non-textarea) element's contents, send it to the end. Motivation: + # * the end is a more useful place to focus than the start, + # * this way preserves the last used position (except when it's at the beginning), so the user can + # 'resume where they left off'. + # NOTE(mrmr1993): Some elements throw an error when we try to access their selection properties, so + # wrap this with a try. + try + if element.selectionStart == 0 and element.selectionEnd == 0 + element.setSelectionRange element.value.length, element.value.length + + 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/utils.coffee b/lib/utils.coffee index b69b926b..c255102e 100644 --- a/lib/utils.coffee +++ b/lib/utils.coffee @@ -281,6 +281,7 @@ Array.copy = (array) -> Array.prototype.slice.call(array, 0) String::startsWith = (str) -> @indexOf(str) == 0 String::ltrim = -> @replace /^\s+/, "" String::rtrim = -> @replace /\s+$/, "" +String::reverse = -> @split("").reverse().join "" globalRoot = window ? global globalRoot.extend = (hash1, hash2) -> 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", diff --git a/tests/dom_tests/dom_tests.coffee b/tests/dom_tests/dom_tests.coffee index e814ec76..1d4703c1 100644 --- a/tests/dom_tests/dom_tests.coffee +++ b/tests/dom_tests/dom_tests.coffee @@ -149,10 +149,8 @@ context "Alphabetical link hints", document.getElementById("test-div").innerHTML = "" should "label the hints correctly", -> - # TODO(philc): This test verifies the current behavior, but the current behavior is incorrect. - # The output here should be something like aa, ab, b. hintMarkers = getHintMarkers() - expectedHints = ["aa", "ba", "ab"] + expectedHints = ["aa", "b", "ab"] for hint, i in expectedHints assert.equal hint, hintMarkers[i].hintString |
