From 89e36e3976b062584f83c22d43f043cf464c3403 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 7 Jun 2019 10:51:01 -0700 Subject: [PATCH] Wrap all calls to localStorage/sessionStorage to avoid potential runtime errors --- shells/browser/shared/src/injectGlobalHook.js | 3 +- shells/browser/shared/src/main.js | 11 +++-- src/backend/agent.js | 42 ++++++++++--------- src/backend/renderer.js | 4 +- src/devtools/store.js | 10 ++--- src/devtools/views/hooks.js | 5 ++- src/storage.js | 41 ++++++++++++++++++ src/utils.js | 5 ++- 8 files changed, 88 insertions(+), 33 deletions(-) create mode 100644 src/storage.js diff --git a/shells/browser/shared/src/injectGlobalHook.js b/shells/browser/shared/src/injectGlobalHook.js index 7a1147a040..d870393602 100644 --- a/shells/browser/shared/src/injectGlobalHook.js +++ b/shells/browser/shared/src/injectGlobalHook.js @@ -3,6 +3,7 @@ import nullthrows from 'nullthrows'; import { installHook } from 'src/hook'; import { LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY } from 'src/constants'; +import { localStorageGetItem } from 'src/storage'; function injectCode(code) { const script = document.createElement('script'); @@ -63,7 +64,7 @@ window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set; `; // If we have just reloaded to profile, we need to inject the renderer interface before the app loads. -if (localStorage.getItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') { +if (localStorageGetItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') { const rendererURL = chrome.runtime.getURL('build/renderer.js'); let rendererCode; diff --git a/shells/browser/shared/src/main.js b/shells/browser/shared/src/main.js index 66f24a4d40..c9fb4c1df2 100644 --- a/shells/browser/shared/src/main.js +++ b/shells/browser/shared/src/main.js @@ -11,6 +11,11 @@ import { getBrowserTheme, } from './utils'; import { getSavedComponentFilters } from 'src/utils'; +import { + localStorageGetItem, + localStorageRemoveItem, + localStorageSetItem, +} from 'src/storage'; import DevTools from 'src/devtools/views/DevTools'; const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY = @@ -86,7 +91,7 @@ function createPanelIfReactLoaded() { }, }); bridge.addListener('reloadAppForProfiling', () => { - localStorage.setItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true'); + localStorageSetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true'); chrome.devtools.inspectedWindow.eval('window.location.reload();'); }); bridge.addListener('captureScreenshot', ({ commitIndex, rootID }) => { @@ -109,11 +114,11 @@ function createPanelIfReactLoaded() { let isProfiling = false; let supportsProfiling = false; if ( - localStorage.getItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true' + localStorageGetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true' ) { supportsProfiling = true; isProfiling = true; - localStorage.removeItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY); + localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY); } const browserName = getBrowserName(); diff --git a/src/backend/agent.js b/src/backend/agent.js index 5a7aa5740f..aa2c14716d 100644 --- a/src/backend/agent.js +++ b/src/backend/agent.js @@ -8,6 +8,14 @@ import { SESSION_STORAGE_LAST_SELECTION_KEY, __DEBUG__, } from '../constants'; +import { + localStorageGetItem, + localStorageRemoveItem, + localStorageSetItem, + sessionStorageGetItem, + sessionStorageRemoveItem, + sessionStorageSetItem, +} from 'src/storage'; import { hideOverlay, showOverlay } from './views/Highlighter'; import type { @@ -71,19 +79,17 @@ export default class Agent extends EventEmitter { constructor(bridge: Bridge) { super(); - if (localStorage.getItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') { + if (localStorageGetItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') { this._isProfiling = true; - localStorage.removeItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY); + localStorageRemoveItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY); } - if (typeof sessionStorage !== 'undefined') { - const persistedSelectionString = sessionStorage.getItem( - SESSION_STORAGE_LAST_SELECTION_KEY - ); - if (persistedSelectionString != null) { - this._persistedSelection = JSON.parse(persistedSelectionString); - } + const persistedSelectionString = sessionStorageGetItem( + SESSION_STORAGE_LAST_SELECTION_KEY + ); + if (persistedSelectionString != null) { + this._persistedSelection = JSON.parse(persistedSelectionString); } this._bridge = bridge; @@ -237,7 +243,7 @@ export default class Agent extends EventEmitter { }; reloadAndProfile = () => { - localStorage.setItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY, 'true'); + localStorageSetItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY, 'true'); // This code path should only be hit if the shell has explicitly told the Store that it supports profiling. // 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 { // This is why we need the defensive checks here. const renderer = this._rendererInterfaces[rendererID]; const path = renderer != null ? renderer.getPathForElement(id) : null; - if (typeof sessionStorage !== 'undefined') { - if (path !== null) { - sessionStorage.setItem( - SESSION_STORAGE_LAST_SELECTION_KEY, - JSON.stringify(({ rendererID, path }: PersistedSelection)) - ); - } else { - sessionStorage.removeItem(SESSION_STORAGE_LAST_SELECTION_KEY); - } + if (path !== null) { + sessionStorageSetItem( + SESSION_STORAGE_LAST_SELECTION_KEY, + JSON.stringify(({ rendererID, path }: PersistedSelection)) + ); + } else { + sessionStorageRemoveItem(SESSION_STORAGE_LAST_SELECTION_KEY); } }, 1000); } diff --git a/src/backend/renderer.js b/src/backend/renderer.js index ad099cf6df..935cb511c9 100644 --- a/src/backend/renderer.js +++ b/src/backend/renderer.js @@ -25,6 +25,7 @@ import { getUID, utfEncodeString, } from 'src/utils'; +import { localStorageGetItem } from 'src/storage'; import { cleanForBridge, copyWithSet, setInObject } from './utils'; import { __DEBUG__, @@ -2200,7 +2201,8 @@ export function attach( } // Automatically start profiling so that we don't miss timing info from initial "mount". - if (localStorage.getItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') { + // TODO This doens't seem right + if (localStorageGetItem(LOCAL_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') { startProfiling(); } diff --git a/src/devtools/store.js b/src/devtools/store.js index b1176baeed..6ad59c848a 100644 --- a/src/devtools/store.js +++ b/src/devtools/store.js @@ -15,6 +15,7 @@ import { separateDisplayNameAndHOCs, utfDecodeString, } from '../utils'; +import { localStorageGetItem, localStorageSetItem } from '../storage'; import { __DEBUG__ } from '../constants'; import { printStore } from 'src/__tests__/storeSerializer'; import ProfilerStore from './ProfilerStore'; @@ -110,7 +111,7 @@ export default class Store extends EventEmitter { // Default this setting to true unless otherwise specified. this._collapseNodesByDefault = - localStorage.getItem(LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY) !== + localStorageGetItem(LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY) !== 'false'; this._componentFilters = getSavedComponentFilters(); @@ -127,8 +128,7 @@ export default class Store extends EventEmitter { if (supportsCaptureScreenshots) { this._supportsCaptureScreenshots = true; this._captureScreenshots = - localStorage.getItem(LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY) === - 'true'; + localStorageGetItem(LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY) === 'true'; } if (supportsProfiling) { this._supportsProfiling = true; @@ -184,7 +184,7 @@ export default class Store extends EventEmitter { set captureScreenshots(value: boolean): void { this._captureScreenshots = value; - localStorage.setItem( + localStorageSetItem( LOCAL_STORAGE_CAPTURE_SCREENSHOTS_KEY, value ? 'true' : 'false' ); @@ -198,7 +198,7 @@ export default class Store extends EventEmitter { set collapseNodesByDefault(value: boolean): void { this._collapseNodesByDefault = value; - localStorage.setItem( + localStorageSetItem( LOCAL_STORAGE_COLLAPSE_ROOTS_BY_DEFAULT_KEY, value ? 'true' : 'false' ); diff --git a/src/devtools/views/hooks.js b/src/devtools/views/hooks.js index d742ff9fdd..ae963047a4 100644 --- a/src/devtools/views/hooks.js +++ b/src/devtools/views/hooks.js @@ -2,6 +2,7 @@ import throttle from 'lodash.throttle'; import { useCallback, useEffect, useLayoutEffect, useState } from 'react'; +import { localStorageGetItem, localStorageSetItem } from 'src/storage'; export function useIsOverflowing( containerRef: { current: HTMLDivElement | null }, @@ -42,7 +43,7 @@ export function useLocalStorage( ): [T, (value: T | (() => T)) => void] { const getValueFromLocalStorage = useCallback(() => { try { - const item = window.localStorage.getItem(key); + const item = localStorageGetItem(key); if (item != null) { return JSON.parse(item); } @@ -64,7 +65,7 @@ export function useLocalStorage( const valueToStore = value instanceof Function ? (value: any)(storedValue) : value; setStoredValue(valueToStore); - window.localStorage.setItem(key, JSON.stringify(valueToStore)); + localStorageSetItem(key, JSON.stringify(valueToStore)); } catch (error) { console.log(error); } diff --git a/src/storage.js b/src/storage.js new file mode 100644 index 0000000000..888009c76e --- /dev/null +++ b/src/storage.js @@ -0,0 +1,41 @@ +// @flow + +export function localStorageGetItem(key: string): any { + try { + return localStorage.getItem(key); + } catch (error) { + return null; + } +} + +export function localStorageRemoveItem(key: string): void { + try { + localStorage.removeItem(key); + } catch (error) {} +} + +export function localStorageSetItem(key: string, value: any): void { + try { + return localStorage.setItem(key, value); + } catch (error) {} +} + +export function sessionStorageGetItem(key: string): any { + try { + return sessionStorage.getItem(key); + } catch (error) { + return null; + } +} + +export function sessionStorageRemoveItem(key: string): void { + try { + sessionStorage.removeItem(key); + } catch (error) {} +} + +export function sessionStorageSetItem(key: string, value: any): void { + try { + return sessionStorage.setItem(key, value); + } catch (error) {} +} diff --git a/src/utils.js b/src/utils.js index 6e87a97d4f..7c7a711f0b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -9,6 +9,7 @@ import { ElementTypeFunction, ElementTypeMemo, } from 'src/types'; +import { localStorageGetItem, localStorageSetItem } from './storage'; import type { ComponentFilter, ElementType } from './types'; @@ -82,7 +83,7 @@ export function getDefaultComponentFilters(): Array { export function getSavedComponentFilters(): Array { try { - const raw = localStorage.getItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY); + const raw = localStorageGetItem(LOCAL_STORAGE_FILTER_PREFERENCES_KEY); if (raw != null) { return JSON.parse(raw); } @@ -93,7 +94,7 @@ export function getSavedComponentFilters(): Array { export function saveComponentFilters( componentFilters: Array ): void { - localStorage.setItem( + localStorageSetItem( LOCAL_STORAGE_FILTER_PREFERENCES_KEY, JSON.stringify(componentFilters) );