Monkeypatch each iframe (#716)
* `setTimeout` and `clearTimeout` are global functions. Think the window versions of them were for the following reason: https://stackoverflow.com/questions/60245787/ * Comments and extra test here helped me understand which inserts were expected and which are to be ignored * Add a test for the style setProperty/removeProperty added in #671 * Add a test to ensure that listeners get added correctly in nested iframes - particularly important for those which rely on prototype monkeypatching * Pass in the window object from the current iframe so that monkeypatching applies to all windows * Satisfy typings * No need to insert an iframe as there's one already set up for us * Enable the console logger to also intercept log messages within iframes * There's no tests for FontFace but presumably the monkeypatching here works similarly to the others
This commit is contained in:
@@ -3347,6 +3347,88 @@ exports[`log 1`] = `
|
||||
\\"payload\\": []
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 0,
|
||||
\\"texts\\": [],
|
||||
\\"attributes\\": [],
|
||||
\\"removes\\": [],
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 16,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"iframe\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 21
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 0,
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 21,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 0,
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"html\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"head\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"rootId\\": 22,
|
||||
\\"id\\": 24
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"body\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"rootId\\": 22,
|
||||
\\"id\\": 25
|
||||
}
|
||||
],
|
||||
\\"rootId\\": 22,
|
||||
\\"id\\": 23
|
||||
}
|
||||
],
|
||||
\\"id\\": 22
|
||||
}
|
||||
}
|
||||
],
|
||||
\\"removes\\": [],
|
||||
\\"texts\\": [],
|
||||
\\"attributes\\": [],
|
||||
\\"isAttachIframe\\": true
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 6,
|
||||
\\"data\\": {
|
||||
\\"plugin\\": \\"rrweb/console@1\\",
|
||||
\\"payload\\": {
|
||||
\\"level\\": \\"log\\",
|
||||
\\"trace\\": [],
|
||||
\\"payload\\": [
|
||||
\\"\\\\\\"from iframe\\\\\\"\\"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]"
|
||||
`;
|
||||
|
||||
@@ -329,6 +329,234 @@ exports[`custom-event 1`] = `
|
||||
]"
|
||||
`;
|
||||
|
||||
exports[`iframe-stylesheet-mutations 1`] = `
|
||||
"[
|
||||
{
|
||||
\\"type\\": 4,
|
||||
\\"data\\": {
|
||||
\\"href\\": \\"about:blank\\",
|
||||
\\"width\\": 1920,
|
||||
\\"height\\": 1080
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"data\\": {
|
||||
\\"node\\": {
|
||||
\\"type\\": 0,
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"html\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"head\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 3
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"body\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 5
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"iframe\\",
|
||||
\\"attributes\\": {
|
||||
\\"srcdoc\\": \\"<button>Mysterious Button</button>\\"
|
||||
},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n </body>\\\\n </html>\\\\n \\",
|
||||
\\"id\\": 7
|
||||
}
|
||||
],
|
||||
\\"id\\": 6
|
||||
}
|
||||
],
|
||||
\\"id\\": 4
|
||||
}
|
||||
],
|
||||
\\"id\\": 2
|
||||
}
|
||||
],
|
||||
\\"id\\": 1
|
||||
},
|
||||
\\"initialOffset\\": {
|
||||
\\"left\\": 0,
|
||||
\\"top\\": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 0,
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 6,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 0,
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"html\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"head\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"rootId\\": 8,
|
||||
\\"id\\": 10
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"body\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"button\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"Mysterious Button\\",
|
||||
\\"rootId\\": 8,
|
||||
\\"id\\": 13
|
||||
}
|
||||
],
|
||||
\\"rootId\\": 8,
|
||||
\\"id\\": 12
|
||||
}
|
||||
],
|
||||
\\"rootId\\": 8,
|
||||
\\"id\\": 11
|
||||
}
|
||||
],
|
||||
\\"rootId\\": 8,
|
||||
\\"id\\": 9
|
||||
}
|
||||
],
|
||||
\\"id\\": 8
|
||||
}
|
||||
}
|
||||
],
|
||||
\\"removes\\": [],
|
||||
\\"texts\\": [],
|
||||
\\"attributes\\": [],
|
||||
\\"isAttachIframe\\": true
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 0,
|
||||
\\"texts\\": [],
|
||||
\\"attributes\\": [],
|
||||
\\"removes\\": [],
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 10,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"style\\",
|
||||
\\"attributes\\": {
|
||||
\\"_cssText\\": \\"body { background: rgb(0, 0, 0); }@media {\\\\n body { background: rgb(0, 0, 0); }\\\\n}\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"rootId\\": 8,
|
||||
\\"id\\": 14
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 8,
|
||||
\\"id\\": 14,
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"rule\\": \\"body { color: #fff; }\\"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 8,
|
||||
\\"id\\": 14,
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"rule\\": \\"body { color: #ccc; }\\",
|
||||
\\"index\\": [
|
||||
2,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 8,
|
||||
\\"id\\": 14,
|
||||
\\"removes\\": [
|
||||
{
|
||||
\\"index\\": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 13,
|
||||
\\"id\\": 14,
|
||||
\\"set\\": {
|
||||
\\"property\\": \\"color\\",
|
||||
\\"value\\": \\"green\\"
|
||||
},
|
||||
\\"index\\": [
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 8,
|
||||
\\"id\\": 14,
|
||||
\\"removes\\": [
|
||||
{
|
||||
\\"index\\": [
|
||||
1,
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]"
|
||||
`;
|
||||
|
||||
exports[`nested-stylesheet-rules 1`] = `
|
||||
"[
|
||||
{
|
||||
@@ -471,6 +699,128 @@ exports[`nested-stylesheet-rules 1`] = `
|
||||
]"
|
||||
`;
|
||||
|
||||
exports[`stylesheet-properties 1`] = `
|
||||
"[
|
||||
{
|
||||
\\"type\\": 4,
|
||||
\\"data\\": {
|
||||
\\"href\\": \\"about:blank\\",
|
||||
\\"width\\": 1920,
|
||||
\\"height\\": 1080
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"data\\": {
|
||||
\\"node\\": {
|
||||
\\"type\\": 0,
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"html\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"head\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 3
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"body\\",
|
||||
\\"attributes\\": {},
|
||||
\\"childNodes\\": [
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\",
|
||||
\\"id\\": 5
|
||||
},
|
||||
{
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"input\\",
|
||||
\\"attributes\\": {
|
||||
\\"type\\": \\"text\\",
|
||||
\\"size\\": \\"40\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 6
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"textContent\\": \\"\\\\n \\\\n \\\\n \\",
|
||||
\\"id\\": 7
|
||||
}
|
||||
],
|
||||
\\"id\\": 4
|
||||
}
|
||||
],
|
||||
\\"id\\": 2
|
||||
}
|
||||
],
|
||||
\\"id\\": 1
|
||||
},
|
||||
\\"initialOffset\\": {
|
||||
\\"left\\": 0,
|
||||
\\"top\\": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 0,
|
||||
\\"texts\\": [],
|
||||
\\"attributes\\": [],
|
||||
\\"removes\\": [],
|
||||
\\"adds\\": [
|
||||
{
|
||||
\\"parentId\\": 3,
|
||||
\\"nextId\\": null,
|
||||
\\"node\\": {
|
||||
\\"type\\": 2,
|
||||
\\"tagName\\": \\"style\\",
|
||||
\\"attributes\\": {
|
||||
\\"_cssText\\": \\"body { background: rgb(0, 0, 0); }\\"
|
||||
},
|
||||
\\"childNodes\\": [],
|
||||
\\"id\\": 8
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 13,
|
||||
\\"id\\": 8,
|
||||
\\"set\\": {
|
||||
\\"property\\": \\"color\\",
|
||||
\\"value\\": \\"green\\"
|
||||
},
|
||||
\\"index\\": [
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"type\\": 3,
|
||||
\\"data\\": {
|
||||
\\"source\\": 13,
|
||||
\\"id\\": 8,
|
||||
\\"remove\\": {
|
||||
\\"property\\": \\"background\\"
|
||||
},
|
||||
\\"index\\": [
|
||||
0
|
||||
]
|
||||
}
|
||||
}
|
||||
]"
|
||||
`;
|
||||
|
||||
exports[`stylesheet-rules 1`] = `
|
||||
"[
|
||||
{
|
||||
|
||||
@@ -471,8 +471,15 @@ describe('record integration tests', function (this: ISuite) {
|
||||
console.trace('trace');
|
||||
console.warn('warn');
|
||||
console.clear();
|
||||
const iframe = document.createElement('iframe');
|
||||
document.body.appendChild(iframe);
|
||||
});
|
||||
|
||||
await page.frames()[1].evaluate(() => {
|
||||
console.log('from iframe');
|
||||
});
|
||||
|
||||
|
||||
const snapshots = await page.evaluate('window.snapshots');
|
||||
assertSnapshot(snapshots, __filename, 'log');
|
||||
});
|
||||
|
||||
@@ -220,9 +220,11 @@ describe('record', function (this: ISuite) {
|
||||
document.head.appendChild(styleElement);
|
||||
|
||||
const styleSheet = <CSSStyleSheet>styleElement.sheet;
|
||||
// begin: pre-serialization
|
||||
const ruleIdx0 = styleSheet.insertRule('body { background: #000; }');
|
||||
const ruleIdx1 = styleSheet.insertRule('body { background: #111; }');
|
||||
styleSheet.deleteRule(ruleIdx1);
|
||||
// end: pre-serialization
|
||||
setTimeout(() => {
|
||||
styleSheet.insertRule('body { color: #fff; }');
|
||||
}, 0);
|
||||
@@ -239,14 +241,15 @@ describe('record', function (this: ISuite) {
|
||||
e.type === EventType.IncrementalSnapshot &&
|
||||
e.data.source === IncrementalSource.StyleSheetRule,
|
||||
);
|
||||
const addRuleCount = styleSheetRuleEvents.filter((e) =>
|
||||
const addRules = styleSheetRuleEvents.filter((e) =>
|
||||
Boolean((e.data as styleSheetRuleData).adds),
|
||||
).length;
|
||||
);
|
||||
const removeRuleCount = styleSheetRuleEvents.filter((e) =>
|
||||
Boolean((e.data as styleSheetRuleData).removes),
|
||||
).length;
|
||||
// sync insert/delete should be ignored
|
||||
expect(addRuleCount).to.equal(2);
|
||||
// pre-serialization insert/delete should be ignored
|
||||
expect(addRules.length).to.equal(2);
|
||||
expect((addRules[0].data as styleSheetRuleData).adds).to.deep.include({rule: "body { color: #fff; }"});
|
||||
expect(removeRuleCount).to.equal(1);
|
||||
assertSnapshot(this.events, __filename, 'stylesheet-rules');
|
||||
});
|
||||
@@ -311,6 +314,30 @@ describe('record', function (this: ISuite) {
|
||||
});
|
||||
it('captures nested stylesheet rules', captureNestedStylesheetRulesTest);
|
||||
});
|
||||
|
||||
it('captures style property changes', async () => {
|
||||
await this.page.evaluate(() => {
|
||||
const { record } = ((window as unknown) as IWindow).rrweb;
|
||||
|
||||
record({
|
||||
emit: ((window as unknown) as IWindow).emit,
|
||||
});
|
||||
|
||||
const styleElement = document.createElement('style');
|
||||
document.head.appendChild(styleElement);
|
||||
|
||||
const styleSheet = <CSSStyleSheet>styleElement.sheet;
|
||||
styleSheet.insertRule('body { background: #000; }');
|
||||
setTimeout(() => {
|
||||
(styleSheet.cssRules[0] as CSSStyleRule).style.setProperty('color', 'green');
|
||||
(styleSheet.cssRules[0] as CSSStyleRule).style.removeProperty('background');
|
||||
}, 0);
|
||||
});
|
||||
await this.page.waitForTimeout(50);
|
||||
assertSnapshot(this.events, __filename, 'stylesheet-properties');
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('record iframes', function (this: ISuite) {
|
||||
@@ -350,4 +377,60 @@ describe('record iframes', function (this: ISuite) {
|
||||
EventType.IncrementalSnapshot,
|
||||
]);
|
||||
});
|
||||
|
||||
it('captures stylesheet mutations in iframes', async () => {
|
||||
await this.page.evaluate(() => {
|
||||
const { record } = ((window as unknown) as IWindow).rrweb;
|
||||
record({
|
||||
// need to reference window.top for when we are in an iframe!
|
||||
emit: ((window.top as unknown) as IWindow).emit,
|
||||
});
|
||||
|
||||
const iframe = document.querySelector('iframe');
|
||||
// outer timeout is needed to wait for initStyleSheetObserver on iframe to be set up
|
||||
setTimeout(() => {
|
||||
|
||||
const idoc = (iframe as HTMLIFrameElement).contentDocument!;
|
||||
const styleElement = idoc.createElement('style');
|
||||
|
||||
idoc.head.appendChild(styleElement);
|
||||
|
||||
const styleSheet = <CSSStyleSheet>styleElement.sheet;
|
||||
styleSheet.insertRule('@media {}');
|
||||
const atMediaRule = styleSheet.cssRules[0] as CSSMediaRule;
|
||||
const atRuleIdx0 = atMediaRule.insertRule('body { background: #000; }', 0);
|
||||
const ruleIdx0 = styleSheet.insertRule('body { background: #000; }'); // inserted before above
|
||||
// pre-serialization insert/delete above should be ignored
|
||||
setTimeout(() => {
|
||||
styleSheet.insertRule('body { color: #fff; }');
|
||||
atMediaRule.insertRule('body { color: #ccc; }', 0);
|
||||
}, 0);
|
||||
setTimeout(() => {
|
||||
styleSheet.deleteRule(ruleIdx0);
|
||||
(styleSheet.cssRules[0] as CSSStyleRule).style.setProperty('color', 'green');
|
||||
}, 5);
|
||||
setTimeout(() =>{
|
||||
atMediaRule.deleteRule(atRuleIdx0);
|
||||
}, 10);
|
||||
}, 10);
|
||||
});
|
||||
await this.page.waitForTimeout(50);
|
||||
const styleRelatedEvents = this.events.filter(
|
||||
(e) =>
|
||||
e.type === EventType.IncrementalSnapshot &&
|
||||
(e.data.source === IncrementalSource.StyleSheetRule ||
|
||||
e.data.source === IncrementalSource.StyleDeclaration),
|
||||
);
|
||||
const addRuleCount = styleRelatedEvents.filter((e) =>
|
||||
Boolean((e.data as styleSheetRuleData).adds),
|
||||
).length;
|
||||
const removeRuleCount = styleRelatedEvents.filter((e) =>
|
||||
Boolean((e.data as styleSheetRuleData).removes),
|
||||
).length;
|
||||
expect(styleRelatedEvents.length).to.equal(5);
|
||||
expect(addRuleCount).to.equal(2);
|
||||
expect(removeRuleCount).to.equal(2);
|
||||
assertSnapshot(this.events, __filename, 'iframe-stylesheet-mutations');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user