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.coffee99
1 files changed, 63 insertions, 36 deletions
diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee
index 72f87a7e..e3188a26 100644
--- a/background_scripts/main.coffee
+++ b/background_scripts/main.coffee
@@ -5,6 +5,7 @@ root = exports ? window
chrome.runtime.onInstalled.addListener ({ reason }) ->
# See https://developer.chrome.com/extensions/runtime#event-onInstalled
return if reason in [ "chrome_update", "shared_module_update" ]
+ return if Utils.isFirefox()
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]
@@ -19,7 +20,7 @@ chrome.runtime.onInstalled.addListener ({ reason }) ->
func tab.id, { file: file, allFrames: contentScripts.all_frames }, checkLastRuntimeError
frameIdsForTab = {}
-portsForTab = {}
+root.portsForTab = {}
root.urlForTab = {}
# This is exported for use by "marks.coffee".
@@ -50,6 +51,10 @@ completers =
completionHandlers =
filter: (completer, request, port) ->
completer.filter request, (response) ->
+ # NOTE(smblott): response contains `relevancyFunction` (function) properties which cause postMessage,
+ # below, to fail in Firefox. See #2576. We cannot simply delete these methods, as they're needed
+ # elsewhere. Converting the response to JSON and back is a quick and easy way to sanitize the object.
+ response = JSON.parse JSON.stringify 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).
@@ -99,10 +104,27 @@ TabOperations =
tabConfig =
url: Utils.convertToUrl request.url
index: request.tab.index + 1
- selected: true
+ active: true
windowId: request.tab.windowId
- openerTabId: request.tab.id
- chrome.tabs.create tabConfig, callback
+ tabConfig.active = request.active if request.active?
+ # Firefox does not support "about:newtab" in chrome.tabs.create.
+ delete tabConfig["url"] if tabConfig["url"] == Settings.defaults.newTabUrl
+
+ # Firefox <57 throws an error when openerTabId is used (issue 1238314).
+ canUseOpenerTabId = not (Utils.isFirefox() and Utils.compareVersions(Utils.firefoxVersion(), "57") < 0)
+ tabConfig.openerTabId = request.tab.id if canUseOpenerTabId
+
+ chrome.tabs.create tabConfig, -> callback request
+
+ # Opens request.url in new window and switches to it.
+ openUrlInNewWindow: (request, callback = (->)) ->
+ winConfig =
+ url: Utils.convertToUrl request.url
+ active: true
+ winConfig.active = request.active if request.active?
+ # Firefox does not support "about:newtab" in chrome.tabs.create.
+ delete tabConfig["url"] if tabConfig["url"] == Settings.defaults.newTabUrl
+ chrome.windows.create winConfig, callback
toggleMuteTab = do ->
muteTab = (tab) -> chrome.tabs.update tab.id, {muted: !tab.mutedInfo.muted}
@@ -126,12 +148,12 @@ toggleMuteTab = do ->
#
selectSpecificTab = (request) ->
chrome.tabs.get(request.id, (tab) ->
- chrome.windows.update(tab.windowId, { focused: true })
- chrome.tabs.update(request.id, { selected: true }))
+ chrome.windows?.update(tab.windowId, { focused: true })
+ chrome.tabs.update(request.id, { active: true }))
moveTab = ({count, tab, registryEntry}) ->
count = -count if registryEntry.command == "moveTabLeft"
- chrome.tabs.getAllInWindow null, (tabs) ->
+ chrome.tabs.query { currentWindow: true }, (tabs) ->
pinnedCount = (tabs.filter (tab) -> tab.pinned).length
minIndex = if tab.pinned then 0 else pinnedCount
maxIndex = (if tab.pinned then pinnedCount else tabs.length) - 1
@@ -166,13 +188,20 @@ BackgroundCommands =
[if request.tab.incognito then "chrome://newtab" else chrome.runtime.getURL newTabUrl]
else
[newTabUrl]
- urls = request.urls[..].reverse()
- do openNextUrl = (request) ->
- if 0 < urls.length
- TabOperations.openUrlInNewTab (extend request, {url: urls.pop()}), (tab) ->
- openNextUrl extend request, {tab, tabId: tab.id}
- else
- callback request
+ if request.registryEntry.options.incognito or request.registryEntry.options.window
+ windowConfig =
+ url: request.urls
+ focused: true
+ incognito: request.registryEntry.options.incognito ? false
+ chrome.windows.create windowConfig, -> callback request
+ else
+ urls = request.urls[..].reverse()
+ do openNextUrl = (request) ->
+ if 0 < urls.length
+ TabOperations.openUrlInNewTab (extend request, {url: urls.pop()}), (tab) ->
+ openNextUrl extend request, {tab, tabId: tab.id}
+ else
+ callback request
duplicateTab: mkRepeatCommand (request, callback) ->
chrome.tabs.duplicate request.tabId, (tab) -> callback extend request, {tab, tabId: tab.id}
moveTabToNewWindow: ({count, tab}) ->
@@ -192,8 +221,6 @@ BackgroundCommands =
startTabIndex = Math.max 0, Math.min activeTabIndex, tabs.length - count
chrome.tabs.remove (tab.id for tab in tabs[startTabIndex...startTabIndex + count])
restoreTab: mkRepeatCommand (request, callback) -> chrome.sessions.restore null, callback request
- openCopiedUrlInCurrentTab: (request) -> TabOperations.openUrlInCurrentTab extend request, url: Clipboard.paste()
- openCopiedUrlInNewTab: (request) -> @createTab extend request, url: Clipboard.paste()
togglePinTab: ({tab}) -> chrome.tabs.update tab.id, {pinned: !tab.pinned}
toggleMuteTab: toggleMuteTab
moveTabLeft: moveTab
@@ -226,7 +253,7 @@ removeTabsRelative = (direction, {tab: activeTab}) ->
# Selects a tab before or after the currently selected tab.
# - direction: "next", "previous", "first" or "last".
selectTab = (direction, {count, tab}) ->
- chrome.tabs.getAllInWindow null, (tabs) ->
+ chrome.tabs.query { currentWindow: true }, (tabs) ->
if 1 < tabs.length
toSelect =
switch direction
@@ -238,12 +265,11 @@ selectTab = (direction, {count, tab}) ->
Math.min tabs.length - 1, count - 1
when "last"
Math.max 0, tabs.length - count
- chrome.tabs.update tabs[toSelect].id, selected: true
+ chrome.tabs.update tabs[toSelect].id, active: true
-chrome.tabs.onUpdated.addListener (tabId, changeInfo, tab) ->
- return unless changeInfo.status == "loading" # Only do this once per URL change.
+chrome.webNavigation.onCommitted.addListener ({tabId, frameId}) ->
cssConf =
- allFrames: true
+ frameId: frameId
code: Settings.get("userDefinedLinkHintCss")
runAt: "document_start"
chrome.tabs.insertCSS tabId, cssConf, -> chrome.runtime.lastError
@@ -275,8 +301,9 @@ for icon in [ENABLED_ICON, DISABLED_ICON, PARTIAL_ICON]
Frames =
onConnect: (sender, port) ->
[tabId, frameId] = [sender.tab.id, sender.frameId]
- port.onDisconnect.addListener -> Frames.unregisterFrame {tabId, frameId}
+ port.onDisconnect.addListener -> Frames.unregisterFrame {tabId, frameId, port}
port.postMessage handler: "registerFrameId", chromeFrameId: frameId
+ (portsForTab[tabId] ?= {})[frameId] = port
# Return our onMessage handler for this port.
(request, port) =>
@@ -284,13 +311,12 @@ Frames =
registerFrame: ({tabId, frameId, port}) ->
frameIdsForTab[tabId].push frameId unless frameId in frameIdsForTab[tabId] ?= []
- (portsForTab[tabId] ?= {})[frameId] = port
- unregisterFrame: ({tabId, frameId}) ->
- # FrameId 0 is the top/main frame. We never unregister that frame. If the tab is closing, then we tidy
- # up elsewhere. If the tab is navigating to a new page, then a new top frame will be along soon.
- # This mitigates against the unregister and register messages arriving out of order. See #2125.
- if 0 < frameId
+ unregisterFrame: ({tabId, frameId, port}) ->
+ # Check that the port trying to unregister the frame hasn't already been replaced by a new frame
+ # registering. See #2125.
+ registeredPort = portsForTab[tabId]?[frameId]
+ if registeredPort == port or not registeredPort
if tabId of frameIdsForTab
frameIdsForTab[tabId] = (fId for fId in frameIdsForTab[tabId] when fId != frameId)
if tabId of portsForTab
@@ -299,10 +325,11 @@ Frames =
isEnabledForUrl: ({request, tabId, port}) ->
urlForTab[tabId] = request.url if request.frameIsFocused
+ request.isFirefox = Utils.isFirefox() # Update the value for Utils.isFirefox in the frontend.
enabledState = Exclusions.isEnabledForUrl request.url
if request.frameIsFocused
- chrome.browserAction.setIcon tabId: tabId, imageData: do ->
+ chrome.browserAction.setIcon? tabId: tabId, imageData: do ->
enabledStateIcon =
if not enabledState.isEnabledForUrl
DISABLED_ICON
@@ -333,7 +360,7 @@ handleFrameFocused = ({tabId, frameId}) ->
# Rotate through frames to the frame count places after frameId.
cycleToFrame = (frames, frameId, count = 0) ->
- # We can't always track which frame chrome has focussed, but here we learn that it's frameId; so add an
+ # We can't always track which frame chrome has focused, 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]...]
@@ -362,7 +389,8 @@ HintCoordinator =
prepareToActivateMode: (tabId, originatingFrameId, {modeIndex, isVimiumHelpDialog}) ->
@tabState[tabId] = {frameIds: frameIdsForTab[tabId][..], hintDescriptors: {}, originatingFrameId, modeIndex}
- @tabState[tabId].ports = extend {}, portsForTab[tabId]
+ @tabState[tabId].ports = {}
+ frameIdsForTab[tabId].map (frameId) => @tabState[tabId].ports[frameId] = portsForTab[tabId][frameId]
@sendMessage "getHintDescriptors", tabId, {modeIndex, isVimiumHelpDialog}
# Receive hint descriptors from all frames and activate link-hints mode when we have them all.
@@ -401,15 +429,14 @@ sendRequestHandlers =
# getCurrentTabUrl is used by the content scripts to get their full URL, because window.location cannot help
# with Chrome-specific URLs like "view-source:http:..".
getCurrentTabUrl: ({tab}) -> tab.url
- openUrlInNewTab: (request) -> TabOperations.openUrlInNewTab request
+ openUrlInNewTab: mkRepeatCommand (request, callback) -> TabOperations.openUrlInNewTab request, callback
+ openUrlInNewWindow: (request) -> TabOperations.openUrlInNewWindow request
openUrlInIncognito: (request) -> chrome.windows.create incognito: true, url: Utils.convertToUrl request.url
openUrlInCurrentTab: TabOperations.openUrlInCurrentTab
openOptionsPageInNewTab: (request) ->
chrome.tabs.create url: chrome.runtime.getURL("pages/options.html"), index: request.tab.index + 1
frameFocused: handleFrameFocused
nextFrame: BackgroundCommands.nextFrame
- copyToClipboard: Clipboard.copy.bind Clipboard
- pasteFromClipboard: Clipboard.paste.bind Clipboard
selectSpecificTab: selectSpecificTab
createMark: Marks.create.bind(Marks)
gotoMark: Marks.goto.bind(Marks)
@@ -426,7 +453,7 @@ chrome.tabs.onRemoved.addListener (tabId) ->
delete cache[tabId] for cache in [frameIdsForTab, urlForTab, portsForTab, HintCoordinator.tabState]
chrome.storage.local.get "findModeRawQueryListIncognito", (items) ->
if items.findModeRawQueryListIncognito
- chrome.windows.getAll null, (windows) ->
+ chrome.windows?.getAll null, (windows) ->
for window in windows
return if window.incognito
# There are no remaining incognito-mode tabs, and findModeRawQueryListIncognito is set.
@@ -467,7 +494,7 @@ do showUpgradeMessage = ->
Settings.set "previousVersion", currentVersion
chrome.notifications.onClicked.addListener (id) ->
if id == notificationId
- chrome.tabs.getSelected null, (tab) ->
+ chrome.tabs.query { active: true, currentWindow: true }, ([tab]) ->
TabOperations.openUrlInNewTab {tab, tabId: tab.id, url: "https://github.com/philc/vimium#release-notes"}
else
# We need to wait for the user to accept the "notifications" permission.