diff options
Diffstat (limited to 'content_scripts/vimium_frontend.coffee')
| -rw-r--r-- | content_scripts/vimium_frontend.coffee | 182 |
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 |
