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