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.coffee128
1 files changed, 76 insertions, 52 deletions
diff --git a/background_scripts/main.coffee b/background_scripts/main.coffee
index e782a217..a13d9d98 100644
--- a/background_scripts/main.coffee
+++ b/background_scripts/main.coffee
@@ -16,7 +16,7 @@ chrome.runtime.onInstalled.addListener ({ reason }) ->
for tab in tabs
for [ func, files ] in jobs
for file in files
- func tab.id, { file: file, allFrames: contentScripts.allFrames }, checkLastRuntimeError
+ func tab.id, { file: file, allFrames: contentScripts.all_frames }, checkLastRuntimeError
currentVersion = Utils.getCurrentVersion()
tabQueue = {} # windowId -> Array
@@ -43,20 +43,36 @@ 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])
+ 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
@@ -215,13 +231,6 @@ handleSettings = (request, port) ->
values[key] = Settings.get key for own key of values
port.postMessage { values }
-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 }))
-
chrome.tabs.onSelectionChanged.addListener (tabId, selectionInfo) ->
if (selectionChangedHandlers.length > 0)
selectionChangedHandlers.pop().call()
@@ -259,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)
@@ -302,13 +311,9 @@ 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.
@@ -340,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.
@@ -389,7 +396,8 @@ setIcon = (request, sender) ->
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
@@ -602,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
@@ -613,14 +624,21 @@ 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]? and request.frameId in frameIdsForTab[tabId]
- frameIdsForTab[tabId] =
- [request.frameId, (frameIdsForTab[tabId].filter (id) -> id != request.frameId)...]
+ if frameIdsForTab[tabId]?
+ 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
@@ -633,7 +651,7 @@ bgLog = (request, sender) ->
portHandlers =
keyDown: handleKeyDown,
settings: handleSettings,
- filterCompleter: filterCompleter
+ completions: handleCompletions
sendRequestHandlers =
getCompletionKeys: getCompletionKeysRequest
@@ -651,7 +669,6 @@ sendRequestHandlers =
pasteFromClipboard: pasteFromClipboard
isEnabledForUrl: isEnabledForUrl
selectSpecificTab: selectSpecificTab
- refreshCompleter: refreshCompleter
createMark: Marks.create.bind(Marks)
gotoMark: Marks.goto.bind(Marks)
setIcon: setIcon
@@ -672,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'))