aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/vimium_frontend.coffee
diff options
context:
space:
mode:
Diffstat (limited to 'content_scripts/vimium_frontend.coffee')
-rw-r--r--content_scripts/vimium_frontend.coffee182
1 files changed, 66 insertions, 116 deletions
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index b322af53..41fb772b 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -133,6 +133,32 @@ class GrabBackFocus extends Mode
element.blur()
@suppressEvent
+# Pages can load new content dynamically and change the displayed URL using history.pushState. Since this can
+# often be indistinguishable from an actual new page load for the user, we should also re-start GrabBackFocus
+# for these as well. This fixes issue #1622.
+handlerStack.push
+ _name: "GrabBackFocus-pushState-monitor"
+ click: (event) ->
+ # If a focusable element is focused, the user must have clicked on it. Retain focus and bail.
+ return true if DomUtils.isFocusable document.activeElement
+
+ target = event.target
+ while target
+ # Often, a link which triggers a content load and url change with javascript will also have the new
+ # url as it's href attribute.
+ if target.tagName == "A" and
+ target.origin == document.location.origin and
+ # Clicking the link will change the url of this frame.
+ (target.pathName != document.location.pathName or
+ target.search != document.location.search) and
+ (target.target in ["", "_self"] or
+ (target.target == "_parent" and window.parent == window) or
+ (target.target == "_top" and window.top == window))
+ return new GrabBackFocus()
+ else
+ target = target.parentElement
+ true
+
# Only exported for tests.
window.initializeModes = ->
class NormalMode extends Mode
@@ -158,8 +184,6 @@ initializePreDomReady = ->
settings.addEventListener("load", LinkHints.init.bind(LinkHints))
settings.load()
- # Note. checkIfEnabledForUrl() must come after initializeModes(), here, because checkIfEnabledForUrl() may
- # install an additional mode (GrabBackFocus).
initializeModes()
checkIfEnabledForUrl()
refreshCompletionKeys()
@@ -230,7 +254,7 @@ window.installListeners = ->
do (type) -> installListener window, type, (event) -> handlerStack.bubbleEvent type, event
installListener document, "DOMActivate", (event) -> handlerStack.bubbleEvent 'DOMActivate', event
installedListeners = true
- # Other one-time initialization operations.
+ # Other once-only initialisation.
FindModeHistory.init()
new GrabBackFocus if isEnabledForUrl
@@ -260,6 +284,7 @@ initializeOnDomReady = ->
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()
registerFrame = ->
# Don't register frameset containers; focusing them is no use.
@@ -303,20 +328,35 @@ setScrollPosition = (scrollX, scrollY) ->
#
# Called from the backend in order to change frame focus.
#
-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.
- # Here we're assuming that there is at least one frame large enough to focus.
- chrome.runtime.sendMessage({ handler: "nextFrame", frameId: frameId })
- return
- window.focus()
- shouldHighlight = request.highlight
- shouldHighlight ||= request.highlightOnlyIfNotTop and not DomUtils.isTopFrame()
- if document.body and shouldHighlight
- borderWas = document.body.style.border
- document.body.style.border = '5px solid yellow'
- setTimeout((-> document.body.style.border = borderWas), 200)
+window.focusThisFrame = do ->
+ # Create a shadow DOM wrapping the frame so the page's styles don't interfere with ours.
+ highlightedFrameElement = document.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
+
+ _frameEl = document.createElement "div"
+ _frameEl.className = "vimiumReset vimiumHighlightedFrame"
+ _shadowDOM.appendChild _frameEl
+
+ (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.
+ # Here we're assuming that there is at least one frame large enough to focus.
+ chrome.runtime.sendMessage({ handler: "nextFrame", frameId: frameId })
+ return
+ window.focus()
+ shouldHighlight = request.highlight
+ shouldHighlight ||= request.highlightOnlyIfNotTop and not DomUtils.isTopFrame()
+ if shouldHighlight
+ document.documentElement.appendChild highlightedFrameElement
+ setTimeout (-> highlightedFrameElement.remove()), 200
extend window,
scrollToBottom: -> Scroller.scrollTo "y", "max"
@@ -398,7 +438,7 @@ extend window,
visibleInputs =
for i in [0...resultSet.snapshotLength] by 1
element = resultSet.snapshotItem i
- rect = DomUtils.getVisibleClientRect element
+ rect = DomUtils.getVisibleClientRect element, true
continue if rect == null
{ element: element, rect: rect }
@@ -466,6 +506,7 @@ extend window,
new mode
singleton: document.activeElement
targetElement: document.activeElement
+ indicator: false
# Track which keydown events we have handled, so that we can subsequently suppress the corresponding keyup
# event.
@@ -677,21 +718,16 @@ updateFindModeQuery = ->
# character. here we grep for the relevant escape sequences.
findModeQuery.isRegex = settings.get 'regexFindMode'
hasNoIgnoreCaseFlag = false
- findModeQuery.parsedQuery = findModeQuery.rawQuery.replace /\\./g, (match) ->
- switch (match)
- when "\\r"
+ findModeQuery.parsedQuery = findModeQuery.rawQuery.replace /(\\{1,2})([rRI]?)/g, (match, slashes, flag) ->
+ return match if flag == "" or slashes.length != 1
+ switch (flag)
+ when "r"
findModeQuery.isRegex = true
- return ""
- when "\\R"
+ when "R"
findModeQuery.isRegex = false
- return ""
- when "\\I"
+ when "I"
hasNoIgnoreCaseFlag = true
- return ""
- when "\\\\"
- return "\\"
- else
- return match
+ ""
# default to 'smartcase' mode, unless noIgnoreCase is explicitly specified
findModeQuery.ignoreCase = !hasNoIgnoreCaseFlag && !Utils.hasUpperCase(findModeQuery.parsedQuery)
@@ -827,8 +863,6 @@ executeFind = (query, options) ->
document.body.classList.add("vimiumFindMode")
- # prevent find from matching its own search query in the HUD
- HUD.hide(true)
# ignore the selectionchange event generated by find()
document.removeEventListener("selectionchange",restoreDefaultSelectionHighlight, true)
result = window.find(query, options.caseSensitive, options.backwards, true, false, true, false)
@@ -1101,89 +1135,6 @@ toggleHelpDialog = (html, fid) ->
else
showHelpDialog(html, fid)
-#
-# A heads-up-display (HUD) for showing Vimium page operations.
-# Note: you cannot interact with the HUD until document.body is available.
-#
-HUD =
- _tweenId: -1
- _displayElement: null
-
- # This HUD is styled to precisely mimick the chrome HUD on Mac. Use the "has_popup_and_link_hud.html"
- # test harness to tweak these styles to match Chrome's. One limitation of our HUD display is that
- # it doesn't sit on top of horizontal scrollbars like Chrome's HUD does.
-
- showForDuration: (text, duration) ->
- HUD.show(text)
- HUD._showForDurationTimerId = setTimeout((-> HUD.hide()), duration)
-
- show: (text) ->
- return unless HUD.enabled()
- clearTimeout(HUD._showForDurationTimerId)
- HUD.displayElement().innerText = text
- clearInterval(HUD._tweenId)
- HUD._tweenId = Tween.fade(HUD.displayElement(), 1.0, 150)
- HUD.displayElement().style.display = ""
-
- #
- # Retrieves the HUD HTML element.
- #
- displayElement: ->
- if (!HUD._displayElement)
- HUD._displayElement = HUD.createHudElement()
- # Keep this far enough to the right so that it doesn't collide with the "popups blocked" chrome HUD.
- HUD._displayElement.style.right = "150px"
- HUD._displayElement
-
- createHudElement: ->
- element = document.createElement("div")
- element.className = "vimiumReset vimiumHUD"
- document.body.appendChild(element)
- element
-
- # Hide the HUD.
- # If :immediate is falsy, then the HUD is faded out smoothly (otherwise it is hidden immediately).
- # If :updateIndicator is truthy, then we also refresh the mode indicator. The only time we don't update the
- # mode indicator, is when hide() is called for the mode indicator itself.
- hide: (immediate = false, updateIndicator = true) ->
- clearInterval(HUD._tweenId)
- if immediate
- HUD.displayElement().style.display = "none" unless updateIndicator
- Mode.setIndicator() if updateIndicator
- else
- HUD._tweenId = Tween.fade HUD.displayElement(), 0, 150, -> HUD.hide true, updateIndicator
-
- isReady: -> document.body != null
-
- # A preference which can be toggled in the Options page. */
- enabled: -> !settings.get("hideHud")
-
-Tween =
- #
- # Fades an element's alpha. Returns a timer ID which can be used to stop the tween via clearInterval.
- #
- fade: (element, toAlpha, duration, onComplete) ->
- state = {}
- state.duration = duration
- state.startTime = (new Date()).getTime()
- state.from = parseInt(element.style.opacity) || 0
- state.to = toAlpha
- state.onUpdate = (value) ->
- element.style.opacity = value
- if (value == state.to && onComplete)
- onComplete()
- state.timerId = setInterval((-> Tween.performTweenStep(state)), 50)
- state.timerId
-
- performTweenStep: (state) ->
- elapsed = (new Date()).getTime() - state.startTime
- if (elapsed >= state.duration)
- clearInterval(state.timerId)
- state.onUpdate(state.to)
- else
- value = (elapsed / state.duration) * (state.to - state.from) + state.from
- state.onUpdate(value)
-
CursorHider =
#
# Hide the cursor when the browser scrolls, and prevent mouse from hovering while invisible.
@@ -1231,7 +1182,6 @@ window.onbeforeunload = ->
root = exports ? window
root.settings = settings
-root.HUD = HUD
root.handlerStack = handlerStack
root.frameId = frameId
root.windowIsFocused = windowIsFocused