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.coffee430
1 files changed, 202 insertions, 228 deletions
diff --git a/content_scripts/vimium_frontend.coffee b/content_scripts/vimium_frontend.coffee
index 5bad1148..c8c83029 100644
--- a/content_scripts/vimium_frontend.coffee
+++ b/content_scripts/vimium_frontend.coffee
@@ -12,13 +12,20 @@ findModeInitialRange = null
isShowingHelpDialog = false
keyPort = null
isEnabledForUrl = true
-isIncognitoMode = false
+isIncognitoMode = chrome.extension.inIncognitoContext
passKeys = null
keyQueue = null
# The user's operating system.
currentCompletionKeys = ""
validFirstKeys = ""
+# We track whther the current window has the focus or not.
+windowIsFocused = do ->
+ windowHasFocus = document.hasFocus()
+ window.addEventListener "focus", (event) -> windowHasFocus = true if event.target == window; true
+ window.addEventListener "blur", (event) -> windowHasFocus = false if event.target == window; true
+ -> windowHasFocus
+
# The types in <input type="..."> that we consider for focusInput command. Right now this is recalculated in
# each content script. Alternatively we could calculate it once in the background page and use a request to
# fetch it each time.
@@ -26,7 +33,7 @@ validFirstKeys = ""
# The corresponding XPath for such elements.
textInputXPath = (->
- textInputTypes = ["text", "search", "email", "url", "number", "password"]
+ textInputTypes = [ "text", "search", "email", "url", "number", "password", "date", "tel" ]
inputElements = ["input[" +
"(" + textInputTypes.map((type) -> '@type="' + type + '"').join(" or ") + "or not(@type))" +
" and not(@disabled or @readonly)]",
@@ -39,18 +46,27 @@ textInputXPath = (->
# must be called beforehand to ensure get() will return up-to-date values.
#
settings =
- port: null
- values: {}
- loadedValues: 0
- valuesToLoad: [ "scrollStepSize", "linkHintCharacters", "linkHintNumbers", "filterLinkHints", "hideHud",
- "previousPatterns", "nextPatterns", "regexFindMode", "userDefinedLinkHintCss",
- "helpDialog_showAdvancedCommands", "smoothScroll", "grabBackFocus" ]
isLoaded: false
+ port: null
eventListeners: {}
+ values:
+ scrollStepSize: null
+ linkHintCharacters: null
+ linkHintNumbers: null
+ filterLinkHints: null
+ hideHud: null
+ previousPatterns: null
+ nextPatterns: null
+ regexFindMode: null
+ userDefinedLinkHintCss: null
+ helpDialog_showAdvancedCommands: null
+ smoothScroll: null
+ grabBackFocus: null
+ searchEngines: null
init: ->
- @port = chrome.runtime.connect({ name: "settings" })
- @port.onMessage.addListener(@receiveMessage)
+ @port = chrome.runtime.connect name: "settings"
+ @port.onMessage.addListener (response) => @receiveMessage response
# If the port is closed, the background page has gone away (since we never close it ourselves). Stub the
# settings object so we don't keep trying to connect to the extension even though it's gone away.
@@ -60,41 +76,36 @@ settings =
# @get doesn't depend on @port, so we can continue to support it to try and reduce errors.
@[property] = (->) if "function" == typeof value and property != "get"
-
get: (key) -> @values[key]
set: (key, value) ->
@init() unless @port
@values[key] = value
- @port.postMessage({ operation: "set", key: key, value: value })
+ @port.postMessage operation: "set", key: key, value: value
load: ->
@init() unless @port
+ @port.postMessage operation: "fetch", values: @values
- for i of @valuesToLoad
- @port.postMessage({ operation: "get", key: @valuesToLoad[i] })
-
- receiveMessage: (args) ->
- # not using 'this' due to issues with binding on callback
- settings.values[args.key] = args.value
- # since load() can be called more than once, loadedValues can be greater than valuesToLoad, but we test
- # for equality so initializeOnReady only runs once
- if (++settings.loadedValues == settings.valuesToLoad.length)
- settings.isLoaded = true
- listener = null
- while (listener = settings.eventListeners["load"].pop())
- listener()
+ receiveMessage: (response) ->
+ @values = response.values if response.values?
+ @values[response.key] = response.value if response.key? and response.value?
+ @isLoaded = true
+ listener() while listener = @eventListeners.load?.pop()
addEventListener: (eventName, callback) ->
- if (!(eventName of @eventListeners))
- @eventListeners[eventName] = []
- @eventListeners[eventName].push(callback)
+ (@eventListeners[eventName] ||= []).push callback
#
-# Give this frame a unique id.
+# Give this frame a unique (non-zero) id.
#
-frameId = Math.floor(Math.random()*999999999)
+frameId = 1 + Math.floor(Math.random()*999999999)
+
+# For debugging only. This logs to the console on the background page.
+bgLog = (args...) ->
+ args = (arg.toString() for arg in args)
+ chrome.runtime.sendMessage handler: "log", frameId: frameId, message: args.join " "
# If an input grabs the focus before the user has interacted with the page, then grab it back (if the
# grabBackFocus option is set).
@@ -123,25 +134,49 @@ 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
constructor: ->
super
name: "normal"
+ indicator: false # There is no mode indicator in normal mode.
keydown: (event) => onKeydown.call @, event
keypress: (event) => onKeypress.call @, event
keyup: (event) => onKeyup.call @, event
- Scroller.init settings
-
# Install the permanent modes. The permanently-installed insert mode tracks focus/blur events, and
# activates/deactivates itself accordingly.
- new BadgeMode
new NormalMode
new PassKeysMode
new InsertMode permanent: true
- new GrabBackFocus
+ Scroller.init settings
#
# Complete initialization work that sould be done prior to DOMReady.
@@ -163,31 +198,38 @@ initializePreDomReady = ->
isEnabledForUrl = false
chrome.runtime.sendMessage = ->
chrome.runtime.connect = ->
+ window.removeEventListener "focus", onFocus
requestHandlers =
- hideUpgradeNotification: -> HUD.hideUpgradeNotification()
- showUpgradeNotification: (request) -> HUD.showUpgradeNotification(request.version)
showHUDforDuration: (request) -> HUD.showForDuration request.text, request.duration
toggleHelpDialog: (request) -> toggleHelpDialog(request.dialogHtml, request.frameId)
- focusFrame: (request) -> if (frameId == request.frameId) then focusThisFrame(request.highlight)
+ focusFrame: (request) -> if (frameId == request.frameId) then focusThisFrame request
refreshCompletionKeys: refreshCompletionKeys
getScrollPosition: -> scrollX: window.scrollX, scrollY: window.scrollY
setScrollPosition: (request) -> setScrollPosition request.scrollX, request.scrollY
executePageCommand: executePageCommand
- getActiveState: getActiveState
- setState: setState
currentKeyQueue: (request) ->
keyQueue = request.keyQueue
handlerStack.bubbleEvent "registerKeyQueue", { keyQueue: keyQueue }
+ # A frame has received the focus. We don't care here (the Vomnibar/UI-component handles this).
+ frameFocused: ->
+ checkEnabledAfterURLChange: checkEnabledAfterURLChange
chrome.runtime.onMessage.addListener (request, sender, sendResponse) ->
# In the options page, we will receive requests from both content and background scripts. ignore those
# from the former.
return if sender.tab and not sender.tab.url.startsWith 'chrome-extension://'
- return unless isEnabledForUrl or request.name == 'getActiveState' or request.name == 'setState'
# These requests are delivered to the options page, but there are no handlers there.
- return if request.handler == "registerFrame" or request.handler == "frameFocused"
- sendResponse requestHandlers[request.name](request, sender)
+ return if request.handler in [ "registerFrame", "frameFocused", "unregisterFrame" ]
+ shouldHandleRequest = isEnabledForUrl
+ # We always handle the message if it's one of these listed message types.
+ shouldHandleRequest ||= request.name in [ "executePageCommand", "checkEnabledAfterURLChange" ]
+ # Requests with a frameId of zero should always and only be handled in the main/top frame (regardless of
+ # whether Vimium is enabled there).
+ if request.frameId == 0 and DomUtils.isTopFrame()
+ request.frameId = frameId
+ shouldHandleRequest = true
+ sendResponse requestHandlers[request.name](request, sender) if shouldHandleRequest
# Ensure the sendResponse callback is freed.
false
@@ -201,9 +243,11 @@ installListener = (element, event, callback) ->
# Installing or uninstalling listeners is error prone. Instead we elect to check isEnabledForUrl each time so
# we know whether the listener should run or not.
# Run this as early as possible, so the page can't register any event handlers before us.
+# Note: We install the listeners even if Vimium is disabled. See comment in commit
+# 6446cf04c7b44c3d419dc450a73b60bcaf5cdf02.
#
installedListeners = false
-window.initializeWhenEnabled = ->
+window.installListeners = ->
unless installedListeners
# Key event handlers fire on window before they do on document. Prefer window for key events so the page
# can't set handlers to grab the keys before us.
@@ -211,28 +255,26 @@ window.initializeWhenEnabled = ->
do (type) -> installListener window, type, (event) -> handlerStack.bubbleEvent type, event
installListener document, "DOMActivate", (event) -> handlerStack.bubbleEvent 'DOMActivate', event
installedListeners = true
-
-setState = (request) ->
- isEnabledForUrl = request.enabled
- passKeys = request.passKeys
- isIncognitoMode = request.incognito
- initializeWhenEnabled() if isEnabledForUrl
- FindModeHistory.init()
- handlerStack.bubbleEvent "registerStateChange",
- enabled: isEnabledForUrl
- passKeys: passKeys
-
-getActiveState = ->
- Mode.updateBadge()
- return { enabled: isEnabledForUrl, passKeys: passKeys }
+ # Other once-only initialisation.
+ FindModeHistory.init()
+ new GrabBackFocus if isEnabledForUrl
#
-# The backend needs to know which frame has focus.
+# Whenever we get the focus:
+# - Reload settings (they may have changed).
+# - Tell the background page this frame's URL.
+# - Check if we should be enabled.
#
-window.addEventListener "focus", ->
- # settings may have changed since the frame last had focus
- settings.load()
- chrome.runtime.sendMessage({ handler: "frameFocused", frameId: frameId })
+onFocus = (event) ->
+ if event.target == window
+ settings.load()
+ chrome.runtime.sendMessage handler: "frameFocused", frameId: frameId
+ checkIfEnabledForUrl true
+
+# We install these listeners directly (that is, we don't use installListener) because we still need to receive
+# events when Vimium is not enabled.
+window.addEventListener "focus", onFocus
+window.addEventListener "hashchange", onFocus
#
# Initialization tasks that must wait for the document to be ready.
@@ -241,7 +283,9 @@ initializeOnDomReady = ->
# Tell the background page we're in the dom ready state.
chrome.runtime.connect({ name: "domReady" })
CursorHider.init()
- Vomnibar.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.
@@ -255,12 +299,23 @@ unregisterFrame = ->
chrome.runtime.sendMessage
handler: "unregisterFrame"
frameId: frameId
- tab_is_closing: window.top == window.self
+ tab_is_closing: DomUtils.isTopFrame()
executePageCommand = (request) ->
- return unless frameId == request.frameId
+ # Vomnibar commands are handled in the tab's main/top frame. They are handled even if Vimium is otherwise
+ # disabled in the frame.
+ if request.command.split(".")[0] == "Vomnibar"
+ if DomUtils.isTopFrame()
+ # We pass the frameId from request. That's the frame which originated the request, so that's the frame
+ # which should receive the focus when the vomnibar closes.
+ Utils.invokeCommandString request.command, [ request.frameId, request.registryEntry ]
+ refreshCompletionKeys request
+ return
- if (request.passCountToFunction)
+ # All other commands are handled in their frame (but only if Vimium is enabled).
+ return unless frameId == request.frameId and isEnabledForUrl
+
+ if request.registryEntry.passCountToFunction
Utils.invokeCommandString(request.command, [request.count])
else
Utils.invokeCommandString(request.command) for i in [0...request.count]
@@ -274,18 +329,35 @@ setScrollPosition = (scrollX, scrollY) ->
#
# Called from the backend in order to change frame focus.
#
-window.focusThisFrame = (shouldHighlight) ->
- 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()
- if (document.body && 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"
@@ -338,7 +410,9 @@ extend window,
HUD.showForDuration("Yanked #{url}", 2000)
enterInsertMode: ->
- new InsertMode global: true
+ # If a focusable element receives the focus, then we exit and leave the permanently-installed insert-mode
+ # instance to take over.
+ new InsertMode global: true, exitOnFocus: true
enterVisualMode: ->
new VisualMode()
@@ -365,7 +439,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 }
@@ -397,7 +471,6 @@ extend window,
constructor: ->
super
name: "focus-selector"
- badge: "?"
exitOnClick: true
keydown: (event) =>
if event.keyCode == KeyboardUtils.keyCodes.tab
@@ -434,6 +507,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.
@@ -554,20 +628,33 @@ onKeyup = (event) ->
DomUtils.suppressPropagation(event)
@stopBubblingAndTrue
-checkIfEnabledForUrl = ->
+# Checks if Vimium should be enabled or not in this frame. As a side effect, it also informs the background
+# page whether this frame has the focus, allowing the background page to track the active frame's URL.
+checkIfEnabledForUrl = (frameIsFocused = windowIsFocused()) ->
url = window.location.toString()
-
- chrome.runtime.sendMessage { handler: "isEnabledForUrl", url: url }, (response) ->
- isEnabledForUrl = response.isEnabledForUrl
- passKeys = response.passKeys
- if isEnabledForUrl
- initializeWhenEnabled()
- else if (HUD.isReady())
+ chrome.runtime.sendMessage { handler: "isEnabledForUrl", url: url, frameIsFocused: frameIsFocused }, (response) ->
+ { isEnabledForUrl, passKeys } = response
+ installListeners() # But only if they have not been installed already.
+ if HUD.isReady() and not isEnabledForUrl
# Quickly hide any HUD we might already be showing, e.g. if we entered insert mode on page load.
HUD.hide()
handlerStack.bubbleEvent "registerStateChange",
enabled: isEnabledForUrl
passKeys: passKeys
+ # Update the page icon, if necessary.
+ if windowIsFocused()
+ chrome.runtime.sendMessage
+ handler: "setIcon"
+ icon:
+ if isEnabledForUrl and not passKeys then "enabled"
+ else if isEnabledForUrl then "partial"
+ else "disabled"
+ null
+
+# When we're informed by the background page that a URL in this tab has changed, we check if we have the
+# correct enabled state (but only if this frame has the focus).
+checkEnabledAfterURLChange = ->
+ checkIfEnabledForUrl() if windowIsFocused()
# Exported to window, but only for DOM tests.
window.refreshCompletionKeys = (response) ->
@@ -632,21 +719,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)
@@ -689,7 +771,6 @@ handleKeyCharForFindMode = (keyChar) ->
updateQueryForFindMode findModeQuery.rawQuery + keyChar
handleEscapeForFindMode = ->
- exitFindMode()
document.body.classList.remove("vimiumFindMode")
# removing the class does not re-color existing selections. we recreate the current selection so it reverts
# back to the default color.
@@ -703,8 +784,7 @@ handleEscapeForFindMode = ->
# Return true if character deleted, false otherwise.
handleDeleteForFindMode = ->
if findModeQuery.rawQuery.length == 0
- exitFindMode()
- performFindInPlace()
+ HUD.hide()
false
else
updateQueryForFindMode findModeQuery.rawQuery.substring(0, findModeQuery.rawQuery.length - 1)
@@ -714,22 +794,25 @@ handleDeleteForFindMode = ->
# <esc> corresponds approximately to 'nevermind, I have found it already' while <cr> means 'I want to save
# this query and do more searches with it'
handleEnterForFindMode = ->
- exitFindMode()
focusFoundLink()
document.body.classList.add("vimiumFindMode")
FindModeHistory.saveQuery findModeQuery.rawQuery
class FindMode extends Mode
- constructor: ->
+ constructor: (options = {}) ->
@historyIndex = -1
@partialQuery = ""
+ if options.returnToViewport
+ @scrollX = window.scrollX
+ @scrollY = window.scrollY
super
name: "find"
- badge: "/"
+ indicator: false
exitOnEscape: true
exitOnClick: true
keydown: (event) =>
+ window.scrollTo @scrollX, @scrollY if options.returnToViewport
if event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey
@exit() unless handleDeleteForFindMode()
@suppressEvent
@@ -752,8 +835,8 @@ class FindMode extends Mode
DomUtils.suppressPropagation(event)
handlerStack.stopBubblingAndFalse
- keypress: (event) ->
- handlerStack.neverContinueBubbling ->
+ keypress: (event) =>
+ handlerStack.neverContinueBubbling =>
if event.keyCode > 31
keyChar = String.fromCharCode event.charCode
handleKeyCharForFindMode keyChar if keyChar
@@ -781,8 +864,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)
@@ -794,8 +875,7 @@ executeFind = (query, options) ->
# previous find landed in an editable element, then that element may still be activated. In this case, we
# don't want to leave it behind (see #1412).
if document.activeElement and DomUtils.isEditable document.activeElement
- if not DomUtils.isSelected document.activeElement
- document.activeElement.blur()
+ document.activeElement.blur() unless DomUtils.isSelected document.activeElement
# we need to save the anchor node here because <esc> seems to nullify it, regardless of whether we do
# preventDefault()
@@ -987,15 +1067,13 @@ findModeRestoreSelection = (range = findModeInitialRange) ->
selection.addRange range
# Enters find mode. Returns the new find-mode instance.
-window.enterFindMode = ->
+window.enterFindMode = (options = {}) ->
# Save the selection, so performFindInPlace can restore it.
findModeSaveSelection()
- findModeQuery = { rawQuery: "" }
- HUD.show("/")
- new FindMode()
-
-exitFindMode = ->
- HUD.hide()
+ findModeQuery = rawQuery: ""
+ findMode = new FindMode options
+ HUD.show "/"
+ findMode
window.showHelpDialog = (html, fid) ->
return if (isShowingHelpDialog || !document.body || fid != frameId)
@@ -1043,6 +1121,8 @@ window.showHelpDialog = (html, fid) ->
chrome.runtime.sendMessage({handler: "openOptionsPageInNewTab"})
false)
+ # Simulating a click on the help dialog makes it the active element for scrolling.
+ DomUtils.simulateClick document.getElementById "vimiumHelpDialog"
hideHelpDialog = (clickEvent) ->
isShowingHelpDialog = false
@@ -1058,113 +1138,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
- _upgradeNotificationElement: 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 = ""
-
- showUpgradeNotification: (version) ->
- HUD.upgradeNotificationElement().innerHTML = "Vimium has been upgraded to #{version}. See
- <a class='vimiumReset' target='_blank'
- href='https://github.com/philc/vimium#release-notes'>
- what's new</a>.<a class='vimiumReset close-button' href='#'>&times;</a>"
- links = HUD.upgradeNotificationElement().getElementsByTagName("a")
- links[0].addEventListener("click", HUD.onUpdateLinkClicked, false)
- links[1].addEventListener "click", (event) ->
- event.preventDefault()
- HUD.onUpdateLinkClicked()
- Tween.fade(HUD.upgradeNotificationElement(), 1.0, 150)
-
- onUpdateLinkClicked: (event) ->
- HUD.hideUpgradeNotification()
- chrome.runtime.sendMessage({ handler: "upgradeNotificationClosed" })
-
- hideUpgradeNotification: (clickEvent) ->
- Tween.fade(HUD.upgradeNotificationElement(), 0, 150,
- -> HUD.upgradeNotificationElement().style.display = "none")
-
- #
- # 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
-
- upgradeNotificationElement: ->
- if (!HUD._upgradeNotificationElement)
- HUD._upgradeNotificationElement = HUD.createHudElement()
- # Position this just to the left of our normal HUD.
- HUD._upgradeNotificationElement.style.right = "315px"
- HUD._upgradeNotificationElement
-
- createHudElement: ->
- element = document.createElement("div")
- element.className = "vimiumReset vimiumHUD"
- document.body.appendChild(element)
- element
-
- hide: (immediate) ->
- clearInterval(HUD._tweenId)
- if (immediate)
- HUD.displayElement().style.display = "none"
- else
- HUD._tweenId = Tween.fade(HUD.displayElement(), 0, 150,
- -> HUD.displayElement().style.display = "none")
-
- 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.
@@ -1212,6 +1185,7 @@ window.onbeforeunload = ->
root = exports ? window
root.settings = settings
-root.HUD = HUD
root.handlerStack = handlerStack
root.frameId = frameId
+root.windowIsFocused = windowIsFocused
+root.bgLog = bgLog