-
Notifications
You must be signed in to change notification settings - Fork 130
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[REPLAY] Add support for shadow DOM (#1787)
* Extract dom node utils * [REPLAY] Serialize element from shadow root * [REPLAY] Capture mutation from inside shadow DOM * [REPLAY] Support target in event for shadow DOM * [REPLAY] Fix privacy setting across shadow DOM * [REPLAY] Support change event in shadow DOM * [REPLAY] Add e2e tests for shadow DOM * [REPLAY] Move shadowDom support behind experimental flag * [REPLAY] Improve shadow dom serialization * [REPLAY] Improve shadow dom mutations capture * [REPLAY] Improve shadow DOM e2e tests * [REPLAY] Add isShadowHost util * [REPLAY] Flush mutation correctly for shadow DOM * [REPLAY] Fix type guards * [REPLAY] Fix event target capture * [REPLAY] Use type guards * [REPLAY] rename flag to use snake case * [REPLAY] Remove mutation controller * [REPLAY] Extract shadow DOM logic in its file * [REPLAY] run shadow dom callback on unfiltered mutation * [REPLAY] Move 'isIE' from describe to beforeEach * [REPLAY] Fix e2e tests * [REPLAY] Fix typo in test label * [REPLAY] Refactor flush mutations system * [REPLAY] Serialize shadow root as document fragment * [REPLAY] Update misplaced comment Co-authored-by: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> * [REPLAY] Factorize export * [REPLAY] Refactor shadow dom controller * [REPLAY] Update replay event * [REPLAY] Use shadow dom controller interface instead of callbacks * [REPLAY] Improve e2e test for shadow Dom * [REPLAY] Remove e2e test that duplicated * [REPLAY] Prefer push over reassign Co-authored-by: Benoît <benoit.zugmeyer@datadoghq.com> * [REPLAY] Reuse existing helpers Co-authored-by: Benoît <benoit.zugmeyer@datadoghq.com> * [REPLAY] Update test labels Co-authored-by: Benoît <benoit.zugmeyer@datadoghq.com> * [REPLAY] Skip unit tests on IE because of shadow DOM * [REPLAY] Fix typo Co-authored-by: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> * [REPLAY] Update labels * [REPLAY] Rename file to match function inside * [REPLAY] Inlines function * [REPLAY] Rename types in shadow root controller Co-authored-by: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Co-authored-by: Benoît <benoit.zugmeyer@datadoghq.com>
- Loading branch information
1 parent
1dcc3a9
commit 9f4cf2f
Showing
22 changed files
with
1,358 additions
and
204 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import { isIE } from '@datadog/browser-core' | ||
import { | ||
isTextNode, | ||
isCommentNode, | ||
isElementNode, | ||
isNodeShadowRoot, | ||
getChildNodes, | ||
getParentNode, | ||
isNodeShadowHost, | ||
} from './htmlDomUtils' | ||
|
||
describe('isTextNode', () => { | ||
const parameters: Array<[Node, boolean]> = [ | ||
[document.createTextNode('hello'), true], | ||
[document.createElement('div'), false], | ||
[document.body, false], | ||
[document.createComment('hello'), false], | ||
['hello' as unknown as Node, false], | ||
] | ||
|
||
parameters.forEach(([element, result]) => { | ||
it(`should return ${String(result)} for "${String(element)}"`, () => { | ||
expect(isTextNode(element)).toBe(result) | ||
}) | ||
}) | ||
}) | ||
|
||
describe('isCommentNode', () => { | ||
const parameters: Array<[Node, boolean]> = [ | ||
[document.createComment('hello'), true], | ||
[document.createTextNode('hello'), false], | ||
[document.createElement('div'), false], | ||
[document.body, false], | ||
['hello' as unknown as Node, false], | ||
] | ||
|
||
parameters.forEach(([element, result]) => { | ||
it(`should return ${String(result)} for "${String(element)}"`, () => { | ||
expect(isCommentNode(element)).toBe(result) | ||
}) | ||
}) | ||
}) | ||
|
||
describe('isElementNode', () => { | ||
const parameters: Array<[Node, boolean]> = [ | ||
[document.createElement('div'), true], | ||
[document.body, true], | ||
[document.createTextNode('hello'), false], | ||
[document.createComment('hello'), false], | ||
['hello' as unknown as Node, false], | ||
] | ||
|
||
parameters.forEach(([element, result]) => { | ||
it(`should return ${String(result)} for "${String(element)}"`, () => { | ||
expect(isElementNode(element)).toBe(result) | ||
}) | ||
}) | ||
}) | ||
|
||
describe('isShadowRoot', () => { | ||
if (isIE()) { | ||
return | ||
} | ||
|
||
const parent = document.createElement('div') | ||
parent.attachShadow({ mode: 'open' }) | ||
const parameters: Array<[Node, boolean]> = [ | ||
[parent.shadowRoot!, true], | ||
[parent, false], | ||
[document.body, false], | ||
[document.createTextNode('hello'), false], | ||
[document.createComment('hello'), false], | ||
] | ||
|
||
parameters.forEach(([element, result]) => { | ||
it(`should return ${String(result)} for "${String(element)}"`, () => { | ||
expect(isNodeShadowRoot(element)).toBe(result) | ||
}) | ||
}) | ||
}) | ||
|
||
describe('isShadowHost', () => { | ||
if (isIE()) { | ||
return | ||
} | ||
const host = document.createElement('div') | ||
host.attachShadow({ mode: 'open' }) | ||
const parameters: Array<[Node, boolean]> = [ | ||
[host, true], | ||
[host.shadowRoot!, false], | ||
[document.body, false], | ||
[document.createTextNode('hello'), false], | ||
[document.createComment('hello'), false], | ||
] | ||
|
||
parameters.forEach(([element, result]) => { | ||
it(`should return ${String(result)} for "${String(element)}"`, () => { | ||
expect(isNodeShadowHost(element)).toBe(result) | ||
}) | ||
}) | ||
}) | ||
|
||
describe('getChildNodes', () => { | ||
it('should return the direct children for a normal node', () => { | ||
if (isIE()) { | ||
pending('IE not supported') | ||
} | ||
|
||
const children: Node[] = [ | ||
document.createTextNode('toto'), | ||
document.createElement('span'), | ||
document.createComment('oops'), | ||
] | ||
const container = document.createElement('div') | ||
children.forEach((node) => { | ||
container.appendChild(node) | ||
}) | ||
|
||
expect(getChildNodes(container).length).toBe(children.length) | ||
}) | ||
|
||
it('should return the children of the shadow root for a node that is a host', () => { | ||
if (isIE()) { | ||
pending('IE not supported') | ||
} | ||
|
||
const children: Node[] = [ | ||
document.createTextNode('toto'), | ||
document.createElement('span'), | ||
document.createComment('oops'), | ||
] | ||
const container = document.createElement('div') | ||
container.attachShadow({ mode: 'open' }) | ||
|
||
children.forEach((node) => { | ||
container.shadowRoot!.appendChild(node) | ||
}) | ||
|
||
expect(getChildNodes(container).length).toBe(children.length) | ||
}) | ||
}) | ||
|
||
describe('getParentNode', () => { | ||
if (isIE()) { | ||
return | ||
} | ||
|
||
const orphanDiv = document.createElement('div') | ||
const parentWithShadowRoot = document.createElement('div') | ||
const shadowRoot = parentWithShadowRoot.attachShadow({ mode: 'open' }) | ||
|
||
const parentWithoutShadowRoot = document.createElement('div') | ||
const child = document.createElement('span') | ||
parentWithoutShadowRoot.appendChild(child) | ||
|
||
const parameters: Array<[string, Node, Node | null]> = [ | ||
['return null if without parent', orphanDiv, null], | ||
['return the host for a shadow root', shadowRoot, parentWithShadowRoot], | ||
['return the parent for normal child', child, parentWithoutShadowRoot], | ||
] | ||
parameters.forEach(([label, element, result]) => { | ||
it(`should ${label}`, () => { | ||
expect(getParentNode(element)).toBe(result) | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
export function isTextNode(node: Node): node is Text { | ||
return node.nodeType === Node.TEXT_NODE | ||
} | ||
|
||
export function isCommentNode(node: Node): node is Comment { | ||
return node.nodeType === Node.COMMENT_NODE | ||
} | ||
|
||
export function isElementNode(node: Node): node is Element { | ||
return node.nodeType === Node.ELEMENT_NODE | ||
} | ||
|
||
export function isNodeShadowHost(node: Node): node is Element & { shadowRoot: ShadowRoot } { | ||
return isElementNode(node) && node.shadowRoot !== null | ||
} | ||
|
||
export function isNodeShadowRoot(node: Node): node is ShadowRoot { | ||
const shadowRoot = node as ShadowRoot | ||
return !!shadowRoot.host && isElementNode(shadowRoot.host) | ||
} | ||
|
||
export function getChildNodes(node: Node) { | ||
return isNodeShadowHost(node) ? node.shadowRoot.childNodes : node.childNodes | ||
} | ||
|
||
/** | ||
* Return `host` in case if the current node is a shadow root otherwise will return the `parentNode` | ||
*/ | ||
export function getParentNode(node: Node): Node | null { | ||
return isNodeShadowRoot(node) ? node.host : node.parentNode | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.