aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md31
-rw-r--r--content_scripts/hud.coffee2
-rw-r--r--content_scripts/link_hints.coffee19
-rw-r--r--content_scripts/mode_passkeys.coffee2
-rw-r--r--content_scripts/scroller.coffee12
-rw-r--r--content_scripts/ui_component.coffee18
-rw-r--r--content_scripts/vimium_frontend.coffee45
-rw-r--r--lib/clipboard.coffee4
-rw-r--r--lib/dom_utils.coffee29
-rw-r--r--lib/utils.coffee6
-rw-r--r--manifest.json3
-rw-r--r--pages/options.html2
-rw-r--r--pages/vomnibar.coffee2
13 files changed, 100 insertions, 75 deletions
diff --git a/README.md b/README.md
index b2b865f1..cb2f1532 100644
--- a/README.md
+++ b/README.md
@@ -153,22 +153,21 @@ Please see [CONTRIBUTING.md](https://github.com/philc/vimium/blob/master/CONTRIB
Release Notes
-------------
-1.52 (not yet released)
-
-- Search completion for selected popular search engines; for details, see the wiki
- ([here](https://github.com/philc/vimium/wiki/Search-Completion) and
- [here](https://github.com/philc/vimium/wiki/Tips-and-Tricks#repeat-recent-queries)).
-- A much improved user interface for custom search engines.
-- `Tab` on an empty Vomnibar now expands the Vomnibar with your recent history.
-- Added <tt>\`\`</tt> to jump back to the previous position after selected jump-like movements: <br/>
- (`gg`, `G`, `n`, `N`, `/` and local mark movements).
-- Global marks are now persistent (across tab closes and browser sessions) and synced.
-- For filtered link hints (not the default), you can now use `Tab` and `Enter`
- to select hints and hints are ordered by best match.
-- Bug fixes, including:
- - Bookmarklets accessed from the Vomnibar.
- - Global marks on non-Windows platforms.
- - Link-hints for non-Latin keyboards.
+1.52 (2015-09-09)
+
+- Search completion for selected custom search engines
+ (details on the [wiki](https://github.com/philc/vimium/wiki/Search-Completion)).
+- Use `Tab` on an empty Vomnibar to repeat or edit recent queries
+ (details on the [wiki](https://github.com/philc/vimium/wiki/Tips-and-Tricks#repeat-recent-vomnibar-queries)).
+- Marks:
+ - Use <tt>\`\`</tt> to jump back to the previous position after jump-like movements: <br/>
+ (`gg`, `G`, `n`, `N`, `/` and local mark movements).
+ - Global marks are now persistent and synced.
+- For numeric link hints, you can now use `Tab` and `Enter` to select hints, and hints are ordered by the best
+ match.
+- The Find Mode text entry box now supports editing, pasting, and better handles non-latin characters.
+- Vimium now works on XML pages.
+- Bug fixes.
1.51 (2015-05-02)
diff --git a/content_scripts/hud.coffee b/content_scripts/hud.coffee
index bfad71b7..5a3d9b79 100644
--- a/content_scripts/hud.coffee
+++ b/content_scripts/hud.coffee
@@ -96,7 +96,7 @@ class Tween
styleElement: null
constructor: (@cssSelector, insertionPoint = document.documentElement) ->
- @styleElement = document.createElement "style"
+ @styleElement = DomUtils.createElement "style"
unless @styleElement.style
# We're in an XML document, so we shouldn't inject any elements. See the comment in UIComponent.
diff --git a/content_scripts/link_hints.coffee b/content_scripts/link_hints.coffee
index 15af15c5..54c10284 100644
--- a/content_scripts/link_hints.coffee
+++ b/content_scripts/link_hints.coffee
@@ -127,7 +127,7 @@ class LinkHintsMode
# Creates a link marker for the given link.
#
createMarkerFor: (link) ->
- marker = document.createElement("div")
+ marker = DomUtils.createElement "div"
marker.className = "vimiumReset internalVimiumHintMarker vimiumHintMarker"
marker.clickableItem = link.element
@@ -167,6 +167,23 @@ class LinkHintsMode
element.getAttribute("aria-disabled")?.toLowerCase() in ["", "true"])
return [] # This element should never have a link hint.
+ # Check for AngularJS listeners on the element.
+ @checkForAngularJs ?= do ->
+ angularElements = document.getElementsByClassName "ng-scope"
+ if angularElements.length == 0
+ -> false
+ else
+ ngAttributes = []
+ for prefix in [ '', 'data-', 'x-' ]
+ for separator in [ '-', ':', '_' ]
+ ngAttributes.push "#{prefix}ng#{separator}click"
+ (element) ->
+ for attribute in ngAttributes
+ return true if element.hasAttribute attribute
+ false
+
+ isClickable ||= @checkForAngularJs element
+
# Check for attributes that make an element clickable regardless of its tagName.
if (element.hasAttribute("onclick") or
element.getAttribute("role")?.toLowerCase() in ["button", "link"] or
diff --git a/content_scripts/mode_passkeys.coffee b/content_scripts/mode_passkeys.coffee
index 1ed69ac2..631eb621 100644
--- a/content_scripts/mode_passkeys.coffee
+++ b/content_scripts/mode_passkeys.coffee
@@ -12,7 +12,7 @@ class PassKeysMode extends Mode
# passKey, then 'gt' and '99t' will neverthless be handled by Vimium.
handleKeyChar: (event, keyChar) ->
return @continueBubbling if event.altKey or event.ctrlKey or event.metaKey
- if keyChar and not @keyQueue and 0 <= @passKeys.indexOf keyChar
+ if keyChar and not @keyQueue and keyChar.length == 1 and 0 <= @passKeys.indexOf keyChar
@stopBubblingAndTrue
else
@continueBubbling
diff --git a/content_scripts/scroller.coffee b/content_scripts/scroller.coffee
index 81c71fcd..5f10ab65 100644
--- a/content_scripts/scroller.coffee
+++ b/content_scripts/scroller.coffee
@@ -81,7 +81,7 @@ doesScroll = (element, direction, amount, factor) ->
findScrollableElement = (element, direction, amount, factor) ->
while element != document.body and
not (doesScroll(element, direction, amount, factor) and shouldScroll(element, direction))
- element = element.parentElement || document.body
+ element = (DomUtils.getContainingElement element) ? document.body
element
# On some pages, document.body is not scrollable. Here, we search the document for the largest visible
@@ -136,6 +136,9 @@ CoreScroller =
handlerStack.alwaysContinueBubbling =>
@keyIsDown = false
@time += 1
+ blur: =>
+ handlerStack.alwaysContinueBubbling =>
+ @time += 1 if event.target == window
# Return true if CoreScroller would not initiate a new scroll right now.
wouldNotInitiateScroll: -> @lastEvent?.repeat and Settings.get "smoothScroll"
@@ -217,7 +220,12 @@ Scroller =
init: ->
handlerStack.push
_name: 'scroller/active-element'
- DOMActivate: (event) -> handlerStack.alwaysContinueBubbling -> activatedElement = event.target
+ DOMActivate: (event) -> handlerStack.alwaysContinueBubbling ->
+ # If event.path is present, the true event taget (potentially inside a Shadow DOM inside
+ # event.target) can be found as its first element.
+ # NOTE(mrmr1993): event.path has been renamed to event.deepPath in the spec, but this change is not
+ # yet implemented by Chrome.
+ activatedElement = event.deepPath?[0] ? event.path?[0] ? event.target
CoreScroller.init()
# scroll the active element in :direction by :amount * :factor.
diff --git a/content_scripts/ui_component.coffee b/content_scripts/ui_component.coffee
index e4cfc293..d935079d 100644
--- a/content_scripts/ui_component.coffee
+++ b/content_scripts/ui_component.coffee
@@ -6,28 +6,16 @@ class UIComponent
shadowDOM: null
constructor: (iframeUrl, className, @handleMessage) ->
- styleSheet = document.createElement "style"
-
- unless styleSheet.style
- # If this is an XML document, nothing we do here works:
- # * <style> elements show their contents inline,
- # * <iframe> elements don't load any content,
- # * document.createElement generates elements that have style == null and ignore CSS.
- # If this is the case we don't want to pollute the DOM to no or negative effect. So we bail
- # immediately, and disable all externally-called methods.
- @postMessage = @activate = @show = @hide = ->
- console.log "This vimium feature is disabled because it is incompatible with this page."
- return
-
+ styleSheet = DomUtils.createElement "style"
styleSheet.type = "text/css"
# Default to everything hidden while the stylesheet loads.
styleSheet.innerHTML = "@import url(\"#{chrome.runtime.getURL("content_scripts/vimium.css")}\");"
- @iframeElement = document.createElement "iframe"
+ @iframeElement = DomUtils.createElement "iframe"
extend @iframeElement,
className: className
seamless: "seamless"
- shadowWrapper = document.createElement "div"
+ shadowWrapper = DomUtils.createElement "div"
# PhantomJS doesn't support createShadowRoot, so guard against its non-existance.
@shadowDOM = shadowWrapper.createShadowRoot?() ? shadowWrapper
@shadowDOM.appendChild styleSheet
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index 8f4c7e82..781223b1 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -274,23 +274,22 @@ setScrollPosition = ({ scrollX, scrollY }) ->
#
# Called from the backend in order to change frame focus.
#
-window.focusThisFrame = do ->
+DomUtils.documentReady ->
# Create a shadow DOM wrapping the frame so the page's styles don't interfere with ours.
- highlightedFrameElement = document.createElement "div"
+ highlightedFrameElement = DomUtils.createElement "div"
# PhantomJS doesn't support createShadowRoot, so guard against its non-existance.
_shadowDOM = highlightedFrameElement.createShadowRoot?() ? highlightedFrameElement
# Inject stylesheet.
- _styleSheet = document.createElement "style"
- if _styleSheet.style?
- _styleSheet.innerHTML = "@import url(\"#{chrome.runtime.getURL("content_scripts/vimium.css")}\");"
- _shadowDOM.appendChild _styleSheet
+ _styleSheet = DomUtils.createElement "style"
+ _styleSheet.innerHTML = "@import url(\"#{chrome.runtime.getURL("content_scripts/vimium.css")}\");"
+ _shadowDOM.appendChild _styleSheet
- _frameEl = document.createElement "div"
+ _frameEl = DomUtils.createElement "div"
_frameEl.className = "vimiumReset vimiumHighlightedFrame"
_shadowDOM.appendChild _frameEl
- (request) ->
+ window.focusThisFrame = (request) ->
if window.innerWidth < 3 or window.innerHeight < 3
# This frame is too small to focus. Cancel and tell the background frame to focus the next one instead.
# This affects sites like Google Inbox, which have many tiny iframes. See #1317.
@@ -304,6 +303,8 @@ window.focusThisFrame = do ->
document.documentElement.appendChild highlightedFrameElement
setTimeout (-> highlightedFrameElement.remove()), 200
+window.focusThisFrame = ->
+
extend window,
scrollToBottom: ->
Marks.setPreviousPosition()
@@ -405,7 +406,7 @@ extend window,
Math.min(count, visibleInputs.length) - 1
hints = for tuple in visibleInputs
- hint = document.createElement "div"
+ hint = DomUtils.createElement "div"
hint.className = "vimiumReset internalVimiumInputHint vimiumInputHint"
# minus 1 for the border
@@ -429,7 +430,7 @@ extend window,
hints[selectedInputIndex].classList.add 'internalVimiumSelectedInputHint'
# Deactivate any active modes on this element (PostFindMode, or a suspended edit mode).
@deactivateSingleton visibleInputs[selectedInputIndex].element
- visibleInputs[selectedInputIndex].element.focus()
+ DomUtils.simulateSelect visibleInputs[selectedInputIndex].element
@suppressEvent
else unless event.keyCode == KeyboardUtils.keyCodes.shiftKey
@exit()
@@ -442,7 +443,7 @@ extend window,
# Deactivate any active modes on this element (PostFindMode, or a suspended edit mode).
@deactivateSingleton visibleInputs[selectedInputIndex].element
- visibleInputs[selectedInputIndex].element.focus()
+ DomUtils.simulateSelect visibleInputs[selectedInputIndex].element
if visibleInputs.length == 1
@exit()
return
@@ -463,25 +464,25 @@ extend window,
KeydownEvents =
handledEvents: {}
- stringify: (event) ->
- JSON.stringify
- metaKey: event.metaKey
- altKey: event.altKey
- ctrlKey: event.ctrlKey
- keyIdentifier: event.keyIdentifier
- keyCode: event.keyCode
+ getEventCode: (event) -> event.keyCode
push: (event) ->
- @handledEvents[@stringify event] = true
+ @handledEvents[@getEventCode event] = true
# Yields truthy or falsy depending upon whether a corresponding keydown event is present (and removes that
# event).
pop: (event) ->
- detailString = @stringify event
+ detailString = @getEventCode event
value = @handledEvents[detailString]
delete @handledEvents[detailString]
value
+ clear: -> @handledEvents = {}
+
+handlerStack.push
+ _name: "KeydownEvents-cleanup"
+ blur: (event) -> KeydownEvents.clear() if event.target == window; true
+
#
# Sends everything except i & ESC to the handler in background_page. i & ESC are special because they control
# insert mode which is local state to the page. The key will be are either a single ascii letter or a
@@ -775,7 +776,7 @@ window.enterFindMode = ->
window.showHelpDialog = (html, fid) ->
return if (isShowingHelpDialog || !document.body || fid != frameId)
isShowingHelpDialog = true
- container = document.createElement("div")
+ container = DomUtils.createElement "div"
container.id = "vimiumHelpDialogContainer"
container.className = "vimiumReset"
@@ -861,7 +862,7 @@ CursorHider =
# See #1345 and #1348.
return unless Utils.haveChromeVersion "39.0.2171.71"
- @cursorHideStyle = document.createElement("style")
+ @cursorHideStyle = DomUtils.createElement "style"
@cursorHideStyle.innerHTML = """
body * {pointer-events: none !important; cursor: none !important;}
body, html {cursor: none !important;}
diff --git a/lib/clipboard.coffee b/lib/clipboard.coffee
index 2b28df70..f0fb83b1 100644
--- a/lib/clipboard.coffee
+++ b/lib/clipboard.coffee
@@ -1,6 +1,6 @@
Clipboard =
_createTextArea: ->
- textArea = document.createElement("textarea")
+ textArea = document.createElement "textarea"
textArea.style.position = "absolute"
textArea.style.left = "-100%"
textArea
@@ -16,7 +16,7 @@ Clipboard =
document.body.removeChild(textArea)
paste: ->
- textArea = @._createTextArea()
+ textArea = @_createTextArea()
document.body.appendChild(textArea)
textArea.focus()
document.execCommand("Paste")
diff --git a/lib/dom_utils.coffee b/lib/dom_utils.coffee
index 9658df2b..ee7d415f 100644
--- a/lib/dom_utils.coffee
+++ b/lib/dom_utils.coffee
@@ -8,12 +8,25 @@ DomUtils =
else
func()
+ createElement: (tagName) ->
+ element = document.createElement tagName
+ if element instanceof HTMLElement
+ # The document namespace provides (X)HTML elements, so we can use them directly.
+ @createElement = (tagName) -> document.createElement tagName
+ element
+ else
+ # The document namespace doesn't give (X)HTML elements, so we create them with the correct namespace
+ # manually.
+ @createElement = (tagName) ->
+ document.createElementNS "http://www.w3.org/1999/xhtml", tagName
+ @createElement(tagName)
+
#
# Adds a list of elements to a page.
# Note that adding these nodes all at once (via the parent div) is significantly faster than one-by-one.
#
addElementList: (els, overlayOptions) ->
- parent = document.createElement("div")
+ parent = @createElement "div"
parent.id = overlayOptions.id if overlayOptions.id?
parent.className = overlayOptions.className if overlayOptions.className?
parent.appendChild(el) for el in els
@@ -82,7 +95,7 @@ DomUtils =
# NOTE(mrmr1993): This ignores floated/absolutely positioned descendants nested within inline
# children.
continue if (computedStyle.getPropertyValue("float") == "none" and
- computedStyle.getPropertyValue("position") != "absolute" and
+ not (computedStyle.getPropertyValue("position") in ["absolute", "fixed"]) and
not (clientRect.height == 0 and isInlineZeroHeight() and
0 == computedStyle.getPropertyValue("display").indexOf "inline"))
childClientRect = @getVisibleClientRect child, true
@@ -236,7 +249,7 @@ DomUtils =
# momentarily flash a rectangular border to give user some visual feedback
flashRect: (rect) ->
- flashEl = document.createElement("div")
+ flashEl = @createElement "div"
flashEl.id = "vimiumFlash"
flashEl.className = "vimiumReset"
flashEl.style.left = rect.left + window.scrollX + "px"
@@ -297,7 +310,7 @@ DomUtils =
'letterSpacing', 'wordSpacing' ]
(element, position) ->
- div = document.createElement "div"
+ div = @createElement "div"
div.id = "vimium-input-textarea-caret-position-mirror-div"
document.body.appendChild div
@@ -315,7 +328,7 @@ DomUtils =
if element.nodeName.toLowerCase() == "input"
div.textContent = div.textContent.replace /\s/g, "\u00a0"
- span = document.createElement "span"
+ span = @createElement "span"
span.textContent = element.value.substring(position) || "."
div.appendChild span
@@ -357,5 +370,11 @@ DomUtils =
text
texts.join " "
+ # Get the element in the DOM hierachy that contains `element`.
+ # If the element is rendered in a shadow DOM via a <content> element, the <content> element will be
+ # returned, so the shadow DOM is traversed rather than passed over.
+ getContainingElement: (element) ->
+ element.getDestinationInsertionPoints()[0] or element.parentElement
+
root = exports ? window
root.DomUtils = DomUtils
diff --git a/lib/utils.coffee b/lib/utils.coffee
index d4beff03..90469fad 100644
--- a/lib/utils.coffee
+++ b/lib/utils.coffee
@@ -19,12 +19,6 @@ Utils =
func = obj[components.pop()]
func.apply(obj, argArray)
- # Creates a single DOM element from :html
- createElementFromHtml: (html) ->
- tmp = document.createElement("div")
- tmp.innerHTML = html
- tmp.firstChild
-
escapeHtml: (string) -> string.replace(/</g, "&lt;").replace(/>/g, "&gt;")
# Generates a unique ID
diff --git a/manifest.json b/manifest.json
index 4ef5edfe..6d971db2 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Vimium",
- "version": "1.51",
+ "version": "1.52",
"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",
@@ -40,7 +40,6 @@
"lib/dom_utils.js",
"lib/rect.js",
"lib/handler_stack.js",
- "lib/clipboard.js",
"lib/settings.js",
"lib/find_mode_history.js",
"content_scripts/ui_component.js",
diff --git a/pages/options.html b/pages/options.html
index 22b041b7..b5aa5936 100644
--- a/pages/options.html
+++ b/pages/options.html
@@ -120,7 +120,7 @@ b: http://b.com/?q=%s description
<td verticalAlign="top" class="booleanOption">
<div class="help">
<div class="example">
- In link-hint mode, this option lets you also select a link by typing its text.
+ In link-hint mode, this option lets you select a link by typing its text.
</div>
</div>
<label>
diff --git a/pages/vomnibar.coffee b/pages/vomnibar.coffee
index cf9ed7b0..742dba10 100644
--- a/pages/vomnibar.coffee
+++ b/pages/vomnibar.coffee
@@ -125,7 +125,7 @@ class VomnibarUI
return true unless action # pass through
openInNewTab = @forceNewTab ||
- (event.shiftKey || event.ctrlKey || KeyboardUtils.isPrimaryModifierKey(event))
+ (event.shiftKey || event.ctrlKey || event.altKey || KeyboardUtils.isPrimaryModifierKey(event))
if (action == "dismiss")
@hide()
else if action in [ "tab", "down" ]