diff --git a/packages/rum-core/src/browser/htmlDomUtils.spec.ts b/packages/rum-core/src/browser/htmlDomUtils.spec.ts index 836574c365..07b0dfdbc7 100644 --- a/packages/rum-core/src/browser/htmlDomUtils.spec.ts +++ b/packages/rum-core/src/browser/htmlDomUtils.spec.ts @@ -1,5 +1,5 @@ import { isIE } from '@datadog/browser-core' -import { appendElement } from '../../test' +import { appendElement, appendText } from '../../test' import { isTextNode, isCommentNode, @@ -8,6 +8,7 @@ import { getParentNode, isNodeShadowHost, forEachChildNodes, + hasChildNodes, } from './htmlDomUtils' describe('isTextNode', () => { @@ -125,6 +126,31 @@ if (!isIE()) { }) } +describe('hasChildNode', () => { + beforeEach(() => { + if (isIE()) { + pending('IE not supported') + } + }) + + it('should return `true` if the element has a direct child node', () => { + expect(hasChildNodes(appendElement('
foo
'))).toBe(true) + expect(hasChildNodes(appendElement('

'))).toBe(true) + expect(hasChildNodes(appendElement('
'))).toBe(true) + }) + + it('should return `true` if the element is a shadow host', () => { + const container = appendElement('
') + container.attachShadow({ mode: 'open' }) + expect(hasChildNodes(container)).toBe(true) + }) + + it('should return `false` otherwise', () => { + expect(hasChildNodes(appendElement('
'))).toBe(false) + expect(hasChildNodes(appendText('foo'))).toBe(false) + }) +}) + describe('forEachChildNodes', () => { it('should iterate over the direct children for a normal node', () => { if (isIE()) { diff --git a/packages/rum-core/src/browser/htmlDomUtils.ts b/packages/rum-core/src/browser/htmlDomUtils.ts index af37d49812..ba70c51d3a 100644 --- a/packages/rum-core/src/browser/htmlDomUtils.ts +++ b/packages/rum-core/src/browser/htmlDomUtils.ts @@ -19,6 +19,10 @@ export function isNodeShadowRoot(node: Node): node is ShadowRoot { return !!shadowRoot.host && shadowRoot.nodeType === Node.DOCUMENT_FRAGMENT_NODE && isElementNode(shadowRoot.host) } +export function hasChildNodes(node: Node) { + return node.childNodes.length > 0 || isNodeShadowHost(node) +} + export function forEachChildNodes(node: Node, callback: (child: Node) => void) { node.childNodes.forEach(callback) if (isNodeShadowHost(node)) { diff --git a/packages/rum/src/domain/record/serialization/serializeNode.spec.ts b/packages/rum/src/domain/record/serialization/serializeNode.spec.ts index 5841da3028..461b6457da 100644 --- a/packages/rum/src/domain/record/serialization/serializeNode.spec.ts +++ b/packages/rum/src/domain/record/serialization/serializeNode.spec.ts @@ -451,68 +451,94 @@ describe('serializeNodeWithId', () => { }) }) - it('serializes a shadow host', () => { - const div = document.createElement('div') - div.attachShadow({ mode: 'open' }) - expect(serializeElement(div, DEFAULT_OPTIONS)).toEqual({ - type: NodeType.Element, - tagName: 'div', - attributes: {}, - isSVG: undefined, - childNodes: [ - { - type: NodeType.DocumentFragment, - isShadowRoot: true, - childNodes: [], - id: jasmine.any(Number) as unknown as number, - adoptedStyleSheets: undefined, - }, - ], - id: jasmine.any(Number) as unknown as number, + describe('shadow dom', () => { + it('serializes a shadow host', () => { + const div = document.createElement('div') + div.attachShadow({ mode: 'open' }) + expect(serializeElement(div, DEFAULT_OPTIONS)).toEqual({ + type: NodeType.Element, + tagName: 'div', + attributes: {}, + isSVG: undefined, + childNodes: [ + { + type: NodeType.DocumentFragment, + isShadowRoot: true, + childNodes: [], + id: jasmine.any(Number) as unknown as number, + adoptedStyleSheets: undefined, + }, + ], + id: jasmine.any(Number) as unknown as number, + }) }) - }) - it('serializes a shadow host with children', () => { - const div = document.createElement('div') - div.attachShadow({ mode: 'open' }) - div.shadowRoot!.appendChild(document.createElement('hr')) + it('serializes a shadow host with children', () => { + const div = document.createElement('div') + div.attachShadow({ mode: 'open' }) + div.shadowRoot!.appendChild(document.createElement('hr')) - const options: SerializeOptions = { - ...DEFAULT_OPTIONS, - serializationContext: { - ...DEFAULT_SERIALIZATION_CONTEXT, - shadowRootsController: { - ...DEFAULT_SHADOW_ROOT_CONTROLLER, - addShadowRoot: addShadowRootSpy, + const options: SerializeOptions = { + ...DEFAULT_OPTIONS, + serializationContext: { + ...DEFAULT_SERIALIZATION_CONTEXT, + shadowRootsController: { + ...DEFAULT_SHADOW_ROOT_CONTROLLER, + addShadowRoot: addShadowRootSpy, + }, }, - }, - } - expect(serializeElement(div, options)).toEqual({ - type: NodeType.Element, - tagName: 'div', - attributes: {}, - isSVG: undefined, - childNodes: [ - { - type: NodeType.DocumentFragment, - isShadowRoot: true, - adoptedStyleSheets: undefined, + } + expect(serializeElement(div, options)).toEqual({ + type: NodeType.Element, + tagName: 'div', + attributes: {}, + isSVG: undefined, + childNodes: [ + { + type: NodeType.DocumentFragment, + isShadowRoot: true, + adoptedStyleSheets: undefined, + childNodes: [ + { + type: NodeType.Element, + tagName: 'hr', + attributes: {}, + isSVG: undefined, + childNodes: [], + id: jasmine.any(Number) as unknown as number, + }, + ], + id: jasmine.any(Number) as unknown as number, + }, + ], + id: jasmine.any(Number) as unknown as number, + }) + expect(addShadowRootSpy).toHaveBeenCalledWith(div.shadowRoot!) + }) + + it('propagates the privacy mode to the shadow root children', () => { + const div = document.createElement('div') + div.setAttribute(PRIVACY_ATTR_NAME, PRIVACY_ATTR_VALUE_MASK) + div.attachShadow({ mode: 'open' }) + div.shadowRoot!.appendChild(document.createTextNode('foo')) + + expect(serializeElement(div, DEFAULT_OPTIONS)).toEqual( + jasmine.objectContaining({ + attributes: { + [PRIVACY_ATTR_NAME]: PRIVACY_ATTR_VALUE_MASK, + }, childNodes: [ - { - type: NodeType.Element, - tagName: 'hr', - attributes: {}, - isSVG: undefined, - childNodes: [], - id: jasmine.any(Number) as unknown as number, - }, + jasmine.objectContaining({ + childNodes: [ + jasmine.objectContaining({ + textContent: 'xxx', + }), + ], + }), ], - id: jasmine.any(Number) as unknown as number, - }, - ], - id: jasmine.any(Number) as unknown as number, + }) + ) }) - expect(addShadowRootSpy).toHaveBeenCalledWith(div.shadowRoot!) }) describe('