Skip to content

Commit

Permalink
feat(replay): Define custom elements (web components)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
billyvg committed Apr 28, 2023
1 parent 78dcb80 commit ed0133c
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 3 deletions.
5 changes: 4 additions & 1 deletion packages/rrweb-snapshot/src/rebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
INode,
BuildCache,
} from './types';
import { isElement } from './utils';
import { defineCustomElement, isElement } from './utils';

const tagMap: tagMap = {
script: 'noscript',
Expand Down Expand Up @@ -284,6 +284,9 @@ function buildNode(
*/
if (!node.shadowRoot) {
node.attachShadow({ mode: 'open' });
if (doc.defaultView) {
defineCustomElement(doc.defaultView, tagName);
}
} else {
while (node.shadowRoot.firstChild) {
node.shadowRoot.removeChild(node.shadowRoot.firstChild);
Expand Down
17 changes: 17 additions & 0 deletions packages/rrweb-snapshot/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,20 @@ export function is2DCanvasBlank(canvas: HTMLCanvasElement): boolean {
}
return true;
}

/**
* Ensure we define custom elements as you can have css that targets the
* `:defined` pseudo class (e.g. hide until defined)
*/
export function defineCustomElement(w: Window, elementName: string) {
// We need to define custom elements inside of the correct window (i.e.
// inside of the iframe)
try {
// Can only define custom element once
if (!w.customElements.get(elementName)) {
// @ts-ignore HTMLElement exists on window
const CustomElement = w.HTMLElement as CustomElementConstructor;
w.customElements.define(elementName, class extends CustomElement {});
}
} catch {}
}
1 change: 1 addition & 0 deletions packages/rrweb-snapshot/typings/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ interface MaskInputValue extends HasInputMaskOptions {
}
export declare function maskInputValue({ input, maskInputSelector, unmaskInputSelector, maskInputOptions, tagName, type, value, maskInputFn, }: MaskInputValue): string;
export declare function is2DCanvasBlank(canvas: HTMLCanvasElement): boolean;
export declare function defineCustomElement(w: Window, elementName: string): void;
export {};
9 changes: 7 additions & 2 deletions packages/rrweb/src/replay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
NodeType,
BuildCache,
createCache,
defineCustomElement,
} from '@sentry-internal/rrweb-snapshot';
import * as mittProxy from 'mitt';
import { polyfill as smoothscrollPolyfill } from './smoothscroll';
Expand Down Expand Up @@ -1458,8 +1459,12 @@ export class Replayer {
if (mutation.node.isShadow) {
// If the parent is attached a shadow dom after it's created, it won't have a shadow root.
if (!hasShadowRoot(parent)) {
((parent as Node) as HTMLElement).attachShadow({ mode: 'open' });
parent = ((parent as Node) as HTMLElement).shadowRoot!;
const parentNode = parent as Node;
(parentNode as HTMLElement).attachShadow({ mode: 'open' });
parent = (parentNode as HTMLElement).shadowRoot!;
if (this.iframe.contentWindow) {
defineCustomElement(this.iframe.contentWindow, parentNode.nodeName);
}
} else parent = parent.shadowRoot;
}

Expand Down

0 comments on commit ed0133c

Please sign in to comment.