From 2d1a48f6ab82923c0440ca89ddf4671be6701fe1 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Fri, 22 Mar 2019 23:02:17 +0100 Subject: [PATCH] fix(overlay): clear duplicate overlay container coming in from the server When coming from a server-side-rendered page, we may end up in a situation where there are multiple overlay containers on the page with stale overlays in them. These changes clear all old overlay containers before creating a new one. Relates to #11817. --- .../fullscreen-overlay-container.spec.ts | 3 +++ .../overlay/fullscreen-overlay-container.ts | 20 +++++++++++------- src/cdk/overlay/overlay-container.spec.ts | 10 +++++++++ src/cdk/overlay/overlay-container.ts | 21 +++++++++++++++---- tools/public_api_guard/cdk/overlay.d.ts | 4 ++-- 5 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/cdk/overlay/fullscreen-overlay-container.spec.ts b/src/cdk/overlay/fullscreen-overlay-container.spec.ts index cf3d97f82b2d..817052984066 100644 --- a/src/cdk/overlay/fullscreen-overlay-container.spec.ts +++ b/src/cdk/overlay/fullscreen-overlay-container.spec.ts @@ -47,6 +47,9 @@ describe('FullscreenOverlayContainer', () => { createElement: function() { return document.createElement.apply(document, arguments); }, + getElementsByClassName: function() { + return document.getElementsByClassName.apply(document, arguments); + } }; return fakeDocument; diff --git a/src/cdk/overlay/fullscreen-overlay-container.ts b/src/cdk/overlay/fullscreen-overlay-container.ts index 1913baa7c51e..0f0d8a541f22 100644 --- a/src/cdk/overlay/fullscreen-overlay-container.ts +++ b/src/cdk/overlay/fullscreen-overlay-container.ts @@ -66,13 +66,15 @@ export class FullscreenOverlayContainer extends OverlayContainer implements OnDe private _getEventName(): string | undefined { if (!this._fullScreenEventName) { - if (this._document.fullscreenEnabled) { + const _document = this._document as any; + + if (_document.fullscreenEnabled) { this._fullScreenEventName = 'fullscreenchange'; - } else if (this._document.webkitFullscreenEnabled) { + } else if (_document.webkitFullscreenEnabled) { this._fullScreenEventName = 'webkitfullscreenchange'; - } else if ((this._document as any).mozFullScreenEnabled) { + } else if (_document.mozFullScreenEnabled) { this._fullScreenEventName = 'mozfullscreenchange'; - } else if ((this._document as any).msFullscreenEnabled) { + } else if (_document.msFullscreenEnabled) { this._fullScreenEventName = 'MSFullscreenChange'; } } @@ -85,10 +87,12 @@ export class FullscreenOverlayContainer extends OverlayContainer implements OnDe * Only that element and its children are visible when in fullscreen mode. */ getFullscreenElement(): Element { - return this._document.fullscreenElement || - this._document.webkitFullscreenElement || - (this._document as any).mozFullScreenElement || - (this._document as any).msFullscreenElement || + const _document = this._document as any; + + return _document.fullscreenElement || + _document.webkitFullscreenElement || + _document.mozFullScreenElement || + _document.msFullscreenElement || null; } } diff --git a/src/cdk/overlay/overlay-container.spec.ts b/src/cdk/overlay/overlay-container.spec.ts index 28363ff9555c..cd92421ebd40 100644 --- a/src/cdk/overlay/overlay-container.spec.ts +++ b/src/cdk/overlay/overlay-container.spec.ts @@ -52,6 +52,16 @@ describe('OverlayContainer', () => { expect(containerElement.classList.contains('commander-shepard')) .toBe(false, 'Expected the overlay container not to have class "commander-shepard"'); }); + + it('should ensure that there is only one overlay container on the page', () => { + const extraContainer = document.createElement('div'); + extraContainer.classList.add('cdk-overlay-container'); + document.body.appendChild(extraContainer); + + overlayContainer.getContainerElement(); + + expect(document.querySelectorAll('.cdk-overlay-container').length).toBe(1); + }); }); /** Test-bed component that contains a TempatePortal and an ElementRef. */ diff --git a/src/cdk/overlay/overlay-container.ts b/src/cdk/overlay/overlay-container.ts index f034691e478c..45afbc69fd74 100644 --- a/src/cdk/overlay/overlay-container.ts +++ b/src/cdk/overlay/overlay-container.ts @@ -21,8 +21,11 @@ import { @Injectable({providedIn: 'root'}) export class OverlayContainer implements OnDestroy { protected _containerElement: HTMLElement; + protected _document: Document; - constructor(@Inject(DOCUMENT) protected _document: any) {} + constructor(@Inject(DOCUMENT) document: any) { + this._document = document; + } ngOnDestroy() { if (this._containerElement && this._containerElement.parentNode) { @@ -37,7 +40,10 @@ export class OverlayContainer implements OnDestroy { * @returns the container element */ getContainerElement(): HTMLElement { - if (!this._containerElement) { this._createContainer(); } + if (!this._containerElement) { + this._createContainer(); + } + return this._containerElement; } @@ -46,9 +52,16 @@ export class OverlayContainer implements OnDestroy { * with the 'cdk-overlay-container' class on the document body. */ protected _createContainer(): void { - const container = this._document.createElement('div'); + const containerClass = 'cdk-overlay-container'; + const previousContainers = this._document.getElementsByClassName(containerClass); - container.classList.add('cdk-overlay-container'); + // Remove any old containers. This can happen when transitioning from the server to the client. + for (let i = 0; i < previousContainers.length; i++) { + previousContainers[i].parentNode!.removeChild(previousContainers[i]); + } + + const container = this._document.createElement('div'); + container.classList.add(containerClass); this._document.body.appendChild(container); this._containerElement = container; } diff --git a/tools/public_api_guard/cdk/overlay.d.ts b/tools/public_api_guard/cdk/overlay.d.ts index 4728aeb96b77..4d81f567d429 100644 --- a/tools/public_api_guard/cdk/overlay.d.ts +++ b/tools/public_api_guard/cdk/overlay.d.ts @@ -196,8 +196,8 @@ export interface OverlayConnectionPosition { export declare class OverlayContainer implements OnDestroy { protected _containerElement: HTMLElement; - protected _document: any; - constructor(_document: any); + protected _document: Document; + constructor(document: any); protected _createContainer(): void; getContainerElement(): HTMLElement; ngOnDestroy(): void;