Skip to content

Commit ed0133c

Browse files
committed
feat(replay): Define custom elements (web components)
Define custom elements so that we the `:defined` pseudo-class works as it can be used as a "loading" indicator while the web component is being loaded. Fixes getsentry/sentry-javascript#7988
1 parent 78dcb80 commit ed0133c

File tree

4 files changed

+29
-3
lines changed

4 files changed

+29
-3
lines changed

packages/rrweb-snapshot/src/rebuild.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
INode,
99
BuildCache,
1010
} from './types';
11-
import { isElement } from './utils';
11+
import { defineCustomElement, isElement } from './utils';
1212

1313
const tagMap: tagMap = {
1414
script: 'noscript',
@@ -284,6 +284,9 @@ function buildNode(
284284
*/
285285
if (!node.shadowRoot) {
286286
node.attachShadow({ mode: 'open' });
287+
if (doc.defaultView) {
288+
defineCustomElement(doc.defaultView, tagName);
289+
}
287290
} else {
288291
while (node.shadowRoot.firstChild) {
289292
node.shadowRoot.removeChild(node.shadowRoot.firstChild);

packages/rrweb-snapshot/src/utils.ts

+17
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,20 @@ export function is2DCanvasBlank(canvas: HTMLCanvasElement): boolean {
136136
}
137137
return true;
138138
}
139+
140+
/**
141+
* Ensure we define custom elements as you can have css that targets the
142+
* `:defined` pseudo class (e.g. hide until defined)
143+
*/
144+
export function defineCustomElement(w: Window, elementName: string) {
145+
// We need to define custom elements inside of the correct window (i.e.
146+
// inside of the iframe)
147+
try {
148+
// Can only define custom element once
149+
if (!w.customElements.get(elementName)) {
150+
// @ts-ignore HTMLElement exists on window
151+
const CustomElement = w.HTMLElement as CustomElementConstructor;
152+
w.customElements.define(elementName, class extends CustomElement {});
153+
}
154+
} catch {}
155+
}

packages/rrweb-snapshot/typings/utils.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ interface MaskInputValue extends HasInputMaskOptions {
1818
}
1919
export declare function maskInputValue({ input, maskInputSelector, unmaskInputSelector, maskInputOptions, tagName, type, value, maskInputFn, }: MaskInputValue): string;
2020
export declare function is2DCanvasBlank(canvas: HTMLCanvasElement): boolean;
21+
export declare function defineCustomElement(w: Window, elementName: string): void;
2122
export {};

packages/rrweb/src/replay/index.ts

+7-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
NodeType,
66
BuildCache,
77
createCache,
8+
defineCustomElement,
89
} from '@sentry-internal/rrweb-snapshot';
910
import * as mittProxy from 'mitt';
1011
import { polyfill as smoothscrollPolyfill } from './smoothscroll';
@@ -1458,8 +1459,12 @@ export class Replayer {
14581459
if (mutation.node.isShadow) {
14591460
// If the parent is attached a shadow dom after it's created, it won't have a shadow root.
14601461
if (!hasShadowRoot(parent)) {
1461-
((parent as Node) as HTMLElement).attachShadow({ mode: 'open' });
1462-
parent = ((parent as Node) as HTMLElement).shadowRoot!;
1462+
const parentNode = parent as Node;
1463+
(parentNode as HTMLElement).attachShadow({ mode: 'open' });
1464+
parent = (parentNode as HTMLElement).shadowRoot!;
1465+
if (this.iframe.contentWindow) {
1466+
defineCustomElement(this.iframe.contentWindow, parentNode.nodeName);
1467+
}
14631468
} else parent = parent.shadowRoot;
14641469
}
14651470

0 commit comments

Comments
 (0)