From 948fb8b189e02f07c98c481298a4841fab36879d Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sat, 23 Jun 2018 17:42:17 +0200 Subject: [PATCH] fix(aria-describer): clear duplicate container coming in from the server When coming in from a server-side-rendered page we may end up in with multiple aria describer containers. These changes ensure that we only have one. Fixes #11817. --- .../aria-describer/aria-describer.spec.ts | 11 +++++++++ src/cdk/a11y/aria-describer/aria-describer.ts | 24 ++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/cdk/a11y/aria-describer/aria-describer.spec.ts b/src/cdk/a11y/aria-describer/aria-describer.spec.ts index 87d2cf8ab178..2a098fedfea3 100644 --- a/src/cdk/a11y/aria-describer/aria-describer.spec.ts +++ b/src/cdk/a11y/aria-describer/aria-describer.spec.ts @@ -121,6 +121,17 @@ describe('AriaDescriber', () => { const node: any = document.createComment('Not an element node'); expect(() => ariaDescriber.describe(node, 'This looks like an element')).not.toThrow(); }); + + it('should clear any pre-existing containers', () => { + const extraContainer = document.createElement('div'); + extraContainer.id = MESSAGES_CONTAINER_ID; + document.body.appendChild(extraContainer); + + ariaDescriber.describe(component.element1, 'Hello'); + + // Use `querySelectorAll` with an attribute since `getElementById` will stop at the first match. + expect(document.querySelectorAll(`[id='${MESSAGES_CONTAINER_ID}']`).length).toBe(1); + }); }); function getMessagesContainer() { diff --git a/src/cdk/a11y/aria-describer/aria-describer.ts b/src/cdk/a11y/aria-describer/aria-describer.ts index db23a516eb64..a63e15f397a4 100644 --- a/src/cdk/a11y/aria-describer/aria-describer.ts +++ b/src/cdk/a11y/aria-describer/aria-describer.ts @@ -127,7 +127,7 @@ export class AriaDescriber implements OnDestroy { messageElement.setAttribute('id', `${CDK_DESCRIBEDBY_ID_PREFIX}-${nextId++}`); messageElement.appendChild(this._document.createTextNode(message)!); - if (!messagesContainer) { this._createMessagesContainer(); } + this._createMessagesContainer(); messagesContainer!.appendChild(messageElement); messageRegistry.set(message, {messageElement, referenceCount: 0}); @@ -145,11 +145,23 @@ export class AriaDescriber implements OnDestroy { /** Creates the global container for all aria-describedby messages. */ private _createMessagesContainer() { - messagesContainer = this._document.createElement('div'); - messagesContainer.setAttribute('id', MESSAGES_CONTAINER_ID); - messagesContainer.setAttribute('aria-hidden', 'true'); - messagesContainer.style.display = 'none'; - this._document.body.appendChild(messagesContainer); + if (!messagesContainer) { + const preExistingContainer = this._document.getElementById(MESSAGES_CONTAINER_ID); + + // When going from the server to the client, we may end up in a situation where there's + // already a container on the page, but we don't have a reference to it. Clear the + // old container so we don't get duplicates. Doing this, instead of emptying the previous + // container, should be slightly faster. + if (preExistingContainer) { + preExistingContainer.parentNode!.removeChild(preExistingContainer); + } + + messagesContainer = this._document.createElement('div'); + messagesContainer.id = MESSAGES_CONTAINER_ID; + messagesContainer.setAttribute('aria-hidden', 'true'); + messagesContainer.style.display = 'none'; + this._document.body.appendChild(messagesContainer); + } } /** Deletes the global messages container. */