This commit fixes a regression from "fix(common): ensure scrollRestoration is writable (#30630)" that caused scrolling to not happen at all in browsers that do not support scroll restoration. The issue was that `supportScrollRestoration` was updated to return `false` if a browser did not have a writable `scrollRestoration`. However, the previous behavior was that the function would return `true` if `window.scrollTo` was defined. Every scrolling function in the `ViewportScroller` used `supportScrollRestoration` and, with the update in bb88c9fa3daac80086efbda951d81c159e3840f4, no scrolling would be performed if a browser did not have writable `scrollRestoration` but _did_ have `window.scrollTo`. Note, that this failure was detected in the saucelabs tests. IE does not support scroll restoration so IE tests were failing. PR Close #38468
		
			
				
	
	
		
			212 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			212 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google LLC All Rights Reserved.
 | |
|  *
 | |
|  * Use of this source code is governed by an MIT-style license that can be
 | |
|  * found in the LICENSE file at https://angular.io/license
 | |
|  */
 | |
| 
 | |
| import {ErrorHandler, ɵɵdefineInjectable, ɵɵinject} from '@angular/core';
 | |
| 
 | |
| import {DOCUMENT} from './dom_tokens';
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Defines a scroll position manager. Implemented by `BrowserViewportScroller`.
 | |
|  *
 | |
|  * @publicApi
 | |
|  */
 | |
| export abstract class ViewportScroller {
 | |
|   // De-sugared tree-shakable injection
 | |
|   // See #23917
 | |
|   /** @nocollapse */
 | |
|   static ɵprov = ɵɵdefineInjectable({
 | |
|     token: ViewportScroller,
 | |
|     providedIn: 'root',
 | |
|     factory: () => new BrowserViewportScroller(ɵɵinject(DOCUMENT), window, ɵɵinject(ErrorHandler))
 | |
|   });
 | |
| 
 | |
|   /**
 | |
|    * Configures the top offset used when scrolling to an anchor.
 | |
|    * @param offset A position in screen coordinates (a tuple with x and y values)
 | |
|    * or a function that returns the top offset position.
 | |
|    *
 | |
|    */
 | |
|   abstract setOffset(offset: [number, number]|(() => [number, number])): void;
 | |
| 
 | |
|   /**
 | |
|    * Retrieves the current scroll position.
 | |
|    * @returns A position in screen coordinates (a tuple with x and y values).
 | |
|    */
 | |
|   abstract getScrollPosition(): [number, number];
 | |
| 
 | |
|   /**
 | |
|    * Scrolls to a specified position.
 | |
|    * @param position A position in screen coordinates (a tuple with x and y values).
 | |
|    */
 | |
|   abstract scrollToPosition(position: [number, number]): void;
 | |
| 
 | |
|   /**
 | |
|    * Scrolls to an anchor element.
 | |
|    * @param anchor The ID of the anchor element.
 | |
|    */
 | |
|   abstract scrollToAnchor(anchor: string): void;
 | |
| 
 | |
|   /**
 | |
|    * Disables automatic scroll restoration provided by the browser.
 | |
|    * See also [window.history.scrollRestoration
 | |
|    * info](https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration).
 | |
|    */
 | |
|   abstract setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Manages the scroll position for a browser window.
 | |
|  */
 | |
| export class BrowserViewportScroller implements ViewportScroller {
 | |
|   private offset: () => [number, number] = () => [0, 0];
 | |
| 
 | |
|   constructor(private document: any, private window: any, private errorHandler: ErrorHandler) {}
 | |
| 
 | |
|   /**
 | |
|    * Configures the top offset used when scrolling to an anchor.
 | |
|    * @param offset A position in screen coordinates (a tuple with x and y values)
 | |
|    * or a function that returns the top offset position.
 | |
|    *
 | |
|    */
 | |
|   setOffset(offset: [number, number]|(() => [number, number])): void {
 | |
|     if (Array.isArray(offset)) {
 | |
|       this.offset = () => offset;
 | |
|     } else {
 | |
|       this.offset = offset;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Retrieves the current scroll position.
 | |
|    * @returns The position in screen coordinates.
 | |
|    */
 | |
|   getScrollPosition(): [number, number] {
 | |
|     if (this.supportsScrolling()) {
 | |
|       return [this.window.scrollX, this.window.scrollY];
 | |
|     } else {
 | |
|       return [0, 0];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Sets the scroll position.
 | |
|    * @param position The new position in screen coordinates.
 | |
|    */
 | |
|   scrollToPosition(position: [number, number]): void {
 | |
|     if (this.supportsScrolling()) {
 | |
|       this.window.scrollTo(position[0], position[1]);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Scrolls to an anchor element.
 | |
|    * @param anchor The ID of the anchor element.
 | |
|    */
 | |
|   scrollToAnchor(anchor: string): void {
 | |
|     if (this.supportsScrolling()) {
 | |
|       const elSelected =
 | |
|           this.document.getElementById(anchor) || this.document.getElementsByName(anchor)[0];
 | |
|       if (elSelected) {
 | |
|         this.scrollToElement(elSelected);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Disables automatic scroll restoration provided by the browser.
 | |
|    */
 | |
|   setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void {
 | |
|     if (this.supportScrollRestoration()) {
 | |
|       const history = this.window.history;
 | |
|       if (history && history.scrollRestoration) {
 | |
|         history.scrollRestoration = scrollRestoration;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   private scrollToElement(el: any): void {
 | |
|     const rect = el.getBoundingClientRect();
 | |
|     const left = rect.left + this.window.pageXOffset;
 | |
|     const top = rect.top + this.window.pageYOffset;
 | |
|     const offset = this.offset();
 | |
|     this.window.scrollTo(left - offset[0], top - offset[1]);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * We only support scroll restoration when we can get a hold of window.
 | |
|    * This means that we do not support this behavior when running in a web worker.
 | |
|    *
 | |
|    * Lifting this restriction right now would require more changes in the dom adapter.
 | |
|    * Since webworkers aren't widely used, we will lift it once RouterScroller is
 | |
|    * battle-tested.
 | |
|    */
 | |
|   private supportScrollRestoration(): boolean {
 | |
|     try {
 | |
|       if (!this.window || !this.window.scrollTo) {
 | |
|         return false;
 | |
|       }
 | |
|       // The `scrollRestoration` property could be on the `history` instance or its prototype.
 | |
|       const scrollRestorationDescriptor = getScrollRestorationProperty(this.window.history) ||
 | |
|           getScrollRestorationProperty(Object.getPrototypeOf(this.window.history));
 | |
|       // We can write to the `scrollRestoration` property if it is a writable data field or it has a
 | |
|       // setter function.
 | |
|       return !!scrollRestorationDescriptor &&
 | |
|           !!(scrollRestorationDescriptor.writable || scrollRestorationDescriptor.set);
 | |
|     } catch {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   private supportsScrolling(): boolean {
 | |
|     try {
 | |
|       return !!this.window.scrollTo;
 | |
|     } catch {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| function getScrollRestorationProperty(obj: any): PropertyDescriptor|undefined {
 | |
|   return Object.getOwnPropertyDescriptor(obj, 'scrollRestoration');
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Provides an empty implementation of the viewport scroller. This will
 | |
|  * live in @angular/common as it will be used by both platform-server and platform-webworker.
 | |
|  */
 | |
| export class NullViewportScroller implements ViewportScroller {
 | |
|   /**
 | |
|    * Empty implementation
 | |
|    */
 | |
|   setOffset(offset: [number, number]|(() => [number, number])): void {}
 | |
| 
 | |
|   /**
 | |
|    * Empty implementation
 | |
|    */
 | |
|   getScrollPosition(): [number, number] {
 | |
|     return [0, 0];
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Empty implementation
 | |
|    */
 | |
|   scrollToPosition(position: [number, number]): void {}
 | |
| 
 | |
|   /**
 | |
|    * Empty implementation
 | |
|    */
 | |
|   scrollToAnchor(anchor: string): void {}
 | |
| 
 | |
|   /**
 | |
|    * Empty implementation
 | |
|    */
 | |
|   setHistoryScrollRestoration(scrollRestoration: 'auto'|'manual'): void {}
 | |
| }
 |