refactor(common): use getElementById in ViewportScroller.scrollToAnchor (#30143)
This commit uses getElementById and getElementsByName when an anchor scroll happens, to avoid escaping the anchor and wrapping the code in a try/catch block. Related to #28960 PR Close #30143
This commit is contained in:
parent
702958e968
commit
354e66efad
@ -49,9 +49,9 @@
|
|||||||
"master": {
|
"master": {
|
||||||
"uncompressed": {
|
"uncompressed": {
|
||||||
"runtime-es2015": 2289,
|
"runtime-es2015": 2289,
|
||||||
"main-es2015": 221897,
|
"main-es2015": 221939,
|
||||||
"polyfills-es2015": 36938,
|
"polyfills-es2015": 36723,
|
||||||
"5-es2015": 779
|
"5-es2015": 781
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -111,26 +111,10 @@ export class BrowserViewportScroller implements ViewportScroller {
|
|||||||
*/
|
*/
|
||||||
scrollToAnchor(anchor: string): void {
|
scrollToAnchor(anchor: string): void {
|
||||||
if (this.supportScrollRestoration()) {
|
if (this.supportScrollRestoration()) {
|
||||||
// Escape anything passed to `querySelector` as it can throw errors and stop the application
|
const elSelected =
|
||||||
// from working if invalid values are passed.
|
this.document.getElementById(anchor) || this.document.getElementsByName(anchor)[0];
|
||||||
if (this.window.CSS && this.window.CSS.escape) {
|
if (elSelected) {
|
||||||
anchor = this.window.CSS.escape(anchor);
|
this.scrollToElement(elSelected);
|
||||||
} else {
|
|
||||||
anchor = anchor.replace(/(\"|\'\ |:|\.|\[|\]|,|=)/g, '\\$1');
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const elSelectedById = this.document.querySelector(`#${anchor}`);
|
|
||||||
if (elSelectedById) {
|
|
||||||
this.scrollToElement(elSelectedById);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const elSelectedByName = this.document.querySelector(`[name='${anchor}']`);
|
|
||||||
if (elSelectedByName) {
|
|
||||||
this.scrollToElement(elSelectedByName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
this.errorHandler.handleError(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,34 +6,23 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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 {describe, expect, it} from '@angular/core/testing/src/testing_internal';
|
import {describe, expect, it} from '@angular/core/testing/src/testing_internal';
|
||||||
import {BrowserViewportScroller, ViewportScroller} from '../src/viewport_scroller';
|
import {BrowserViewportScroller, ViewportScroller} from '../src/viewport_scroller';
|
||||||
|
|
||||||
{
|
describe('BrowserViewportScroller', () => {
|
||||||
describe('BrowserViewportScroller', () => {
|
let scroller: ViewportScroller;
|
||||||
let scroller: ViewportScroller;
|
let documentSpy: any;
|
||||||
let documentSpy: any;
|
let windowSpy: any;
|
||||||
let windowSpy: any;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
windowSpy = jasmine.createSpyObj('window', ['history']);
|
windowSpy = jasmine.createSpyObj('window', ['history']);
|
||||||
windowSpy.scrollTo = 1;
|
windowSpy.scrollTo = 1;
|
||||||
windowSpy.history.scrollRestoration = 'auto';
|
windowSpy.history.scrollRestoration = 'auto';
|
||||||
|
documentSpy = jasmine.createSpyObj('document', ['getElementById', 'getElementsByName']);
|
||||||
documentSpy = jasmine.createSpyObj('document', ['querySelector']);
|
scroller = new BrowserViewportScroller(documentSpy, windowSpy, null!);
|
||||||
scroller = new BrowserViewportScroller(documentSpy, windowSpy, null!);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
|
describe('setHistoryScrollRestoration', () => {
|
||||||
it('should not crash when scrollRestoration is not writable', () => {
|
it('should not crash when scrollRestoration is not writable', () => {
|
||||||
Object.defineProperty(windowSpy.history, 'scrollRestoration', {
|
Object.defineProperty(windowSpy.history, 'scrollRestoration', {
|
||||||
value: 'auto',
|
value: 'auto',
|
||||||
@ -41,15 +30,37 @@ import {BrowserViewportScroller, ViewportScroller} from '../src/viewport_scrolle
|
|||||||
});
|
});
|
||||||
expect(() => scroller.setHistoryScrollRestoration('manual')).not.toThrow();
|
expect(() => scroller.setHistoryScrollRestoration('manual')).not.toThrow();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('escapes invalid characters selectors', () => {
|
describe('scrollToAnchor', () => {
|
||||||
const invalidSelectorChars = `"' :.[],=`;
|
const anchor = 'anchor';
|
||||||
// Double escaped to make sure we match the actual value passed to `querySelector`
|
const el = document.createElement('a');
|
||||||
const escapedInvalids = `\\"\\' \\:\\.\\[\\]\\,\\=`;
|
|
||||||
scroller.scrollToAnchor(`specials=${invalidSelectorChars}`);
|
it('should only call getElementById when an element is found by id', () => {
|
||||||
expect(documentSpy.querySelector).toHaveBeenCalledWith(`#specials\\=${escapedInvalids}`);
|
documentSpy.getElementById.and.returnValue(el);
|
||||||
expect(documentSpy.querySelector)
|
spyOn<any>(scroller, 'scrollToElement');
|
||||||
.toHaveBeenCalledWith(`[name='specials\\=${escapedInvalids}']`);
|
scroller.scrollToAnchor(anchor);
|
||||||
|
expect(documentSpy.getElementById).toHaveBeenCalledWith(anchor);
|
||||||
|
expect(documentSpy.getElementsByName).not.toHaveBeenCalled();
|
||||||
|
expect((scroller as any).scrollToElement).toHaveBeenCalledWith(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call getElementById and getElementsByName when an element is found by name', () => {
|
||||||
|
documentSpy.getElementsByName.and.returnValue([el]);
|
||||||
|
spyOn<any>(scroller, 'scrollToElement');
|
||||||
|
scroller.scrollToAnchor(anchor);
|
||||||
|
expect(documentSpy.getElementById).toHaveBeenCalledWith(anchor);
|
||||||
|
expect(documentSpy.getElementsByName).toHaveBeenCalledWith(anchor);
|
||||||
|
expect((scroller as any).scrollToElement).toHaveBeenCalledWith(el);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call scrollToElement when an element is not found by its id or its name', () => {
|
||||||
|
documentSpy.getElementsByName.and.returnValue([]);
|
||||||
|
spyOn<any>(scroller, 'scrollToElement');
|
||||||
|
scroller.scrollToAnchor(anchor);
|
||||||
|
expect(documentSpy.getElementById).toHaveBeenCalledWith(anchor);
|
||||||
|
expect(documentSpy.getElementsByName).toHaveBeenCalledWith(anchor);
|
||||||
|
expect((scroller as any).scrollToElement).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user