-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbackground.js
388 lines (355 loc) · 11.5 KB
/
background.js
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// Data Structures
// windowIDs: [windowID] // Use a Set when not serializing to JSON
// "windowID": { groupsIDs: [Int], tabIDs: [Int]}
// "tabID": { title: String, url: String, favIcon: String, groupID: Int }
// sleeping: [Int]
// groups: { "groupID": { title: String, color: String, collapsed: Bool } }
// Global
let homeTabIDs // Is this still used?
let windowIDs // Is this still used?
let manageHomeRunning = false
let startedManageHomeTabs
let startedUpdateEvents
let updateAlreadyQueued
let manageAlreadyQueued
let uniqueID = 0
let _
// Initialization of Extension
chrome.runtime.onInstalled.addListener(init)
// Handle messages from home tabs
chrome.runtime.onMessage.addListener(handleIncomingMessages)
// Events that will create a new window
chrome.windows.onCreated.addListener(manageHomeTabs)
chrome.tabs.onDetached.addListener(manageHomeTabs)
// Listen for if home tab is deleted or if home tab is only tab in window
chrome.tabs.onRemoved.addListener((id) => {
manageHomeTabs()
// Remove deleted tab from Chrome Storage
deleteFromMemory(String(id))
})
// If home tab is unpinned, create new home tab and delete old one? ////////////////////////////////////////////////
// chrome.tabs.onUpdated.addListener(updateOnEvent)
chrome.tabs.onUpdated.addListener(manageHomeTabs)
// Order of events if other pinned tabs are moved in front of home tab (is onUpdated first?) /////////////////////
// chrome.tabs.onMoved.addListener(updateOnEvent)
chrome.tabs.onMoved.addListener(manageHomeTabs)
// Events that don't require home tabs to be modified
chrome.windows.onRemoved.addListener((id) => {
manageHomeTabs()
// Remove deleted window from Chrome Storage
deleteFromMemory(String(id))
})
chrome.tabGroups.onRemoved.addListener((group) => {
manageHomeTabs()
// Remove deleted tab group from Chrome Storage
deleteFromMemory(String(group.id))
}) //////////////////////////////////////////////////////////////////////////////////////////////// Investigate error
chrome.tabs.onCreated.addListener(manageHomeTabs)
chrome.tabGroups.onCreated.addListener(manageHomeTabs)
chrome.tabGroups.onMoved.addListener(manageHomeTabs)
chrome.tabGroups.onUpdated.addListener(manageHomeTabs)
// chrome.tabs.onCreated.addListener(updateOnEvent)
// chrome.tabGroups.onCreated.addListener(updateOnEvent)
// chrome.tabGroups.onMoved.addListener(updateOnEvent)
// chrome.tabGroups.onUpdated.addListener(updateOnEvent)
// Clear leftover memory and create home tabs
function init() {
chrome.storage.local.clear()
manageHomeTabs()
}
// Query windows (including tabs) and groups and save to storage
async function updateData() {
try {
let rawWindows = await chrome.windows.getAll({
populate: true,
windowTypes: ['normal'],
})
homeTabIDs = parseWindows(rawWindows)
} catch (err) {
console.error(err)
}
try {
let rawGroups = await chrome.tabGroups.query({})
// console.log('rawGroups', rawGroups)
parseGroups(rawGroups)
} catch (err) {
console.log('Error in Update Data', err)
}
}
// Parse windows query and save windowIDs to storage
function createWindowIDs(rawData) {
let setWindowIDs = new Set()
rawData.forEach((window) => setWindowIDs.add(window.id))
chrome.storage.local.set(
{ windowIDs: JSON.stringify(Array.from(setWindowIDs)) },
() => {
let error = chrome.runtime.lastError
if (error) {
console.error(error)
}
}
)
windowIDs = Array.from(setWindowIDs)
}
// Parse raw window and tab data and save individually according to window / tab id to Chrome Storage
function parseWindows(rawWindows) {
const homeTabIDs = []
// Loop through all windows
rawWindows.forEach((window) => {
let tabsInWindow = []
let groupsInWindow = new Set()
// Loop through all tabs in window
window.tabs.forEach((tab) => {
// Add tabs and groups
tabsInWindow.push(tab.id)
if (tab.groupId !== -1) groupsInWindow.add(tab.groupId)
// Save tab info to local storage for each individual tab
let tabContent = {
title: tab.title,
url: tab.url,
favIcon: tab.favIconUrl,
groupID: tab.groupId,
}
chrome.storage.local.set(
{ [String(tab.id)]: JSON.stringify(tabContent) },
() => {
let error = chrome.runtime.lastError
if (error) {
console.error(error)
}
}
)
})
let windowContent = {
tabIDs: tabsInWindow,
groupIDs: Array.from(groupsInWindow),
}
homeTabIDs.push(tabsInWindow[0])
chrome.storage.local.set(
{ [String(window.id)]: JSON.stringify(windowContent) },
() => {
let error = chrome.runtime.lastError
if (error) {
console.error(error)
}
}
)
})
return homeTabIDs
}
// Parse raw group data and save groups to Chrome Storage
function parseGroups(rawGroups) {
rawGroups.forEach((group) => {
let groupContent = {
title: group.title,
color: group.color,
collapsed: group.collapsed,
}
chrome.storage.local.set(
{ [String(group.id)]: JSON.stringify(groupContent) },
() => {
let error = chrome.runtime.lastError
if (error) {
console.error(error)
}
}
)
})
}
// Handle communication between background service and home tabs
async function handleIncomingMessages(message, sender) {
const windowIdOfSender = sender.tab.windowId
const [messageType, tabId, url, favIcon, ...titleSegments] =
message.split(' ')
const title = titleSegments.join(' ')
let sleeping
switch (messageType) {
case 'windowID':
chrome.tabs.sendMessage(
sender.tab.id,
String(windowIdOfSender),
sendMessageCallback
)
break
case 'sleep':
sleeping = await chrome.storage.local.get('sleeping')
sleeping =
Object.keys(sleeping).length > 0 ? JSON.parse(sleeping['sleeping']) : []
chrome.storage.local.set({
[String(uniqueID)]: JSON.stringify({ title, url, favIcon }),
})
sleeping.push(uniqueID++)
chrome.storage.local.set({ sleeping: JSON.stringify(sleeping) }, () => {
let error = chrome.runtime.lastError
if (error) {
console.error(error)
}
})
case 'delete':
deleteTabs(+tabId)
break
case 'wake':
chrome.tabs.create({ windowId: windowIdOfSender, url: url })
case 'delete-sleeping':
sleeping = await chrome.storage.local.get('sleeping')
sleeping =
Object.keys(sleeping).length > 0 ? JSON.parse(sleeping['sleeping']) : []
sleeping = sleeping.filter((id) => id !== +tabId)
chrome.storage.local.set({ sleeping: JSON.stringify(sleeping) }, () => {
let error = chrome.runtime.lastError
if (error) {
console.error(error)
}
})
deleteFromMemory(tabId)
chrome.tabs.sendMessage(sender.tab.id, 'update', sendMessageCallback)
//// send to all active tabs?
break
default:
console.log('default case')
}
}
// Function to update stored data on chrome events
async function updateOnEvent() {
// Only run function if it hasn't been ran for 500 ms and manageHomeTabs isn't running
if (Date.now() - startedUpdateEvents < 500 || manageHomeRunning) {
if (!updateAlreadyQueued) {
setTimeout(updateOnEvent, 1000)
updateAlreadyQueued = true
}
return
}
startedUpdateEvents = Date.now()
updateAlreadyQueued = false
// Update data and message active home tabs, so they know to update
_ = await updateData()
const activeTabs = await chrome.tabs.query({ active: true })
activeTabs.forEach((active) => {
if (homeTabIDs.includes(active.id))
chrome.tabs.sendMessage(active.id, 'update', sendMessageCallback)
})
}
// Create home tabs at index 0 and delete out of place home tabs
async function manageHomeTabs() {
// Block update function from running
manageHomeRunning = true
// Only run function if it hasn't been ran for 500 ms
if (Date.now() - startedManageHomeTabs < 500) {
// Queue up manageHomeTabs to run after current instance is finished
if (!manageAlreadyQueued) {
setTimeout(manageHomeTabs, 500)
manageAlreadyQueued = true
}
return
}
startedManageHomeTabs = Date.now()
manageAlreadyQueued = false
// Query all windows with their tabs included
const allWindows = await chrome.windows.getAll({
windowTypes: ['normal'],
populate: true,
})
// Set variable windowIDs
createWindowIDs(allWindows) ///////////////////////// Necessary Here? / In Update Instead?
// Delete all home tabs that aren't in index 0
const tabsToDelete = findHomeTabsToDelete(allWindows)
_ = await deleteHomeTabs(tabsToDelete)
// Add home tabs to windows without home tab in position 0
_ = await addHomeTabs(allWindows)
// Unblock update from running and run update
manageHomeRunning = false
updateOnEvent()
}
// Create list of home tabs not in index 0 or in their own window
function findHomeTabsToDelete(allWindows) {
const tabsToDelete = []
allWindows.forEach((window) => {
// Add tab ids of any home tabs not in index 0 to list to delete
for (let i = 1; i < window.tabs.length; i++) {
if (window.tabs[i].title === 'Browse Smart') {
tabsToDelete.push(window.tabs[i].id)
}
}
// Add tab id of home tab to list to delete if its the only tab in the window
if (window.tabs.length === 1 && window.tabs[0].title === 'Browse Smart') {
tabsToDelete.push(window.tabs[0].id)
}
})
return tabsToDelete
}
// Delete all out of place home tabs
async function deleteHomeTabs(tabsToDelete) {
// Loop to try to delete home tabs until it is successful
// Home tabs can't be created while a tab is being dragged
let deletedSuccessfully = false
do {
try {
_ = await deleteTabs(tabsToDelete)
deletedSuccessfully = true
} catch (err) {
_ = await wait()
}
} while (!deletedSuccessfully)
}
// Close tab and remove from Chrome Storage
async function deleteTabs(tabsToDelete) {
_ = await chrome.tabs.remove(tabsToDelete)
}
// Create home tabs for all windows
async function addHomeTabs(allWindows) {
// Loop to try to add home tabs until it is successful
// Home tabs can't be created while a tab is being dragged
let tabCreationSuccessful = false
do {
try {
for (let window of allWindows) {
if (window.tabs[0].title !== 'Browse Smart') {
_ = await createHomeTab(window.id)
}
}
tabCreationSuccessful = true
} catch (err) {
let _ = await wait()
}
} while (!tabCreationSuccessful)
}
// Create new home tab
async function createHomeTab(windowID) {
_ = await chrome.tabs.create({
windowId: windowID,
active: false,
pinned: true,
index: 0,
url: './index.html',
})
}
// Half second delay
function wait() {
return new Promise((resolve) => setTimeout(resolve, 500))
}
// Remove deleted tabs, tab groups, and windows from Chrome Storage
function deleteFromMemory(id) {
chrome.storage.local.remove(id, () => {
const err = chrome.runtime.lastError
if (err) console.error(err)
})
}
function sendMessageCallback() {
const lastError = chrome.runtime.lastError
if (
lastError &&
lastError.message !==
'The message port closed before a response was received.'
) {
console.log('Error', lastError.message)
}
}
//
//
//
//
//
//
// chrome.windows.onRemoved.addListener((e) => console.log('window removed', e)) // window id
// chrome.tabs.onRemoved.addListener((e) => console.log('tab deleted', e)) // tab id
// chrome.tabGroups.onRemoved.addListener((e) => console.log('group removed', e)) // {id}