Skip to content

Commit fa96c8a

Browse files
authored
refactor(platform): consolidate and expose shadow dom detection utilities (#18582)
Over time we had accumulated the same utility for finding the shadow root of an element in multiple places. These changes move it into a common place under `cdk/platform`. I also decided to remove the underscore from `_supportsShadowDom` so it's officially a public API. It was already exposed under `cdk/platform` so there was nothing stopping people from using it, but the underscore looked weird. The API should be stable enough, since we've had it for more than a year in multiple components and we haven't had issues with it.
1 parent ee3958d commit fa96c8a

File tree

6 files changed

+29
-59
lines changed

6 files changed

+29
-59
lines changed

src/cdk/drag-drop/drop-list-ref.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {ElementRef, NgZone} from '@angular/core';
1010
import {Direction} from '@angular/cdk/bidi';
1111
import {coerceElement} from '@angular/cdk/coercion';
1212
import {ViewportRuler} from '@angular/cdk/scrolling';
13-
import {_supportsShadowDom} from '@angular/cdk/platform';
13+
import {_getShadowRoot} from '@angular/cdk/platform';
1414
import {Subject, Subscription, interval, animationFrameScheduler} from 'rxjs';
1515
import {takeUntil} from 'rxjs/operators';
1616
import {moveItemInArray} from './drag-utils';
@@ -907,7 +907,8 @@ export class DropListRef<T = any> {
907907
*/
908908
private _getShadowRoot(): DocumentOrShadowRoot {
909909
if (!this._cachedShadowRoot) {
910-
this._cachedShadowRoot = getShadowRoot(coerceElement(this.element)) || this._document;
910+
const shadowRoot = _getShadowRoot(coerceElement(this.element)) as ShadowRoot | null;
911+
this._cachedShadowRoot = shadowRoot || this._document;
911912
}
912913

913914
return this._cachedShadowRoot;
@@ -1102,16 +1103,3 @@ function getElementScrollDirections(element: HTMLElement, clientRect: ClientRect
11021103

11031104
return [verticalScrollDirection, horizontalScrollDirection];
11041105
}
1105-
1106-
/** Gets the shadow root of an element, if any. */
1107-
function getShadowRoot(element: HTMLElement): DocumentOrShadowRoot | null {
1108-
if (_supportsShadowDom()) {
1109-
const rootNode = element.getRootNode ? element.getRootNode() : null;
1110-
1111-
if (rootNode instanceof ShadowRoot) {
1112-
return rootNode;
1113-
}
1114-
}
1115-
1116-
return null;
1117-
}

src/cdk/platform/features/shadow-dom.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,16 @@ export function _supportsShadowDom(): boolean {
1717

1818
return shadowDomIsSupported;
1919
}
20+
21+
/** Gets the shadow root of an element, if supported and the element is inside the Shadow DOM. */
22+
export function _getShadowRoot(element: HTMLElement): Node | null {
23+
if (_supportsShadowDom()) {
24+
const rootNode = element.getRootNode ? element.getRootNode() : null;
25+
26+
if (rootNode instanceof ShadowRoot) {
27+
return rootNode;
28+
}
29+
}
30+
31+
return null;
32+
}

src/material/autocomplete/autocomplete-trigger.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
ScrollStrategy,
1818
ConnectedPosition,
1919
} from '@angular/cdk/overlay';
20-
import {_supportsShadowDom} from '@angular/cdk/platform';
20+
import {_getShadowRoot} from '@angular/cdk/platform';
2121
import {TemplatePortal} from '@angular/cdk/portal';
2222
import {ViewportRuler} from '@angular/cdk/scrolling';
2323
import {DOCUMENT} from '@angular/common';
@@ -230,14 +230,7 @@ export class MatAutocompleteTrigger implements ControlValueAccessor, AfterViewIn
230230
window.addEventListener('blur', this._windowBlurHandler);
231231
});
232232

233-
if (_supportsShadowDom()) {
234-
const element = this._element.nativeElement;
235-
const rootNode = element.getRootNode ? element.getRootNode() : null;
236-
237-
// We need to take the `ShadowRoot` off of `window`, because the built-in types are
238-
// incorrect. See https://github.com/Microsoft/TypeScript/issues/27929.
239-
this._isInsideShadowRoot = rootNode instanceof (window as any).ShadowRoot;
240-
}
233+
this._isInsideShadowRoot = !!_getShadowRoot(this._element.nativeElement);
241234
}
242235
}
243236

src/material/progress-spinner/progress-spinner.spec.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
import {TestBed, async, inject} from '@angular/core/testing';
22
import {Component, ViewEncapsulation, ViewChild, ElementRef} from '@angular/core';
33
import {By} from '@angular/platform-browser';
4-
import {Platform} from '@angular/cdk/platform';
4+
import {Platform, _getShadowRoot, _supportsShadowDom} from '@angular/cdk/platform';
55
import {CommonModule} from '@angular/common';
6-
import {_getShadowRoot} from './progress-spinner';
76
import {
87
MatProgressSpinnerModule,
98
MatProgressSpinner,
109
MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS,
1110
} from './index';
1211

1312
describe('MatProgressSpinner', () => {
14-
const supportsShadowDom = typeof document.createElement('div').attachShadow !== 'undefined';
15-
1613
beforeEach(async(() => {
1714
TestBed.configureTestingModule({
1815
imports: [MatProgressSpinnerModule, CommonModule],
@@ -357,7 +354,7 @@ describe('MatProgressSpinner', () => {
357354

358355
it('should add the indeterminate animation style tag to the Shadow root', () => {
359356
// The test is only relevant in browsers that support Shadow DOM.
360-
if (!supportsShadowDom) {
357+
if (!_supportsShadowDom()) {
361358
return;
362359
}
363360

@@ -366,7 +363,7 @@ describe('MatProgressSpinner', () => {
366363
fixture.detectChanges();
367364

368365
const spinner = fixture.debugElement.query(By.css('mat-progress-spinner'))!.nativeElement;
369-
const shadowRoot = _getShadowRoot(spinner, document) as HTMLElement;
366+
const shadowRoot = _getShadowRoot(spinner) as HTMLElement;
370367

371368
expect(shadowRoot.querySelector('style[mat-spinner-animation="27"]')).toBeTruthy();
372369

@@ -378,7 +375,7 @@ describe('MatProgressSpinner', () => {
378375

379376
it('should not duplicate style tags inside the Shadow root', () => {
380377
// The test is only relevant in browsers that support Shadow DOM.
381-
if (!supportsShadowDom) {
378+
if (!_supportsShadowDom()) {
382379
return;
383380
}
384381

@@ -387,7 +384,7 @@ describe('MatProgressSpinner', () => {
387384
fixture.detectChanges();
388385

389386
const spinner = fixture.debugElement.query(By.css('mat-progress-spinner'))!.nativeElement;
390-
const shadowRoot = _getShadowRoot(spinner, document) as HTMLElement;
387+
const shadowRoot = _getShadowRoot(spinner) as HTMLElement;
391388

392389
expect(shadowRoot.querySelectorAll('style[mat-spinner-animation="39"]').length).toBe(1);
393390

@@ -409,7 +406,7 @@ describe('MatProgressSpinner', () => {
409406
it('should add the indeterminate animation style tag to the Shadow root if the element is ' +
410407
'inside an ngIf', () => {
411408
// The test is only relevant in browsers that support Shadow DOM.
412-
if (!supportsShadowDom) {
409+
if (!_supportsShadowDom()) {
413410
return;
414411
}
415412

@@ -418,7 +415,7 @@ describe('MatProgressSpinner', () => {
418415
fixture.detectChanges();
419416

420417
const spinner = fixture.componentInstance.spinner.nativeElement;
421-
const shadowRoot = _getShadowRoot(spinner, document) as HTMLElement;
418+
const shadowRoot = _getShadowRoot(spinner) as HTMLElement;
422419

423420
expect(shadowRoot.querySelector('style[mat-spinner-animation="27"]')).toBeTruthy();
424421

src/material/progress-spinner/progress-spinner.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {coerceNumberProperty, NumberInput} from '@angular/cdk/coercion';
10-
import {Platform} from '@angular/cdk/platform';
10+
import {Platform, _getShadowRoot} from '@angular/cdk/platform';
1111
import {DOCUMENT} from '@angular/common';
1212
import {
1313
ChangeDetectionStrategy,
@@ -218,7 +218,7 @@ export class MatProgressSpinner extends _MatProgressSpinnerMixinBase implements
218218
// Note that we need to look up the root node in ngOnInit, rather than the constructor, because
219219
// Angular seems to create the element outside the shadow root and then moves it inside, if the
220220
// node is inside an `ngIf` and a ShadowDom-encapsulated component.
221-
this._styleRoot = _getShadowRoot(element, this._document) || this._document.head;
221+
this._styleRoot = _getShadowRoot(element) || this._document.head;
222222
this._attachStyleNode();
223223

224224
// On IE and Edge, we can't animate the `stroke-dashoffset`
@@ -333,26 +333,3 @@ export class MatSpinner extends MatProgressSpinner {
333333
this.mode = 'indeterminate';
334334
}
335335
}
336-
337-
338-
/** Gets the shadow root of an element, if supported and the element is inside the Shadow DOM. */
339-
export function _getShadowRoot(element: HTMLElement, _document: Document): Node | null {
340-
// TODO(crisbeto): see whether we should move this into the CDK
341-
// feature detection utilities once #15616 gets merged in.
342-
if (typeof window !== 'undefined') {
343-
const head = _document.head;
344-
345-
// Check whether the browser supports Shadow DOM.
346-
if (head && ((head as any).createShadowRoot || head.attachShadow)) {
347-
const rootNode = element.getRootNode ? element.getRootNode() : null;
348-
349-
// We need to take the `ShadowRoot` off of `window`, because the built-in types are
350-
// incorrect. See https://github.com/Microsoft/TypeScript/issues/27929.
351-
if (rootNode instanceof (window as any).ShadowRoot) {
352-
return rootNode;
353-
}
354-
}
355-
}
356-
357-
return null;
358-
}

tools/public_api_guard/cdk/platform.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
export declare function _getShadowRoot(element: HTMLElement): Node | null;
2+
13
export declare function _supportsShadowDom(): boolean;
24

35
export declare function getRtlScrollAxisType(): RtlScrollAxisType;

0 commit comments

Comments
 (0)