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
1090 lines (991 loc) · 37.3 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
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* 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 path = require('path')
const electron = require('electron')
const os = require('os')
const assert = require('assert')
const Immutable = require('immutable')
const app = electron.app
const compareVersions = require('compare-versions')
const merge = require('deepmerge')
// 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 {defaultSiteSettingsList} = require('../js/data/siteSettingsList')
const filtering = require('./filtering')
const autofill = require('./autofill')
const {navigatableTypes} = require('../js/lib/appUrlUtil')
const {isDataUrl, parseFaviconDataUrl} = require('../js/lib/urlutil')
const Channel = require('./channel')
const BuildConfig = require('./buildConfig')
const {isImmutable, isMap, makeImmutable, deleteImmutablePaths} = 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 = (filename = sessionStorageName) => {
return path.join(app.getPath('userData'), filename)
}
/**
* Saves the specified immutable browser state to storage.
*
* @param {object} immutablePayload - Application immutable 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 = (immutablePayload, isShutdown) => {
assert(isImmutable(immutablePayload))
return new Promise((resolve, reject) => {
let startupModeSettingValue = getSetting(settings.STARTUP_MODE)
const savePerWindowState = startupModeSettingValue == null ||
startupModeSettingValue === 'lastTime'
// Don't persist private frames
if (immutablePayload.get('perWindowState')) {
if (savePerWindowState) {
immutablePayload.get('perWindowState').forEach((immutableWndPayload, i) => {
const frames = immutableWndPayload.get('frames').filter((frame) => !frame.get('isPrivate'))
immutableWndPayload = immutableWndPayload.set('frames', frames)
immutablePayload = immutablePayload.setIn(['perWindowState', i], immutableWndPayload)
})
} else {
// we still need to preserve window position/size info
immutablePayload.get('perWindowState').forEach((immutableWndPayload, i) => {
let windowInfo = Immutable.Map()
windowInfo = windowInfo.set('windowInfo', immutableWndPayload.get('windowInfo'))
immutablePayload = immutablePayload.setIn(['perWindowState', i], windowInfo)
})
}
}
try {
immutablePayload = module.exports.cleanAppData(immutablePayload, isShutdown)
immutablePayload = immutablePayload.set('cleanedOnShutdown', isShutdown)
} catch (e) {
immutablePayload = immutablePayload.set('cleanedOnShutdown', false)
}
immutablePayload = immutablePayload.set('lastAppVersion', app.getVersion())
if (isShutdown) {
module.exports.cleanSessionDataOnShutdown()
}
const storagePath = getStoragePath()
const json = JSON.stringify(immutablePayload)
muon.file.writeImportant(storagePath, json, (success) => {
if (success) {
resolve()
} else {
reject(new Error('Could not save app state to ' + getStoragePath()))
}
})
})
}
/**
* Cleans session data from unwanted values.
* @param immutablePerWindowData - Per window data in ImmutableJS format
* @return ImmutableJS cleaned window data
*/
module.exports.cleanPerWindowData = (immutablePerWindowData, isShutdown) => {
if (!immutablePerWindowData) {
immutablePerWindowData = Immutable.Map()
}
assert(isImmutable(immutablePerWindowData))
// delete the frame index because tabId is per-session
immutablePerWindowData = immutablePerWindowData.delete('framesInternal')
immutablePerWindowData = deleteImmutablePaths(immutablePerWindowData, [
// Hide the context menu when we restore.
'contextMenuDetail',
// Don't save preview frame since they are only related to hovering on a tab
'previewFrameKey',
// Don't save widevine panel detail
'widevinePanelDetail',
// Don't save preview tab pages
['ui', 'tabs', 'previewTabPageIndex'],
// Don't restore add/edit dialog
'bookmarkDetail',
// Don't restore bravery panel
'braveryPanelDetail',
// Don't restore drag data and clearBrowsingDataPanel's visibility
// This is no longer stored, we can remove this line eventually
['ui', 'mouseInTitlebar'],
['ui', 'mouseInFrame'],
['ui', 'dragging'],
['ui', 'isClearBrowsingDataPanelVisible']
])
if (!immutablePerWindowData.get('frames')) {
immutablePerWindowData = immutablePerWindowData.set('frames', Immutable.List())
}
let newKey = 0
let activeFrameKey = immutablePerWindowData.get('activeFrameKey')
// If adjustActive is set to true then activeFrameKey will be set to the new frame key.
// We re-use this function for both closedFrames and frames, and we only want to adjust the active for frames.
const cleanFrame = (immutableFrame, adjustActive) => {
newKey++
// Reset the ids back to sequential numbers
if (adjustActive &&
immutableFrame.get('key') === immutablePerWindowData.get('activeFrameKey')) {
activeFrameKey = newKey
} else {
// For now just set everything to unloaded unless it's the active frame
immutableFrame = immutableFrame.set('unloaded', true)
}
immutableFrame = immutableFrame.set('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
immutableFrame = immutableFrame.set('src', immutableFrame.get('pinnedLocation') || immutableFrame.get('location'))
// If a blob is present for the thumbnail, create the object URL
if (immutableFrame.get('thumbnailBlob')) {
try {
immutableFrame = immutableFrame.set('thumbnailUrl', window.URL.createObjectURL(immutableFrame.get('thumbnailBlob')))
} catch (e) {
immutableFrame = immutableFrame.delete('thumbnailUrl')
}
}
immutableFrame = deleteImmutablePaths(immutableFrame, [
// Delete lists of blocked sites
'trackingProtection',
'httpsEverywhere',
'adblock',
'noScript',
// clean up any legacy frame opening props
'openInForeground',
'disposition',
// Guest instance ID's are not valid after restarting.
// Electron won't know about them.
'guestInstanceId',
// Tab ids are per-session and should not be persisted
'tabId',
'openerTabId',
// Do not show the audio indicator until audio starts playing
'audioMuted',
'audioPlaybackActive',
// Let's not assume wknow anything about loading
'loading',
// Always re-determine the security data
'security',
// Value is only used for local storage
'isActive',
// Hide modal prompts.
'modalPromptDetail',
// Remove HTTP basic authentication requests.
'basicAuthDetail',
// Remove open search details
'searchDetail',
// Remove find in page details
['findDetail', 'numberOfMatches'],
['findDetail', 'activeMatchOrdinal'],
['findDetail', 'internalFindStatePresent'],
'findbarShown',
// Don't restore full screen state
'isFullScreen',
'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.
'parentFrameKey',
// Delete the active shortcut details
'activeShortcut',
'activeShortcutDetails'
])
if (immutableFrame.get('navbar') && immutableFrame.getIn(['navbar', 'urlbar'])) {
if (immutableFrame.getIn(['navbar', 'urlbar', 'suggestions'])) {
immutableFrame = immutableFrame.setIn(['navbar', 'urlbar', 'suggestions', 'selectedIndex'], null)
immutableFrame = immutableFrame.setIn(['navbar', 'urlbar', 'suggestions', 'suggestionList'], null)
}
immutableFrame = immutableFrame.deleteIn(['navbar', 'urlbar', 'searchDetail'])
}
return immutableFrame
}
const clearHistory = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_HISTORY) === true
if (clearHistory) {
immutablePerWindowData = immutablePerWindowData.set('closedFrames', Immutable.List())
}
// 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.
let closedFrames = immutablePerWindowData.get('closedFrames')
if (closedFrames) {
closedFrames = closedFrames.filter((frame) => frame)
immutablePerWindowData = immutablePerWindowData.set('closedFrames', closedFrames)
// clean each frame
immutablePerWindowData =
closedFrames.reduce((immutablePerWindowData, immutableFrame, index) => {
const cleanImmutableFrame = cleanFrame(immutableFrame, false)
return immutablePerWindowData.setIn(['closedFrames', index], cleanImmutableFrame)
}, immutablePerWindowData)
}
let frames = immutablePerWindowData.get('frames')
if (frames) {
// Don't restore pinned locations because they will be auto created by the app state change event
frames = frames.filter((frame) => frame && !frame.get('pinnedLocation'))
immutablePerWindowData = immutablePerWindowData.set('frames', frames)
// clean each frame
immutablePerWindowData =
frames.reduce((immutablePerWindowData, immutableFrame, index) => {
const cleanImmutableFrame = cleanFrame(immutableFrame, true)
return immutablePerWindowData.setIn(['frames', index], cleanImmutableFrame)
}, immutablePerWindowData)
if (activeFrameKey !== undefined) {
immutablePerWindowData = immutablePerWindowData.set('activeFrameKey', activeFrameKey)
}
}
return immutablePerWindowData
}
/**
* Cleans app data before it's written to disk.
* @param {Object} data - top-level app data in ImmutableJS format
* @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
* @return Immutable JS cleaned up data
*/
module.exports.cleanAppData = (immutableData, isShutdown) => {
assert(isImmutable(immutableData))
// Don't show notifications from the last session
immutableData = immutableData.set('notifications', Immutable.List())
// Delete temp site settings
immutableData = immutableData.set('temporarySiteSettings', Immutable.Map())
if (immutableData.getIn(['settings', settings.CHECK_DEFAULT_ON_STARTUP]) === true) {
// Delete defaultBrowserCheckComplete state since this is checked on startup
immutableData = immutableData.delete('defaultBrowserCheckComplete')
}
// Delete Recovery status on shut down
try {
immutableData = immutableData.deleteIn(['ui', 'about', 'preferences', 'recoverySucceeded'])
} catch (e) {}
const perWindowStateList = immutableData.get('perWindowState')
if (perWindowStateList) {
perWindowStateList.forEach((immutablePerWindowState, i) => {
const cleanedImmutablePerWindowState = module.exports.cleanPerWindowData(immutablePerWindowState, isShutdown)
immutableData = immutableData.setIn(['perWindowState', i], cleanedImmutablePerWindowState)
})
}
const clearAutocompleteData = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_AUTOCOMPLETE_DATA) === true
if (clearAutocompleteData) {
try {
autofill.clearAutocompleteData()
} catch (e) {
console.error('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()
immutableData = immutableData.set('autofill', Immutable.fromJS({
addresses: {
guid: [],
timestamp: date
},
creditCards: {
guid: [],
timestamp: date
}
}))
}
immutableData = immutableData.delete('dragData')
if (immutableData.get('sync')) {
// clear sync site cache
immutableData = immutableData.deleteIn(['sync', 'objectsById'], Immutable.Map())
}
const clearSiteSettings = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_SITE_SETTINGS) === true
if (clearSiteSettings) {
immutableData = immutableData.set('siteSettings', Immutable.Map())
}
// Delete expired Flash and NoScript allow-once approvals
let now = Date.now()
immutableData.get('siteSettings', Immutable.Map()).forEach((value, host) => {
let expireTime = value.get('flash')
if (typeof expireTime === 'number' && expireTime < now) {
immutableData = immutableData.deleteIn(['siteSettings', host, 'flash'])
}
let noScript = immutableData.getIn(['siteSettings', host, 'noScript'])
if (typeof noScript === 'number') {
immutableData = immutableData.deleteIn(['siteSettings', host, 'noScript'])
}
// Don't persist any noScript exceptions
immutableData = immutableData.deleteIn(['siteSettings', host, 'noScriptExceptions'])
// Don't write runInsecureContent to session
immutableData = immutableData.deleteIn(['siteSettings', host, 'runInsecureContent'])
// If the site setting is empty, delete it for privacy
if (Array.from(immutableData.getIn(['siteSettings', host]).keys()).length === 0) {
immutableData = immutableData.deleteIn(['siteSettings', host])
}
})
const clearHistory = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_HISTORY) === true
if (clearHistory) {
immutableData = immutableData.set('historySites', Immutable.Map())
immutableData = deleteImmutablePaths(immutableData, [
['about', 'history'],
['about', 'newtab']
])
}
if (immutableData.get('downloads')) {
const clearDownloads = isShutdown && getSetting(settings.SHUTDOWN_CLEAR_DOWNLOADS) === true
if (clearDownloads) {
immutableData = immutableData.delete('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
Array.from(immutableData.get('downloads').keys()).forEach((downloadId) => {
if (immutableData.getIn(['downloads', downloadId, 'startTime']) < lastWeek) {
immutableData = immutableData.deleteIn(['downloads', downloadId])
} else {
const state = immutableData.getIn(['downloads', downloadId, 'state'])
if (state === downloadStates.IN_PROGRESS || state === downloadStates.PAUSED) {
immutableData = immutableData.setIn(['downloads', downloadId, 'state'], downloadStates.INTERRUPTED)
}
}
})
}
}
immutableData = immutableData.delete('menu')
immutableData = immutableData.delete('pageData')
try {
immutableData = tabState.getPersistentState(immutableData)
} catch (e) {
console.error('cleanAppData: error calling tabState.getPersistentState: ', e)
immutableData = immutableData.set('tabs', Immutable.List())
}
try {
immutableData = windowState.getPersistentState(immutableData)
} catch (e) {
console.error('cleanAppData: error calling windowState.getPersistentState: ', e)
immutableData = immutableData.set('windows', Immutable.List())
}
if (immutableData.get('extensions')) {
Array.from(immutableData.get('extensions').keys()).forEach((extensionId) => {
immutableData = immutableData.deleteIn(['extensions', extensionId, 'tabs'])
})
}
// Ledger cleanup
if (immutableData.has('pageData')) {
immutableData = immutableData.delete('pageData')
}
if (immutableData.hasIn(['ledger', 'locations'])) {
immutableData = immutableData.deleteIn(['ledger', 'locations'])
}
// Remove windowState from perWindowState if there are no frames
// ex: if window only had a single private tab, let's remove it (they aren't saved)
if (perWindowStateList) {
perWindowStateList.forEach((immutablePerWindowState, i) => {
if (isMap(immutablePerWindowState) && immutablePerWindowState.has('frames')) {
if (!immutablePerWindowState.get('frames').size) {
immutableData = immutableData.deleteIn(['perWindowState', i])
}
}
})
}
try {
// Prune data: favicons by moving them to external files
const basePath = getStoragePath('ledger-favicons')
if (immutableData.get('createdFaviconDirectory') !== true) {
const fs = require('fs')
if (!fs.existsSync(basePath)) {
fs.mkdirSync(basePath)
}
immutableData = immutableData.set('createdFaviconDirectory', true)
}
immutableData = cleanFavicons(basePath, immutableData)
} catch (e) {
console.error('cleanAppData: error cleaning up data: urls', e)
}
return immutableData
}
/**
* 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 cleanFavicons = (basePath, immutableData) => {
const fs = require('fs')
const synopsisPaths = [
// TODO (nejc) - remove duplicate entries in synopsis and about/synopsis
['ledger', 'synopsis', 'publishers'],
['ledger', 'about', 'synopsis']
]
// Map of favicon content to location on disk to avoid saving dupes
const savedFavicons = {}
synopsisPaths.forEach((synopsisPath) => {
if (immutableData.getIn(synopsisPath)) {
immutableData.getIn(synopsisPath).forEach((value, index) => {
// Fix #11582
if (value && value.get && isDataUrl(value.get('faviconURL', ''))) {
const parsed = parseFaviconDataUrl(value.get('faviconURL'))
if (!parsed) {
immutableData = immutableData.setIn(
synopsisPath.concat([index, 'faviconURL']), '')
return
}
let faviconPath = savedFavicons[parsed.data]
if (!faviconPath) {
faviconPath = path.join(basePath,
typeof index === 'number'
? `${Date.now()}.${parsed.ext}`
: `${index.replace(/[^a-z0-9]/gi, '_')}.${parsed.ext}`
)
savedFavicons[parsed.data] = faviconPath
fs.writeFile(faviconPath, parsed.data, 'base64', (err) => {
if (err) {
console.error(`Error writing file: ${faviconPath} ${err}`)
}
})
}
immutableData = immutableData.setIn(
synopsisPath.concat([index, 'faviconURL']), `file://${faviconPath}`)
}
})
}
})
return immutableData
}
const safeGetVersion = (fieldName, getFieldVersion) => {
const versionField = {
name: fieldName,
version: undefined
}
try {
if (typeof getFieldVersion === 'function') {
versionField.version = getFieldVersion()
return versionField
}
console.error('ERROR getting value for field ' + fieldName + ' in sessionStore::setVersionInformation(): ', getFieldVersion, ' is not a function')
} catch (e) {
console.error('ERROR getting value for field ' + fieldName + ' in sessionStore::setVersionInformation(): ', e)
}
return versionField
}
/**
* version information (shown on about:brave)
*/
const setVersionInformation = (immutableData) => {
const versionFields = [
['Brave', app.getVersion],
['rev', BuildConfig.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.formattedChannel],
['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
})
if (!immutableData.get('about')) {
immutableData = immutableData.set('about', Immutable.Map())
}
immutableData = immutableData.setIn(['about', 'brave', 'versionInformation'], Immutable.fromJS(versionInformation))
return immutableData
}
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 && 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 && 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
}
}
if (data.settings) {
// xml migration
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'
}
// ledger payments migration. see PR #10164
// changes was introduced in 0.21.x.
// if legacy setting exist, make sure the new setting inherits the legacy value
if (data.settings[settings.AUTO_SUGGEST_SITES] != null) {
data.settings[settings.PAYMENTS_SITES_AUTO_SUGGEST] = data.settings[settings.AUTO_SUGGEST_SITES]
delete data.settings[settings.AUTO_SUGGEST_SITES]
}
if (data.settings[settings.MINIMUM_VISIT_TIME] != null) {
data.settings[settings.PAYMENTS_MINIMUM_VISIT_TIME] = data.settings[settings.MINIMUM_VISIT_TIME]
delete data.settings[settings.MINIMUM_VISIT_TIME]
}
if (data.settings[settings.MINIMUM_VISITS] != null) {
data.settings[settings.PAYMENTS_MINIMUM_VISITS] = data.settings[settings.MINIMUM_VISITS]
delete data.settings[settings.MINIMUM_VISITS]
}
if (data.settings[settings.HIDE_LOWER_SITES] != null) {
data.settings[settings.PAYMENTS_SITES_SHOW_LESS] = data.settings[settings.HIDE_LOWER_SITES]
delete data.settings[settings.HIDE_LOWER_SITES]
}
if (data.settings[settings.HIDE_EXCLUDED_SITES] != null) {
data.settings[settings.PAYMENTS_SITES_HIDE_EXCLUDED] = data.settings[settings.HIDE_EXCLUDED_SITES]
delete data.settings[settings.HIDE_EXCLUDED_SITES]
}
// PAYMENTS_NOTIFICATION_TRY_PAYMENTS_DISMISSED kept the same
// constant but has its value changed.
if (data.settings['payments.notificationTryPaymentsDismissed'] != null) {
data.settings[settings.PAYMENTS_NOTIFICATION_TRY_PAYMENTS_DISMISSED] = data.settings['payments.notificationTryPaymentsDismissed']
delete data.settings['payments.notificationTryPaymentsDismissed']
}
}
if (data.sites) {
// pinned sites
data.pinnedSites = {}
// get pre-site split pinned sites, in order
const sitesToPin = Object.keys(data.sites)
.map(key => data.sites[key])
.filter(site => site.tags && site.tags.includes('pinned'))
.sort((a, b) => a.order - b.order)
for (const site of sitesToPin) {
// convert to new format (split to its own pinnedSites key)
// reset 'order', same as pinnedSitesState
const pinnedSite = Object.assign({}, site, { order: Object.keys(data.pinnedSites).length })
delete pinnedSite.tags
// matches `getKey` from pinnedSitesUtil
const pinnedSiteKey = `${site.location}|${site.partitionNumber}`
data.pinnedSites[pinnedSiteKey] = pinnedSite
}
// 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
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
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
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
}
if (data.lastAppVersion) {
// Force WidevineCdm to be upgraded when last app version <= 0.18.25
let runWidevineCleanup = false
try { runWidevineCleanup = compareVersions(data.lastAppVersion, '0.18.25') < 1 } catch (e) {}
if (runWidevineCleanup) {
const fs = require('fs-extra')
const wvExtPath = path.join(app.getPath('userData'), 'Extensions', 'WidevineCdm')
fs.remove(wvExtPath, (err) => {
if (err) {
console.error(`Could not remove ${wvExtPath}`)
}
})
}
}
return data
}
// 0.19.x -> 0.20.x fingerprinting protection migration
const fingerprintingProtectionMigration = (immutableData) => {
if (immutableData.get('fingerprintingProtectionAll')) {
return immutableData
}
try {
const siteSettings = immutableData.get('siteSettings', Immutable.Map())
.map((setting) => {
const fpSetting = setting.get('fingerprintingProtection')
if (fpSetting === true) {
return setting.set('fingerprintingProtection', 'blockAllFingerprinting')
} else if (fpSetting === false) {
return setting.set('fingerprintingProtection', 'allowAllFingerprinting')
}
return setting
})
immutableData = immutableData.set('siteSettings', siteSettings)
const globalFpSetting = !!immutableData.getIn(['settings', 'privacy.block-canvas-fingerprinting'])
immutableData = immutableData.setIn(['fingerprintingProtectionAll', 'enabled'],
globalFpSetting).deleteIn(['settings', 'privacy.block-canvas-fingerprinting'])
} catch (e) {
console.error('fingerprinting protection migration failed', e)
}
return immutableData
}
module.exports.runPostMigrations = (immutableData) => {
immutableData = fingerprintingProtectionMigration(immutableData)
return immutableData
}
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) => {
const fs = require('fs')
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.error('could not parse data: ', data, e)
}
data = {}
}
data = merge(module.exports.defaultAppState(), data)
data = module.exports.runImportDefaultSettings(data)
if (loaded) {
data = module.exports.runPreMigrations(data)
}
let immutableData = makeImmutable(data)
if (loaded) {
// Clean app data here if it wasn't cleared on shutdown
if (immutableData.get('cleanedOnShutdown') !== true || immutableData.get('lastAppVersion') !== app.getVersion()) {
immutableData = module.exports.cleanAppData(immutableData, false)
}
immutableData = immutableData.set('cleanedOnShutdown', false)
// Always recalculate the update status
if (immutableData.get('updates')) {
const updateStatus = immutableData.getIn(['updates', 'status'])
immutableData = immutableData.deleteIn(['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(immutableData, 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
}
}
immutableData = module.exports.runPostMigrations(immutableData)
}
locale.init(immutableData.getIn(['settings', settings.LANGUAGE])).then((locale) => {
immutableData = setVersionInformation(immutableData)
app.setLocale(locale)
resolve(immutableData)
})
})
}
/**
* Called when session is suspected for corruption; this will move it out of the way
*/
module.exports.backupSession = () => {
const fs = require('fs-extra')
const src = getStoragePath()
const dest = getTempStoragePath('backup')
if (fs.existsSync(src)) {
try {
fs.copySync(src, dest)
console.error('An error occurred. For support purposes, file "' + src + '" has been copied to "' + dest + '".')
} catch (e) {
console.error('backupSession: error making copy of session file: ', e)
}
}
}
/**
* Obtains the default application level state
*/
module.exports.defaultAppState = () => {
const now = new Date().getTime()
return {
firstRunTimestamp: now,
sync: {
devices: {},
lastFetchTimestamp: 0,
objectsById: {},
pendingRecords: {},
lastConfirmedRecordTimestamp: 0