Skip to content

Commit

Permalink
Fix missed adopted style sheets of shadow doms in checkout full snaps…
Browse files Browse the repository at this point in the history
…hot (#1086)

* fix: adoptedStyleSheets in shadow doms are missed when a full snapshot is checked out after recording has started

* fix: avoid removing monkey patch of all existed shadow doms when take a new full snapshot

* Apply formatting changes

* Update packages/rrweb/test/record.test.ts

Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>

* fix typo

* update outdated snapshot

Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>
  • Loading branch information
YunFeng0817 and Juice10 authored Jan 10, 2023
1 parent fe69bd6 commit d08913d
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 9 deletions.
2 changes: 2 additions & 0 deletions packages/rrweb/src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,8 @@ function record<T = eventWithTime>(

// When we take a full snapshot, old tracked StyleSheets need to be removed.
stylesheetManager.reset();
// Old shadow doms cache need to be cleared.
shadowDomManager.clearCache();

mutationBuffers.forEach((buf) => buf.lock()); // don't allow any mirror modifications during snapshotting
const node = snapshot(document, {
Expand Down
6 changes: 5 additions & 1 deletion packages/rrweb/src/record/shadow-dom-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,12 @@ export class ShadowDomManager {
}
}

public clearCache() {
this.shadowDoms = new WeakSet();
}

public reset() {
this.restorePatches.forEach((restorePatch) => restorePatch());
this.shadowDoms = new WeakSet();
this.clearCache();
}
}
205 changes: 205 additions & 0 deletions packages/rrweb/test/__snapshots__/record.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,211 @@ exports[`record captures adopted stylesheets in shadow doms and iframe 1`] = `
]"
`;

exports[`record captures adopted stylesheets of shadow doms in checkout full snapshot 1`] = `
"[
{
\\"type\\": 4,
\\"data\\": {
\\"href\\": \\"about:blank\\",
\\"width\\": 1920,
\\"height\\": 1080
}
},
{
\\"type\\": 2,
\\"data\\": {
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 1,
\\"name\\": \\"html\\",
\\"publicId\\": \\"\\",
\\"systemId\\": \\"\\",
\\"id\\": 2
},
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [],
\\"id\\": 4
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 6
},
{
\\"type\\": 2,
\\"tagName\\": \\"div\\",
\\"attributes\\": {
\\"id\\": \\"shadow-host-1\\"
},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"entry\\",
\\"id\\": 8
}
],
\\"id\\": 7,
\\"isShadowHost\\": true
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 9
}
],
\\"id\\": 5
}
],
\\"id\\": 3
}
],
\\"id\\": 1
},
\\"initialOffset\\": {
\\"left\\": 0,
\\"top\\": 0
}
}
},
{
\\"type\\": 3,
\\"data\\": {
\\"source\\": 15,
\\"id\\": 7,
\\"styleIds\\": [
1
],
\\"styles\\": [
{
\\"styleId\\": 1,
\\"rules\\": [
{
\\"rule\\": \\"h1 { color: blue; }\\",
\\"index\\": 0
}
]
}
]
}
},
{
\\"type\\": 4,
\\"data\\": {
\\"href\\": \\"about:blank\\",
\\"width\\": 1920,
\\"height\\": 1080
}
},
{
\\"type\\": 2,
\\"data\\": {
\\"node\\": {
\\"type\\": 0,
\\"childNodes\\": [
{
\\"type\\": 1,
\\"name\\": \\"html\\",
\\"publicId\\": \\"\\",
\\"systemId\\": \\"\\",
\\"id\\": 2
},
{
\\"type\\": 2,
\\"tagName\\": \\"html\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 2,
\\"tagName\\": \\"head\\",
\\"attributes\\": {},
\\"childNodes\\": [],
\\"id\\": 4
},
{
\\"type\\": 2,
\\"tagName\\": \\"body\\",
\\"attributes\\": {},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 6
},
{
\\"type\\": 2,
\\"tagName\\": \\"div\\",
\\"attributes\\": {
\\"id\\": \\"shadow-host-1\\"
},
\\"childNodes\\": [
{
\\"type\\": 3,
\\"textContent\\": \\"entry\\",
\\"id\\": 8
}
],
\\"id\\": 7,
\\"isShadowHost\\": true
},
{
\\"type\\": 3,
\\"textContent\\": \\"\\\\n \\",
\\"id\\": 9
}
],
\\"id\\": 5
}
],
\\"id\\": 3
}
],
\\"id\\": 1
},
\\"initialOffset\\": {
\\"left\\": 0,
\\"top\\": 0
}
}
},
{
\\"type\\": 3,
\\"data\\": {
\\"source\\": 15,
\\"id\\": 7,
\\"styleIds\\": [
1
],
\\"styles\\": [
{
\\"styleId\\": 1,
\\"rules\\": [
{
\\"rule\\": \\"h1 { color: blue; }\\",
\\"index\\": 0
}
]
}
]
}
}
]"
`;

exports[`record captures inserted style text nodes correctly 1`] = `
"[
{
Expand Down
48 changes: 40 additions & 8 deletions packages/rrweb/test/record.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@ interface ISuite {

interface IWindow extends Window {
rrweb: {
record: (
record: ((
options: recordOptions<eventWithTime>,
) => listenerHandler | undefined;
) => listenerHandler | undefined) & {
takeFullSnapshot: (isCheckout?: boolean | undefined) => void;
};

addCustomEvent<T>(tag: string, payload: T): void;
};
emit: (e: eventWithTime) => undefined;
Expand Down Expand Up @@ -491,9 +494,9 @@ describe('record', function (this: ISuite) {
iframe!.contentDocument!.adoptedStyleSheets = [sheet2];
iframe!.contentDocument!.body.innerHTML = '<h1>h1 in iframe</h1>';

const { record } = ((window as unknown) as IWindow).rrweb;
record({
emit: ((window as unknown) as IWindow).emit,
const { rrweb, emit } = (window as unknown) as IWindow;
rrweb.record({
emit,
});

setTimeout(() => {
Expand Down Expand Up @@ -565,9 +568,9 @@ describe('record', function (this: ISuite) {
sheet2.replaceSync!('div {font-size: large;}');
shadowHost.shadowRoot!.adoptedStyleSheets = [sheet2];

const { record } = ((window as unknown) as IWindow).rrweb;
record({
emit: ((window as unknown) as IWindow).emit,
const { rrweb, emit } = (window as unknown) as IWindow;
rrweb.record({
emit,
});

setTimeout(() => {
Expand All @@ -586,6 +589,35 @@ describe('record', function (this: ISuite) {
assertSnapshot(ctx.events);
});

it('captures adopted stylesheets of shadow doms in checkout full snapshot', async () => {
await ctx.page.evaluate(() => {
return new Promise((resolve) => {
document.body.innerHTML = `
<div id="shadow-host-1">entry</div>
`;

let shadowHost = document.querySelector('div')!;
shadowHost!.attachShadow({ mode: 'open' });
const sheet = new CSSStyleSheet();
sheet.replaceSync!('h1 {color: blue;}');
shadowHost.shadowRoot!.adoptedStyleSheets = [sheet];

const { rrweb, emit } = (window as unknown) as IWindow;
rrweb.record({
emit,
});

setTimeout(() => {
// When a full snapshot is checked out manually, all adoptedStylesheets should also be captured.
rrweb.record.takeFullSnapshot(true);
resolve(undefined);
}, 10);
});
});
await waitForRAF(ctx.page);
assertSnapshot(ctx.events);
});

it('captures stylesheets in iframes with `blob:` url', async () => {
await ctx.page.evaluate(() => {
const iframe = document.createElement('iframe');
Expand Down

0 comments on commit d08913d

Please sign in to comment.