]> git.wh0rd.org - chrome-ext/tabs-backup.git/blob - background.js
5aff375df3e747389f1a761198e7dd80d6ba882d
[chrome-ext/tabs-backup.git] / background.js
1 // Create a backup on first install (or if storage is wiped for some reason.
2 chrome.storage.local.get(function(items) {
3 // Setup defaults.
4 if (items.prefs_max_backup_items === undefined) {
5 chrome.storage.local.set({prefs_max_backup_items: 10});
6 }
7 if (items.prefs_backup_timer === undefined) {
8 chrome.storage.local.set({prefs_backup_timer: 30});
9 }
10
11 // If a backup exists already, nothing to do.
12 if (items.backups_list) {
13 return;
14 }
15
16 // Create a backup now
17 var d = new Date();
18 var formattedDate = date_format (d);
19
20 backupNow(true, formattedDate, function({success, backupName, backupObj}) {
21 // backup completed
22 });
23 });
24
25 function initAlarm () {
26 //console.log("initAlarm");
27
28 var BACKUP_ALARM_NAME = "backup_alarm";
29
30 // Clear any previous alarm
31 chrome.alarms.clearAll();
32
33 chrome.storage.local.get(function(items) {
34 const timerMinutes = items.prefs_backup_timer;
35 chrome.alarms.create(BACKUP_ALARM_NAME, {periodInMinutes: timerMinutes});
36 });
37 }
38
39 initAlarm();
40
41 function onAlarm (alarm) {
42 var d = new Date();
43 var formattedDate = date_format (d);
44
45 console.log("Alarm {" + alarm + "} fired up: " + formattedDate);
46
47 // if last backup time != lastTabsEdit
48 // perform automatic backup
49 backupNow(true, formattedDate, function({success, backupName, backupObj}) {
50 // automatic backup completed
51 chrome.runtime.sendMessage({
52 action: 'insertBackupItem',
53 args: [backupName, backupObj, true /*insertAtBeginning*/, true /*doAnimation*/],
54 });
55 });
56 }
57
58 chrome.alarms.onAlarm.addListener(onAlarm);
59
60 function date_prependZero (val) {
61 return val < 10 ? "0" + val : "" + val;
62 }
63
64 // yyyy-m-d h:i:s
65 function date_format (d) {
66 var monthOneOffset = d.getMonth() + 1; // convert from 0-11 to 1-12
67
68 var formattedDate = d.getFullYear() + "-" + date_prependZero(monthOneOffset) + "-" + date_prependZero(d.getDate())
69 + " " + date_prependZero(d.getHours()) + ":" + date_prependZero(d.getMinutes()) + ":" + date_prependZero(d.getSeconds());
70
71 return formattedDate;
72 }
73
74
75 function backupNowManual (callbackDone) {
76 var d = new Date();
77 var formattedDate = date_format (d);
78
79 backupNow(false, formattedDate, callbackDone);
80
81
82 }
83
84 function deleteOldestBackup () {
85 chrome.storage.local.get(function(items) {
86 if(!items.backups_list) {
87 return;
88 }
89
90 var backupsList = items.backups_list;
91 var numItemsToDelete = backupsList.length - items.prefs_max_backup_items;
92 if (numItemsToDelete > 0) {
93 var i = 0;
94 var loopFunc = function () {
95 //
96 if (i > 0) {
97 var deletedBackupName = backupsList[i-1];
98 chrome.runtime.sendMessage({
99 action: 'removeBackupItemDiv',
100 args: [deletedBackupName],
101 });
102 }
103 //
104
105 if (i >= numItemsToDelete) {
106 return;
107 }
108
109 deleteBackup(backupsList[i]).then(loopFunc);
110 i++;
111 };
112
113 loopFunc ();
114 }
115 });
116 }
117
118 //var isCreatingBackup = false;
119
120 function backupNow(isAutomatic, backupName, callbackDone) {
121 console.log("backupNow - isAutomatic: " + isAutomatic + " name: " + backupName);
122 /*if (isCreatingBackup === true) {
123 console.log("backupNow - already running..skipping..");
124 return;
125 }*/
126
127 //isCreatingBackup = true;
128
129 /*if (!confirm("Perform a full backup? All windows and their tabs will be saved!")) {
130 return;
131 }*/
132
133 var fullBackup = {
134 windows: [],
135 isAutomatic: isAutomatic,
136 totNumTabs: 0
137 };
138
139 chrome.windows.getAll({populate : true}, function (window_list) {
140 var totNumTabs = 0;
141
142 for(var i=0;i<window_list.length;i++) {
143 var window = window_list[i];
144
145 //console.log ("Window " + i);
146
147 var bkpWindow = {
148 state: window.state,
149 top: window.top,
150 left: window.left,
151 width: window.width,
152 height: window.height,
153 tabs: []
154 };
155
156 var windowTabs = window.tabs;
157
158 // If it's a single window sittig at the new tab page, don't bother
159 // saving it. This is a nice shortcut when things crash as it will
160 // only show a single window.
161 if (windowTabs.length == 1) {
162 const tab = windowTabs[0];
163 if (tab.url == 'chrome://newtab/')
164 continue;
165 }
166
167 for (var j = 0; j < windowTabs.length; j++) {
168 var tab = windowTabs[j];
169
170 //console.log("==> Tab " + j + " (" + tab.index + "): " + tabUrl);
171
172 // Ignore windows that we can't/shouldn't backup.
173 if (tab.url.startsWith('chrome-untrusted://')) {
174 continue;
175 }
176
177 var bkpTab = {
178 url: tab.url,
179 title: tab.title,
180 highlighted: tab.highlighted,
181 pinned: tab.pinned,
182 };
183
184 // Add tab to tabs arrays
185 bkpWindow.tabs.push(bkpTab);
186 }
187
188 if (bkpWindow.tabs.length) {
189 totNumTabs += bkpWindow.tabs.length;
190
191 fullBackup.windows.push(bkpWindow);
192 }
193 }
194
195 if (totNumTabs == 0)
196 return;
197
198 fullBackup.totNumTabs = totNumTabs;
199
200 var storageSetValues = {};
201 storageSetValues[backupName] = fullBackup;
202
203 // Store backup
204 chrome.storage.local.set(storageSetValues, function () {
205 if (chrome.runtime.lastError) {
206 //isCreatingBackup = false;
207 // TODO change icon to error..
208 //alert ("Error: " + chrome.runtime.lastError.message);
209 updateBrowserActionIcon (1);
210
211 callbackDone({success: false});
212 } else {
213 console.log("backup saved");
214 //alert("Backup saved successfully!");
215
216 chrome.storage.local.get(function(items) {
217 var backupsList = [];
218 if(items.backups_list) {
219 backupsList = items.backups_list;
220 }
221
222 console.log("Updating 'backups_list' - cur. size: " + backupsList.length);
223
224 backupsList.push(backupName);
225
226 chrome.storage.local.set({"backups_list": backupsList}, function () {
227 //isCreatingBackup = false;
228
229 if (chrome.runtime.lastError) {
230 console.log ("Error saving backups_list: " + chrome.runtime.lastError.message);
231 updateBrowserActionIcon (1);
232 callbackDone({success: false});
233 } else {
234 console.log("Backups list saved successfully");
235
236 updateBrowserActionIcon (0);
237 callbackDone({
238 success: true,
239 backupName,
240 backupObj: fullBackup,
241 });
242
243 if (backupsList.length > items.prefs_max_backup_items) {
244 deleteOldestBackup();
245 }
246 }
247 });
248 });
249 }
250 });
251 });
252 }
253
254 /**
255 * 0 ==> OK
256 * 1 ==> ERROR
257 */
258 function updateBrowserActionIcon (status) {
259 var icon;
260 switch(status) {
261 case 0:
262 icon = "icon_ok.png";
263 break;
264 default:
265 icon = "icon_error.png";
266 break;
267 }
268
269 chrome.action.setIcon({path: icon});
270 }
271
272 async function deleteBackup(backupName) {
273 console.log("Deleting backup " + backupName);
274
275 await chrome.storage.local.remove(backupName);
276 //console.log("=> Deleted backup " + backupName);
277
278 const items = await chrome.storage.local.get("backups_list");
279 //console.log("==> got backups_list " + backupName);
280
281 if (!items.backups_list) {
282 return;
283 }
284
285 var backupsList = items.backups_list;
286
287 var index = backupsList.indexOf(backupName);
288 if (index >= 0) {
289 backupsList.splice(index, 1);
290 }
291
292 //console.log("===> Updating backups_list (removing " + backupName + ")");
293
294 await chrome.storage.local.set({"backups_list": backupsList});
295 //console.log("===> Updated backups_list (removed " + backupName + ")");
296 }
297
298 function restoreNow(backupName) {
299 console.log("restoreNow");
300
301 chrome.storage.local.get(backupName, function(items) {
302 if(!items[backupName]) {
303 alert("No Backup found");
304 return;
305 }
306
307 /*if (!confirm("Restore full backup?")) {
308 return;
309 }*/
310 /*
311 if (confirm("Would you like to close all existing windows first?")) {
312 chrome.windows.getAll({populate : false}, function (window_list) {
313 for(var i=0;i<window_list.length;i++) {
314 var window = window_list[i];
315 chrome.windows.remove(window.id);
316 }
317 });
318 }*/
319
320
321 var fullBackup = items[backupName];
322
323 for(var i=0;i<fullBackup.windows.length;i++) {
324 const window = fullBackup.windows[i];
325
326 //console.log ("Window " + i);
327
328 urlsToOpen = [];
329
330 const windowTabs = window.tabs;
331 for (let j = 0; j < windowTabs.length; j++) {
332 const tab = windowTabs[j];
333 const tabUrl = tab.url;
334 urlsToOpen.push(tabUrl);
335 }
336
337 const windowProperties = {
338 state: 'normal',
339 url: urlsToOpen,
340 top: window.top,
341 left: window.left,
342 width: window.width,
343 height: window.height,
344 };
345
346 // Create a new Window
347 chrome.windows.create(windowProperties, function(createdWindow) {
348 // Chrome errors if the dimensions are set on non-normal windows.
349 // So we create the window first with the right settings, then
350 // update the window state.
351 if (window.state != 'normal') {
352 chrome.windows.update(createdWindow.id, {state: window.state});
353 }
354
355 chrome.windows.get(createdWindow.id, {populate: true}, ({tabs}) => {
356 for (let tabi = 0; tabi < windowTabs.length; ++tabi) {
357 const oldtab = windowTabs[tabi];
358 const newtab = tabs[tabi];
359 chrome.tabs.update(newtab.id, {
360 highlighted: oldtab.highlighted,
361 pinned: oldtab.pinned,
362 }, () => {
363 if (!oldtab.highlighted) {
364 // If we discard a tab too fast, Chrome will completely
365 // throw it away. Wait until it's in a stable enough
366 // state for us to discard it.
367 let retryCount = 60;
368 const checktab = (id) => {
369 if (retryCount-- < 0)
370 return;
371 chrome.tabs.get(id, (tab) => {
372 if (tab.pendingUrl)
373 setTimeout(() => checktab(id), 500);
374 else
375 chrome.tabs.discard(id);
376 });
377 };
378 checktab(newtab.id);
379 }
380 });
381 }
382 });
383 });
384 }
385 });
386 }
387
388 /**
389 * Callback from other pages (like the popup).
390 */
391 chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
392 console.log(`Got message from ${sender.id}: action=${request.action}`, request);
393
394 let asyncResponse = false;
395 switch (request?.action) {
396 case 'initAlarm':
397 initAlarm();
398 break;
399
400 case 'restoreNow':
401 restoreNow(...request.args);
402 break;
403
404 case 'deleteBackup':
405 deleteBackup(...request.args).then(sendResponse);
406 asyncResponse = true;
407 break;
408
409 case 'backupNowManual':
410 backupNowManual(sendResponse);
411 asyncResponse = true;
412 break;
413 }
414 return asyncResponse;
415 });