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('