1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
Marks =
# This returns the key which is used for storing mark locations in chrome.storage.sync.
getLocationKey: (markName) -> "vimiumGlobalMark|#{markName}"
# Get the part of a URL we use for matching here (that is, everything up to the first anchor).
getBaseUrl: (url) -> url.split("#")[0]
# Create a global mark. We record vimiumSecret with the mark so that we can tell later, when the mark is
# used, whether this is the original Vimium session or a subsequent session. This affects whether or not
# tabId can be considered valid.
create: (req, sender) ->
chrome.storage.local.get "vimiumSecret", (items) =>
markInfo =
vimiumSecret: items.vimiumSecret
markName: req.markName
url: @getBaseUrl sender.tab.url
tabId: sender.tab.id
scrollX: req.scrollX
scrollY: req.scrollY
if markInfo.scrollX? and markInfo.scrollY?
@saveMark markInfo
else
# The front-end frame hasn't provided the scroll position (because it's not the top frame within its
# tab). We need to ask the top frame what its scroll position is. (With the frame Id set to 0, below,
# the request will only be handled by the top frame within the tab.)
chrome.tabs.sendMessage sender.tab.id, name: "getScrollPosition", frameId: 0, (response) =>
@saveMark extend markInfo, scrollX: response.scrollX, scrollY: response.scrollY
saveMark: (markInfo) ->
item = {}
item[@getLocationKey markInfo.markName] = markInfo
chrome.storage.sync.set item
# Goto a global mark. We try to find the original tab. If we can't find that, then we try to find another
# tab with the original URL, and use that. And if we can't find such an existing tab, then we create a new
# one. Whichever of those we do, we then set the scroll position to the original scroll position.
goto: (req, sender) ->
chrome.storage.local.get "vimiumSecret", (items) =>
vimiumSecret = items.vimiumSecret
key = @getLocationKey req.markName
chrome.storage.sync.get key, (items) =>
markInfo = items[key]
if not markInfo
# The mark is not defined.
chrome.tabs.sendMessage sender.tab.id,
name: "showHUDforDuration",
text: "Global mark not set: '#{req.markName}'."
duration: 1000
else if markInfo.vimiumSecret != vimiumSecret
# This is a different Vimium instantiation, so markInfo.tabId is definitely out of date.
@focusOrLaunch markInfo
else
# Check whether markInfo.tabId still exists. According to here (https://developer.chrome.com/extensions/tabs),
# tab Ids are unqiue within a Chrome session. So, if we find a match, we can use it.
chrome.tabs.get markInfo.tabId, (tab) =>
if not chrome.runtime.lastError and tab?.url and markInfo.url == @getBaseUrl tab.url
# The original tab still exists.
@gotoPositionInTab markInfo
else
# The original tab no longer exists.
@focusOrLaunch markInfo
# Focus an existing tab and scroll to the given position within it.
gotoPositionInTab: ({ tabId, scrollX, scrollY, markName }) ->
chrome.tabs.update tabId, { selected: true }, ->
chrome.tabs.sendMessage tabId,
{ name: "setScrollPosition", scrollX: scrollX, scrollY: scrollY }, ->
chrome.tabs.sendMessage tabId,
name: "showHUDforDuration",
text: "Jumped to global mark '#{markName}'."
duration: 1000
# The tab we're trying to find no longer exists. We either find another tab with a matching URL and use it,
# or we create a new tab.
focusOrLaunch: (markInfo) ->
chrome.tabs.query { url: markInfo.url }, (tabs) =>
if 0 < tabs.length
# We have a matching tab: use it (prefering, if there are more than one, one in the current window).
@pickTabInWindow tabs, (tab) =>
@gotoPositionInTab extend markInfo, tabId: tab.id
else
# There is no existing matching tab, we'll have to create one.
chrome.tabs.create { url: @getBaseUrl markInfo.url }, (tab) =>
# Note. tabLoadedHandlers is defined in "main.coffee". The handler below will be called when the tab
# is loaded, its DOM is ready and it registers with the background page.
tabLoadedHandlers[tab.id] = => @gotoPositionInTab extend markInfo, tabId: tab.id
# Given a list of tabs, pick one in the current window, if possible, otherwise just pick any.
pickTabInWindow: (tabs, continuation) ->
chrome.windows.getCurrent ({ id }) ->
tabsInWindow = tabs.filter (tab) -> tab.windowId == id
continuation tabsInWindow[0] ? tabs[0]
root = exports ? window
root.Marks = Marks
|