navigation-stack 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +603 -163
- package/data-storage/package.json +6 -0
- package/karma.conf.cjs +21 -4
- package/lib/cjs/NavigationStack.js +73 -0
- package/lib/cjs/data-storage/DataStorage.js +71 -0
- package/lib/cjs/data-storage/LocationDataStorage.js +29 -0
- package/lib/cjs/data-storage/index.js +9 -0
- package/lib/cjs/environment/InMemoryEnvironment.js +15 -0
- package/lib/cjs/environment/WebBrowserEnvironment.js +15 -0
- package/lib/cjs/environment/data-storage/InMemoryDataStorage.js +27 -0
- package/lib/cjs/environment/data-storage/WebBrowserDataStorage.js +21 -0
- package/lib/cjs/environment/scroll-position/InMemoryScrollPosition.js +44 -0
- package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +60 -0
- package/lib/cjs/getLocationFromInternalLocation.js +14 -0
- package/lib/cjs/index.js +20 -16
- package/lib/cjs/navigationBlockers.js +25 -23
- package/lib/cjs/{normalizeInputLocation.js → parseInputLocation.js} +25 -9
- package/lib/cjs/{ActionTypes.js → redux/ActionTypes.js} +1 -1
- package/lib/cjs/redux/ActionTypesInternal.js +8 -0
- package/lib/cjs/{Actions.js → redux/Actions.js} +5 -4
- package/lib/cjs/redux/createMiddlewares.js +60 -0
- package/lib/cjs/redux/index.js +13 -0
- package/lib/cjs/redux/internalLocationReducer.js +14 -0
- package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +32 -0
- package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +113 -0
- package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
- package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +30 -0
- package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +73 -0
- package/lib/cjs/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +11 -8
- package/lib/cjs/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +6 -4
- package/lib/cjs/redux/middleware/updateLocationMiddleware.js +34 -0
- package/lib/cjs/scroll-position/PageScrollPositionSetter.js +97 -0
- package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +130 -0
- package/lib/cjs/scroll-position/ScrollPositionRestoration.js +383 -0
- package/lib/cjs/scroll-position/ScrollPositionSaver.js +81 -0
- package/lib/cjs/scroll-position/ScrollPositionSetter.js +16 -0
- package/lib/cjs/scroll-position/constants.js +5 -0
- package/lib/cjs/scroll-position/index.js +7 -0
- package/lib/cjs/scroll-position/scheduleNextTick.js +11 -0
- package/lib/cjs/session/InMemorySession.js +22 -0
- package/lib/cjs/session/ServerSideRenderSession.js +17 -0
- package/lib/cjs/session/Session.js +196 -0
- package/lib/cjs/session/WebBrowserSession.js +20 -0
- package/lib/cjs/session/key/createSessionKey.js +23 -0
- package/lib/cjs/session/lifecycle/InMemorySessionLifecycle.js +19 -0
- package/lib/cjs/session/lifecycle/WebBrowserSessionLifecycle.js +128 -0
- package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycle.js +269 -0
- package/lib/cjs/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +8 -0
- package/lib/cjs/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +33 -0
- package/lib/cjs/session/navigation/InMemoryNavigation.js +104 -0
- package/lib/cjs/session/navigation/ServerSideNavigation.js +61 -0
- package/lib/cjs/session/navigation/WebBrowserNavigation.js +221 -0
- package/lib/cjs/session/navigation/error/NavigationOutOfBoundsError.js +12 -0
- package/lib/cjs/session/navigation/error/ServerSideNavigationError.js +21 -0
- package/lib/cjs/session/navigation/operation/operations.js +11 -0
- package/lib/cjs/session/subscription/Subscription.js +81 -0
- package/lib/data-storage/index.d.ts +35 -0
- package/lib/esm/NavigationStack.js +66 -0
- package/lib/esm/data-storage/DataStorage.js +65 -0
- package/lib/esm/data-storage/LocationDataStorage.js +22 -0
- package/lib/esm/data-storage/index.js +2 -0
- package/lib/esm/environment/InMemoryEnvironment.js +8 -0
- package/lib/esm/environment/WebBrowserEnvironment.js +8 -0
- package/lib/esm/environment/data-storage/InMemoryDataStorage.js +21 -0
- package/lib/esm/environment/data-storage/WebBrowserDataStorage.js +15 -0
- package/lib/esm/environment/scroll-position/InMemoryScrollPosition.js +38 -0
- package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +54 -0
- package/lib/esm/getLocationFromInternalLocation.js +9 -0
- package/lib/esm/index.js +10 -8
- package/lib/esm/navigationBlockers.js +25 -23
- package/lib/esm/{normalizeInputLocation.js → parseInputLocation.js} +24 -8
- package/lib/esm/{ActionTypes.js → redux/ActionTypes.js} +1 -1
- package/lib/esm/redux/ActionTypesInternal.js +3 -0
- package/lib/esm/{Actions.js → redux/Actions.js} +5 -4
- package/lib/esm/redux/createMiddlewares.js +54 -0
- package/lib/esm/redux/index.js +4 -0
- package/lib/esm/redux/internalLocationReducer.js +8 -0
- package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
- package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +108 -0
- package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +88 -0
- package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +25 -0
- package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +68 -0
- package/lib/esm/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -7
- package/lib/esm/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
- package/lib/esm/redux/middleware/updateLocationMiddleware.js +28 -0
- package/lib/esm/scroll-position/PageScrollPositionSetter.js +91 -0
- package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +123 -0
- package/lib/esm/scroll-position/ScrollPositionRestoration.js +376 -0
- package/lib/esm/scroll-position/ScrollPositionSaver.js +74 -0
- package/lib/esm/scroll-position/ScrollPositionSetter.js +10 -0
- package/lib/esm/scroll-position/constants.js +1 -0
- package/lib/esm/scroll-position/index.js +1 -0
- package/lib/esm/scroll-position/scheduleNextTick.js +6 -0
- package/lib/esm/session/InMemorySession.js +15 -0
- package/lib/esm/session/ServerSideRenderSession.js +11 -0
- package/lib/esm/session/Session.js +189 -0
- package/lib/esm/session/WebBrowserSession.js +13 -0
- package/lib/esm/session/key/createSessionKey.js +18 -0
- package/lib/esm/session/lifecycle/InMemorySessionLifecycle.js +13 -0
- package/lib/esm/session/lifecycle/WebBrowserSessionLifecycle.js +120 -0
- package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycle.js +263 -0
- package/lib/esm/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +2 -0
- package/lib/esm/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +30 -0
- package/lib/esm/session/navigation/InMemoryNavigation.js +97 -0
- package/lib/esm/session/navigation/ServerSideNavigation.js +54 -0
- package/lib/esm/session/navigation/WebBrowserNavigation.js +213 -0
- package/lib/esm/session/navigation/error/NavigationOutOfBoundsError.js +6 -0
- package/lib/esm/session/navigation/error/ServerSideNavigationError.js +14 -0
- package/lib/esm/session/navigation/operation/operations.js +6 -0
- package/lib/esm/session/subscription/Subscription.js +75 -0
- package/lib/index.d.ts +178 -157
- package/lib/redux/index.d.ts +90 -0
- package/lib/scroll-position/index.d.ts +107 -0
- package/package.json +9 -5
- package/redux/package.json +6 -0
- package/scroll-position/package.json +6 -0
- package/src/NavigationStack.js +84 -0
- package/src/data-storage/DataStorage.js +69 -0
- package/src/data-storage/LocationDataStorage.js +23 -0
- package/src/data-storage/index.js +2 -0
- package/src/environment/InMemoryEnvironment.js +9 -0
- package/src/environment/WebBrowserEnvironment.js +9 -0
- package/src/environment/data-storage/InMemoryDataStorage.js +23 -0
- package/src/environment/data-storage/WebBrowserDataStorage.js +17 -0
- package/src/environment/scroll-position/InMemoryScrollPosition.js +45 -0
- package/src/environment/scroll-position/WebBrowserScrollPosition.js +72 -0
- package/src/getLocationFromInternalLocation.js +7 -0
- package/src/index.js +10 -8
- package/src/navigationBlockers.js +28 -27
- package/src/{normalizeInputLocation.js → parseInputLocation.js} +23 -8
- package/src/{ActionTypes.js → redux/ActionTypes.js} +1 -1
- package/src/redux/ActionTypesInternal.js +3 -0
- package/src/{Actions.js → redux/Actions.js} +4 -3
- package/src/redux/createMiddlewares.js +65 -0
- package/src/redux/index.js +4 -0
- package/src/redux/internalLocationReducer.js +9 -0
- package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +27 -0
- package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +119 -0
- package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +94 -0
- package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +26 -0
- package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +72 -0
- package/src/{middleware/navigationActionMiddleware.js → redux/middleware/navigationOperationMiddleware.js} +10 -3
- package/src/{middleware/normalizeInputLocationMiddleware.js → redux/middleware/parseInputLocationMiddleware.js} +5 -3
- package/src/redux/middleware/updateLocationMiddleware.js +28 -0
- package/src/scroll-position/PageScrollPositionSetter.js +110 -0
- package/src/scroll-position/ScrollPositionAutoSaver.js +151 -0
- package/src/scroll-position/ScrollPositionRestoration.js +506 -0
- package/src/scroll-position/ScrollPositionSaver.js +100 -0
- package/src/scroll-position/ScrollPositionSetter.js +16 -0
- package/src/scroll-position/constants.js +1 -0
- package/src/scroll-position/index.js +1 -0
- package/src/scroll-position/scheduleNextTick.js +6 -0
- package/src/session/InMemorySession.js +13 -0
- package/src/session/ServerSideRenderSession.js +9 -0
- package/src/session/Session.js +216 -0
- package/src/session/WebBrowserSession.js +13 -0
- package/src/session/key/createSessionKey.js +18 -0
- package/src/session/lifecycle/InMemorySessionLifecycle.js +13 -0
- package/src/session/lifecycle/WebBrowserSessionLifecycle.js +126 -0
- package/src/session/lifecycle/page-lifecycle/PageLifecycle.js +291 -0
- package/src/session/lifecycle/page-lifecycle/PageLifecycleInstance.js +3 -0
- package/src/session/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +32 -0
- package/src/session/navigation/InMemoryNavigation.js +78 -0
- package/src/session/navigation/ServerSideNavigation.js +43 -0
- package/src/session/navigation/WebBrowserNavigation.js +224 -0
- package/src/session/navigation/error/NavigationOutOfBoundsError.js +7 -0
- package/src/session/navigation/error/ServerSideNavigationError.js +18 -0
- package/src/session/navigation/operation/operations.js +6 -0
- package/src/session/subscription/Subscription.js +76 -0
- package/test/NavigationStack.test.js +296 -0
- package/test/{LocationDataStorage.test.js → data-storage/LocationDataStorage.test.js} +3 -3
- package/test/data-storage/index.test.js +8 -0
- package/test/index.js +12 -0
- package/test/index.test.js +8 -7
- package/test/{helpers.js → middlewareTestUtil.js} +9 -12
- package/test/{normalizeInputLocation.test.js → parseInputLocationMiddleware.test.js} +9 -9
- package/test/{Action.test.js → redux/Action.test.js} +7 -6
- package/test/{ActionTypes.test.js → redux/ActionTypes.test.js} +2 -2
- package/test/redux/createMiddlewares.test.js +96 -0
- package/test/redux/index.test.js +10 -0
- package/test/{locationReducer.test.js → redux/locationReducer.test.js} +4 -7
- package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +40 -0
- package/test/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js +264 -0
- package/test/redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js +312 -0
- package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +51 -0
- package/test/{middleware/navigationActionMiddleware.test.js → redux/middleware/navigationOperationMiddleware.test.js} +16 -12
- package/test/{middleware/normalizeInputLocationMiddleware.test.js → redux/middleware/parseInputLocationMiddleware.test.js} +4 -4
- package/test/scroll-position/ScrollPositionRestoration.test.js +418 -0
- package/test/scroll-position/addScrollableContainer.js +36 -0
- package/test/scroll-position/addScrollableContainerWithHyperlink.js +50 -0
- package/test/scroll-position/createApp.js +112 -0
- package/test/scroll-position/delay.js +9 -0
- package/test/scroll-position/mockPageLifecycle.js +17 -0
- package/test/scroll-position/runApp.js +24 -0
- package/test/scroll-position/withScrollableContainerAtIndexPage.js +62 -0
- package/test/session/InMemorySession.test.js +348 -0
- package/test/session/ServerSession.test.js +17 -9
- package/test/session/WebBrowserSession.test.js +265 -0
- package/test/testUtil.js +3 -0
- package/types/data-storage/index.d.ts +35 -0
- package/types/index.d.ts +178 -157
- package/types/redux/index.d.ts +90 -0
- package/types/scroll-position/index.d.ts +107 -0
- package/types/tsconfig.json +1 -1
- package/lib/cjs/LocationDataStorage.js +0 -61
- package/lib/cjs/addBeforeLocationChangeListener.js +0 -7
- package/lib/cjs/beforeLocationChangeListeners.js +0 -51
- package/lib/cjs/createMiddlewares.js +0 -47
- package/lib/cjs/middleware/createBasePathMiddleware.js +0 -24
- package/lib/cjs/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -39
- package/lib/cjs/middleware/createLocationMiddleware.js +0 -56
- package/lib/cjs/middleware/createNavigationBlockerMiddleware.js +0 -161
- package/lib/cjs/middleware/createTransformLocationMiddleware.js +0 -38
- package/lib/cjs/onlyAllowedOnClientSide.js +0 -10
- package/lib/cjs/session/BrowserSession.js +0 -235
- package/lib/cjs/session/MemorySession.js +0 -223
- package/lib/cjs/session/ServerSession.js +0 -65
- package/lib/esm/LocationDataStorage.js +0 -54
- package/lib/esm/addBeforeLocationChangeListener.js +0 -2
- package/lib/esm/beforeLocationChangeListeners.js +0 -44
- package/lib/esm/createMiddlewares.js +0 -41
- package/lib/esm/middleware/createBasePathMiddleware.js +0 -19
- package/lib/esm/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -34
- package/lib/esm/middleware/createLocationMiddleware.js +0 -50
- package/lib/esm/middleware/createNavigationBlockerMiddleware.js +0 -156
- package/lib/esm/middleware/createTransformLocationMiddleware.js +0 -33
- package/lib/esm/onlyAllowedOnClientSide.js +0 -5
- package/lib/esm/session/BrowserSession.js +0 -229
- package/lib/esm/session/MemorySession.js +0 -217
- package/lib/esm/session/ServerSession.js +0 -58
- package/src/LocationDataStorage.js +0 -60
- package/src/addBeforeLocationChangeListener.js +0 -2
- package/src/beforeLocationChangeListeners.js +0 -54
- package/src/createMiddlewares.js +0 -45
- package/src/middleware/createBasePathMiddleware.js +0 -20
- package/src/middleware/createBeforeLocationChangeListenerMiddleware.js +0 -40
- package/src/middleware/createLocationMiddleware.js +0 -55
- package/src/middleware/createNavigationBlockerMiddleware.js +0 -168
- package/src/middleware/createTransformLocationMiddleware.js +0 -29
- package/src/onlyAllowedOnClientSide.js +0 -5
- package/src/session/BrowserSession.js +0 -235
- package/src/session/MemorySession.js +0 -219
- package/src/session/ServerSession.js +0 -67
- package/test/createMiddlewares.test.js +0 -62
- package/test/middleware/createBasePathMiddleware.test.js +0 -67
- package/test/middleware/createBeforeLocationChangeListenerMiddleware.test.js +0 -141
- package/test/middleware/createNavigationBlockerMiddleware.test.js +0 -471
- package/test/middleware/createTransformLocationMiddleware.test.js +0 -44
- package/test/session/BrowserSession.test.js +0 -182
- package/test/session/MemorySession.test.js +0 -244
- /package/lib/cjs/{locationReducer.js → redux/locationReducer.js} +0 -0
- /package/lib/esm/{locationReducer.js → redux/locationReducer.js} +0 -0
- /package/src/{locationReducer.js → redux/locationReducer.js} +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import parseInputLocation from '../parseInputLocation';
|
|
2
|
+
import createSessionKey from './key/createSessionKey';
|
|
3
|
+
import NavigationOutOfBoundsError from './navigation/error/NavigationOutOfBoundsError';
|
|
4
|
+
import NavigationOperations from './navigation/operation/operations';
|
|
5
|
+
import Subscription from './subscription/Subscription';
|
|
6
|
+
const INITIAL_KEY_INDEX = -1;
|
|
7
|
+
const INITIAL_INDEX = -1;
|
|
8
|
+
const INIT_LOCATION_DELTA = 0;
|
|
9
|
+
export default class Session {
|
|
10
|
+
constructor({
|
|
11
|
+
navigation
|
|
12
|
+
}) {
|
|
13
|
+
// This function is used by navigation.
|
|
14
|
+
this._getCurrentLocationIndex = () => {
|
|
15
|
+
return this._currentLocationIndex;
|
|
16
|
+
};
|
|
17
|
+
// `key` is used in `WebBrowserSession` to uniquely identify a session
|
|
18
|
+
// when storing data in a `WebBrowserDataStorage` which uses `window.sessionStorage`
|
|
19
|
+
// under the hood, and `window.sessionStorage` is shared between different sessions.
|
|
20
|
+
this.key = createSessionKey();
|
|
21
|
+
|
|
22
|
+
// `this._locationKeyIndex` is incremented every time the current location changes.
|
|
23
|
+
this._locationKeyIndex = INITIAL_KEY_INDEX;
|
|
24
|
+
|
|
25
|
+
// `this._currentLocationIndex` is the index of the top element in the navigation stack.
|
|
26
|
+
// I.e. it's the index of the "current" location in the navigation stack.
|
|
27
|
+
this._currentLocationIndex = INITIAL_INDEX;
|
|
28
|
+
|
|
29
|
+
// The `index` of the terminal (rightmost) location in the navigation history.
|
|
30
|
+
// In other words, this is the last location index that it can `.shift()` to.
|
|
31
|
+
this._terminalLocationIndex = this._currentLocationIndex;
|
|
32
|
+
|
|
33
|
+
// Create `navigation`.
|
|
34
|
+
this._navigation = navigation;
|
|
35
|
+
|
|
36
|
+
// Manages subscriptions.
|
|
37
|
+
this._subscription = new Subscription({
|
|
38
|
+
activateSubscription: listener => {
|
|
39
|
+
return this._navigation.subscribe(listener);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Update current location index when a location change was not initiated
|
|
44
|
+
// by this session but rather by the user clicking "Back" or "Forward" button.
|
|
45
|
+
this._unsubscribe = this.subscribe(location => {
|
|
46
|
+
// Update `this._currentLocationIndex` when the location change was not initiated
|
|
47
|
+
// by this session but rather by the user clicking "Back" or "Forward" button.
|
|
48
|
+
this._currentLocationIndex = location.index;
|
|
49
|
+
// Since `currentLocationIndex` has been updated, update `terminalLocationIndex`.
|
|
50
|
+
// It's not really currently possible to see a "PUSH" or a "REPLACE" operation here,
|
|
51
|
+
// but if it was possible, this call would be required. It would also be required
|
|
52
|
+
// by `navigation` to call `session.getNextKey()` function to increment `locationKeyIndex`.
|
|
53
|
+
this._updateTerminalLocationIndex(location);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Subscribes to changes in location.
|
|
58
|
+
subscribe(listener) {
|
|
59
|
+
return this._subscription.subscribe(location => {
|
|
60
|
+
if (!this._isStarted() && location.operation !== NavigationOperations.INIT) {
|
|
61
|
+
// eslint-disable-next-line no-console
|
|
62
|
+
console.error('Unexpected location change', location);
|
|
63
|
+
throw new Error('Not started');
|
|
64
|
+
} else {
|
|
65
|
+
// Call the listener.
|
|
66
|
+
listener(location);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
start(initialLocation) {
|
|
71
|
+
if (this._stopped) {
|
|
72
|
+
throw new Error('Can not be restarted');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Simplify "developer experience" by automatically calling
|
|
76
|
+
// `.init(initialLocation)` in case of using a `WebBrowserSession`.
|
|
77
|
+
//
|
|
78
|
+
// That's because `WebBrowserSession` environment already knows
|
|
79
|
+
// the initial location by the time javascript code starts execution.
|
|
80
|
+
//
|
|
81
|
+
if (!initialLocation) {
|
|
82
|
+
initialLocation = this._navigation.getInitialLocation();
|
|
83
|
+
if (initialLocation) {
|
|
84
|
+
initialLocation = parseInputLocation(initialLocation);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!initialLocation) {
|
|
88
|
+
throw new Error('`initialLocation` is required');
|
|
89
|
+
}
|
|
90
|
+
if (this._currentLocationIndex !== INITIAL_INDEX) {
|
|
91
|
+
throw new Error('Already started');
|
|
92
|
+
}
|
|
93
|
+
this._started = true;
|
|
94
|
+
const key = this._getNextLocationKey();
|
|
95
|
+
const index = INITIAL_INDEX + 1;
|
|
96
|
+
const delta = INIT_LOCATION_DELTA;
|
|
97
|
+
const locationResult = this._navigation.init(initialLocation, {
|
|
98
|
+
operation: NavigationOperations.INIT,
|
|
99
|
+
key,
|
|
100
|
+
index,
|
|
101
|
+
delta
|
|
102
|
+
});
|
|
103
|
+
if (locationResult) {
|
|
104
|
+
this._subscription.notifySubscribers(locationResult);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
stop() {
|
|
108
|
+
if (this._stopped) {
|
|
109
|
+
throw Error('Already stopped');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Once stopped, it won't be able to be restarted.
|
|
113
|
+
this._stopped = true;
|
|
114
|
+
|
|
115
|
+
// Remove location change subscription.
|
|
116
|
+
this._unsubscribe();
|
|
117
|
+
|
|
118
|
+
// Even if it calls `unsubscribe()` function above, any other subscriptions
|
|
119
|
+
// would still stay. For example, subscriptions created by the application code.
|
|
120
|
+
// To work around that, `.stop()` function removes all subscriptions.
|
|
121
|
+
this._subscription.stop();
|
|
122
|
+
}
|
|
123
|
+
navigate(operation, location) {
|
|
124
|
+
if (!this._isStarted()) {
|
|
125
|
+
throw Error('Not started');
|
|
126
|
+
}
|
|
127
|
+
if (operation !== NavigationOperations.PUSH && operation !== NavigationOperations.REPLACE) {
|
|
128
|
+
throw Error(`Unknown navigation operation: ${operation}`);
|
|
129
|
+
}
|
|
130
|
+
const delta = operation === NavigationOperations.PUSH ? 1 : 0;
|
|
131
|
+
this._updateTerminalLocationIndex({
|
|
132
|
+
operation
|
|
133
|
+
});
|
|
134
|
+
const key = this._getNextLocationKey();
|
|
135
|
+
const index = this._currentLocationIndex + delta;
|
|
136
|
+
|
|
137
|
+
// Navigate to the location.
|
|
138
|
+
const locationResult = this._navigation.navigate(location, {
|
|
139
|
+
operation,
|
|
140
|
+
key,
|
|
141
|
+
index,
|
|
142
|
+
delta
|
|
143
|
+
});
|
|
144
|
+
if (locationResult) {
|
|
145
|
+
this._subscription.notifySubscribers(locationResult);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
shift(delta) {
|
|
149
|
+
if (!this._isStarted()) {
|
|
150
|
+
throw Error('Not started');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// If there'll be no navigation, return.
|
|
154
|
+
if (delta === 0) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const index = this._currentLocationIndex + delta;
|
|
158
|
+
|
|
159
|
+
// Validate that the new `index` is not out of bounds.
|
|
160
|
+
if (index < 0 || index > this._terminalLocationIndex) {
|
|
161
|
+
throw new NavigationOutOfBoundsError(index);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Navigate to the location.
|
|
165
|
+
const locationResult = this._navigation.shift({
|
|
166
|
+
operation: NavigationOperations.SHIFT,
|
|
167
|
+
index,
|
|
168
|
+
delta
|
|
169
|
+
});
|
|
170
|
+
if (locationResult) {
|
|
171
|
+
this._subscription.notifySubscribers(locationResult);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
_updateTerminalLocationIndex({
|
|
175
|
+
operation
|
|
176
|
+
}) {
|
|
177
|
+
// A `PUSH` navigation sets a new terminal (rightmost) location.
|
|
178
|
+
if (operation === NavigationOperations.PUSH || operation === NavigationOperations.INIT) {
|
|
179
|
+
this._terminalLocationIndex = this._currentLocationIndex;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
_getNextLocationKey() {
|
|
183
|
+
this._locationKeyIndex++;
|
|
184
|
+
return this._locationKeyIndex.toString(36);
|
|
185
|
+
}
|
|
186
|
+
_isStarted() {
|
|
187
|
+
return !this._stopped && this._currentLocationIndex !== INITIAL_INDEX;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Session from './Session';
|
|
2
|
+
import WebBrowserEnvironment from '../environment/WebBrowserEnvironment';
|
|
3
|
+
import WebBrowserSessionLifecycle from './lifecycle/WebBrowserSessionLifecycle';
|
|
4
|
+
import WebBrowserNavigation from './navigation/WebBrowserNavigation';
|
|
5
|
+
export default class WebBrowserSession extends Session {
|
|
6
|
+
constructor() {
|
|
7
|
+
super({
|
|
8
|
+
navigation: new WebBrowserNavigation()
|
|
9
|
+
});
|
|
10
|
+
this.environment = new WebBrowserEnvironment();
|
|
11
|
+
this.lifecycle = new WebBrowserSessionLifecycle();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// `session.key` exists to avoid `location.key` collision after a page refresh.
|
|
2
|
+
// After a page refresh, a different `session.key` is created
|
|
3
|
+
// while the previous navigation history still exists because
|
|
4
|
+
// web browser navigation history survives a page reload.
|
|
5
|
+
// So web browser navigation history after a refresh contains records from different sessions.
|
|
6
|
+
// This means that some of those history records end up having same `location.key`s
|
|
7
|
+
// because `location.key` always starts from `0` for each different session.
|
|
8
|
+
// So `location.key` alone can't be used to identify navigation history entries
|
|
9
|
+
// because it's not unique among them. In order to get a unique key for a navigation history entry,
|
|
10
|
+
// one should combine a unique `session.key` with a `location.key`.
|
|
11
|
+
// That's what `session.key` exists for.
|
|
12
|
+
// Supplementary features such as scroll position restoration
|
|
13
|
+
// use `window.sessionStorage` to store supplementary data for a given navigation history entry.
|
|
14
|
+
// Because `window.sessionStorage` is shared between all navigation history entries from different websites,
|
|
15
|
+
// the keys used for storing that supplementary data have to be unique.
|
|
16
|
+
export default function createSessionKey() {
|
|
17
|
+
return Date.now().toString(36);
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default class InMemorySessionLifecycle {
|
|
2
|
+
// Termination blockers of an "in-memory session" are currently ignored.
|
|
3
|
+
// eslint-disable-next-line no-unused-vars
|
|
4
|
+
addTerminationBlocker(blocker) {
|
|
5
|
+
return () => {};
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// An "in-memory session" execution status is always `running: true`.
|
|
9
|
+
// eslint-disable-next-line no-unused-vars
|
|
10
|
+
addExecutionStatusListener(listener) {
|
|
11
|
+
return () => {};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// https://developers.google.com/web/updates/2018/07/page-lifecycle-api
|
|
2
|
+
// https://github.com/GoogleChromeLabs/page-lifecycle
|
|
3
|
+
import PageLifecycle from './page-lifecycle/PageLifecycleInstance';
|
|
4
|
+
export default class WebBrowserSessionLifecycle {
|
|
5
|
+
constructor() {
|
|
6
|
+
this._running = true;
|
|
7
|
+
}
|
|
8
|
+
addTerminationBlocker(terminationBlocker) {
|
|
9
|
+
const onBeforeUnload = event => {
|
|
10
|
+
if (terminationBlocker()) {
|
|
11
|
+
// Calling `event.preventDefault()` will cause a web browser
|
|
12
|
+
// to show a generic "Ok"/"Cancel" modal with some generic text:
|
|
13
|
+
// "Are you sure to leave the current page?".
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
window.addEventListener('beforeunload', onBeforeUnload);
|
|
18
|
+
return () => {
|
|
19
|
+
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
addExecutionStatusListener(listener) {
|
|
23
|
+
const pageLifecycleListener = stateChange => {
|
|
24
|
+
// (stateChange: PageLifecycleStateChange)
|
|
25
|
+
const {
|
|
26
|
+
newState
|
|
27
|
+
} = stateChange;
|
|
28
|
+
const running = !['terminated', 'frozen', 'discarded'].includes(newState);
|
|
29
|
+
if (this._running !== running) {
|
|
30
|
+
this._running = running;
|
|
31
|
+
listener({
|
|
32
|
+
running
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
PageLifecycle.addEventListener('statechange', pageLifecycleListener);
|
|
37
|
+
return () => {
|
|
38
|
+
PageLifecycle.removeEventListener('statechange', pageLifecycleListener);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// interface PageLifecycleStateChange {
|
|
44
|
+
// newState: PageLifecycleState;
|
|
45
|
+
// oldState: PageLifecycleState;
|
|
46
|
+
// originalEvent: Event;
|
|
47
|
+
// }
|
|
48
|
+
|
|
49
|
+
// // Page Lifecycle API event types.
|
|
50
|
+
// // https://developer.chrome.com/docs/web-platform/page-lifecycle-api#states
|
|
51
|
+
// // https://wicg.github.io/page-lifecycle/spec.html
|
|
52
|
+
// //
|
|
53
|
+
// type PageLifecycleState =
|
|
54
|
+
// // The page is visible and is focused.
|
|
55
|
+
// | 'active'
|
|
56
|
+
// // The page is visible but is not focused.
|
|
57
|
+
// | 'passive'
|
|
58
|
+
// // The page is not visible (and has not been frozen, discarded, or terminated).
|
|
59
|
+
// | 'hidden'
|
|
60
|
+
// // If a page is hidden, a browser may choose to freeze it to reduce energy consumption.
|
|
61
|
+
// | 'frozen'
|
|
62
|
+
// // The process of terminating (destroying, closing) the page has started.
|
|
63
|
+
// | 'terminated'
|
|
64
|
+
// // The page is discarded by the web browser due to insufficient resources.
|
|
65
|
+
// // The page snapshot could still be visible to the user even though it's no longer running.
|
|
66
|
+
// | 'discarded';
|
|
67
|
+
|
|
68
|
+
// // Page Lifecycle API event types.
|
|
69
|
+
// // https://developer.chrome.com/docs/web-platform/page-lifecycle-api
|
|
70
|
+
// // https://wicg.github.io/page-lifecycle/spec.html
|
|
71
|
+
// //
|
|
72
|
+
// type PageLifecycleEvent =
|
|
73
|
+
// // When the web browser window with an opened page gets focus, a `focus` event is emitted.
|
|
74
|
+
// | 'focus'
|
|
75
|
+
//
|
|
76
|
+
// // When the web browser window with an opened page is no longer focused, a `blur` event is emitted.
|
|
77
|
+
// | 'blur'
|
|
78
|
+
//
|
|
79
|
+
// // `visibilitychange` event fires with `document.visibilityState` being "hidden"
|
|
80
|
+
// // when a user navigates to a new page, switches tabs, closes the tab, minimizes or closes the browser,
|
|
81
|
+
// // or, on mobile, switches from the browser to a different app.
|
|
82
|
+
// //
|
|
83
|
+
// // Transitioning to "hidden" is the last event that's reliably observable by the page,
|
|
84
|
+
// // so developers should treat it as the likely end of the user's session
|
|
85
|
+
// // (for example, for sending analytics data).
|
|
86
|
+
// //
|
|
87
|
+
// // The transition to "hidden" is also a good point at which pages can stop making UI updates
|
|
88
|
+
// // and stop any tasks that the user doesn't want to have running in the background.
|
|
89
|
+
// //
|
|
90
|
+
// | 'visibilitychange'
|
|
91
|
+
//
|
|
92
|
+
// // Sometimes browsers "freeze" hidden pages in order to reduce energy consumption on mobile devices.
|
|
93
|
+
// // In case of freezing an already-hidden page, a `freeze` event will be emitted, if supported by the browser.
|
|
94
|
+
// | 'freeze'
|
|
95
|
+
//
|
|
96
|
+
// // Sometimes browsers "freeze" hidden pages in order to reduce energy consumption on mobile devices.
|
|
97
|
+
// // In case of unfreezing an already-frozen page, a `resume` event will be emitted, if supported by the browser.
|
|
98
|
+
// | 'resume'
|
|
99
|
+
//
|
|
100
|
+
// // `pageshow` event is emitted when a new page gets shown.
|
|
101
|
+
// //
|
|
102
|
+
// // For example, `pageshow` event is emitted when visiting a web page
|
|
103
|
+
// // or after being navigated to a new page by clicking a hyperlink.
|
|
104
|
+
// //
|
|
105
|
+
// // `pageshow` event is also emitted when the user performs "Back" or "Forward" transition.
|
|
106
|
+
// //
|
|
107
|
+
// | 'pageshow'
|
|
108
|
+
//
|
|
109
|
+
// // `pagehide` event is emitted when the current page gets "destroyed".
|
|
110
|
+
// //
|
|
111
|
+
// // For example, `pagehide` event is emitted when the user performs "Back" or "Forward" transition.
|
|
112
|
+
// // In that case, `pagehide` event will be emitted for the current page before the transition.
|
|
113
|
+
// //
|
|
114
|
+
// // In any other cases of "destroying" The current page, `pagehide` event is not guaranteed to be emitted.
|
|
115
|
+
// // For example, it won't be emitted when closing the web browser app via a task manager.
|
|
116
|
+
// //
|
|
117
|
+
// // Hence, `pagehide` event is unreliable and it's adivised to use `visibilitychange` event instead.
|
|
118
|
+
// // Only if `visibilitychange` even is not supported by a web browser should one consider resorting to using `pagehide` event.
|
|
119
|
+
// //
|
|
120
|
+
// | 'pagehide';
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/* eslint-disable max-classes-per-file */
|
|
2
|
+
|
|
3
|
+
// This code was copy-pasted from the final read-only version of `page-lifecycle` repo:
|
|
4
|
+
// https://github.com/GoogleChromeLabs/page-lifecycle/blob/master/src/Lifecycle.mjs
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
Copyright 2018 Google Inc. All Rights Reserved.
|
|
8
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
you may not use this file except in compliance with the License.
|
|
10
|
+
You may obtain a copy of the License at
|
|
11
|
+
|
|
12
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
13
|
+
|
|
14
|
+
Unless required by applicable law or agreed to in writing, software
|
|
15
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
16
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
17
|
+
See the License for the specific language governing permissions and
|
|
18
|
+
limitations under the License.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const ACTIVE = 'active';
|
|
22
|
+
const PASSIVE = 'passive';
|
|
23
|
+
const HIDDEN = 'hidden';
|
|
24
|
+
const FROZEN = 'frozen';
|
|
25
|
+
// const DISCARDED = 'discarded'; Not used but show to completeness.
|
|
26
|
+
const TERMINATED = 'terminated';
|
|
27
|
+
const EVENTS = ['focus', 'blur', 'visibilitychange', 'freeze', 'resume', 'pageshow',
|
|
28
|
+
// IE9-10 do not support the "pagehide" event, so the code could fall back to "unload" event here.
|
|
29
|
+
// Note: using "unload" event instead of "pagehide" event will
|
|
30
|
+
// prevent page navigation caching (a.k.a bfcache).
|
|
31
|
+
'pagehide'];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {!Event} evt
|
|
35
|
+
* @return {string}
|
|
36
|
+
*/
|
|
37
|
+
const onbeforeunload = evt => {
|
|
38
|
+
evt.preventDefault();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Converts an array of states into an object where the state is the key
|
|
43
|
+
* and the value is the index.
|
|
44
|
+
* @param {!Array<string>} arr
|
|
45
|
+
* @return {!Object}
|
|
46
|
+
*/
|
|
47
|
+
const toIndexedObject = arr => arr.reduce((acc, val, idx) => {
|
|
48
|
+
acc[val] = idx;
|
|
49
|
+
return acc;
|
|
50
|
+
}, {});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @type {!Array<!Object>}
|
|
54
|
+
*/
|
|
55
|
+
const LEGAL_STATE_TRANSITIONS = [
|
|
56
|
+
// The normal unload process (bfcache process is addressed above).
|
|
57
|
+
[ACTIVE, PASSIVE, HIDDEN, TERMINATED],
|
|
58
|
+
// An active page transitioning to frozen,
|
|
59
|
+
// or an unloading page going into the bfcache.
|
|
60
|
+
[ACTIVE, PASSIVE, HIDDEN, FROZEN],
|
|
61
|
+
// A hidden page transitioning back to active.
|
|
62
|
+
[HIDDEN, PASSIVE, ACTIVE],
|
|
63
|
+
// A frozen page being resumed
|
|
64
|
+
[FROZEN, HIDDEN],
|
|
65
|
+
// A frozen (bfcached) page navigated back to
|
|
66
|
+
// Note: [FROZEN, HIDDEN] can happen here, but it's already covered above.
|
|
67
|
+
[FROZEN, ACTIVE], [FROZEN, PASSIVE]].map(toIndexedObject);
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Accepts a current state and a future state and returns an array of legal
|
|
71
|
+
* state transition paths. This is needed to normalize behavior across browsers
|
|
72
|
+
* since some browsers do not fire events in certain cases and thus skip
|
|
73
|
+
* states.
|
|
74
|
+
* @param {string} oldState
|
|
75
|
+
* @param {string} newState
|
|
76
|
+
* @return {!Array<string>}
|
|
77
|
+
*/
|
|
78
|
+
const getLegalStateTransitionPath = (oldState, newState) => {
|
|
79
|
+
for (const order of LEGAL_STATE_TRANSITIONS) {
|
|
80
|
+
const oldIndex = order[oldState];
|
|
81
|
+
const newIndex = order[newState];
|
|
82
|
+
if (oldIndex >= 0 && newIndex >= 0 && newIndex > oldIndex) {
|
|
83
|
+
// Differences greater than one should be reported
|
|
84
|
+
// because it means a state was skipped.
|
|
85
|
+
return Object.keys(order).slice(oldIndex, newIndex + 1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return [];
|
|
89
|
+
// TODO(philipwalton): it shouldn't be possible to get here, but
|
|
90
|
+
// consider some kind of warning or call to action if it happens.
|
|
91
|
+
// console.warn(`Invalid state change detected: ${oldState} > ${newState}`);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the current state based on the document's visibility and
|
|
96
|
+
* in input focus states. Note this method is only used to determine
|
|
97
|
+
* active vs passive vs hidden states, as other states require listening
|
|
98
|
+
* for events.
|
|
99
|
+
* @return {string}
|
|
100
|
+
*/
|
|
101
|
+
const getCurrentState = () => {
|
|
102
|
+
if (document.visibilityState === HIDDEN) {
|
|
103
|
+
return HIDDEN;
|
|
104
|
+
}
|
|
105
|
+
if (document.hasFocus()) {
|
|
106
|
+
return ACTIVE;
|
|
107
|
+
}
|
|
108
|
+
return PASSIVE;
|
|
109
|
+
};
|
|
110
|
+
class StateChangeEvent extends Event {
|
|
111
|
+
/**
|
|
112
|
+
* @param {string} type
|
|
113
|
+
* @param {!Object} initDict
|
|
114
|
+
*/
|
|
115
|
+
constructor(type, initDict) {
|
|
116
|
+
super(type);
|
|
117
|
+
this.newState = initDict.newState;
|
|
118
|
+
this.oldState = initDict.oldState;
|
|
119
|
+
this.originalEvent = initDict.originalEvent;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Class definition for the exported, singleton lifecycle instance.
|
|
125
|
+
*/
|
|
126
|
+
export default class Lifecycle extends EventTarget {
|
|
127
|
+
/**
|
|
128
|
+
* Initializes state, state history, and adds event listeners to monitor
|
|
129
|
+
* state changes.
|
|
130
|
+
*/
|
|
131
|
+
constructor() {
|
|
132
|
+
super();
|
|
133
|
+
const state = getCurrentState();
|
|
134
|
+
this._state = state;
|
|
135
|
+
this._unsavedChanges = [];
|
|
136
|
+
|
|
137
|
+
// Bind the callback and add event listeners.
|
|
138
|
+
this._handleEvents = this._handleEvents.bind(this);
|
|
139
|
+
|
|
140
|
+
// Add capturing events on window so they run immediately.
|
|
141
|
+
EVENTS.forEach(evt => window.addEventListener(evt, this._handleEvents, true));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @return {string}
|
|
146
|
+
*/
|
|
147
|
+
get state() {
|
|
148
|
+
return this._state;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns the value of document.wasDiscarded. This is arguably unnecessary
|
|
153
|
+
* but I think there's value in having the entire API in one place and
|
|
154
|
+
* consistent across browsers.
|
|
155
|
+
* @return {boolean}
|
|
156
|
+
*/
|
|
157
|
+
get pageWasDiscarded() {
|
|
158
|
+
return document.wasDiscarded || false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @param {Symbol|Object} id A unique symbol or object identifying the
|
|
163
|
+
*. pending state. This ID is required when removing the state later.
|
|
164
|
+
*/
|
|
165
|
+
addUnsavedChanges(id) {
|
|
166
|
+
// Don't add duplicate state. Note: ideall this would be a set, but for
|
|
167
|
+
// better browser compatibility we're using an array.
|
|
168
|
+
if (!this._unsavedChanges.indexOf(id) > -1) {
|
|
169
|
+
// If this is the first state being added,
|
|
170
|
+
// also add a beforeunload listener.
|
|
171
|
+
if (this._unsavedChanges.length === 0) {
|
|
172
|
+
window.addEventListener('beforeunload', onbeforeunload);
|
|
173
|
+
}
|
|
174
|
+
this._unsavedChanges.push(id);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {Symbol|Object} id A unique symbol or object identifying the
|
|
180
|
+
*. pending state. This ID is required when removing the state later.
|
|
181
|
+
*/
|
|
182
|
+
removeUnsavedChanges(id) {
|
|
183
|
+
const idIndex = this._unsavedChanges.indexOf(id);
|
|
184
|
+
if (idIndex > -1) {
|
|
185
|
+
this._unsavedChanges.splice(idIndex, 1);
|
|
186
|
+
|
|
187
|
+
// If there's no more pending state, remove the event listener.
|
|
188
|
+
if (this._unsavedChanges.length === 0) {
|
|
189
|
+
window.removeEventListener('beforeunload', onbeforeunload);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Transitions from `this._state` to `newState`
|
|
196
|
+
* going through any intermediate states if required.
|
|
197
|
+
* @private
|
|
198
|
+
* @param {!Event} originalEvent
|
|
199
|
+
* @param {string} newState
|
|
200
|
+
*/
|
|
201
|
+
_dispatchChangesIfNeeded(originalEvent, newState) {
|
|
202
|
+
if (newState !== this._state) {
|
|
203
|
+
const oldState = this._state;
|
|
204
|
+
// Get the full chain of states for properly transitioning
|
|
205
|
+
// from `oldState` to `newState`.
|
|
206
|
+
const path = getLegalStateTransitionPath(oldState, newState);
|
|
207
|
+
|
|
208
|
+
// Start from `oldState` and transition to `newState`
|
|
209
|
+
// going through any intermediate states if required.
|
|
210
|
+
// Go through each intermediate state if required.
|
|
211
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
212
|
+
const prevState = path[i];
|
|
213
|
+
const nextState = path[i + 1];
|
|
214
|
+
this._state = nextState;
|
|
215
|
+
this.dispatchEvent(new StateChangeEvent('statechange', {
|
|
216
|
+
oldState: prevState,
|
|
217
|
+
newState: nextState,
|
|
218
|
+
originalEvent
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @private
|
|
226
|
+
* @param {!Event} evt
|
|
227
|
+
*/
|
|
228
|
+
_handleEvents(evt) {
|
|
229
|
+
switch (evt.type) {
|
|
230
|
+
case 'pageshow':
|
|
231
|
+
case 'resume':
|
|
232
|
+
this._dispatchChangesIfNeeded(evt, getCurrentState());
|
|
233
|
+
break;
|
|
234
|
+
case 'focus':
|
|
235
|
+
this._dispatchChangesIfNeeded(evt, ACTIVE);
|
|
236
|
+
break;
|
|
237
|
+
case 'blur':
|
|
238
|
+
// The `blur` event can fire while the page is being unloaded, so we
|
|
239
|
+
// only need to update the state if the current state is "active".
|
|
240
|
+
if (this._state === ACTIVE) {
|
|
241
|
+
this._dispatchChangesIfNeeded(evt, getCurrentState());
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
case 'pagehide':
|
|
245
|
+
case 'unload':
|
|
246
|
+
this._dispatchChangesIfNeeded(evt, evt.persisted ? FROZEN : TERMINATED);
|
|
247
|
+
break;
|
|
248
|
+
case 'visibilitychange':
|
|
249
|
+
// The document's `visibilityState` will change to hidden as the page
|
|
250
|
+
// is being unloaded, but in such cases the lifecycle state shouldn't
|
|
251
|
+
// change.
|
|
252
|
+
if (this._state !== FROZEN && this._state !== TERMINATED) {
|
|
253
|
+
this._dispatchChangesIfNeeded(evt, getCurrentState());
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
case 'freeze':
|
|
257
|
+
this._dispatchChangesIfNeeded(evt, FROZEN);
|
|
258
|
+
break;
|
|
259
|
+
default:
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2018 Google Inc. All Rights Reserved.
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
function doesSupportConstructableEventTarget() {
|
|
17
|
+
try {
|
|
18
|
+
// eslint-disable-next-line no-unused-vars
|
|
19
|
+
const eventTarget = new EventTarget();
|
|
20
|
+
|
|
21
|
+
// When transpiled with babel and rollup, the `IS_CODE_TRANSPILED` constant
|
|
22
|
+
// is replaced with the boolean `true`, so this statement will always
|
|
23
|
+
// evaluate to `false`. When not transpiled, it will be true.
|
|
24
|
+
return typeof IS_CODE_TRANSPILED === 'undefined';
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const supportsConstructableEventTarget = doesSupportConstructableEventTarget();
|
|
30
|
+
export { supportsConstructableEventTarget };
|