fix(common): ensure scrollRestoration is writable (#30630)

Some specialised browsers that do not support scroll restoration
(e.g. some web crawlers) do not allow `scrollRestoration` to be
writable.

We already sniff the browser to see if it has the `window.scrollTo`
method, so now we also check whether `window.history.scrollRestoration`
is writable too.

Fixes #30629

PR Close #30630
This commit is contained in:
marcvincenti
2019-05-23 14:44:46 +02:00
committed by Andrew Kushnir
parent 8227b56f9e
commit bb88c9fa3d
5 changed files with 69 additions and 6 deletions

View File

@ -80,6 +80,26 @@ describe('ScrollService', () => {
expect(updateScrollPositionInHistorySpy).toHaveBeenCalledTimes(1);
}));
it('should not support `manual` scrollRestoration when it is not writable', () => {
const original = Object.getOwnPropertyDescriptor(window.history, 'scrollRestoration');
try {
Object.defineProperty(window.history, 'scrollRestoration', {
value: 'auto',
configurable: true,
});
scrollService = createScrollService(
document, platformLocation as PlatformLocation, viewportScrollerStub, location);
expect(scrollService.supportManualScrollRestoration).toBe(false);
} finally {
if (original !== undefined) {
Object.defineProperty(window.history, 'scrollRestoration', original);
} else {
delete window.history.scrollRestoration;
}
}
});
it('should set `scrollRestoration` to `manual` if supported', () => {
if (scrollService.supportManualScrollRestoration) {
expect(window.history.scrollRestoration).toBe('manual');

View File

@ -24,8 +24,7 @@ export class ScrollService implements OnDestroy {
poppedStateScrollPosition: ScrollPosition|null = null;
// Whether the browser supports the necessary features for manual scroll restoration.
supportManualScrollRestoration: boolean = !!window && ('scrollTo' in window) &&
('scrollX' in window) && ('scrollY' in window) && !!history &&
('scrollRestoration' in history);
('scrollX' in window) && ('scrollY' in window) && isScrollRestorationWritable();
// Offset from the top of the document to bottom of any static elements
// at the top (e.g. toolbar) + some margin
@ -243,3 +242,20 @@ export class ScrollService implements OnDestroy {
return decodeURIComponent(this.platformLocation.hash.replace(/^#/, ''));
}
}
/**
* We need to check whether we can write to `history.scrollRestoration`
*
* We do this by checking the property descriptor of the property, but
* it might actually be defined on the `history` prototype not the instance.
*
* In this context "writable" means either than the property is a `writable`
* data file or a property that has a setter.
*/
function isScrollRestorationWritable() {
const scrollRestorationDescriptor =
Object.getOwnPropertyDescriptor(history, 'scrollRestoration') ||
Object.getOwnPropertyDescriptor(Object.getPrototypeOf(history), 'scrollRestoration');
return scrollRestorationDescriptor !== undefined &&
!!(scrollRestorationDescriptor.writable || scrollRestorationDescriptor.set);
}