Fix missed adopted style sheets of shadow doms in checkout full snapshot (#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>
This commit is contained in:
@@ -335,6 +335,8 @@ function record<T = eventWithTime>(
|
|||||||
|
|
||||||
// When we take a full snapshot, old tracked StyleSheets need to be removed.
|
// When we take a full snapshot, old tracked StyleSheets need to be removed.
|
||||||
stylesheetManager.reset();
|
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
|
mutationBuffers.forEach((buf) => buf.lock()); // don't allow any mirror modifications during snapshotting
|
||||||
const node = snapshot(document, {
|
const node = snapshot(document, {
|
||||||
|
|||||||
@@ -129,8 +129,12 @@ export class ShadowDomManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public reset() {
|
public clearCache() {
|
||||||
this.restorePatches.forEach((restorePatch) => restorePatch());
|
|
||||||
this.shadowDoms = new WeakSet();
|
this.shadowDoms = new WeakSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public reset() {
|
||||||
|
this.restorePatches.forEach((restorePatch) => restorePatch());
|
||||||
|
this.clearCache();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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`] = `
|
exports[`record captures inserted style text nodes correctly 1`] = `
|
||||||
"[
|
"[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,9 +29,12 @@ interface ISuite {
|
|||||||
|
|
||||||
interface IWindow extends Window {
|
interface IWindow extends Window {
|
||||||
rrweb: {
|
rrweb: {
|
||||||
record: (
|
record: ((
|
||||||
options: recordOptions<eventWithTime>,
|
options: recordOptions<eventWithTime>,
|
||||||
) => listenerHandler | undefined;
|
) => listenerHandler | undefined) & {
|
||||||
|
takeFullSnapshot: (isCheckout?: boolean | undefined) => void;
|
||||||
|
};
|
||||||
|
|
||||||
addCustomEvent<T>(tag: string, payload: T): void;
|
addCustomEvent<T>(tag: string, payload: T): void;
|
||||||
};
|
};
|
||||||
emit: (e: eventWithTime) => undefined;
|
emit: (e: eventWithTime) => undefined;
|
||||||
@@ -491,9 +494,9 @@ describe('record', function (this: ISuite) {
|
|||||||
iframe!.contentDocument!.adoptedStyleSheets = [sheet2];
|
iframe!.contentDocument!.adoptedStyleSheets = [sheet2];
|
||||||
iframe!.contentDocument!.body.innerHTML = '<h1>h1 in iframe</h1>';
|
iframe!.contentDocument!.body.innerHTML = '<h1>h1 in iframe</h1>';
|
||||||
|
|
||||||
const { record } = ((window as unknown) as IWindow).rrweb;
|
const { rrweb, emit } = (window as unknown) as IWindow;
|
||||||
record({
|
rrweb.record({
|
||||||
emit: ((window as unknown) as IWindow).emit,
|
emit,
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -565,9 +568,9 @@ describe('record', function (this: ISuite) {
|
|||||||
sheet2.replaceSync!('div {font-size: large;}');
|
sheet2.replaceSync!('div {font-size: large;}');
|
||||||
shadowHost.shadowRoot!.adoptedStyleSheets = [sheet2];
|
shadowHost.shadowRoot!.adoptedStyleSheets = [sheet2];
|
||||||
|
|
||||||
const { record } = ((window as unknown) as IWindow).rrweb;
|
const { rrweb, emit } = (window as unknown) as IWindow;
|
||||||
record({
|
rrweb.record({
|
||||||
emit: ((window as unknown) as IWindow).emit,
|
emit,
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -586,6 +589,35 @@ describe('record', function (this: ISuite) {
|
|||||||
assertSnapshot(ctx.events);
|
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 () => {
|
it('captures stylesheets in iframes with `blob:` url', async () => {
|
||||||
await ctx.page.evaluate(() => {
|
await ctx.page.evaluate(() => {
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe');
|
||||||
|
|||||||
Reference in New Issue
Block a user