diff options
| -rw-r--r-- | content_scripts/link_hints.coffee | 101 | ||||
| -rw-r--r-- | content_scripts/vimium.css | 19 | ||||
| -rw-r--r-- | lib/rect.coffee | 9 | ||||
| -rw-r--r-- | pages/vomnibar.css | 3 | ||||
| -rw-r--r-- | tests/unit_tests/rect_test.coffee | 56 | 
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 + | 
