diff --git a/aio/src/app/shared/scroll.service.spec.ts b/aio/src/app/shared/scroll.service.spec.ts index 50e1175882..99b405fc1a 100644 --- a/aio/src/app/shared/scroll.service.spec.ts +++ b/aio/src/app/shared/scroll.service.spec.ts @@ -94,6 +94,22 @@ describe('ScrollService', () => { } }); + it('should not break when cookies are disabled in the browser', () => { + // Simulate `window.sessionStorage` being inaccessible, when cookies are disabled. + spyOnProperty(window, 'sessionStorage', 'get').and.throwError('The operation is insecure'); + + expect(() => { + const platformLoc = platformLocation as PlatformLocation; + const service = new ScrollService(document, platformLoc, viewportScrollerStub, location); + + service.updateScrollLocationHref(); + expect(service.getStoredScrollLocationHref()).toBeNull(); + + service.removeStoredScrollInfo(); + expect(service.getStoredScrollPosition()).toBeNull(); + }).not.toThrow(); + }); + describe('#topOffset', () => { it('should query for the top-bar by CSS selector', () => { expect(document.querySelector).not.toHaveBeenCalled(); diff --git a/aio/src/app/shared/scroll.service.ts b/aio/src/app/shared/scroll.service.ts index 690ea3fc63..18295cf31e 100644 --- a/aio/src/app/shared/scroll.service.ts +++ b/aio/src/app/shared/scroll.service.ts @@ -19,6 +19,7 @@ export class ScrollService implements OnDestroy { private _topOffset: number | null; private _topOfPageElement: Element; private onDestroy = new Subject(); + private storage: Storage; // The scroll position which has to be restored, after a `popstate` event. poppedStateScrollPosition: ScrollPosition | null = null; @@ -49,6 +50,21 @@ export class ScrollService implements OnDestroy { private platformLocation: PlatformLocation, private viewportScroller: ViewportScroller, private location: Location) { + try { + this.storage = window.sessionStorage; + } catch { + // When cookies are disabled in the browser, even trying to access + // `window.sessionStorage` throws an error. Use a no-op storage. + this.storage = { + length: 0, + clear: () => undefined, + getItem: () => null, + key: () => null, + removeItem: () => undefined, + setItem: () => undefined + }; + } + // On resize, the toolbar might change height, so "invalidate" the top offset. fromEvent(window, 'resize') .pipe(takeUntil(this.onDestroy)) @@ -180,7 +196,7 @@ export class ScrollService implements OnDestroy { } updateScrollLocationHref(): void { - window.sessionStorage.setItem('scrollLocationHref', window.location.href); + this.storage.setItem('scrollLocationHref', window.location.href); } /** @@ -190,17 +206,17 @@ export class ScrollService implements OnDestroy { if (this.supportManualScrollRestoration) { const currentScrollPosition = this.viewportScroller.getScrollPosition(); this.location.replaceState(this.location.path(true), undefined, {scrollPosition: currentScrollPosition}); - window.sessionStorage.setItem('scrollPosition', currentScrollPosition.join(',')); + this.storage.setItem('scrollPosition', currentScrollPosition.join(',')); } } getStoredScrollLocationHref(): string | null { - const href = window.sessionStorage.getItem('scrollLocationHref'); + const href = this.storage.getItem('scrollLocationHref'); return href || null; } getStoredScrollPosition(): ScrollPosition | null { - const position = window.sessionStorage.getItem('scrollPosition'); + const position = this.storage.getItem('scrollPosition'); if (!position) { return null; } const [x, y] = position.split(','); @@ -208,8 +224,8 @@ export class ScrollService implements OnDestroy { } removeStoredScrollInfo() { - window.sessionStorage.removeItem('scrollLocationHref'); - window.sessionStorage.removeItem('scrollPosition'); + this.storage.removeItem('scrollLocationHref'); + this.storage.removeItem('scrollPosition'); } /**