navigation-stack 0.5.3 → 0.6.1

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.
Files changed (210) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +144 -282
  3. package/karma.conf.cjs +1 -1
  4. package/lib/cjs/NavigationStack.js +138 -49
  5. package/lib/cjs/data-storage/DataStorage.js +7 -6
  6. package/lib/cjs/environment/InMemoryEnvironment.js +6 -0
  7. package/lib/cjs/{session/ServerSideRenderSession.js → environment/ServerSideRenderEnvironment.js} +5 -6
  8. package/lib/cjs/environment/WebBrowserEnvironment.js +6 -0
  9. package/lib/cjs/environment/log/InMemoryLog.js +23 -0
  10. package/lib/cjs/environment/log/WebBrowserLog.js +22 -0
  11. package/lib/cjs/{session → environment}/navigation/InMemoryNavigation.js +16 -5
  12. package/lib/cjs/{session → environment}/navigation/ServerSideNavigation.js +16 -7
  13. package/lib/cjs/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
  14. package/lib/cjs/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +2 -2
  15. package/lib/cjs/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
  16. package/lib/cjs/getLocationBaseFromLocation.js +14 -0
  17. package/lib/cjs/getLocationUrl.js +3 -5
  18. package/lib/cjs/index.js +10 -16
  19. package/lib/cjs/navigationBlockers.js +34 -32
  20. package/lib/cjs/navigationBlockersEvaluation.js +150 -0
  21. package/lib/cjs/parseInputLocation.js +2 -2
  22. package/lib/cjs/parseQueryFromSearch.js +3 -6
  23. package/lib/cjs/parseQueryString.js +77 -0
  24. package/lib/cjs/scroll-position/ScrollPositionAutoSaver.js +7 -6
  25. package/lib/cjs/scroll-position/ScrollPositionRestoration.js +31 -27
  26. package/lib/cjs/scroll-position/ScrollPositionSaver.js +6 -4
  27. package/lib/cjs/session/Session.js +61 -26
  28. package/lib/cjs/session/subscription/Subscription.js +36 -18
  29. package/lib/cjs/stringifyQuery.js +66 -0
  30. package/lib/cjs/stringifyQueryAsSearch.js +14 -0
  31. package/lib/esm/NavigationStack.js +138 -49
  32. package/lib/esm/data-storage/DataStorage.js +7 -6
  33. package/lib/esm/environment/InMemoryEnvironment.js +6 -0
  34. package/lib/esm/environment/ServerSideRenderEnvironment.js +10 -0
  35. package/lib/esm/environment/WebBrowserEnvironment.js +6 -0
  36. package/lib/esm/environment/log/InMemoryLog.js +17 -0
  37. package/lib/esm/environment/log/WebBrowserLog.js +16 -0
  38. package/lib/esm/{session → environment}/navigation/InMemoryNavigation.js +16 -5
  39. package/lib/esm/{session → environment}/navigation/ServerSideNavigation.js +16 -7
  40. package/lib/esm/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
  41. package/lib/esm/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +1 -1
  42. package/lib/esm/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
  43. package/lib/esm/getLocationBaseFromLocation.js +9 -0
  44. package/lib/esm/getLocationUrl.js +2 -5
  45. package/lib/esm/index.js +5 -8
  46. package/lib/esm/navigationBlockers.js +34 -32
  47. package/lib/esm/navigationBlockersEvaluation.js +145 -0
  48. package/lib/esm/parseInputLocation.js +2 -2
  49. package/lib/esm/parseQueryFromSearch.js +2 -6
  50. package/lib/esm/parseQueryString.js +72 -0
  51. package/lib/esm/scroll-position/ScrollPositionAutoSaver.js +7 -6
  52. package/lib/esm/scroll-position/ScrollPositionRestoration.js +31 -27
  53. package/lib/esm/scroll-position/ScrollPositionSaver.js +6 -4
  54. package/lib/esm/session/Session.js +61 -26
  55. package/lib/esm/session/subscription/Subscription.js +36 -18
  56. package/lib/esm/stringifyQuery.js +61 -0
  57. package/lib/esm/stringifyQueryAsSearch.js +8 -0
  58. package/lib/index.d.ts +180 -34
  59. package/package.json +4 -7
  60. package/src/NavigationStack.js +166 -56
  61. package/src/data-storage/DataStorage.js +9 -6
  62. package/src/environment/InMemoryEnvironment.js +6 -0
  63. package/src/environment/ServerSideRenderEnvironment.js +10 -0
  64. package/src/environment/WebBrowserEnvironment.js +6 -0
  65. package/src/environment/log/InMemoryLog.js +20 -0
  66. package/src/environment/log/WebBrowserLog.js +18 -0
  67. package/src/{session → environment}/navigation/InMemoryNavigation.js +16 -5
  68. package/src/{session → environment}/navigation/ServerSideNavigation.js +16 -7
  69. package/src/{session → environment}/navigation/WebBrowserNavigation.js +48 -8
  70. package/src/{session/navigation/error/ServerSideNavigationError.js → environment/navigation/error/ServerSideRedirectError.js} +1 -1
  71. package/src/environment/scroll-position/WebBrowserScrollPosition.js +15 -0
  72. package/src/getLocationBaseFromLocation.js +7 -0
  73. package/src/getLocationUrl.js +2 -5
  74. package/src/index.js +10 -13
  75. package/src/navigationBlockers.js +55 -34
  76. package/src/navigationBlockersEvaluation.js +161 -0
  77. package/src/parseInputLocation.js +2 -2
  78. package/src/parseQueryFromSearch.js +2 -6
  79. package/src/parseQueryString.js +81 -0
  80. package/src/scroll-position/ScrollPositionAutoSaver.js +10 -6
  81. package/src/scroll-position/ScrollPositionRestoration.js +36 -30
  82. package/src/scroll-position/ScrollPositionSaver.js +6 -4
  83. package/src/scroll-position/index.js +1 -1
  84. package/src/session/Session.js +68 -24
  85. package/src/session/subscription/Subscription.js +36 -11
  86. package/src/stringifyQuery.js +71 -0
  87. package/src/stringifyQueryAsSearch.js +9 -0
  88. package/test/NavigationStack.addBasePath.test.js +50 -0
  89. package/test/{redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.test.js → NavigationStack.blockNonProgrammaticNavigationIfRequired.test.js} +51 -63
  90. package/test/{redux/middleware/createProgrammaticNavigationBlockerMiddleware.test.js → NavigationStack.blockProgrammaticNavigationIfRequired.test.js} +98 -78
  91. package/test/NavigationStack.general.test.js +68 -0
  92. package/test/NavigationStack.parseInputLocation.test.js +52 -0
  93. package/test/NavigationStack.removeBasePath.test.js +69 -0
  94. package/test/NavigationStack.test.js +97 -29
  95. package/test/data-storage/LocationDataStorage.test.js +3 -2
  96. package/test/index.js +7 -31
  97. package/test/index.test.js +4 -5
  98. package/test/parseQueryFromSearch.test.js +19 -0
  99. package/test/parseQueryString.test.js +18 -0
  100. package/test/scroll-position/ScrollPositionRestoration.test.js +34 -13
  101. package/test/scroll-position/createApp.js +8 -8
  102. package/test/scroll-position/withScrollableContainerAtIndexPageWithDisabledAutomaticScrollPositionRestoration.js +4 -4
  103. package/test/session/{InMemorySession.test.js → Session.InMemoryEnvironment.test.js} +10 -9
  104. package/test/session/{ServerSession.test.js → Session.ServerSideRenderEnvironment.test.js} +5 -4
  105. package/test/session/{WebBrowserSession.test.js → Session.WebBrowserEnvironment.test.js} +63 -13
  106. package/test/shouldWarn.js +44 -0
  107. package/test/stringifyQuery.test.js +65 -0
  108. package/types/index.d.ts +180 -34
  109. package/types/tsconfig.json +0 -1
  110. package/data-storage/package.json +0 -7
  111. package/lib/cjs/createSearchFromQuery.js +0 -13
  112. package/lib/cjs/debug.js +0 -12
  113. package/lib/cjs/redux/ActionTypes.js +0 -14
  114. package/lib/cjs/redux/ActionTypesInternal.js +0 -8
  115. package/lib/cjs/redux/Actions.js +0 -28
  116. package/lib/cjs/redux/createMiddlewares.js +0 -60
  117. package/lib/cjs/redux/index.js +0 -13
  118. package/lib/cjs/redux/internalLocationReducer.js +0 -14
  119. package/lib/cjs/redux/locationReducer.js +0 -13
  120. package/lib/cjs/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -32
  121. package/lib/cjs/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -113
  122. package/lib/cjs/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -94
  123. package/lib/cjs/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -30
  124. package/lib/cjs/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -73
  125. package/lib/cjs/redux/middleware/navigationOperationMiddleware.js +0 -40
  126. package/lib/cjs/redux/middleware/parseInputLocationMiddleware.js +0 -29
  127. package/lib/cjs/redux/middleware/updateLocationMiddleware.js +0 -34
  128. package/lib/cjs/session/InMemorySession.js +0 -22
  129. package/lib/cjs/session/WebBrowserSession.js +0 -20
  130. package/lib/data-storage/index.d.ts +0 -35
  131. package/lib/esm/createSearchFromQuery.js +0 -8
  132. package/lib/esm/debug.js +0 -7
  133. package/lib/esm/redux/ActionTypes.js +0 -9
  134. package/lib/esm/redux/ActionTypesInternal.js +0 -3
  135. package/lib/esm/redux/Actions.js +0 -22
  136. package/lib/esm/redux/createMiddlewares.js +0 -54
  137. package/lib/esm/redux/index.js +0 -4
  138. package/lib/esm/redux/internalLocationReducer.js +0 -8
  139. package/lib/esm/redux/locationReducer.js +0 -7
  140. package/lib/esm/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -27
  141. package/lib/esm/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -108
  142. package/lib/esm/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -88
  143. package/lib/esm/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -25
  144. package/lib/esm/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -68
  145. package/lib/esm/redux/middleware/navigationOperationMiddleware.js +0 -35
  146. package/lib/esm/redux/middleware/parseInputLocationMiddleware.js +0 -24
  147. package/lib/esm/redux/middleware/updateLocationMiddleware.js +0 -28
  148. package/lib/esm/session/InMemorySession.js +0 -15
  149. package/lib/esm/session/ServerSideRenderSession.js +0 -11
  150. package/lib/esm/session/WebBrowserSession.js +0 -13
  151. package/lib/redux/index.d.ts +0 -90
  152. package/lib/scroll-position/index.d.ts +0 -107
  153. package/redux/package.json +0 -7
  154. package/scroll-position/package.json +0 -7
  155. package/src/createSearchFromQuery.js +0 -9
  156. package/src/debug.js +0 -8
  157. package/src/redux/ActionTypes.js +0 -9
  158. package/src/redux/ActionTypesInternal.js +0 -3
  159. package/src/redux/Actions.js +0 -27
  160. package/src/redux/createMiddlewares.js +0 -65
  161. package/src/redux/index.js +0 -4
  162. package/src/redux/internalLocationReducer.js +0 -9
  163. package/src/redux/locationReducer.js +0 -8
  164. package/src/redux/middleware/createAddInputLocationBasePathMiddleware.js +0 -27
  165. package/src/redux/middleware/createNonProgrammaticNavigationBlockerMiddleware.js +0 -119
  166. package/src/redux/middleware/createProgrammaticNavigationBlockerMiddleware.js +0 -94
  167. package/src/redux/middleware/createRemoveOutputLocationBasePathMiddleware.js +0 -26
  168. package/src/redux/middleware/createUpdateInternalLocationMiddleware.js +0 -72
  169. package/src/redux/middleware/navigationOperationMiddleware.js +0 -34
  170. package/src/redux/middleware/parseInputLocationMiddleware.js +0 -23
  171. package/src/redux/middleware/updateLocationMiddleware.js +0 -28
  172. package/src/session/InMemorySession.js +0 -13
  173. package/src/session/ServerSideRenderSession.js +0 -9
  174. package/src/session/WebBrowserSession.js +0 -13
  175. package/test/middlewareTestUtil.js +0 -31
  176. package/test/redux/Action.test.js +0 -73
  177. package/test/redux/ActionTypes.test.js +0 -13
  178. package/test/redux/createMiddlewares.test.js +0 -96
  179. package/test/redux/index.test.js +0 -10
  180. package/test/redux/locationReducer.test.js +0 -39
  181. package/test/redux/middleware/createAddInputLocationBasePathMiddleware.test.js +0 -40
  182. package/test/redux/middleware/createRemoveOutputLocationBasePathMiddleware.test.js +0 -51
  183. package/test/redux/middleware/navigationOperationMiddleware.test.js +0 -78
  184. package/test/redux/middleware/parseInputLocationMiddleware.test.js +0 -62
  185. package/test/testUtil.js +0 -3
  186. package/types/data-storage/index.d.ts +0 -35
  187. package/types/redux/index.d.ts +0 -90
  188. package/types/scroll-position/index.d.ts +0 -107
  189. /package/lib/cjs/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
  190. /package/lib/cjs/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
  191. /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
  192. /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
  193. /package/lib/cjs/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
  194. /package/lib/cjs/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
  195. /package/lib/cjs/{session → environment}/navigation/operation/operations.js +0 -0
  196. /package/lib/esm/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
  197. /package/lib/esm/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
  198. /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
  199. /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
  200. /package/lib/esm/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
  201. /package/lib/esm/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
  202. /package/lib/esm/{session → environment}/navigation/operation/operations.js +0 -0
  203. /package/src/{session → environment}/lifecycle/InMemorySessionLifecycle.js +0 -0
  204. /package/src/{session → environment}/lifecycle/WebBrowserSessionLifecycle.js +0 -0
  205. /package/src/{session → environment}/lifecycle/page-lifecycle/PageLifecycle.js +0 -0
  206. /package/src/{session → environment}/lifecycle/page-lifecycle/PageLifecycleInstance.js +0 -0
  207. /package/src/{session → environment}/lifecycle/page-lifecycle/supportsConstructableEventTarget.js +0 -0
  208. /package/src/{session → environment}/navigation/error/NavigationOutOfBoundsError.js +0 -0
  209. /package/src/{session → environment}/navigation/operation/operations.js +0 -0
  210. /package/test/{parseInputLocationMiddleware.test.js → parseInputLocation.test.js} +0 -0
@@ -1,54 +1,51 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
 
3
- import debug from './debug';
4
3
  import isPromise from './isPromise';
5
- export function getNavigationBlockers(session) {
6
- return session._navigationBlockersList || [];
4
+ export function getNavigationBlockers(container) {
5
+ return container._navigationBlockersList || [];
7
6
  }
8
- function addNavigationBlockerToTheList(blocker, session) {
9
- if (!session._navigationBlockersList) {
10
- session._navigationBlockersList = [];
7
+ function addNavigationBlockerToTheList(blocker, container) {
8
+ if (!container._navigationBlockersList) {
9
+ container._navigationBlockersList = [];
11
10
  }
12
- session._navigationBlockersList.push(blocker);
11
+ container._navigationBlockersList.push(blocker);
13
12
  }
14
- function removeNavigationBlockerFromTheList(blocker, session) {
15
- if (session._navigationBlockersList) {
16
- session._navigationBlockersList = session._navigationBlockersList.filter(_ => _ !== blocker);
13
+ function removeNavigationBlockerFromTheList(blocker, container) {
14
+ if (container._navigationBlockersList) {
15
+ container._navigationBlockersList = container._navigationBlockersList.filter(_ => _ !== blocker);
17
16
  }
18
17
  }
19
18
  export function removeAllNavigationBlockers(session) {
20
- if (getNavigationBlockers(session).some(blocker => blocker.beforeTermination)) {
19
+ // `navigationBlockers` are stored in `session`.
20
+ const container = session;
21
+ if (getNavigationBlockers(container).some(blocker => blocker.beforeTermination)) {
21
22
  if (!session._removeTerminationBlocker) {
22
23
  throw new Error('`_removeTerminationBlocker` property not found in the `session`');
23
24
  }
24
25
  session._removeTerminationBlocker();
25
26
  session._removeTerminationBlocker = undefined;
26
27
  }
27
- session._navigationBlockersList = [];
28
+ container._navigationBlockersList = [];
28
29
  }
29
30
 
30
31
  // Runs the `blocker` while ignoring any errors that might be thrown by it.
31
32
  function runNavigationBlocker({
32
33
  blocker
33
- }, location) {
34
+ }, location, environment) {
34
35
  let result;
35
36
  try {
36
37
  result = blocker(location);
37
38
  } catch (error) {
38
- // eslint-disable-next-line no-console
39
- console.warn(`Ignoring navigation blocker \`${blocker.name}\` that failed with \`${error}\`.`);
40
- // eslint-disable-next-line no-console
41
- console.error(error);
39
+ environment.log.warn(`Ignoring navigation blocker \`${blocker.name}\` that failed with \`${error}\`.`);
40
+ environment.log.error(error);
42
41
  }
43
42
 
44
43
  // If the blocker returned a `Promise`, await for that `Promise`
45
44
  // and then return the result.
46
45
  if (isPromise(result)) {
47
46
  return result.catch(error => {
48
- // eslint-disable-next-line no-console
49
- console.warn(`Ignoring navigation blocker \`${blocker.name}\` that failed with \`${error}\`.`);
50
- // eslint-disable-next-line no-console
51
- console.error(error);
47
+ environment.log.warn(`Ignoring navigation blocker \`${blocker.name}\` that failed with \`${error}\`.`);
48
+ environment.log.error(error);
52
49
  });
53
50
  }
54
51
  // The blocker didn't return a `Promise`.
@@ -59,28 +56,28 @@ function runNavigationBlocker({
59
56
  // Runs all blockers in order.
60
57
  // If any blocker returns `true`, it stops and returns the result.
61
58
  // If there's no such blocker, returns `undefined`.
62
- export function runNavigationBlockers(navigationBlockers, toLocation) {
59
+ export function runNavigationBlockers(navigationBlockers, toLocation, environment) {
63
60
  if (navigationBlockers.length === 0) {
64
61
  return undefined;
65
62
  }
66
63
 
67
64
  // Call the first blocker in the list.
68
- const result = runNavigationBlocker(navigationBlockers[0], toLocation);
65
+ const result = runNavigationBlocker(navigationBlockers[0], toLocation, environment);
69
66
  const next = () => {
70
67
  // Proceed to the next blocker.
71
- return runNavigationBlockers(navigationBlockers.slice(1), toLocation);
68
+ return runNavigationBlockers(navigationBlockers.slice(1), toLocation, environment);
72
69
  };
73
70
  if (isPromise(result)) {
74
71
  return result.then(resultValue => {
75
72
  if (resultValue) {
76
- debug('Navigation blocked', toLocation.pathname);
73
+ environment.log.debug('Navigation blocked', toLocation.pathname);
77
74
  return resultValue;
78
75
  }
79
76
  return next();
80
77
  });
81
78
  }
82
79
  if (result) {
83
- debug('Navigation blocked', toLocation.pathname);
80
+ environment.log.debug('Navigation blocked', toLocation.pathname);
84
81
  return result;
85
82
  }
86
83
  return next();
@@ -88,7 +85,9 @@ export function runNavigationBlockers(navigationBlockers, toLocation) {
88
85
 
89
86
  /* istanbul ignore next: not testable with Karma */
90
87
  function terminationBlocker(session) {
91
- const result = runNavigationBlockers(getNavigationBlockers(session), null);
88
+ // `navigationBlockers` are stored in `session`.
89
+ const container = session;
90
+ const result = runNavigationBlockers(getNavigationBlockers(container), null, session.environment);
92
91
 
93
92
  // If no blocker returned anything, so don't prevent the navigation.
94
93
  if (!result) {
@@ -107,6 +106,9 @@ function terminationBlocker(session) {
107
106
  return true;
108
107
  }
109
108
  export function addNavigationBlocker(session, blocker) {
109
+ // `navigationBlockers` are stored in `session`.
110
+ const container = session;
111
+
110
112
  // All navigation blockers also run on `beforeTermination` event.
111
113
  // If required, this could be a parameter of this function.
112
114
  // The rationale could be that adding a `beforeunload` listener
@@ -123,11 +125,11 @@ export function addNavigationBlocker(session, blocker) {
123
125
  // and this is bad for performance."
124
126
  //
125
127
  // https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
126
- if (beforeTermination && !getNavigationBlockers(session).some(navigationBlocker => navigationBlocker.beforeTermination)) {
128
+ if (beforeTermination && !getNavigationBlockers(container).some(navigationBlocker => navigationBlocker.beforeTermination)) {
127
129
  if (session._removeTerminationBlocker) {
128
130
  throw new Error('Unexpected `_removeTerminationBlocker` property found in the `session`');
129
131
  }
130
- session._removeTerminationBlocker = session.lifecycle.addTerminationBlocker(() => {
132
+ session._removeTerminationBlocker = session.environment.lifecycle.addTerminationBlocker(() => {
131
133
  return terminationBlocker(session);
132
134
  });
133
135
  }
@@ -135,12 +137,12 @@ export function addNavigationBlocker(session, blocker) {
135
137
  blocker,
136
138
  beforeTermination
137
139
  };
138
- addNavigationBlockerToTheList(newNavigationBlocker, session);
140
+ addNavigationBlockerToTheList(newNavigationBlocker, container);
139
141
  return () => {
140
- removeNavigationBlockerFromTheList(newNavigationBlocker, session);
142
+ removeNavigationBlockerFromTheList(newNavigationBlocker, container);
141
143
 
142
144
  // If it was the last "beforeTermination" blocker, remove navigation blocker.
143
- if (beforeTermination && !getNavigationBlockers(session).some(navigationBlocker => navigationBlocker.beforeTermination)) {
145
+ if (beforeTermination && !getNavigationBlockers(container).some(navigationBlocker => navigationBlocker.beforeTermination)) {
144
146
  if (!session._removeTerminationBlocker) {
145
147
  throw new Error('`_removeTerminationBlocker` property not found in the `session`');
146
148
  }
@@ -0,0 +1,145 @@
1
+ import getLocationBaseFromLocation from './getLocationBaseFromLocation';
2
+ import getLocationFromInternalLocation from './getLocationFromInternalLocation';
3
+ import isPromise from './isPromise';
4
+ import { getNavigationBlockers, runNavigationBlockers } from './navigationBlockers';
5
+
6
+ // Creates "navigation blockers evaluation" status object.
7
+ // It tracks the "cancelled" status of the evaluation:
8
+ // when next navigation happens, the previous one is no longer relevant
9
+ // so the evaluation of navigation blockers for it can be cancelled.
10
+ function createNavigationBlockersEvaluationStatus(container) {
11
+ /* eslint-disable no-underscore-dangle */
12
+ if (container._navigationBlockersEvaluationStatus) {
13
+ container._navigationBlockersEvaluationStatus.cancelled = true;
14
+ }
15
+ container._navigationBlockersEvaluationStatus = {
16
+ cancelled: false
17
+ };
18
+ return container._navigationBlockersEvaluationStatus;
19
+ }
20
+
21
+ // Prevents or allows navigation that was initiated by the application code
22
+ // by making a `.push()` or `.replace()` method call.
23
+ //
24
+ // It doesn't handle `.shift()` method calls because it doesn't yet know
25
+ // the `location` that it's gonna `shift` to. Instead, it waits for the web browser
26
+ // to "shift" to that `location` and then reads the `location` from the address bar
27
+ // and, if such "shift" should've been blocked, it "rewinds" the address bar back
28
+ // to the previous location. This part is handled by another function.
29
+ //
30
+ // Such type of "shifting" and then rewinding the "shift" doesn't really matter to the application code at all.
31
+ // From the application code's point of view, all web browser's address bar doesn't matter and even doesn't exist.
32
+ // All that exists from the application code's point of view is the `location` object in the `NavigationStack`'s state.
33
+ // Until the `location` object in the `NavigationStack`'s state is updated, the "old" page is still rendered.
34
+ // The appliation is only concerned with the updates of the `location` object in the `NavigationStack`'s state
35
+ // and completely ignores any updates to the URL in the web browser's address bar.
36
+ //
37
+ export function blockProgrammaticNavigationIfRequired(toLocationBase,
38
+ // `location` of type `LocationBase`
39
+ session) {
40
+ // `resultValue` variable name works around a stupid javascript error:
41
+ // "Cannot redeclare block-scoped variable 'result'".
42
+ const result = runNavigationBlockers(getNavigationBlockers(session),
43
+ // Here `payload.location` is `LocationBase`.
44
+ toLocationBase, session.environment);
45
+ if (isPromise(result)) {
46
+ const evaluationStatus = createNavigationBlockersEvaluationStatus(session);
47
+ // eslint-disable-next-line consistent-return
48
+ return result.then(promiseResult => {
49
+ if (evaluationStatus.cancelled) {
50
+ return true;
51
+ }
52
+ return promiseResult;
53
+ });
54
+ }
55
+ return result;
56
+ }
57
+
58
+ // Runs navigation blockers on internal location update and "undoes" the location update
59
+ // if it should've been blocked.
60
+ //
61
+ // One could ask: Why the hassle of running navigation blockers on internal location update
62
+ // and then rewinding back if the location change should've been blocked?
63
+ // Why not just run navigation blockers on `.push()`/`.replace()`/`.shift()`?
64
+ //
65
+ // The reason why it runs on internal location update here is because
66
+ // aside from programmatic `.shift()` that can be initiated from the application code,
67
+ // there's non-programmatic "shift" navigation when the user manually clicks "Back" or "Forward" button
68
+ // in a web browser. And even if a "shift" navigation is initiated programmatically in the application code,
69
+ // it still doesn't know yet what the new location is gonna be cause it only knows the numeric `delta`.
70
+ // Such cases could only be handled by reacting to internal location updates which,
71
+ // in case of "Back"/"Forward", only happen after the URL in the browser's address bar has changed.
72
+ //
73
+ // There's no real drawback in reacting to an internal location update "post factum" because
74
+ // from the application code's point of view, web browser's address bar doesn't matter and even doesn't exist.
75
+ // All that exists from the application code's point of view is the `navigationStack.current` location
76
+ // returned from the `NavigationStack`. Until that location is updated, the "old" page is still rendered.
77
+ // The appliation is only concerned with the updates of the internal `location` object in the `NavigationStack``
78
+ // and completely ignores any updates to the URL in the web browser's address bar.
79
+ //
80
+ // So here, the code attempts to prevent or allow navigation that has already happened
81
+ // in the web browser's address bar but hasn't yet happened in the `NavigationStack`'s state.
82
+ // For example, it could be a user clicking a "Back"/"Forward" button in a web browser.
83
+ // If such navigation should've been blocked, it will simply not update the `location` object
84
+ // in the `NavigationStack`'s state, and it will also "rewind" the change of the URL in the web browser's
85
+ // address bar so that it's consistent with the `location` in the `NavigationStack`'s state.
86
+ //
87
+ // Returns either a `boolean` value or a `Promise` that resolves to a `boolean` value:
88
+ // * `false` when navigation should not have been blocked and therefore was not "rewinded".
89
+ // * `true` when navigation should have been blocked and therefore was "rewinded".
90
+ // * `true` when it "rewinded" the navigation "just in case" and then started evaluating async blockers,
91
+ // but while doing that, next navigation already happened so this one is no longer relevant.
92
+ //
93
+ export function blockNonProgrammaticNavigationIfRequired(toLocationInternal,
94
+ // `location` of type `LocationInternal`
95
+ session, doAndIgnoreLocationUpdates) {
96
+ // If there're no navigation blockers to run, don't do anything.
97
+ if (getNavigationBlockers(session).length === 0) {
98
+ return false;
99
+ }
100
+
101
+ // If it was the initial page load or a redirect,
102
+ // it's not really a navigation that could be rolled back.
103
+ if (toLocationInternal.delta === 0) {
104
+ return false;
105
+ }
106
+ const result = runNavigationBlockers(getNavigationBlockers(session), getLocationBaseFromLocation(getLocationFromInternalLocation(toLocationInternal)), session.environment);
107
+
108
+ // If some navigation blocker returned a `Promise`.
109
+ if (isPromise(result)) {
110
+ const evaluationStatus = createNavigationBlockersEvaluationStatus(session);
111
+
112
+ // While location blockers are running, rewind to the previous location.
113
+ doAndIgnoreLocationUpdates(() => {
114
+ session.shift(-toLocationInternal.delta);
115
+ });
116
+ return result.then(promiseResult => {
117
+ if (evaluationStatus.cancelled) {
118
+ return true;
119
+ }
120
+ if (promiseResult) {
121
+ // Navigation blocked.
122
+ // Already rewound to a previous location.
123
+ return true;
124
+ }
125
+ // Navigation not blocked.
126
+ // Rewind back to the new location.
127
+ doAndIgnoreLocationUpdates(() => {
128
+ session.shift(toLocationInternal.delta);
129
+ });
130
+ // Update the location.
131
+ return false;
132
+ });
133
+ }
134
+
135
+ // Navigation blockers did not return a `Promise`.
136
+ if (result) {
137
+ // Prevent the navigation: rewind to the previous location.
138
+ doAndIgnoreLocationUpdates(() => {
139
+ session.shift(-toLocationInternal.delta);
140
+ });
141
+ return true;
142
+ }
143
+ // Update the location.
144
+ return false;
145
+ }
@@ -1,6 +1,6 @@
1
- import createSearchFromQuery from './createSearchFromQuery';
2
1
  import parseLocationUrl from './parseLocationUrl';
3
2
  import parseQueryFromSearch from './parseQueryFromSearch';
3
+ import stringifyQueryAsSearch from './stringifyQueryAsSearch';
4
4
  function stringifyQueryParameterValue(value) {
5
5
  if (value === null || value === undefined) {
6
6
  return value;
@@ -40,7 +40,7 @@ export default function parseInputLocation(location) {
40
40
  // if `query` is present but `search` is not.
41
41
  if (location.query && !location.search) {
42
42
  location = Object.assign({}, location, {
43
- search: createSearchFromQuery(location.query)
43
+ search: stringifyQueryAsSearch(location.query)
44
44
  });
45
45
  }
46
46
 
@@ -1,11 +1,7 @@
1
- import { parse as parseQuery } from 'query-string';
1
+ import parseQueryString from './parseQueryString';
2
2
  export default function parseQueryFromSearch(search) {
3
3
  if (search.length > '?'.length) {
4
- try {
5
- return parseQuery(search.slice(1));
6
- } catch (error) {
7
- // Ignore any query parsing errors.
8
- }
4
+ return parseQueryString(search.slice('?'.length));
9
5
  }
10
6
  return {};
11
7
  }
@@ -0,0 +1,72 @@
1
+ function splitAtFirstOccurence(string, separator) {
2
+ const separatorIndex = string.indexOf(separator);
3
+ if (separatorIndex === -1) {
4
+ return [string, ''];
5
+ }
6
+ return [string.slice(0, separatorIndex), string.slice(separatorIndex + separator.length)];
7
+ }
8
+ function decode(value) {
9
+ // There's a convention that a space character could be encoded
10
+ // either as "%20" or as "+". Both of them are valid.
11
+ // The "+" character is unusally preferred because it results in a more
12
+ // human-readable URL.
13
+ //
14
+ // https://dev.to/lico/understanding-how-spaces-are-encoded-20-with-encodeuri-vs-with-url-2d6c
15
+ // https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding
16
+ //
17
+ // Those "+" characters don't get transformed to spaces by `decodeURIComponent()` function.
18
+ // This means that they should be transformed to spaces manually.
19
+ //
20
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#decoding_query_parameters_from_a_url
21
+ //
22
+ value = value.replaceAll('+', ' ');
23
+
24
+ // `decodeURIComponent()` could throw an error of class `URIError`.
25
+ //
26
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/URIError
27
+ //
28
+ // Example: "URIError: malformed URI sequence".
29
+ try {
30
+ return decodeURIComponent(value);
31
+ } catch (error) {
32
+ // eslint-disable-next-line no-console
33
+ console.error(error);
34
+ return value;
35
+ }
36
+ }
37
+ export default function parseQueryString(queryString) {
38
+ // Create an object with no prototype
39
+ const query = Object.create(null);
40
+
41
+ // query parameter parsing is described in the specification:
42
+ // https://url.spec.whatwg.org/#urlencoded-parsing
43
+ for (const keyValuePair of queryString.split('&')) {
44
+ if (!keyValuePair) {
45
+ continue;
46
+ }
47
+ let [key, value] = splitAtFirstOccurence(keyValuePair, '=');
48
+
49
+ // If `key` is empty, the specification considers this a valid case with `key: null`.
50
+ // But, there seems to be no practical use for a query parameter with `key: null`.
51
+ // So just skip it.
52
+ if (!key) {
53
+ continue;
54
+ }
55
+ key = decode(key);
56
+
57
+ // According to the specification, missing `=` should be treated as `value: null`.
58
+ if (value === '') {
59
+ value = null;
60
+ } else {
61
+ value = decode(value);
62
+ }
63
+
64
+ // The handling of duplicate URL query parameters is not explicitly defined by a single,
65
+ // universally enforced specification. Hence, we just assume such query parameters invalid
66
+ // and only include the first occurrence of the query parameter in the query string.
67
+ if (query[key] === undefined) {
68
+ query[key] = value;
69
+ }
70
+ }
71
+ return query;
72
+ }
@@ -2,14 +2,15 @@
2
2
 
3
3
  import { PAGE_SCROLLABLE_CONTAINER_KEY } from './constants';
4
4
  import scheduleNextTick from './scheduleNextTick';
5
- import debug from '../debug';
6
5
  export default class ScrollPositionAutoSaver {
7
6
  constructor({
7
+ log,
8
8
  scrollPosition,
9
9
  scrollPositionSaver,
10
10
  getScrollableContainers,
11
11
  shouldSaveScrollPosition
12
12
  }) {
13
+ this._log = log;
13
14
  this._scrollPosition = scrollPosition;
14
15
  this._scrollPositionSaver = scrollPositionSaver;
15
16
  this._shouldSaveScrollPosition = shouldSaveScrollPosition;
@@ -61,7 +62,7 @@ export default class ScrollPositionAutoSaver {
61
62
  cancelSavePageScrollPosition(hasRun) {
62
63
  if (this._cancelSavePageScrollPosition) {
63
64
  if (!hasRun) {
64
- debug('cancel delayed save scroll position', PAGE_SCROLLABLE_CONTAINER_KEY);
65
+ this._log.debug('cancel delayed save scroll position', PAGE_SCROLLABLE_CONTAINER_KEY);
65
66
  }
66
67
  this._cancelSavePageScrollPosition();
67
68
  this._cancelSavePageScrollPosition = null;
@@ -71,7 +72,7 @@ export default class ScrollPositionAutoSaver {
71
72
  const scrollableContainerEntry = this._getScrollableContainers()[scrollableContainerKey];
72
73
  if (scrollableContainerEntry.cancelSaveScrollPosition) {
73
74
  if (!hasRun) {
74
- debug('cancel delayed save scroll position', scrollableContainerKey);
75
+ this._log.debug('cancel delayed save scroll position', scrollableContainerKey);
75
76
  }
76
77
  scrollableContainerEntry.cancelSaveScrollPosition();
77
78
  scrollableContainerEntry.cancelSaveScrollPosition = null;
@@ -103,9 +104,9 @@ export default class ScrollPositionAutoSaver {
103
104
  // because there might be too many in a given short period of time
104
105
  // which could affect the performance of the application.
105
106
  if (!scrollableContainerEntry.cancelSaveScrollPosition) {
106
- debug('scroll detected', scrollableContainerKey);
107
+ this._log.debug('scroll detected', scrollableContainerKey);
107
108
  scrollableContainerEntry.cancelSaveScrollPosition = scheduleNextTick(() => {
108
- debug('auto-save scroll position after scroll', scrollableContainerKey);
109
+ this._log.debug('auto-save scroll position after scroll', scrollableContainerKey);
109
110
  this._scrollPositionSaver.saveScrollableContainerScrollPosition(scrollableContainerKey, scrollableContainerEntry.scrollableContainer);
110
111
  });
111
112
  }
@@ -114,7 +115,7 @@ export default class ScrollPositionAutoSaver {
114
115
  addPageScrollListener() {
115
116
  // Set up scroll listener on the page.
116
117
  this._removePageScrollListener = this._scrollPosition.addPageScrollListener(() => {
117
- debug('scroll detected', PAGE_SCROLLABLE_CONTAINER_KEY);
118
+ this._log.debug('scroll detected', PAGE_SCROLLABLE_CONTAINER_KEY);
118
119
 
119
120
  // This flag is not used in real life and is only used in tests (for some reason).
120
121
  if (!this._shouldSaveScrollPosition()) {
@@ -5,7 +5,6 @@ import ScrollPositionSaver from './ScrollPositionSaver';
5
5
  import ScrollPositionSetter from './ScrollPositionSetter';
6
6
  import { PAGE_SCROLLABLE_CONTAINER_KEY } from './constants';
7
7
  import LocationDataStorage from '../data-storage/LocationDataStorage';
8
- import debug from '../debug';
9
8
  function areEqualScrollPositions(scrollPosition1, scrollPosition2) {
10
9
  let i = 0;
11
10
  while (i < scrollPosition1.length) {
@@ -17,7 +16,7 @@ function areEqualScrollPositions(scrollPosition1, scrollPosition2) {
17
16
  return true;
18
17
  }
19
18
  export default class ScrollPositionRestoration {
20
- constructor(session, _options) {
19
+ constructor(session, options) {
21
20
  // Once configured, scroll restoration mode persists across page reloads.
22
21
  // I.e. even if a user refreshes the page in a web browser, the custom
23
22
  // `window.history.scrollRestoration` value will still remain.
@@ -49,10 +48,10 @@ export default class ScrollPositionRestoration {
49
48
  running
50
49
  }) => {
51
50
  if (running) {
52
- debug('▶ running');
51
+ this._log.debug('▶ running');
53
52
  this._disableAutomaticScrollRestoration();
54
53
  } else {
55
- debug('⏹ not running');
54
+ this._log.debug('⏹ not running');
56
55
  this._enableAutomaticScrollRestoration();
57
56
 
58
57
  // There might be previous scroll position already saved in the data storage.
@@ -80,27 +79,30 @@ export default class ScrollPositionRestoration {
80
79
  try {
81
80
  this._scrollPosition.disableAutomaticScrollRestoration();
82
81
  } catch (error) {
83
- // eslint-disable-next-line no-console
84
- console.error('[navigation-stack] could not disable default scroll restoration mode');
82
+ this._log.error('[navigation-stack] could not disable default scroll restoration mode');
85
83
  }
86
84
  };
87
85
  this._enableAutomaticScrollRestoration = () => {
88
86
  try {
89
87
  this._scrollPosition.enableAutomaticScrollRestoration();
90
88
  } catch (error) {
91
- // eslint-disable-next-line no-console
92
- console.error('[navigation-stack] could not enable default scroll restoration mode');
89
+ this._log.error('[navigation-stack] could not enable default scroll restoration mode');
93
90
  }
94
91
  };
95
92
  this._saveScrollPositionForLocation = (location, scrollableContainerKey, scrollPosition) => {
96
93
  this._locationDataStorage.set(location, scrollableContainerKey || PAGE_SCROLLABLE_CONTAINER_KEY, scrollPosition);
97
94
  };
95
+ this._log = session.environment.log;
98
96
  this._scrollPosition = session.environment.scrollPosition;
99
- this._sessionLifecycle = session.lifecycle;
97
+
98
+ // Custom `ScrollPositionSetter`.
99
+ this._scrollPositionSetter = options.scrollPositionSetter;
100
+ this._sessionLifecycle = session.environment.lifecycle;
100
101
  this._locationDataStorage = new LocationDataStorage(session, {
101
- namespace: 'navigation-stack/scroll-position'
102
+ namespace: 'navigation-stack-scroll-position'
102
103
  });
103
104
  this._scrollPositionSaver = new ScrollPositionSaver({
105
+ log: this._log,
104
106
  scrollPosition: this._scrollPosition,
105
107
  saveScrollPositionForLocation: this._saveScrollPositionForLocation,
106
108
  getScrollableContainers: () => this._scrollableContainers,
@@ -122,20 +124,22 @@ export default class ScrollPositionRestoration {
122
124
  // Using this option, a developer could theoretically provide their own implementation
123
125
  // of setting a scroll position. For example, it could use "smooth" (animated) scrolling, etc.
124
126
  // This could be part of the public API if anyone provided a sensible real-world use case for it.
125
- scrollPositionSetter: _options && _options._pageScrollPositionSetter ||
126
- // The default page scroll position setter.
127
+ scrollPositionSetter: options && options._pageScrollPositionSetter ||
128
+ // eslint-disable-next-line new-cap
129
+ this._scrollPositionSetter && new this._scrollPositionSetter() ||
130
+ // A default `ScrollPositionSetter` for a page (sets page scroll position twice with a momentary delay).
127
131
  new PageScrollPositionSetter(),
128
132
  // This function is only used in tests.
129
133
  // There seems to be no use of it in real life, hence it's not public API.
130
134
  // It's only used in tests.
131
- _getSavedScrollPositionOnLocationChange: _options && _options._getSavedPageScrollPositionOnLocationChange,
135
+ _getSavedScrollPositionOnLocationChange: options && options._getSavedPageScrollPositionOnLocationChange,
132
136
  // This function is only used in tests.
133
137
  // There seems to be no use of it in real life, hence it's not public API.
134
138
  // It's only used in tests.
135
- _shouldSetScrollPositionOnLocationChange: _options && _options._shouldSetPageScrollPositionOnLocationChange
139
+ shouldChangeScrollPositionOnLocationChange: options && options.shouldChangePageScrollPositionOnLocationChange
136
140
  };
137
141
  }
138
- addScrollableContainer(scrollableContainerKey, scrollableContainer, _options) {
142
+ addScrollableContainer(scrollableContainerKey, scrollableContainer, options) {
139
143
  // Originally, `scrollableContainerKey` was auto-generated,
140
144
  // but then it didn't work with the concept of dynamically adding or removing
141
145
  // scrollable containers after `ScrollPositionRestoration` has already started.
@@ -152,7 +156,7 @@ export default class ScrollPositionRestoration {
152
156
  if (this._scrollableContainers[scrollableContainerKey]) {
153
157
  throw new Error(`Scrollable container key "${scrollableContainerKey}" is already added`);
154
158
  }
155
- debug('add scrollable container', scrollableContainerKey);
159
+ this._log.debug('add scrollable container', scrollableContainerKey);
156
160
 
157
161
  // Add scrollable container entry.
158
162
  this._scrollableContainers[scrollableContainerKey] = {
@@ -161,17 +165,17 @@ export default class ScrollPositionRestoration {
161
165
  // Using this option, a developer could theoretically provide their own implementation
162
166
  // of setting a scroll position. For example, it could use "smooth" (animated) scrolling, etc.
163
167
  // This could be part of the public API if anyone provided a sensible real-world use case for it.
164
- scrollPositionSetter: _options && _options._scrollPositionSetter ||
168
+ scrollPositionSetter: options && options._scrollPositionSetter || this._scrollPositionSetter && new this._scrollPositionSetter() ||
165
169
  // The default basic "immediate" scroll position setter.
166
170
  new ScrollPositionSetter(),
167
171
  // This function is only used in tests.
168
172
  // There seems to be no use of it in real life, hence it's not public API.
169
173
  // It's only used in tests.
170
- _shouldSetScrollPositionOnLocationChange: _options && _options._shouldSetScrollPositionOnLocationChange,
174
+ shouldChangeScrollPositionOnLocationChange: options && options.shouldChangeScrollPositionOnLocationChange,
171
175
  // This function is only used in tests.
172
176
  // There seems to be no use of it in real life, hence it's not public API.
173
177
  // It's only used in tests.
174
- _getSavedScrollPositionOnLocationChange: _options && _options._getSavedScrollPositionOnLocationChange
178
+ _getSavedScrollPositionOnLocationChange: options && options._getSavedScrollPositionOnLocationChange
175
179
  };
176
180
 
177
181
  // Scrollable containers could be added at any time, including page mount.
@@ -183,10 +187,10 @@ export default class ScrollPositionRestoration {
183
187
  if (this._location) {
184
188
  const previouslySavedScrollPosition = this._getSavedScrollPositionForLocation(this._location, scrollableContainerKey);
185
189
  if (previouslySavedScrollPosition) {
186
- debug('restore scroll position on add scrollable container', this._location.pathname, scrollableContainerKey, previouslySavedScrollPosition);
190
+ this._log.debug('restore scroll position on add scrollable container', this._location.pathname, scrollableContainerKey, previouslySavedScrollPosition);
187
191
  this._scrollPosition.setScrollableContainerScrollPosition(scrollableContainer, previouslySavedScrollPosition);
188
192
  } else {
189
- debug('save scroll position on add scrollable container', this._location.pathname, scrollableContainerKey);
193
+ this._log.debug('save scroll position on add scrollable container', this._location.pathname, scrollableContainerKey);
190
194
  this._scrollPositionSaver.saveScrollableContainerScrollPosition(scrollableContainerKey, scrollableContainer);
191
195
  }
192
196
  }
@@ -196,7 +200,7 @@ export default class ScrollPositionRestoration {
196
200
 
197
201
  // Removes the scrollable container.
198
202
  return () => {
199
- debug('remove scrollable container', scrollableContainerKey);
203
+ this._log.debug('remove scrollable container', scrollableContainerKey);
200
204
  this._scrollPositionSaver._scrollPositionAutoSaver.cancelSaveScrollableContainerScrollPosition(scrollableContainerKey);
201
205
  this._scrollPositionSaver._scrollPositionAutoSaver.removeScrollableContainerScrollListener(scrollableContainerKey);
202
206
  delete this._scrollableContainers[scrollableContainerKey];
@@ -268,7 +272,7 @@ export default class ScrollPositionRestoration {
268
272
  if (!location.key) {
269
273
  throw new Error('`location` must have a `key`');
270
274
  }
271
- debug('rendered location', location.pathname);
275
+ this._log.debug('rendered location', location.pathname);
272
276
  this._prevLocation = this._location;
273
277
  this._location = location;
274
278
  this._scrollPosition.init();
@@ -329,8 +333,8 @@ export default class ScrollPositionRestoration {
329
333
  // This function is only used in tests.
330
334
  // There seems to be no use of it in real life, hence it's not public API.
331
335
  // It's only used in tests.
332
- if (scrollableContainerEntry._shouldSetScrollPositionOnLocationChange) {
333
- if (!scrollableContainerEntry._shouldSetScrollPositionOnLocationChange(this._location, this._prevLocation)) {
336
+ if (scrollableContainerEntry.shouldChangeScrollPositionOnLocationChange) {
337
+ if (!scrollableContainerEntry.shouldChangeScrollPositionOnLocationChange(this._prevLocation, this._location)) {
334
338
  return Promise.resolve();
335
339
  }
336
340
  }
@@ -342,14 +346,14 @@ export default class ScrollPositionRestoration {
342
346
  // There seems to be no use of it in real life, hence it's not public API.
343
347
  // It's only used in tests.
344
348
  if (scrollableContainerEntry._getSavedScrollPositionOnLocationChange) {
345
- scrollPositionOrAnchorToSet = scrollableContainerEntry._getSavedScrollPositionOnLocationChange(this._location, this._prevLocation);
349
+ scrollPositionOrAnchorToSet = scrollableContainerEntry._getSavedScrollPositionOnLocationChange(this._prevLocation, this._location);
346
350
  }
347
351
 
348
352
  // Get scroll position (or anchor) to set.
349
353
  if (!scrollPositionOrAnchorToSet) {
350
354
  scrollPositionOrAnchorToSet = scrollableContainerKey === PAGE_SCROLLABLE_CONTAINER_KEY ? this._getPageScrollPositionOrAnchorToSet(this._location) : this._getScrollableContainerScrollPositionToSet(this._location, scrollableContainerKey);
351
355
  }
352
- debug('restore scroll position', this._location.pathname, scrollableContainerKey, scrollPositionOrAnchorToSet);
356
+ this._log.debug('restore scroll position', this._location.pathname, scrollableContainerKey, scrollPositionOrAnchorToSet);
353
357
 
354
358
  // Set scroll position of scrollable container.
355
359
  return scrollableContainerEntry.scrollPositionSetter.set(scrollableContainerEntry.scrollableContainer, scrollPositionOrAnchorToSet, this._scrollPosition);