aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--content_scripts/link_hints.coffee101
-rw-r--r--content_scripts/vimium.css19
-rw-r--r--lib/rect.coffee9
-rw-r--r--pages/vomnibar.css3
-rw-r--r--tests/unit_tests/rect_test.coffee56
5 files changed, 156 insertions, 32 deletions
diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee
index 2548abb3..751aa12c 100644
--- a/content_scripts/link_hints.coffee
+++ b/content_scripts/link_hints.coffee
@@ -110,6 +110,7 @@ HintCoordinator =
# The following messages are exchanged between frames while link-hints mode is active.
updateKeyState: (request) -> @linkHintsMode.updateKeyState request
+ rotateHints: -> @linkHintsMode.rotateHints()
setOpenLinkMode: ({modeIndex}) -> @linkHintsMode.setOpenLinkMode availableModes[modeIndex], false
activateActiveHintMarker: -> @linkHintsMode.activateLink @linkHintsMode.markerMatcher.activeHintMarker
getLocalHintMarker: (hint) -> if hint.frameId == frameId then @localHints[hint.localIndex] else null
@@ -199,26 +200,33 @@ class LinkHintsMode
#
# Creates a link marker for the given link.
#
- createMarkerFor: (desc) ->
- marker =
- if desc.frameId == frameId
- localHintDescriptor = HintCoordinator.getLocalHintMarker desc
- el = DomUtils.createElement "div"
- el.rect = localHintDescriptor.rect
- el.style.left = el.rect.left + "px"
- el.style.top = el.rect.top + "px"
- extend el,
- className: "vimiumReset internalVimiumHintMarker vimiumHintMarker"
- showLinkText: localHintDescriptor.showLinkText
- localHintDescriptor: localHintDescriptor
- else
- {}
+ createMarkerFor: do ->
+ # This is the starting z-index value; it produces z-index values which are greater than all of the other
+ # z-index values used by Vimium.
+ baseZIndex = 2140000000
+
+ (desc) ->
+ marker =
+ if desc.frameId == frameId
+ localHintDescriptor = HintCoordinator.getLocalHintMarker desc
+ el = DomUtils.createElement "div"
+ el.rect = localHintDescriptor.rect
+ el.style.left = el.rect.left + "px"
+ el.style.top = el.rect.top + "px"
+ # Each hint marker is assigned a different z-index.
+ el.style.zIndex = baseZIndex += 1
+ extend el,
+ className: "vimiumReset internalVimiumHintMarker vimiumHintMarker"
+ showLinkText: localHintDescriptor.showLinkText
+ localHintDescriptor: localHintDescriptor
+ else
+ {}
- extend marker,
- hintDescriptor: desc
- isLocalMarker: desc.frameId == frameId
- linkText: desc.linkText
- stableSortCount: ++@stableSortCount
+ extend marker,
+ hintDescriptor: desc
+ isLocalMarker: desc.frameId == frameId
+ linkText: desc.linkText
+ stableSortCount: ++@stableSortCount
# Handles <Shift> and <Ctrl>.
onKeyDownInMode: (event) ->
@@ -274,6 +282,9 @@ class LinkHintsMode
@tabCount = previousTabCount + (if event.shiftKey then -1 else 1)
@updateVisibleMarkers @tabCount
+ else if event.keyCode == keyCodes.space and @markerMatcher.shouldRotateHints event
+ HintCoordinator.sendMessage "rotateHints"
+
else
return
@@ -310,6 +321,51 @@ class LinkHintsMode
@setIndicator()
+ # Rotate the hints' z-index values so that hidden hints become visible.
+ rotateHints: do ->
+ markerOverlapsStack = (marker, stack) ->
+ for otherMarker in stack
+ return true if Rect.rectsOverlap marker.markerRect, otherMarker.markerRect
+ false
+
+ ->
+ # Get local, visible hint markers.
+ localHintMarkers = @hintMarkers.filter (marker) ->
+ marker.isLocalMarker and marker.style.display != "none"
+
+ # Fill in the markers' rects, if necessary.
+ marker.markerRect ?= marker.getClientRects()[0] for marker in localHintMarkers
+
+ # Calculate the overlapping groups of hints. This is O(n^2) in the best case and O(n^3) in the worst
+ # case.
+ stacks = []
+ for marker in localHintMarkers
+ stackForThisMarker = null
+ stacks =
+ for stack in stacks
+ markerOverlapsThisStack = markerOverlapsStack marker, stack
+ if markerOverlapsThisStack and not stackForThisMarker?
+ # We've found an existing stack for this marker.
+ stack.push marker
+ stackForThisMarker = stack
+ else if markerOverlapsThisStack and stackForThisMarker?
+ # This marker overlaps a second (or subsequent) stack; merge that stack into stackForThisMarker
+ # and discard it.
+ stackForThisMarker.push stack...
+ continue # Discard this stack.
+ else
+ stack # Keep this stack.
+ stacks.push [marker] unless stackForThisMarker?
+
+ # Rotate the z-indexes within each stack.
+ for stack in stacks
+ if 1 < stack.length
+ zIndexes = (marker.style.zIndex for marker in stack)
+ zIndexes.push zIndexes[0]
+ marker.style.zIndex = zIndexes[index + 1] for marker, index in stack
+
+ null # Prevent Coffeescript from building an unnecessary array.
+
# 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.
@@ -420,6 +476,9 @@ class AlphabetHints
@hintKeystrokeQueue.push (if @useKeydown then keydownKeyChar else keyChar)
popKeyChar: -> @hintKeystrokeQueue.pop()
+ # For alphabet hints, <Space> always rotates the hints, regardless of modifiers.
+ shouldRotateHints: -> true
+
# Use numbers (usually) for hints, and also filter links by their text.
class FilterHints
constructor: ->
@@ -540,6 +599,10 @@ class FilterHints
# them as if their length was 100 (so, quite long).
score / Math.log 1 + (linkMarker.linkText.length || 100)
+ # For filtered hints, we require a modifier (because <Space> on its own is a token separator).
+ shouldRotateHints: (event) ->
+ event.ctrlKey or event.altKey or event.metaKey
+
#
# Make each hint character a span, so that we can highlight the typed characters as you type them.
#
diff --git a/content_scripts/vimium.css b/content_scripts/vimium.css
index be9d4a65..edb1ab85 100644
--- a/content_scripts/vimium.css
+++ b/content_scripts/vimium.css
@@ -3,9 +3,9 @@
* use the same CSS class names that the page is using, so the page's CSS doesn't mess with the style of our
* Vimium dialogs.
*
- * 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 signed 32 bit int. Let's try larger values if 2**31 empirically isn't large enough.
+ * The z-indexes of Vimium elements are very large, because we always want them to show on top. We know
+ * that Chrome supports z-index values up to about 2^31. The values we use are large numbers approaching
+ * that bound. However, we must leave headroom for link hints. Hint marker z-indexes start at 2140000001.
*/
/*
@@ -58,7 +58,7 @@ tr.vimiumReset {
vertical-align: baseline;
white-space: normal;
width: auto;
- z-index: 2147483648;
+ z-index: 2140000000; /* Vimium's reference z-index value. */
}
thead.vimiumReset, tbody.vimiumReset {
@@ -147,7 +147,7 @@ iframe.vimiumHelpDialogFrame {
display: block;
position: fixed;
border: none;
- z-index: 2147483645; /* One less than hint markers. */
+ z-index: 2139999997; /* Three less than the reference value. */
}
div#vimiumHelpDialogContainer {
@@ -335,8 +335,7 @@ iframe.vimiumHUDFrame {
padding: 0px;
margin: 0;
border: none;
- /* One less than vimium's hint markers, so link hints can be shown e.g. for the HUD panel's close button. */
- z-index: 2147483646;
+ z-index: 2149999998; /* Two less than the reference value. */
opacity: 0;
}
@@ -377,9 +376,7 @@ iframe.vomnibarFrame {
margin: 0 0 0 -40%;
border: none;
font-family: sans-serif;
-
- /* One less than hint markers. */
- z-index: 2147483646;
+ z-index: 2149999998; /* Two less than the reference value. */
}
div.vimiumFlash {
@@ -387,7 +384,7 @@ div.vimiumFlash {
padding: 1px;
background-color: transparent;
position: absolute;
- z-index: 2147483648;
+ z-index: 2140000000;
}
/* UIComponent CSS */
diff --git a/lib/rect.coffee b/lib/rect.coffee
index 0c67d287..d4807cc2 100644
--- a/lib/rect.coffee
+++ b/lib/rect.coffee
@@ -82,5 +82,14 @@ Rect =
@create (Math.max rect1.left, rect2.left), (Math.max rect1.top, rect2.top),
(Math.min rect1.right, rect2.right), (Math.min rect1.bottom, rect2.bottom)
+ # Determine whether two rects overlap.
+ rectsOverlap: do ->
+ halfOverlapChecker = (rect1, rect2) ->
+ (rect1.left <= rect2.left <= rect1.right or rect1.left <= rect2.right <= rect1.right) and
+ (rect1.top <= rect2.top <= rect1.bottom or rect1.top <= rect2.bottom <= rect1.bottom)
+
+ (rect1, rect2) ->
+ halfOverlapChecker(rect1, rect2) or halfOverlapChecker rect2, rect1
+
root = exports ? window
root.Rect = Rect
diff --git a/pages/vomnibar.css b/pages/vomnibar.css
index 1b19daad..2f279a28 100644
--- a/pages/vomnibar.css
+++ b/pages/vomnibar.css
@@ -23,8 +23,7 @@
border-radius: 4px;
box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.8);
border: 1px solid #aaa;
- /* One less than hint markers and the help dialog. */
- z-index: 2147483646;
+ z-index: 2139999999; /* One less than hint markers and the help dialog (see ../content_scripts/vimium.css). */
}
#vomnibar input {
diff --git a/tests/unit_tests/rect_test.coffee b/tests/unit_tests/rect_test.coffee
index cfb26b05..0773dbcf 100644
--- a/tests/unit_tests/rect_test.coffee
+++ b/tests/unit_tests/rect_test.coffee
@@ -230,3 +230,59 @@ context "Rect subtraction",
if resultComplement.length == 1
complementRect = resultComplement[0]
assert.isTrue Rect.contains subtractRect, complementRect
+
+context "Rect overlaps",
+ should "detect that a rect overlaps itself", ->
+ rect = Rect.create 2, 2, 4, 4
+ assert.isTrue Rect.rectsOverlap rect, rect
+
+ should "detect that non-overlapping rectangles do not overlap on the left", ->
+ rect1 = Rect.create 2, 2, 4, 4
+ rect2 = Rect.create 0, 2, 1, 4
+ assert.isFalse Rect.rectsOverlap rect1, rect2
+
+ should "detect that non-overlapping rectangles do not overlap on the right", ->
+ rect1 = Rect.create 2, 2, 4, 4
+ rect2 = Rect.create 5, 2, 6, 4
+ assert.isFalse Rect.rectsOverlap rect1, rect2
+
+ should "detect that non-overlapping rectangles do not overlap on the top", ->
+ rect1 = Rect.create 2, 2, 4, 4
+ rect2 = Rect.create 2, 0, 2, 1
+ assert.isFalse Rect.rectsOverlap rect1, rect2
+
+ should "detect that non-overlapping rectangles do not overlap on the bottom", ->
+ rect1 = Rect.create 2, 2, 4, 4
+ rect2 = Rect.create 2, 5, 2, 6
+ assert.isFalse Rect.rectsOverlap rect1, rect2
+
+ should "detect overlapping rectangles on the left", ->
+ rect1 = Rect.create 2, 2, 4, 4
+ rect2 = Rect.create 0, 2, 2, 4
+ assert.isTrue Rect.rectsOverlap rect1, rect2
+
+ should "detect overlapping rectangles on the right", ->
+ rect1 = Rect.create 2, 2, 4, 4
+ rect2 = Rect.create 4, 2, 5, 4
+ assert.isTrue Rect.rectsOverlap rect1, rect2
+
+ should "detect overlapping rectangles on the top", ->
+ rect1 = Rect.create 2, 2, 4, 4
+ rect2 = Rect.create 2, 4, 4, 5
+ assert.isTrue Rect.rectsOverlap rect1, rect2
+
+ should "detect overlapping rectangles on the bottom", ->
+ rect1 = Rect.create 2, 2, 4, 4
+ rect2 = Rect.create 2, 0, 4, 2
+ assert.isTrue Rect.rectsOverlap rect1, rect2
+
+ should "detect overlapping rectangles when second rectangle is contained in first", ->
+ rect1 = Rect.create 1, 1, 4, 4
+ rect2 = Rect.create 2, 2, 3, 3
+ assert.isTrue Rect.rectsOverlap rect1, rect2
+
+ should "detect overlapping rectangles when first rectangle is contained in second", ->
+ rect1 = Rect.create 1, 1, 4, 4
+ rect2 = Rect.create 2, 2, 3, 3
+ assert.isTrue Rect.rectsOverlap rect2, rect1
+