aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTING.md2
-rw-r--r--README.md10
-rw-r--r--background_scripts/commands.coffee4
-rw-r--r--content_scripts/link_hints.coffee88
-rw-r--r--content_scripts/mode_visual_edit.coffee4
-rw-r--r--content_scripts/vimium.css2
-rw-r--r--content_scripts/vimium_frontend.coffee35
-rw-r--r--icons/browser_action_disabled.pngbin32025 -> 5161 bytes
-rw-r--r--icons/browser_action_enabled.pngbin37455 -> 5370 bytes
-rw-r--r--icons/browser_action_partial.pngbin34384 -> 7118 bytes
-rw-r--r--icons/icon128.pngbin19884 -> 15172 bytes
-rw-r--r--icons/icon16.pngbin781 -> 737 bytes
-rw-r--r--icons/icon48.pngbin4254 -> 3915 bytes
-rw-r--r--icons/icon48disabled.pngbin3648 -> 1831 bytes
-rw-r--r--icons/icon48partial.pngbin3815 -> 3585 bytes
-rw-r--r--icons/vimium.pngbin81847 -> 44314 bytes
-rw-r--r--lib/dom_utils.coffee41
-rw-r--r--lib/utils.coffee1
-rw-r--r--manifest.json2
-rw-r--r--tests/dom_tests/dom_tests.coffee4
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.
diff --git a/README.md b/README.md
index b8558692..c914e864 100644
--- a/README.md
+++ b/README.md
@@ -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
index 458ffe72..1051b22e 100644
--- a/icons/browser_action_disabled.png
+++ b/icons/browser_action_disabled.png
Binary files differ
diff --git a/icons/browser_action_enabled.png b/icons/browser_action_enabled.png
index fe4c36bb..1f003ccb 100644
--- a/icons/browser_action_enabled.png
+++ b/icons/browser_action_enabled.png
Binary files differ
diff --git a/icons/browser_action_partial.png b/icons/browser_action_partial.png
index e713f005..95a53dde 100644
--- a/icons/browser_action_partial.png
+++ b/icons/browser_action_partial.png
Binary files differ
diff --git a/icons/icon128.png b/icons/icon128.png
index 312d8462..81fb9422 100644
--- a/icons/icon128.png
+++ b/icons/icon128.png
Binary files differ
diff --git a/icons/icon16.png b/icons/icon16.png
index b353889d..0f48503d 100644
--- a/icons/icon16.png
+++ b/icons/icon16.png
Binary files differ
diff --git a/icons/icon48.png b/icons/icon48.png
index 9eb94d4f..78cf8cc2 100644
--- a/icons/icon48.png
+++ b/icons/icon48.png
Binary files differ
diff --git a/icons/icon48disabled.png b/icons/icon48disabled.png
index fa6279d3..54963abe 100644
--- a/icons/icon48disabled.png
+++ b/icons/icon48disabled.png
Binary files differ
diff --git a/icons/icon48partial.png b/icons/icon48partial.png
index 088099b1..32e1a21c 100644
--- a/icons/icon48partial.png
+++ b/icons/icon48partial.png
Binary files differ
diff --git a/icons/vimium.png b/icons/vimium.png
index 2261f411..7009cac8 100644
--- a/icons/vimium.png
+++ b/icons/vimium.png
Binary files differ
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