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