aboutsummaryrefslogtreecommitdiffstats
path: root/background_scripts/main.coffee
diff options
context:
space:
mode:
Diffstat (limited to 'background_scripts/main.coffee')
-rw-r--r--background_scripts/main.coffee363
1 files changed, 207 insertions, 156 deletions
diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee
index d034ffb0..99a5672b 100644
--- a/background_scripts/main.coffee
+++ b/background_scripts/main.coffee
@@ -1,7 +1,24 @@
root = exports ? window
-currentVersion = Utils.getCurrentVersion()
+# The browser may have tabs already open. We inject the content scripts immediately so that they work straight
+# away.
+chrome.runtime.onInstalled.addListener ({ reason }) ->
+ # See https://developer.chrome.com/extensions/runtime#event-onInstalled
+ return if reason in [ "chrome_update", "shared_module_update" ]
+ manifest = chrome.runtime.getManifest()
+ # Content scripts loaded on every page should be in the same group. We assume it is the first.
+ contentScripts = manifest.content_scripts[0]
+ jobs = [ [ chrome.tabs.executeScript, contentScripts.js ], [ chrome.tabs.insertCSS, contentScripts.css ] ]
+ # Chrome complains if we don't evaluate chrome.runtime.lastError on errors (and we get errors for tabs on
+ # which Vimium cannot run).
+ checkLastRuntimeError = -> chrome.runtime.lastError
+ chrome.tabs.query { status: "complete" }, (tabs) ->
+ for tab in tabs
+ for [ func, files ] in jobs
+ for file in files
+ func tab.id, { file: file, allFrames: contentScripts.all_frames }, checkLastRuntimeError
+currentVersion = Utils.getCurrentVersion()
tabQueue = {} # windowId -> Array
tabInfoMap = {} # tabId -> object with various tab properties
keyQueue = "" # Queue of keys typed
@@ -9,6 +26,7 @@ validFirstKeys = {}
singleKeyCommands = []
focusedFrame = null
frameIdsForTab = {}
+root.urlForTab = {}
# Keys are either literal characters, or "named" - for example <a-b> (alt+b), <left> (left arrow) or <f12>
# This regular expression captures two groups: the first is a named key, the second is the remainder of
@@ -25,22 +43,38 @@ chrome.storage.local.set
vimiumSecret: Math.floor Math.random() * 2000000000
completionSources =
- bookmarks: new BookmarkCompleter()
- history: new HistoryCompleter()
- domains: new DomainCompleter()
- tabs: new TabCompleter()
- seachEngines: new SearchEngineCompleter()
+ bookmarks: new BookmarkCompleter
+ history: new HistoryCompleter
+ domains: new DomainCompleter
+ tabs: new TabCompleter
+ searchEngines: new SearchEngineCompleter
completers =
- omni: new MultiCompleter([
- completionSources.seachEngines,
- completionSources.bookmarks,
- completionSources.history,
- completionSources.domains])
- bookmarks: new MultiCompleter([completionSources.bookmarks])
- tabs: new MultiCompleter([completionSources.tabs])
-
-chrome.runtime.onConnect.addListener((port, name) ->
+ omni: new MultiCompleter [
+ completionSources.bookmarks
+ completionSources.history
+ completionSources.domains
+ completionSources.searchEngines
+ ]
+ bookmarks: new MultiCompleter [completionSources.bookmarks]
+ tabs: new MultiCompleter [completionSources.tabs]
+
+completionHandlers =
+ filter: (completer, request, port) ->
+ completer.filter request, (response) ->
+ # We use try here because this may fail if the sender has already navigated away from the original page.
+ # This can happen, for example, when posting completion suggestions from the SearchEngineCompleter
+ # (which is done asynchronously).
+ try
+ port.postMessage extend request, extend response, handler: "completions"
+
+ refresh: (completer, _, port) -> completer.refresh port
+ cancel: (completer, _, port) -> completer.cancel port
+
+handleCompletions = (request, port) ->
+ completionHandlers[request.handler] completers[request.name], request, port
+
+chrome.runtime.onConnect.addListener (port, name) ->
senderTabId = if port.sender.tab then port.sender.tab.id else null
# If this is a tab we've been waiting to open, execute any "tab loaded" handlers, e.g. to restore
# the tab's scroll position. Wait until domReady before doing this; otherwise operations like restoring
@@ -52,14 +86,8 @@ chrome.runtime.onConnect.addListener((port, name) ->
delete tabLoadedHandlers[senderTabId]
toCall.call()
- # domReady is the appropriate time to show the "vimium has been upgraded" message.
- # TODO: This might be broken on pages with frames.
- if (shouldShowUpgradeMessage())
- chrome.tabs.sendMessage(senderTabId, { name: "showUpgradeNotification", version: currentVersion })
-
if (portHandlers[port.name])
port.onMessage.addListener(portHandlers[port.name])
-)
chrome.runtime.onMessage.addListener((request, sender, sendResponse) ->
if (sendRequestHandlers[request.handler])
@@ -76,14 +104,24 @@ getCurrentTabUrl = (request, sender) -> sender.tab.url
#
# Checks the user's preferences in local storage to determine if Vimium is enabled for the given URL, and
# whether any keys should be passed through to the underlying page.
+# The source frame also informs us whether or not it has the focus, which allows us to track the URL of the
+# active frame.
#
-root.isEnabledForUrl = isEnabledForUrl = (request) ->
+root.isEnabledForUrl = isEnabledForUrl = (request, sender) ->
+ urlForTab[sender.tab.id] = request.url if request.frameIsFocused
rule = Exclusions.getRule(request.url)
{
isEnabledForUrl: not rule or rule.passKeys
passKeys: rule?.passKeys or ""
}
+onURLChange = (details) ->
+ chrome.tabs.sendMessage details.tabId, name: "checkEnabledAfterURLChange"
+
+# Re-check whether Vimium is enabled for a frame when the url changes without a reload.
+chrome.webNavigation.onHistoryStateUpdated.addListener onURLChange # history.pushState.
+chrome.webNavigation.onReferenceFragmentUpdated.addListener onURLChange # Hash changed.
+
# Retrieves the help dialog HTML template from a file, and populates it with the latest keybindings.
# This is called by options.coffee.
root.helpDialogHtml = (showUnboundCommands, showCommandNames, customTitle) ->
@@ -149,22 +187,22 @@ openUrlInCurrentTab = (request) ->
#
# Opens request.url in new tab and switches to it if request.selected is true.
#
-openUrlInNewTab = (request) ->
- chrome.tabs.getSelected(null, (tab) ->
- chrome.tabs.create({ url: Utils.convertToUrl(request.url), index: tab.index + 1, selected: true }))
+openUrlInNewTab = (request, callback) ->
+ chrome.tabs.getSelected null, (tab) ->
+ tabConfig =
+ url: Utils.convertToUrl request.url
+ index: tab.index + 1
+ selected: true
+ windowId: tab.windowId
+ # FIXME(smblott). openUrlInNewTab is being called in two different ways with different arguments. We
+ # should refactor it such that this check on callback isn't necessary.
+ callback = (->) unless typeof callback == "function"
+ chrome.tabs.create tabConfig, callback
openUrlInIncognito = (request) ->
chrome.windows.create({ url: Utils.convertToUrl(request.url), incognito: true})
#
-# Called when the user has clicked the close icon on the "Vimium has been updated" message.
-# We should now dismiss that message in all tabs.
-#
-upgradeNotificationClosed = (request) ->
- Settings.set("previousVersion", currentVersion)
- sendRequestToAllTabs({ name: "hideUpgradeNotification" })
-
-#
# Copies or pastes some data (request.data) to/from the clipboard.
# We return null to avoid the return value from the copy operations being passed to sendResponse.
#
@@ -182,21 +220,16 @@ selectSpecificTab = (request) ->
#
# Used by the content scripts to get settings from the local storage.
#
-handleSettings = (args, port) ->
- if (args.operation == "get")
- value = Settings.get(args.key)
- port.postMessage({ key: args.key, value: value })
- else # operation == "set"
- Settings.set(args.key, args.value)
-
-refreshCompleter = (request) -> completers[request.name].refresh()
-
-whitespaceRegexp = /\s+/
-filterCompleter = (args, port) ->
- queryTerms = if (args.query == "") then [] else args.query.split(whitespaceRegexp)
- completers[args.name].filter(queryTerms, (results) -> port.postMessage({ id: args.id, results: results }))
-
-getCurrentTimeInSeconds = -> Math.floor((new Date()).getTime() / 1000)
+handleSettings = (request, port) ->
+ switch request.operation
+ when "get" # Get a single settings value.
+ port.postMessage key: request.key, value: Settings.get request.key
+ when "set" # Set a single settings value.
+ Settings.set request.key, request.value
+ when "fetch" # Fetch multiple settings values.
+ values = request.values
+ values[key] = Settings.get key for own key of values
+ port.postMessage { values }
chrome.tabs.onSelectionChanged.addListener (tabId, selectionInfo) ->
if (selectionChangedHandlers.length > 0)
@@ -219,7 +252,14 @@ moveTab = (callback, direction) ->
# These are commands which are bound to keystroke which must be handled by the background page. They are
# mapped in commands.coffee.
BackgroundCommands =
- createTab: (callback) -> chrome.tabs.create({url: Settings.get("newTabUrl")}, (tab) -> callback())
+ createTab: (callback) ->
+ chrome.tabs.query { active: true, currentWindow: true }, (tabs) ->
+ tab = tabs[0]
+ url = Settings.get "newTabUrl"
+ if url == "pages/blank.html"
+ # "pages/blank.html" does not work in incognito mode, so fall back to "chrome://newtab" instead.
+ url = if tab.incognito then "chrome://newtab" else chrome.runtime.getURL url
+ openUrlInNewTab { url }, callback
duplicateTab: (callback) ->
chrome.tabs.getSelected(null, (tab) ->
chrome.tabs.duplicate(tab.id)
@@ -228,10 +268,10 @@ BackgroundCommands =
chrome.tabs.query {active: true, currentWindow: true}, (tabs) ->
tab = tabs[0]
chrome.windows.create {tabId: tab.id, incognito: tab.incognito}
- nextTab: (callback) -> selectTab(callback, "next")
- previousTab: (callback) -> selectTab(callback, "previous")
- firstTab: (callback) -> selectTab(callback, "first")
- lastTab: (callback) -> selectTab(callback, "last")
+ nextTab: (count) -> selectTab "next", count
+ previousTab: (count) -> selectTab "previous", count
+ firstTab: -> selectTab "first"
+ lastTab: -> selectTab "last"
removeTab: (callback) ->
chrome.tabs.getSelected(null, (tab) ->
chrome.tabs.remove(tab.id)
@@ -271,13 +311,13 @@ BackgroundCommands =
moveTabLeft: (count) -> moveTab(null, -count)
moveTabRight: (count) -> moveTab(null, count)
nextFrame: (count,frameId) ->
- chrome.tabs.getSelected(null, (tab) ->
- frames = frameIdsForTab[tab.id]
- # We can't always track which frame chrome has focussed, but here we learn that it's frameId; so add an
- # additional offset such that we do indeed start from frameId.
- count = (count + Math.max 0, frameIdsForTab[tab.id].indexOf frameId) % frames.length
- frames = frameIdsForTab[tab.id] = [frames[count..]..., frames[0...count]...]
- chrome.tabs.sendMessage(tab.id, { name: "focusFrame", frameId: frames[0], highlight: true }))
+ chrome.tabs.getSelected null, (tab) ->
+ frameIdsForTab[tab.id] = cycleToFrame frameIdsForTab[tab.id], frameId, count
+ chrome.tabs.sendMessage tab.id, name: "focusFrame", frameId: frameIdsForTab[tab.id][0], highlight: true
+ mainFrame: ->
+ chrome.tabs.getSelected null, (tab) ->
+ # The front end interprets a frameId of 0 to mean the main/top from.
+ chrome.tabs.sendMessage tab.id, name: "focusFrame", frameId: 0, highlight: true
closeTabsOnLeft: -> removeTabsRelative "before"
closeTabsOnRight: -> removeTabsRelative "after"
@@ -305,21 +345,23 @@ removeTabsRelative = (direction) ->
# Selects a tab before or after the currently selected tab.
# - direction: "next", "previous", "first" or "last".
-selectTab = (callback, direction) ->
- chrome.tabs.getAllInWindow(null, (tabs) ->
+selectTab = (direction, count = 1) ->
+ chrome.tabs.getAllInWindow null, (tabs) ->
return unless tabs.length > 1
- chrome.tabs.getSelected(null, (currentTab) ->
- switch direction
- when "next"
- toSelect = tabs[(currentTab.index + 1 + tabs.length) % tabs.length]
- when "previous"
- toSelect = tabs[(currentTab.index - 1 + tabs.length) % tabs.length]
- when "first"
- toSelect = tabs[0]
- when "last"
- toSelect = tabs[tabs.length - 1]
- selectionChangedHandlers.push(callback)
- chrome.tabs.update(toSelect.id, { selected: true })))
+ chrome.tabs.getSelected null, (currentTab) ->
+ toSelect =
+ switch direction
+ when "next"
+ currentTab.index + count
+ when "previous"
+ currentTab.index - count
+ when "first"
+ 0
+ when "last"
+ tabs.length - 1
+ # Bring toSelect into the range [0,tabs.length).
+ toSelect = (toSelect + tabs.length * Math.abs count) % tabs.length
+ chrome.tabs.update tabs[toSelect].id, selected: true
updateOpenTabs = (tab, deleteFrames = false) ->
# Chrome might reuse the tab ID of a recently removed tab.
@@ -335,60 +377,27 @@ updateOpenTabs = (tab, deleteFrames = false) ->
# Frames are recreated on refresh
delete frameIdsForTab[tab.id] if deleteFrames
-setBrowserActionIcon = (tabId,path) ->
- chrome.browserAction.setIcon({ tabId: tabId, path: path })
-
-chrome.browserAction.setBadgeBackgroundColor
- # This is Vimium blue (from the icon).
- # color: [102, 176, 226, 255]
- # This is a slightly darker blue. It makes the badge more striking in the corner of the eye, and the symbol
- # easier to read.
- color: [82, 156, 206, 255]
-
-setBadge = do ->
- current = null
- timer = null
- updateBadge = (badge) -> -> chrome.browserAction.setBadgeText text: badge
- (request) ->
- badge = request.badge
- if badge? and badge != current
- current = badge
- clearTimeout timer if timer
- # We wait a few moments. This avoids badge flicker when there are rapid changes.
- timer = setTimeout updateBadge(badge), 50
-
-# Updates the browserAction icon to indicate whether Vimium is enabled or disabled on the current page.
-# Also propagates new enabled/disabled/passkeys state to active window, if necessary.
-# This lets you disable Vimium on a page without needing to reload.
-# Exported via root because it's called from the page popup.
-root.updateActiveState = updateActiveState = (tabId) ->
- enabledIcon = "icons/browser_action_enabled.png"
- disabledIcon = "icons/browser_action_disabled.png"
- partialIcon = "icons/browser_action_partial.png"
- chrome.tabs.get tabId, (tab) ->
- # Default to disabled state in case we can't connect to Vimium, primarily for the "New Tab" page.
- setBrowserActionIcon(tabId,disabledIcon)
- setBadge badge: ""
- chrome.tabs.sendMessage tabId, { name: "getActiveState" }, (response) ->
- if response
- isCurrentlyEnabled = response.enabled
- currentPasskeys = response.passKeys
- config = isEnabledForUrl({url: tab.url})
- enabled = config.isEnabledForUrl
- passKeys = config.passKeys
- if (enabled and passKeys)
- setBrowserActionIcon(tabId,partialIcon)
- else if (enabled)
- setBrowserActionIcon(tabId,enabledIcon)
- else
- setBrowserActionIcon(tabId,disabledIcon)
- # Propagate the new state only if it has changed.
- if (isCurrentlyEnabled != enabled || currentPasskeys != passKeys)
- chrome.tabs.sendMessage(tabId, { name: "setState", enabled: enabled, passKeys: passKeys, incognito: tab.incognito })
-
+# Here's how we set the page icon. The default is "disabled", so if we do nothing else, then we get the
+# grey-out disabled icon. Thereafter, we only set tab-specific icons, so there's no need to update the icon
+# when we visit a tab on which Vimium isn't running.
+#
+# For active tabs, when a frame starts, it requests its active state via isEnabledForUrl. We also check the
+# state every time a frame gets the focus. In both cases, the frame then updates the tab's icon accordingly.
+#
+# Exclusion rule changes (from either the options page or the page popup) propagate via the subsequent focus
+# change. In particular, whenever a frame next gets the focus, it requests its new state and sets the icon
+# accordingly.
+#
+setIcon = (request, sender) ->
+ path = switch request.icon
+ when "enabled" then "icons/browser_action_enabled.png"
+ when "partial" then "icons/browser_action_partial.png"
+ when "disabled" then "icons/browser_action_disabled.png"
+ chrome.browserAction.setIcon tabId: sender.tab.id, path: path
handleUpdateScrollPosition = (request, sender) ->
- updateScrollPosition(sender.tab, request.scrollX, request.scrollY)
+ # See note regarding sender.tab at unregisterFrame.
+ updateScrollPosition sender.tab, request.scrollX, request.scrollY if sender.tab?
updateScrollPosition = (tab, scrollX, scrollY) ->
tabInfoMap[tab.id].scrollX = scrollX
@@ -402,7 +411,6 @@ chrome.tabs.onUpdated.addListener (tabId, changeInfo, tab) ->
runAt: "document_start"
chrome.tabs.insertCSS tabId, cssConf, -> chrome.runtime.lastError
updateOpenTabs(tab) if changeInfo.url?
- updateActiveState(tabId)
chrome.tabs.onAttached.addListener (tabId, attachedInfo) ->
# We should update all the tabs in the old window and the new window.
@@ -437,8 +445,7 @@ chrome.tabs.onRemoved.addListener (tabId) ->
tabInfoMap.deletor = -> delete tabInfoMap[tabId]
setTimeout tabInfoMap.deletor, 1000
delete frameIdsForTab[tabId]
-
-chrome.tabs.onActiveChanged.addListener (tabId, selectInfo) -> updateActiveState(tabId)
+ delete urlForTab[tabId]
unless chrome.sessions
chrome.windows.onRemoved.addListener (windowId) -> delete tabQueue[windowId]
@@ -551,13 +558,13 @@ checkKeyQueue = (keysToCheck, tabId, frameId) ->
if runCommand
if not registryEntry.isBackgroundCommand
- chrome.tabs.sendMessage(tabId,
- name: "executePageCommand",
- command: registryEntry.command,
- frameId: frameId,
- count: count,
- passCountToFunction: registryEntry.passCountToFunction,
- completionKeys: generateCompletionKeys(""))
+ chrome.tabs.sendMessage tabId,
+ name: "executePageCommand"
+ command: registryEntry.command
+ frameId: frameId
+ count: count
+ completionKeys: generateCompletionKeys ""
+ registryEntry: registryEntry
refreshedCompletionKeys = true
else
if registryEntry.passCountToFunction
@@ -595,16 +602,6 @@ sendRequestToAllTabs = (args) ->
for tab in window.tabs
chrome.tabs.sendMessage(tab.id, args, null))
-#
-# Returns true if the current extension version is greater than the previously recorded version in
-# localStorage, and false otherwise.
-#
-shouldShowUpgradeMessage = ->
- # Avoid showing the upgrade notification when previousVersion is undefined, which is the case for new
- # installs.
- Settings.set("previousVersion", currentVersion) unless Settings.get("previousVersion")
- Utils.compareVersions(currentVersion, Settings.get("previousVersion")) == 1
-
openOptionsPageInNewTab = ->
chrome.tabs.getSelected(null, (tab) ->
chrome.tabs.create({ url: chrome.runtime.getURL("pages/options.html"), index: tab.index + 1 }))
@@ -613,7 +610,10 @@ registerFrame = (request, sender) ->
(frameIdsForTab[sender.tab.id] ?= []).push request.frameId
unregisterFrame = (request, sender) ->
- tabId = sender.tab.id
+ # When a tab is closing, Chrome sometimes passes messages without sender.tab. Therefore, we guard against
+ # this.
+ tabId = sender.tab?.id
+ return unless tabId?
if frameIdsForTab[tabId]?
if request.tab_is_closing
updateOpenTabs sender.tab, true
@@ -622,15 +622,36 @@ unregisterFrame = (request, sender) ->
handleFrameFocused = (request, sender) ->
tabId = sender.tab.id
+ # Cycle frameIdsForTab to the focused frame. However, also ensure that we don't inadvertently register a
+ # frame which wasn't previously registered (such as a frameset).
if frameIdsForTab[tabId]?
- frameIdsForTab[tabId] =
- [request.frameId, (frameIdsForTab[tabId].filter (id) -> id != request.frameId)...]
+ frameIdsForTab[tabId] = cycleToFrame frameIdsForTab[tabId], request.frameId
+ # Inform all frames that a frame has received the focus.
+ chrome.tabs.sendMessage sender.tab.id,
+ name: "frameFocused"
+ focusFrameId: request.frameId
+
+# Rotate through frames to the frame count places after frameId.
+cycleToFrame = (frames, frameId, count = 0) ->
+ frames ||= []
+ # We can't always track which frame chrome has focussed, but here we learn that it's frameId; so add an
+ # additional offset such that we do indeed start from frameId.
+ count = (count + Math.max 0, frames.indexOf frameId) % frames.length
+ [frames[count..]..., frames[0...count]...]
+
+# Send a message to all frames in the current tab.
+sendMessageToFrames = (request, sender) ->
+ chrome.tabs.sendMessage sender.tab.id, request.message
+
+# For debugging only. This allows content scripts to log messages to the background page's console.
+bgLog = (request, sender) ->
+ console.log "#{sender.tab.id}/#{request.frameId}", request.message
# Port handler mapping
portHandlers =
keyDown: handleKeyDown,
settings: handleSettings,
- filterCompleter: filterCompleter
+ completions: handleCompletions
sendRequestHandlers =
getCompletionKeys: getCompletionKeysRequest
@@ -643,16 +664,16 @@ sendRequestHandlers =
unregisterFrame: unregisterFrame
frameFocused: handleFrameFocused
nextFrame: (request) -> BackgroundCommands.nextFrame 1, request.frameId
- upgradeNotificationClosed: upgradeNotificationClosed
updateScrollPosition: handleUpdateScrollPosition
copyToClipboard: copyToClipboard
pasteFromClipboard: pasteFromClipboard
isEnabledForUrl: isEnabledForUrl
selectSpecificTab: selectSpecificTab
- refreshCompleter: refreshCompleter
createMark: Marks.create.bind(Marks)
gotoMark: Marks.goto.bind(Marks)
- setBadge: setBadge
+ setIcon: setIcon
+ sendMessageToFrames: sendMessageToFrames
+ log: bgLog
# We always remove chrome.storage.local/findModeRawQueryListIncognito on startup.
chrome.storage.local.remove "findModeRawQueryListIncognito"
@@ -668,6 +689,13 @@ chrome.tabs.onRemoved.addListener (tabId) ->
# There are no remaining incognito-mode tabs, and findModeRawQueryListIncognito is set.
chrome.storage.local.remove "findModeRawQueryListIncognito"
+# Tidy up tab caches when tabs are removed. We cannot rely on unregisterFrame because Chrome does not always
+# provide sender.tab there.
+# NOTE(smblott) (2015-05-05) This may break restoreTab on legacy Chrome versions, but we'll be moving to
+# chrome.sessions support only soon anyway.
+chrome.tabs.onRemoved.addListener (tabId) ->
+ delete cache[tabId] for cache in [ frameIdsForTab, urlForTab, tabInfoMap ]
+
# Convenience function for development use.
window.runTests = -> open(chrome.runtime.getURL('tests/dom_tests/dom_tests.html'))
@@ -681,8 +709,30 @@ if Settings.has("keyMappings")
populateValidFirstKeys()
populateSingleKeyCommands()
-if shouldShowUpgradeMessage()
- sendRequestToAllTabs({ name: "showUpgradeNotification", version: currentVersion })
+
+# Show notification on upgrade.
+showUpgradeMessage = ->
+ # Avoid showing the upgrade notification when previousVersion is undefined, which is the case for new
+ # installs.
+ Settings.set "previousVersion", currentVersion unless Settings.get "previousVersion"
+ if Utils.compareVersions(currentVersion, Settings.get "previousVersion" ) == 1
+ notificationId = "VimiumUpgradeNotification"
+ notification =
+ type: "basic"
+ iconUrl: chrome.runtime.getURL "icons/vimium.png"
+ title: "Vimium Upgrade"
+ message: "Vimium has been upgraded to version #{currentVersion}. Click here for more information."
+ isClickable: true
+ if chrome.notifications?.create?
+ chrome.notifications.create notificationId, notification, ->
+ unless chrome.runtime.lastError
+ Settings.set "previousVersion", currentVersion
+ chrome.notifications.onClicked.addListener (id) ->
+ if id == notificationId
+ openUrlInNewTab url: "https://github.com/philc/vimium#release-notes"
+ else
+ # We need to wait for the user to accept the "notifications" permission.
+ chrome.permissions.onAdded.addListener showUpgradeMessage
# Ensure that tabInfoMap is populated when Vimium is installed.
chrome.windows.getAll { populate: true }, (windows) ->
@@ -694,4 +744,5 @@ chrome.windows.getAll { populate: true }, (windows) ->
chrome.tabs.sendMessage(tab.id, { name: "getScrollPosition" }, createScrollPositionHandler())
# Start pulling changes from synchronized storage.
-Sync.init()
+Settings.init()
+showUpgradeMessage()