Wrap all calls to localStorage/sessionStorage to avoid potential runtime errors
Brian Vaughn committed
Jun 7, 2019 at 10:51 UTC
89e36e3976b062584f83c22d43f043cf464c3403
8 files changed
+88
-33
shells/browser/shared/src/injectGlobalHook.js
+2
-1
@@ -3,6 +3,7 @@
3
import nullthrows from 'nullthrows';
4
import { installHook } from 'src/hook';
5
import { LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY } from 'src/constants';
6
+import { localStorageGetItem } from 'src/storage';
7
8
function injectCode(code) {
9
const script = document.createElement('script');
@@ -63,7 +64,7 @@ window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set;
64
`;
65
66
// If we have just reloaded to profile, we need to inject the renderer interface before the app loads.
66
-if (localStorage.getItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
67
+if (localStorageGetItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
68
const rendererURL = chrome.runtime.getURL('build/renderer.js');
69
let rendererCode;
70
shells/browser/shared/src/main.js
+8
-3
@@ -11,6 +11,11 @@ import {
11
getBrowserTheme,
12
} from './utils';
13
import { getSavedComponentFilters } from 'src/utils';
14
+import {
15
+ localStorageGetItem,
16
+ localStorageRemoveItem,
17
+ localStorageSetItem,
18
+} from 'src/storage';
19
import DevTools from 'src/devtools/views/DevTools';
20
21
const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY =
@@ -86,7 +91,7 @@ function createPanelIfReactLoaded() {
91
},
92
});
93
bridge.addListener('reloadAppForProfiling', () => {
89
- localStorage.setItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true');
94
+ localStorageSetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true');
95
chrome.devtools.inspectedWindow.eval('window.location.reload();');
96
});
97
bridge.addListener('captureScreenshot', ({ commitIndex, rootID }) => {
@@ -109,11 +114,11 @@ function createPanelIfReactLoaded() {
114
let isProfiling = false;
115
let supportsProfiling = false;
116
if (
112
- localStorage.getItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true'
117
+ localStorageGetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true'
118
) {
119
supportsProfiling = true;
120
isProfiling = true;
116
- localStorage.removeItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY);
121
+ localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY);
122
}
123
124
const browserName = getBrowserName();
src/backend/agent.js
+23
-19
@@ -8,6 +8,14 @@ import {
8
SESSION_STORAGE_LAST_SELECTION_KEY,
9
__DEBUG__,
10
} from '../constants';
11
+import {
12
+ localStorageGetItem,
13
+ localStorageRemoveItem,
14
+ localStorageSetItem,
15
+ sessionStorageGetItem,
16
+ sessionStorageRemoveItem,
17
+ sessionStorageSetItem,
18
+} from 'src/storage';
19
import { hideOverlay, showOverlay } from './views/Highlighter';
20
21
import type {
@@ -71,19 +79,17 @@ export default class Agent extends EventEmitter {
79
constructor(bridge: Bridge) {
80
super();
81
74
- if (localStorage.getItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
82
+ if (localStorageGetItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
83
this._isProfiling = true;
84
77
- localStorage.removeItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY);
85
+ localStorageRemoveItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY);
86
}
87
80
- if (typeof sessionStorage !== 'undefined') {
81
- const persistedSelectionString = sessionStorage.getItem(
82
- SESSION_STORAGE_LAST_SELECTION_KEY
83
- );
84
- if (persistedSelectionString != null) {
85
- this._persistedSelection = JSON.parse(persistedSelectionString);
86
- }
88
+ const persistedSelectionString = sessionStorageGetItem(
89
+ SESSION_STORAGE_LAST_SELECTION_KEY
90
+ );
91
+ if (persistedSelectionString != null) {
92
+ this._persistedSelection = JSON.parse(persistedSelectionString);
93
}
94
95
this._bridge = bridge;
@@ -237,7 +243,7 @@ export default class Agent extends EventEmitter {
243
};
244
245
reloadAndProfile = () => {
240
- localStorage.setItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');
246
+ localStorageSetItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY, 'true');
247
248
// This code path should only be hit if the shell has explicitly told the Store that it supports profiling.
249
// In that case, the shell must also listen for this specific message to know when it needs to reload the app.
@@ -547,15 +553,13 @@ export default class Agent extends EventEmitter {
553
// This is why we need the defensive checks here.
554
const renderer = this._rendererInterfaces[rendererID];
555
const path = renderer != null ? renderer.getPathForElement(id) : null;
550
- if (typeof sessionStorage !== 'undefined') {
551
- if (path !== null) {
552
- sessionStorage.setItem(
553
- SESSION_STORAGE_LAST_SELECTION_KEY,
554
- JSON.stringify(({ rendererID, path }: PersistedSelection))
555
- );
556
- } else {
557
- sessionStorage.removeItem(SESSION_STORAGE_LAST_SELECTION_KEY);
558
- }
556
+ if (path !== null) {
557
+ sessionStorageSetItem(
558
+ SESSION_STORAGE_LAST_SELECTION_KEY,
559
+ JSON.stringify(({ rendererID, path }: PersistedSelection))
560
+ );
561
+ } else {
562
+ sessionStorageRemoveItem(SESSION_STORAGE_LAST_SELECTION_KEY);
563
}
564
}, 1000);
565
}
src/backend/renderer.js
+3
-1
@@ -25,6 +25,7 @@ import {
25
getUID,
26
utfEncodeString,
27
} from 'src/utils';
28
+import { localStorageGetItem } from 'src/storage';
29
import { cleanForBridge, copyWithSet, setInObject } from './utils';
30
import {
31
__DEBUG__,
@@ -2200,7 +2201,8 @@ export function attach(
2201
}
2202
2203
// Automatically start profiling so that we don't miss timing info from initial "mount".
2203
- if (localStorage.getItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
2204
+ // TODO This doens't seem right
2205
+ if (localStorageGetItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
2206
startProfiling();
2207
}
2208
src/devtools/store.js
+5
-5
@@ -15,6 +15,7 @@ import {
15
separateDisplayNameAndHOCs,
16
utfDecodeString,
17
} from '../utils';
18
+import { localStorageGetItem, localStorageSetItem } from '../storage';
19
import { __DEBUG__ } from '../constants';
20
import { printStore } from 'src/__tests__/storeSerializer';
21
import ProfilerStore from './ProfilerStore';
@@ -110,7 +111,7 @@ export default class Store extends EventEmitter {
111
112
// Default this setting to true unless otherwise specified.
113
this._collapseNodesByDefault =
113
- localStorage.getItem(LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY) !==
114
+ localStorageGetItem(LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY) !==
115
'false';
116
117
this._componentFilters = getSavedComponentFilters();
@@ -127,8 +128,7 @@ export default class Store extends EventEmitter {
128
if (supportsCaptureScreenshots) {
129
this._supportsCaptureScreenshots = true;
130
this._captureScreenshots =
130
- localStorage.getItem(LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY) ===
131
- 'true';
131
+ localStorageGetItem(LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY) === 'true';
132
}
133
if (supportsProfiling) {
134
this._supportsProfiling = true;
@@ -184,7 +184,7 @@ export default class Store extends EventEmitter {
184
set captureScreenshots(value: boolean): void {
185
this._captureScreenshots = value;
186
187
- localStorage.setItem(
187
+ localStorageSetItem(
188
LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY,
189
value ? 'true' : 'false'
190
);
@@ -198,7 +198,7 @@ export default class Store extends EventEmitter {
198
set collapseNodesByDefault(value: boolean): void {
199
this._collapseNodesByDefault = value;
200
201
- localStorage.setItem(
201
+ localStorageSetItem(
202
LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY,
203
value ? 'true' : 'false'
204
);
src/devtools/views/hooks.js
+3
-2
@@ -2,6 +2,7 @@
2
3
import throttle from 'lodash.throttle';
4
import { useCallback, useEffect, useLayoutEffect, useState } from 'react';
5
+import { localStorageGetItem, localStorageSetItem } from 'src/storage';
6
7
export function useIsOverflowing(
8
containerRef: { current: HTMLDivElement | null },
@@ -42,7 +43,7 @@ export function useLocalStorage<T>(
43
): [T, (value: T | (() => T)) => void] {
44
const getValueFromLocalStorage = useCallback(() => {
45
try {
45
- const item = window.localStorage.getItem(key);
46
+ const item = localStorageGetItem(key);
47
if (item != null) {
48
return JSON.parse(item);
49
}
@@ -64,7 +65,7 @@ export function useLocalStorage<T>(
65
const valueToStore =
66
value instanceof Function ? (value: any)(storedValue) : value;
67
setStoredValue(valueToStore);
67
- window.localStorage.setItem(key, JSON.stringify(valueToStore));
68
+ localStorageSetItem(key, JSON.stringify(valueToStore));
69
} catch (error) {
70
console.log(error);
71
}
src/storage.js
new
+41
@@ -0,0 +1,41 @@
1
+// @flow
2
+
3
+export function localStorageGetItem(key: string): any {
4
+ try {
5
+ return localStorage.getItem(key);
6
+ } catch (error) {
7
+ return null;
8
+ }
9
+}
10
+
11
+export function localStorageRemoveItem(key: string): void {
12
+ try {
13
+ localStorage.removeItem(key);
14
+ } catch (error) {}
15
+}
16
+
17
+export function localStorageSetItem(key: string, value: any): void {
18
+ try {
19
+ return localStorage.setItem(key, value);
20
+ } catch (error) {}
21
+}
22
+
23
+export function sessionStorageGetItem(key: string): any {
24
+ try {
25
+ return sessionStorage.getItem(key);
26
+ } catch (error) {
27
+ return null;
28
+ }
29
+}
30
+
31
+export function sessionStorageRemoveItem(key: string): void {
32
+ try {
33
+ sessionStorage.removeItem(key);
34
+ } catch (error) {}
35
+}
36
+
37
+export function sessionStorageSetItem(key: string, value: any): void {
38
+ try {
39
+ return sessionStorage.setItem(key, value);
40
+ } catch (error) {}
41
+}
src/utils.js
+3
-2
@@ -9,6 +9,7 @@ import {
9
ElementTypeFunction,
10
ElementTypeMemo,
11
} from 'src/types';
12
+import { localStorageGetItem, localStorageSetItem } from './storage';
13
14
import type { ComponentFilter, ElementType } from './types';
15
@@ -82,7 +83,7 @@ export function getDefaultComponentFilters(): Array<ComponentFilter> {
83
84
export function getSavedComponentFilters(): Array<ComponentFilter> {
85
try {
85
- const raw = localStorage.getItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
86
+ const raw = localStorageGetItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY);
87
if (raw != null) {
88
return JSON.parse(raw);
89
}
@@ -93,7 +94,7 @@ export function getSavedComponentFilters(): Array<ComponentFilter> {
94
export function saveComponentFilters(
95
componentFilters: Array<ComponentFilter>
96
): void {
96
- localStorage.setItem(
97
+ localStorageSetItem(
98
LOCAL_STORAGE_FILTER_PREFERENCES_KEY,
99
JSON.stringify(componentFilters)
100
);