diff options
Diffstat (limited to 'background_scripts/main.coffee')
| -rw-r--r-- | background_scripts/main.coffee | 160 | 
1 files changed, 89 insertions, 71 deletions
| diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee index 9eafc2a2..72fe1092 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.allFrames }, 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 @@ -40,7 +58,7 @@ completers =    bookmarks: new MultiCompleter([completionSources.bookmarks])    tabs: new MultiCompleter([completionSources.tabs]) -chrome.runtime.onConnect.addListener((port, name) -> +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 +70,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]) @@ -150,22 +162,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.  # @@ -220,7 +232,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) @@ -336,9 +355,6 @@ 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] @@ -349,43 +365,33 @@ chrome.browserAction.setBadgeBackgroundColor  setBadge = do ->    current = null    timer = null -  updateBadge = (badge) -> -> chrome.browserAction.setBadgeText text: badge -  (request) -> +  updateBadge = (badge, tabId) -> -> chrome.browserAction.setBadgeText text: badge, tabId: tabId +  (request, sender) ->      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) -> -    setBadge badge: "" -    chrome.tabs.sendMessage tabId, { name: "getActiveState" }, (response) -> -      if response -        isCurrentlyEnabled = response.enabled -        currentPasskeys = response.passKeys -        config = isEnabledForUrl { url: tab.url }, { tab: tab } -        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 }) -      else -        setBrowserActionIcon tabId, disabledIcon +      timer = setTimeout updateBadge(badge, sender.tab.id), 50 + +# 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.  Once the frame learns its active state, it updates the current +# tab's badge (but only if that frame has the focus). +# +# 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) @@ -402,7 +408,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 +442,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] @@ -595,16 +599,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 })) @@ -622,6 +616,7 @@ unregisterFrame = (request, sender) ->  handleFrameFocused = (request, sender) ->    tabId = sender.tab.id +  urlForTab[tabId] = request.url    if frameIdsForTab[tabId]?      frameIdsForTab[tabId] =        [request.frameId, (frameIdsForTab[tabId].filter (id) -> id != request.frameId)...] @@ -643,7 +638,6 @@ sendRequestHandlers =    unregisterFrame: unregisterFrame    frameFocused: handleFrameFocused    nextFrame: (request) -> BackgroundCommands.nextFrame 1, request.frameId -  upgradeNotificationClosed: upgradeNotificationClosed    updateScrollPosition: handleUpdateScrollPosition    copyToClipboard: copyToClipboard    pasteFromClipboard: pasteFromClipboard @@ -652,6 +646,7 @@ sendRequestHandlers =    refreshCompleter: refreshCompleter    createMark: Marks.create.bind(Marks)    gotoMark: Marks.goto.bind(Marks) +  setIcon: setIcon    setBadge: setBadge  # We always remove chrome.storage.local/findModeRawQueryListIncognito on startup. @@ -681,8 +676,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) -> @@ -695,3 +712,4 @@ chrome.windows.getAll { populate: true }, (windows) ->  # Start pulling changes from synchronized storage.  Sync.init() +showUpgradeMessage() | 
