This repository has been archived by the owner on Dec 11, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 974
/
sessionStore.js
858 lines (778 loc) · 25.9 KB
/
sessionStore.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
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
'use strict'
// Session store in Brave works as follows:
// - Electron sends a ‘before-quit’ event
// - Brave sends REQUEST_WINDOW_STATE to each renderer process
// - Each renderer responds with its window state with a RESPONSE_WINDOW_STATE IPC message
// - When all state is collected save it to a JSON file and close the app
// - NODE_ENV of ‘test’ bypassing session state or else they all fail.
const fs = require('fs-extra')
const path = require('path')
const electron = require('electron')
const os = require('os')
const app = electron.app
// Constants
const UpdateStatus = require('../js/constants/updateStatus')
const settings = require('../js/constants/settings')
const siteTags = require('../js/constants/siteTags')
const downloadStates = require('../js/constants/downloadStates')
// State
const tabState = require('./common/state/tabState')
const windowState = require('./common/state/windowState')
// Utils
const locale = require('./locale')
const {pinnedTopSites} = require('../js/data/newTabData')
const {defaultSiteSettingsList} = require('../js/data/siteSettingsList')
const filtering = require('./filtering')
const autofill = require('./autofill')
const {navigatableTypes} = require('../js/lib/appUrlUtil')
const Channel = require('./channel')
const {makeImmutable} = require('./common/state/immutableUtil')
const {getSetting} = require('../js/settings')
const platformUtil = require('./common/lib/platformUtil')
const historyUtil = require('./common/lib/historyUtil')
const sessionStorageVersion = 1
const sessionStorageName = `session-store-${sessionStorageVersion}`
const getTempStoragePath = (filename) => {
const epochTimestamp = (new Date()).getTime().toString()
filename = filename || 'tmp'
return process.env.NODE_ENV !== 'test'
? path.join(app.getPath('userData'), 'session-store-' + filename + '-' + epochTimestamp)
: path.join(process.env.HOME, '.brave-test-session-store-' + filename + '-' + epochTimestamp)
}
const getStoragePath = () => {
return path.join(app.getPath('userData'), sessionStorageName)
}
/**
* Saves the specified immutable browser state to storage.
*
* @param {object} payload - Application state as per
* https://github.com/brave/browser/wiki/Application-State
* (not immutable data)
* @return a promise which resolves when the state is saved
*/
module.exports.saveAppState = (payload, isShutdown) => {
return new Promise((resolve, reject) => {
// Don't persist private frames
let startupModeSettingValue = getSetting(settings.STARTUP_MODE)
const savePerWindowState = startupModeSettingValue == null ||
startupModeSettingValue === 'lastTime'
if (payload.perWindowState && savePerWindowState) {
payload.perWindowState.forEach((wndPayload) => {
wndPayload.frames = wndPayload.frames.filter((frame) => !frame.isPrivate)
})
} else {
delete payload.perWindowState
}
try {
payload = module.exports.cleanAppData(payload, isShutdown)
payload.cleanedOnShutdown = isShutdown
} catch (e) {
payload.cleanedOnShutdown = false
}
payload.lastAppVersion = app.getVersion()
if (isShutdown) {
module.exports.cleanSessionDataOnShutdown()
}
muon.file.writeImportant(getStoragePath(), JSON.stringify(payload), (success) => {
if (success) {
resolve()
} else {
reject(new Error('Could not save app state to ' + getStoragePath()))
}
})
})
}
/**
* Cleans session data from unwanted values.
*/
module.exports.cleanPerWindowData = (perWindowData, isShutdown) => {
if (!perWindowData) {
perWindowData = {}
}
// delete the frame index because tabId is per-session
delete perWindowData.framesInternal
// Hide the context menu when we restore.
delete perWindowData.contextMenuDetail
// Don't save preview frame since they are only related to hovering on a tab
delete perWindowData.previewFrameKey
// Don't save widevine panel detail
delete perWindowData.widevinePanelDetail
// Don't save preview tab pages
if (perWindowData.ui && perWindowData.ui.tabs) {
delete perWindowData.ui.tabs.previewTabPageIndex
}
// Don't restore add/edit dialog
delete perWindowData.bookmarkDetail
// Don't restore bravery panel
delete perWindowData.braveryPanelDetail
// Don't restore drag data and clearBrowsingDataPanel's visibility
if (perWindowData.ui) {
// This is no longer stored, we can remove this line eventually
delete perWindowData.ui.isFocused
delete perWindowData.ui.mouseInTitlebar
delete perWindowData.ui.mouseInFrame
delete perWindowData.ui.dragging
delete perWindowData.ui.isClearBrowsingDataPanelVisible
}
perWindowData.frames = perWindowData.frames || []
let newKey = 0
const cleanFrame = (frame) => {
newKey++
// Reset the ids back to sequential numbers
if (frame.key === perWindowData.activeFrameKey) {
perWindowData.activeFrameKey = newKey
} else {
// For now just set everything to unloaded unless it's the active frame
frame.unloaded = true
}
frame.key = newKey
// Set the frame src to the last visited location
// or else users will see the first visited URL.
// Pinned location always get reset to what they are
frame.src = frame.pinnedLocation || frame.location
// If a blob is present for the thumbnail, create the object URL
if (frame.thumbnailBlob) {
try {
frame.thumbnailUrl = window.URL.createObjectURL(frame.thumbnailBlob)
} catch (e) {
delete frame.thumbnailUrl
}
}
// Delete lists of blocked sites
delete frame.trackingProtection
delete frame.httpsEverywhere
delete frame.adblock
delete frame.noScript
// clean up any legacy frame opening props
delete frame.openInForeground
delete frame.disposition
// Guest instance ID's are not valid after restarting.
// Electron won't know about them.
delete frame.guestInstanceId
// Tab ids are per-session and should not be persisted
delete frame.tabId
// Do not show the audio indicator until audio starts playing
delete frame.audioMuted
delete frame.audioPlaybackActive
// Let's not assume wknow anything about loading
delete frame.loading
// Always re-determine the security data
delete frame.security
// Value is only used for local storage
delete frame.isActive
// Hide modal prompts.
delete frame.modalPromptDetail
// Remove HTTP basic authentication requests.
delete frame.basicAuthDetail
// Remove open search details
delete frame.searchDetail
// Remove find in page details
if (frame.findDetail) {
delete frame.findDetail.numberOfMatches
delete frame.findDetail.activeMatchOrdinal
delete frame.findDetail.internalFindStatePresent
}
delete frame.findbarShown
// Don't restore full screen state
delete frame.isFullScreen
delete frame.showFullScreenWarning
// Don't store child tab open ordering since keys
// currently get re-generated when session store is
// restored. We will be able to keep this once we
// don't regenerate new frame keys when opening storage.
delete frame.parentFrameKey
// Delete the active shortcut details
delete frame.activeShortcut
delete frame.activeShortcutDetails
if (frame.navbar && frame.navbar.urlbar) {
if (frame.navbar.urlbar.suggestions) {
frame.navbar.urlbar.suggestions.selectedIndex = null
frame.navbar.urlbar.suggestions.suggestionList = null
}
delete frame.navbar.urlbar.searchDetail
}
}
const clearHistory = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_HISTORY) === true
if (clearHistory) {
perWindowData.closedFrames = []
}
// Clean closed frame data before frames because the keys are re-ordered
// and the new next key is calculated in windowStore.js based on
// the max frame key ID.
if (perWindowData.closedFrames) {
perWindowData.closedFrames.forEach(cleanFrame)
}
if (perWindowData.frames) {
// Don't restore pinned locations because they will be auto created by the app state change event
perWindowData.frames = perWindowData.frames
.filter((frame) => !frame.pinnedLocation)
perWindowData.frames.forEach(cleanFrame)
}
}
/**
* Cleans app data before it's written to disk.
* @param {Object} data - top-level app data
* @param {Object} isShutdown - true if the data is being cleared for a shutdown
* WARNING: getPrefs is only available in this function when isShutdown is true
*/
module.exports.cleanAppData = (data, isShutdown) => {
// make a copy
// TODO(bridiver) use immutable
data = makeImmutable(data).toJS()
// Don't show notifications from the last session
data.notifications = []
// Delete temp site settings
data.temporarySiteSettings = {}
if (data.settings && data.settings[settings.CHECK_DEFAULT_ON_STARTUP] === true) {
// Delete defaultBrowserCheckComplete state since this is checked on startup
delete data.defaultBrowserCheckComplete
}
// Delete Recovery status on shut down
try {
delete data.ui.about.preferences.recoverySucceeded
} catch (e) {}
if (data.perWindowState) {
data.perWindowState.forEach((perWindowState) =>
module.exports.cleanPerWindowData(perWindowState, isShutdown))
}
const clearAutocompleteData = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_AUTOCOMPLETE_DATA) === true
if (clearAutocompleteData) {
try {
autofill.clearAutocompleteData()
} catch (e) {
console.log('cleanAppData: error calling autofill.clearAutocompleteData: ', e)
}
}
const clearAutofillData = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_AUTOFILL_DATA) === true
if (clearAutofillData) {
autofill.clearAutofillData()
const date = new Date().getTime()
data.autofill = {
addresses: {
guid: [],
timestamp: date
},
creditCards: {
guid: [],
timestamp: date
}
}
}
if (data.dragData) {
delete data.dragData
}
if (data.sync) {
// clear sync site cache
data.sync.objectsById = {}
}
const clearSiteSettings = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_SITE_SETTINGS) === true
if (clearSiteSettings) {
data.siteSettings = {}
}
// Delete expired Flash and NoScript allow-once approvals
let now = Date.now()
for (var host in data.siteSettings) {
let expireTime = data.siteSettings[host].flash
if (typeof expireTime === 'number' && expireTime < now) {
delete data.siteSettings[host].flash
}
let noScript = data.siteSettings[host].noScript
if (typeof noScript === 'number') {
delete data.siteSettings[host].noScript
}
// Don't persist any noScript exceptions
delete data.siteSettings[host].noScriptExceptions
// Don't write runInsecureContent to session
delete data.siteSettings[host].runInsecureContent
// If the site setting is empty, delete it for privacy
if (Object.keys(data.siteSettings[host]).length === 0) {
delete data.siteSettings[host]
}
}
if (data.sites) {
const clearHistory = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_HISTORY) === true
if (clearHistory) {
data.historySites = {}
if (data.about) {
delete data.about.history
delete data.about.newtab
}
}
}
if (data.downloads) {
const clearDownloads = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_DOWNLOADS) === true
if (clearDownloads) {
delete data.downloads
} else {
// Always at least delete downloaded items older than a week
const dateOffset = 7 * 24 * 60 * 60 * 1000
const lastWeek = new Date().getTime() - dateOffset
Object.keys(data.downloads).forEach((downloadId) => {
if (data.downloads[downloadId].startTime < lastWeek) {
delete data.downloads[downloadId]
} else {
const state = data.downloads[downloadId].state
if (state === downloadStates.IN_PROGRESS || state === downloadStates.PAUSED) {
data.downloads[downloadId].state = downloadStates.INTERRUPTED
}
}
})
}
}
if (data.menu) {
delete data.menu
}
try {
data = tabState.getPersistentState(data).toJS()
} catch (e) {
delete data.tabs
console.log('cleanAppData: error calling tabState.getPersistentState: ', e)
}
try {
data = windowState.getPersistentState(data).toJS()
} catch (e) {
delete data.windows
console.log('cleanAppData: error calling windowState.getPersistentState: ', e)
}
if (data.extensions) {
Object.keys(data.extensions).forEach((extensionId) => {
delete data.extensions[extensionId].tabs
})
}
return data
}
/**
* Cleans session data on shutdown if the prefs are on.
* @return a promise which resolve when the work is done.
*/
module.exports.cleanSessionDataOnShutdown = () => {
if (getSetting(settings.SHUTDOWN_CLEAR_ALL_SITE_COOKIES) === true) {
filtering.clearStorageData()
}
if (getSetting(settings.SHUTDOWN_CLEAR_CACHE) === true) {
filtering.clearCache()
}
if (getSetting(settings.SHUTDOWN_CLEAR_HISTORY) === true) {
filtering.clearHistory()
}
}
const safeGetVersion = (fieldName, getFieldVersion) => {
const versionField = {
name: fieldName,
version: undefined
}
try {
if (typeof getFieldVersion === 'function') {
versionField.version = getFieldVersion()
return versionField
}
console.log('ERROR getting value for field ' + fieldName + ' in sessionStore::setVersionInformation(): ', getFieldVersion, ' is not a function')
} catch (e) {
console.log('ERROR getting value for field ' + fieldName + ' in sessionStore::setVersionInformation(): ', e)
}
return versionField
}
/**
* version information (shown on about:brave)
*/
const setVersionInformation = (data) => {
const versionFields = [
['Brave', app.getVersion],
['rev', Channel.browserLaptopRev],
['Muon', () => { return process.versions['atom-shell'] }],
['libchromiumcontent', () => { return process.versions['chrome'] }],
['V8', () => { return process.versions.v8 }],
['Node.js', () => { return process.versions.node }],
['Update Channel', Channel.channel],
['OS Platform', () => platformUtil.formatOsPlatform(os.platform())],
['OS Release', os.release],
['OS Architecture', os.arch]
]
const versionInformation = {}
versionFields.forEach((field) => {
const versionField = safeGetVersion(field[0], field[1])
versionInformation[versionField.name] = versionField.version
})
data.about = data.about || {}
data.about.brave = {
versionInformation: versionInformation
}
return data
}
const sortBookmarkOrder = (bookmarkOrder) => {
const newOrder = {}
for (let key of Object.keys(bookmarkOrder)) {
let i = 0
const order = bookmarkOrder[key].sort((x, y) => {
if (x.order < y.order) {
return -1
} else if (x.order > y.order) {
return 1
} else {
return 0
}
}).map(item => {
item.order = i
i++
return item
})
newOrder[key] = order
}
return newOrder
}
module.exports.runPreMigrations = (data) => {
// autofill data migration
if (data.autofill) {
if (Array.isArray(data.autofill.addresses)) {
let addresses = exports.defaultAppState().autofill.addresses
data.autofill.addresses.forEach((guid) => {
addresses.guid.push(guid)
addresses.timestamp = new Date().getTime()
})
data.autofill.addresses = addresses
}
if (Array.isArray(data.autofill.creditCards)) {
let creditCards = exports.defaultAppState().autofill.creditCards
data.autofill.creditCards.forEach((guid) => {
creditCards.guid.push(guid)
creditCards.timestamp = new Date().getTime()
})
data.autofill.creditCards = creditCards
}
if (data.autofill.addresses.guid) {
let guids = []
data.autofill.addresses.guid.forEach((guid) => {
if (typeof guid === 'object') {
guids.push(guid['persist:default'])
} else {
guids.push(guid)
}
})
data.autofill.addresses.guid = guids
}
if (data.autofill.creditCards.guid) {
let guids = []
data.autofill.creditCards.guid.forEach((guid) => {
if (typeof guid === 'object') {
guids.push(guid['persist:default'])
} else {
guids.push(guid)
}
})
data.autofill.creditCards.guid = guids
}
}
// xml migration
if (data.settings) {
if (data.settings[settings.DEFAULT_SEARCH_ENGINE] === 'content/search/google.xml') {
data.settings[settings.DEFAULT_SEARCH_ENGINE] = 'Google'
}
if (data.settings[settings.DEFAULT_SEARCH_ENGINE] === 'content/search/duckduckgo.xml') {
data.settings[settings.DEFAULT_SEARCH_ENGINE] = 'DuckDuckGo'
}
}
if (data.sites) {
// pinned sites
if (!data.pinnedSites) {
data.pinnedSites = {}
for (let key of Object.keys(data.sites)) {
const site = data.sites[key]
if (site.tags && site.tags.includes('pinned')) {
delete site.tags
data.pinnedSites[key] = site
}
}
}
// default sites
let newTab = data.about.newtab
if (newTab) {
const ignoredSites = []
const pinnedSites = []
if (newTab.ignoredTopSites) {
for (let site of newTab.ignoredTopSites) {
if (site) {
ignoredSites.push(`${site.location}|0|0`)
}
}
data.about.newtab.ignoredTopSites = ignoredSites
}
if (newTab.pinnedTopSites) {
for (let site of newTab.pinnedTopSites) {
if (site) {
site.key = `${site.location}|0|0`
pinnedSites.push(site)
}
}
data.about.newtab.pinnedTopSites = pinnedSites
}
data.about.newtab.sites = []
}
// bookmark order
let bookmarkOrder = {}
// bookmark folders
if (!data.bookmarkFolders) {
data.bookmarkFolders = {}
for (let key of Object.keys(data.sites)) {
const oldFolder = data.sites[key]
if (oldFolder.tags && oldFolder.tags.includes(siteTags.BOOKMARK_FOLDER)) {
let folder = {}
key = key.toString()
if (oldFolder.customTitle) {
folder.title = oldFolder.customTitle
} else {
folder.title = oldFolder.title
}
if (oldFolder.parentFolderId == null) {
folder.parentFolderId = 0
} else {
folder.parentFolderId = oldFolder.parentFolderId
}
folder.folderId = oldFolder.folderId
folder.partitionNumber = oldFolder.partitionNumber
folder.objectId = oldFolder.objectId
folder.type = siteTags.BOOKMARK_FOLDER
folder.key = key
data.bookmarkFolders[key] = folder
// bookmark order
const id = folder.parentFolderId.toString()
if (!bookmarkOrder[id]) {
bookmarkOrder[id] = []
}
bookmarkOrder[id].push({
key: key,
order: oldFolder.order,
type: siteTags.BOOKMARK_FOLDER
})
}
}
}
// bookmarks
if (!data.bookmarks) {
data.bookmarks = {}
for (let key of Object.keys(data.sites)) {
const oldBookmark = data.sites[key]
if (oldBookmark.tags && oldBookmark.tags.includes(siteTags.BOOKMARK)) {
let bookmark = {}
if (oldBookmark.customTitle && oldBookmark.customTitle.length > 0) {
bookmark.title = oldBookmark.customTitle
} else {
bookmark.title = oldBookmark.title
}
if (oldBookmark.parentFolderId == null) {
bookmark.parentFolderId = 0
} else {
bookmark.parentFolderId = oldBookmark.parentFolderId
}
bookmark.location = oldBookmark.location
bookmark.partitionNumber = oldBookmark.partitionNumber
bookmark.objectId = oldBookmark.objectId
bookmark.favicon = oldBookmark.favicon
bookmark.themeColor = oldBookmark.themeColor
bookmark.type = siteTags.BOOKMARK
bookmark.key = key
data.bookmarks[key] = bookmark
// bookmark order
const id = bookmark.parentFolderId.toString()
if (!bookmarkOrder[id]) {
bookmarkOrder[id] = []
}
bookmarkOrder[id].push({
key: key,
order: oldBookmark.order,
type: siteTags.BOOKMARK
})
}
}
}
// Add cache to the state
if (!data.cache) {
data.cache = {}
data.cache.bookmarkLocation = data.locationSiteKeysCache
data.cache.bookmarkOrder = sortBookmarkOrder(bookmarkOrder)
}
// history
if (!data.historySites) {
data.historySites = {}
for (let key of Object.keys(data.sites)) {
const site = data.sites[key]
const newKey = historyUtil.getKey(makeImmutable(site))
if (site.lastAccessedTime || !site.tags || site.tags.length === 0) {
data.historySites[newKey] = site
}
}
}
delete data.sites
}
return data
}
module.exports.runPostMigrations = (data) => {
return data
}
module.exports.runImportDefaultSettings = (data) => {
// import default site settings list
if (!data.defaultSiteSettingsListImported) {
for (var i = 0; i < defaultSiteSettingsList.length; ++i) {
let setting = defaultSiteSettingsList[i]
if (!data.siteSettings[setting.pattern]) {
data.siteSettings[setting.pattern] = {}
}
let targetSetting = data.siteSettings[setting.pattern]
if (!targetSetting.hasOwnProperty[setting.name]) {
targetSetting[setting.name] = setting.value
}
}
data.defaultSiteSettingsListImported = true
}
return data
}
/**
* Loads the browser state from storage.
*
* @return a promise which resolves with the immutable browser state or
* rejects if the state cannot be loaded.
*/
module.exports.loadAppState = () => {
return new Promise((resolve, reject) => {
let data
try {
data = fs.readFileSync(getStoragePath())
} catch (e) {}
let loaded = false
try {
data = JSON.parse(data)
loaded = true
} catch (e) {
// Session state might be corrupted; let's backup this
// corrupted value for people to report into support.
module.exports.backupSession()
if (data) {
console.log('could not parse data: ', data, e)
}
data = exports.defaultAppState()
data = module.exports.runImportDefaultSettings(data)
}
if (loaded) {
data = module.exports.runPreMigrations(data)
// Clean app data here if it wasn't cleared on shutdown
if (data.cleanedOnShutdown !== true || data.lastAppVersion !== app.getVersion()) {
data = module.exports.cleanAppData(data, false)
}
data = Object.assign(module.exports.defaultAppState(), data)
data.cleanedOnShutdown = false
// Always recalculate the update status
if (data.updates) {
const updateStatus = data.updates.status
delete data.updates.status
// The process always restarts after an update so if the state
// indicates that a restart isn't wanted, close right away.
if (updateStatus === UpdateStatus.UPDATE_APPLYING_NO_RESTART) {
module.exports.saveAppState(data, true).then(() => {
// Exit immediately without doing the session store saving stuff
// since we want the same state saved except for the update status
app.exit(0)
})
return
}
}
data = module.exports.runPostMigrations(data)
data = module.exports.runImportDefaultSettings(data)
}
data = setVersionInformation(data)
locale.init(data.settings[settings.LANGUAGE]).then((locale) => {
app.setLocale(locale)
resolve(data)
})
})
}
/**
* Called when session is suspected for corruption; this will move it out of the way
*/
module.exports.backupSession = () => {
const src = getStoragePath()
const dest = getTempStoragePath('backup')
if (fs.existsSync(src)) {
try {
fs.copySync(src, dest)
console.log('An error occurred. For support purposes, file "' + src + '" has been copied to "' + dest + '".')
} catch (e) {
console.log('backupSession: error making copy of session file: ', e)
}
}
}
/**
* Obtains the default application level state
*/
module.exports.defaultAppState = () => {
return {
firstRunTimestamp: new Date().getTime(),
sync: {
devices: {},
lastFetchTimestamp: 0,
objectsById: {},
pendingRecords: {},
lastConfirmedRecordTimestamp: 0
},
cache: {
bookmarkLocation: undefined,
bookmarkOrder: {}
},
pinnedSites: {},
bookmarks: {},
bookmarkFolders: {},
historySites: {},
tabs: [],
windows: [],
extensions: {},
visits: [],
settings: {},
siteSettings: {},
passwords: [],
notifications: [],
temporarySiteSettings: {},
autofill: {
addresses: {
guid: [],
timestamp: 0
},
creditCards: {
guid: [],
timestamp: 0
}
},
menubar: {},
about: {
newtab: {
gridLayoutSize: 'small',
sites: [],
ignoredTopSites: [],
pinnedTopSites: pinnedTopSites
},
welcome: {
showOnLoad: !['test', 'development'].includes(process.env.NODE_ENV)
}
},
trackingProtection: {
count: 0
},
adblock: {
count: 0
},
httpsEverywhere: {
count: 0
},
defaultWindowParams: {},
searchDetail: null
}
}
/**
* Determines if a protocol is handled.
* app.on('ready') must have been fired before this is called.
*/
module.exports.isProtocolHandled = (protocol) => {
protocol = (protocol || '').split(':')[0]
return navigatableTypes.includes(`${protocol}:`) ||
electron.session.defaultSession.protocol.isNavigatorProtocolHandled(protocol)
}