fix: custom style rules don't get inserted into some iframe elements (#823)

* fix: custom style rules don't get inserted into some iframe elements

* code style tweak
This commit is contained in:
Yun Feng
2022-02-03 22:58:01 +11:00
committed by GitHub
parent c1111ed79d
commit 5ec7d9e740
4 changed files with 709 additions and 12 deletions

View File

@@ -660,10 +660,6 @@ export class Replayer {
this.newDocumentQueue = this.newDocumentQueue.filter(
(m) => m !== mutationInQueue,
);
if (builtNode.contentDocument) {
const { documentElement, head } = builtNode.contentDocument;
this.insertStyleRules(documentElement, head);
}
}
const { documentElement, head } = this.iframe.contentDocument;
this.insertStyleRules(documentElement, head);
@@ -726,6 +722,13 @@ export class Replayer {
skipChild: false,
afterAppend: (builtNode) => {
this.collectIframeAndAttachDocument(collected, builtNode);
if (
builtNode.__sn.type === NodeType.Element &&
builtNode.__sn.tagName.toUpperCase() === 'HTML'
) {
const { documentElement, head } = iframeEl.contentDocument!;
this.insertStyleRules(documentElement, head);
}
},
cache: this.cache,
});
@@ -734,10 +737,6 @@ export class Replayer {
this.newDocumentQueue = this.newDocumentQueue.filter(
(m) => m !== mutationInQueue,
);
if (builtNode.contentDocument) {
const { documentElement, head } = builtNode.contentDocument;
this.insertStyleRules(documentElement, head);
}
}
}
@@ -1482,10 +1481,6 @@ export class Replayer {
(m) => m !== mutationInQueue,
);
}
if (target.contentDocument) {
const { documentElement, head } = target.contentDocument;
this.insertStyleRules(documentElement, head);
}
}
if (mutation.previousId || mutation.nextId) {

View File

@@ -0,0 +1,591 @@
import { EventType, eventWithTime, IncrementalSource } from '../../src/types';
const now = Date.now();
const events: eventWithTime[] = [
{
type: EventType.DomContentLoaded,
data: {},
timestamp: now,
},
{
type: EventType.Load,
data: {},
timestamp: now + 100,
},
{
type: EventType.Meta,
data: {
href: 'http://localhost',
width: 1200,
height: 500,
},
timestamp: now + 100,
},
{
type: EventType.FullSnapshot,
data: {
node: {
id: 1,
type: 0,
childNodes: [
{ id: 2, name: 'html', type: 1, publicId: '', systemId: '' },
{
id: 3,
type: 2,
tagName: 'html',
attributes: { lang: 'en' },
childNodes: [
{
id: 4,
type: 2,
tagName: 'head',
attributes: {},
childNodes: [],
},
{
id: 5,
type: 2,
tagName: 'body',
attributes: {},
childNodes: [],
},
],
},
],
},
initialOffset: { top: 0, left: 0 },
},
timestamp: now + 200,
},
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
texts: [],
attributes: [],
removes: [],
adds: [
{
parentId: 5,
nextId: null,
node: {
type: 2,
tagName: 'iframe',
attributes: { id: 'one' },
childNodes: [],
id: 6,
},
},
],
},
timestamp: now + 500,
},
// add iframe one
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
adds: [
{
parentId: 6,
nextId: null,
node: {
type: 0,
childNodes: [
{
type: 1,
name: 'html',
publicId: '',
systemId: '',
rootId: 7,
id: 8,
},
{
type: 2,
tagName: 'html',
attributes: { lang: 'en' },
childNodes: [
{
type: 2,
tagName: 'head',
attributes: {},
childNodes: [],
rootId: 7,
id: 10,
},
{
type: 2,
tagName: 'body',
attributes: {},
childNodes: [
{
type: 2,
tagName: 'div',
attributes: {},
childNodes: [
{
type: 3,
textContent: '\n\t\tiframe 1\n\t',
rootId: 7,
id: 13,
},
],
rootId: 7,
id: 12,
},
{ type: 3, textContent: '\n\t', rootId: 7, id: 14 },
{
type: 2,
tagName: 'script',
attributes: {},
childNodes: [
{
type: 3,
textContent: 'SCRIPT_PLACEHOLDER',
rootId: 7,
id: 16,
},
],
rootId: 7,
id: 15,
},
{ type: 3, textContent: '\t\n', rootId: 7, id: 17 },
],
rootId: 7,
id: 11,
},
],
rootId: 7,
id: 9,
},
],
id: 7,
},
},
],
removes: [],
texts: [],
attributes: [],
isAttachIframe: true,
},
timestamp: now + 500,
},
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
texts: [],
attributes: [],
removes: [],
adds: [
{
parentId: 5,
nextId: null,
node: {
type: 2,
tagName: 'iframe',
attributes: { id: 'two' },
childNodes: [],
id: 38,
},
},
],
},
timestamp: now + 1000,
},
// add iframe two
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
adds: [
{
parentId: 38,
nextId: null,
node: {
type: 0,
childNodes: [
{
type: 1,
name: 'html',
publicId: '',
systemId: '',
rootId: 39,
id: 40,
},
{
type: 2,
tagName: 'html',
attributes: { lang: 'en' },
childNodes: [
{
type: 2,
tagName: 'head',
attributes: {},
childNodes: [
{ type: 3, textContent: '\n ', rootId: 39, id: 43 },
{
type: 2,
tagName: 'meta',
attributes: { charset: 'UTF-8' },
childNodes: [],
rootId: 39,
id: 44,
},
{ type: 3, textContent: '\n ', rootId: 39, id: 45 },
{
type: 2,
tagName: 'meta',
attributes: {
name: 'viewport',
content: 'width=device-width, initial-scale=1.0',
},
childNodes: [],
rootId: 39,
id: 46,
},
{ type: 3, textContent: '\n ', rootId: 39, id: 47 },
{
type: 2,
tagName: 'title',
attributes: {},
childNodes: [
{
type: 3,
textContent: 'iframe 2',
rootId: 39,
id: 49,
},
],
rootId: 39,
id: 48,
},
{ type: 3, textContent: '\n ', rootId: 39, id: 50 },
],
rootId: 39,
id: 42,
},
{ type: 3, textContent: '\n ', rootId: 39, id: 51 },
{
type: 2,
tagName: 'body',
attributes: {},
childNodes: [
{
type: 3,
textContent: '\n iframe 2\n ',
rootId: 39,
id: 53,
},
{
type: 2,
tagName: 'iframe',
attributes: { id: 'three', frameborder: '0' },
childNodes: [],
rootId: 39,
id: 54,
},
{ type: 3, textContent: '\n ', rootId: 39, id: 55 },
{
type: 2,
tagName: 'iframe',
attributes: { id: 'four', frameborder: '0' },
childNodes: [],
rootId: 39,
id: 56,
},
{ type: 3, textContent: '\n \n\n', rootId: 39, id: 57 },
],
rootId: 39,
id: 52,
},
],
rootId: 39,
id: 41,
},
],
id: 39,
},
},
],
removes: [],
texts: [],
attributes: [],
isAttachIframe: true,
},
timestamp: now + 1000,
},
// add iframe three
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
adds: [
{
parentId: 54,
nextId: null,
node: {
type: 0,
childNodes: [
{
type: 2,
tagName: 'html',
attributes: {},
childNodes: [
{
type: 2,
tagName: 'head',
attributes: {},
childNodes: [],
rootId: 58,
id: 60,
},
{
type: 2,
tagName: 'body',
attributes: {},
childNodes: [],
rootId: 58,
id: 61,
},
],
rootId: 58,
id: 59,
},
],
id: 58,
},
},
],
removes: [],
texts: [],
attributes: [],
isAttachIframe: true,
},
timestamp: now + 1000,
},
// add iframe four
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
adds: [
{
parentId: 56,
nextId: null,
node: {
type: 0,
childNodes: [
{
type: 1,
name: 'html',
publicId: '',
systemId: '',
rootId: 62,
id: 63,
},
{
type: 2,
tagName: 'html',
attributes: { lang: 'en' },
childNodes: [
{
type: 2,
tagName: 'head',
attributes: {},
childNodes: [
{ type: 3, textContent: '\n ', rootId: 62, id: 66 },
{
type: 2,
tagName: 'meta',
attributes: { charset: 'UTF-8' },
childNodes: [],
rootId: 62,
id: 67,
},
{ type: 3, textContent: '\n ', rootId: 62, id: 68 },
{
type: 2,
tagName: 'meta',
attributes: {
name: 'viewport',
content: 'width=device-width, initial-scale=1.0',
},
childNodes: [],
rootId: 62,
id: 69,
},
{ type: 3, textContent: '\n ', rootId: 62, id: 70 },
{
type: 2,
tagName: 'title',
attributes: {},
childNodes: [
{
type: 3,
textContent: 'iframe 4',
rootId: 62,
id: 72,
},
],
rootId: 62,
id: 71,
},
{ type: 3, textContent: '\n ', rootId: 62, id: 73 },
],
rootId: 62,
id: 65,
},
{ type: 3, textContent: '\n ', rootId: 62, id: 74 },
{
type: 2,
tagName: 'body',
attributes: {},
childNodes: [
{
type: 3,
textContent: '\n iframe 4\n \n ',
rootId: 62,
id: 76,
},
{
type: 2,
tagName: 'script',
attributes: {},
childNodes: [
{
type: 3,
textContent: 'SCRIPT_PLACEHOLDER',
rootId: 62,
id: 78,
},
],
rootId: 62,
id: 77,
},
{ type: 3, textContent: '\n\n', rootId: 62, id: 79 },
],
rootId: 62,
id: 75,
},
],
rootId: 62,
id: 64,
},
],
id: 62,
},
},
],
removes: [],
texts: [],
attributes: [],
isAttachIframe: true,
},
timestamp: now + 1500,
},
// add iframe five
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
adds: [
{
parentId: 80,
nextId: null,
node: {
type: 0,
childNodes: [
{
type: 2,
tagName: 'html',
attributes: {},
childNodes: [
{
type: 2,
tagName: 'head',
attributes: {},
childNodes: [],
rootId: 81,
id: 83,
},
{
type: 2,
tagName: 'body',
attributes: {},
childNodes: [
{
type: 2,
tagName: 'script',
attributes: {},
childNodes: [
{
type: 3,
textContent: 'SCRIPT_PLACEHOLDER',
rootId: 81,
id: 86,
},
],
rootId: 81,
id: 85,
},
],
rootId: 81,
id: 84,
},
],
rootId: 81,
id: 82,
},
],
id: 81,
},
},
],
removes: [],
texts: [],
attributes: [],
isAttachIframe: true,
},
timestamp: now + 2000,
},
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
texts: [],
attributes: [],
removes: [],
adds: [
{
parentId: 75,
nextId: null,
node: {
type: 2,
tagName: 'iframe',
attributes: { id: 'five' },
childNodes: [],
rootId: 62,
id: 80,
},
},
],
},
timestamp: now + 2000,
},
// remove the html element of iframe four
{
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.Mutation,
texts: [],
attributes: [],
removes: [{ parentId: 62, id: 64 }],
adds: [],
},
timestamp: now + 2500,
},
];
export default events;

View File

@@ -11,6 +11,7 @@ import {
} from './utils';
import styleSheetRuleEvents from './events/style-sheet-rule-events';
import orderingEvents from './events/ordering';
import iframeEvents from './events/iframe';
interface ISuite {
code: string;
@@ -222,6 +223,115 @@ describe('replayer', function () {
expect(result).toEqual(false);
});
it('can fast-forward mutation events containing nested iframe elements', async () => {
await page.evaluate(`
events = ${JSON.stringify(iframeEvents)};
const { Replayer } = rrweb;
var replayer = new Replayer(events,{showDebug:true});
replayer.pause(250);
`);
const iframe = await page.$('iframe');
const contentDocument = await iframe!.contentFrame()!;
expect(await contentDocument!.$('iframe')).toBeNull();
const delay = 50;
// restart the replayer
await page.evaluate('replayer.play(0);');
await page.waitForTimeout(delay);
await page.evaluate('replayer.pause(550);'); // add 'iframe one' at 500
expect(await contentDocument!.$('iframe')).not.toBeNull();
const iframeOneDocument = await (await contentDocument!.$(
'iframe',
))!.contentFrame();
expect(iframeOneDocument).not.toBeNull();
expect(await iframeOneDocument!.$('noscript')).not.toBeNull();
// make sure custom style rules are inserted rules
expect((await iframeOneDocument!.$$('style')).length).toBe(1);
expect(
await iframeOneDocument!.$eval(
'noscript',
(element) => window.getComputedStyle(element).display,
),
).toEqual('none');
// add 'iframe two' and 'iframe three' at 1000
await page.evaluate('replayer.play(0);');
await page.waitForTimeout(delay);
await page.evaluate('replayer.pause(1050);');
expect((await contentDocument!.$$('iframe')).length).toEqual(2);
let iframeTwoDocument = await (
await contentDocument!.$$('iframe')
)[1]!.contentFrame();
expect(iframeTwoDocument).not.toBeNull();
expect((await iframeTwoDocument!.$$('iframe')).length).toEqual(2);
let iframeThreeDocument = await (
await iframeTwoDocument!.$$('iframe')
)[0]!.contentFrame();
let iframeFourDocument = await (
await iframeTwoDocument!.$$('iframe')
)[1]!.contentFrame();
expect(iframeThreeDocument).not.toBeNull();
expect(iframeFourDocument).not.toBeNull();
// add 'iframe four' at 1500
await page.evaluate('replayer.play(0);');
await page.waitForTimeout(delay);
await page.evaluate('replayer.pause(1550);');
iframeTwoDocument = await (
await contentDocument!.$$('iframe')
)[1]!.contentFrame();
iframeFourDocument = await (
await iframeTwoDocument!.$$('iframe')
)[1]!.contentFrame();
expect(await iframeFourDocument!.$('iframe')).toBeNull();
expect(await iframeFourDocument!.$('style')).not.toBeNull();
expect(await iframeFourDocument!.title()).toEqual('iframe 4');
// add 'iframe five' at 2000
await page.evaluate('replayer.play(0);');
await page.waitForTimeout(delay);
await page.evaluate('replayer.pause(2050);');
iframeTwoDocument = await (
await contentDocument!.$$('iframe')
)[1]!.contentFrame();
iframeFourDocument = await (
await iframeTwoDocument!.$$('iframe')
)[1]!.contentFrame();
expect(await iframeFourDocument!.$('iframe')).not.toBeNull();
const iframeFiveDocument = await (await iframeFourDocument!.$(
'iframe',
))!.contentFrame();
expect(iframeFiveDocument).not.toBeNull();
expect((await iframeFiveDocument!.$$('style')).length).toBe(1);
expect(await iframeFiveDocument!.$('noscript')).not.toBeNull();
expect(
await iframeFiveDocument!.$eval(
'noscript',
(element) => window.getComputedStyle(element).display,
),
).toEqual('none');
// remove the html element of 'iframe four' at 2500
await page.evaluate('replayer.play(0);');
await page.waitForTimeout(delay);
await page.evaluate('replayer.pause(2550);');
iframeTwoDocument = await (
await contentDocument!.$$('iframe')
)[1]!.contentFrame();
iframeFourDocument = await (
await iframeTwoDocument!.$$('iframe')
)[1]!.contentFrame();
// the html element should be removed
expect(await iframeFourDocument!.$('html')).toBeNull();
// the doctype should still exist
expect(
await iframeTwoDocument!.evaluate(
(iframe) => (iframe as HTMLIFrameElement)!.contentDocument!.doctype,
(await iframeTwoDocument!.$$('iframe'))[1],
),
).not.toBeNull();
});
it('can stream events in live mode', async () => {
const status = await page.evaluate(`
const { Replayer } = rrweb;

View File

@@ -1,3 +1,4 @@
/// <reference types="css-font-loading-module" />
import { serializedNodeWithId, idNodeMap, INode, MaskInputOptions, SlimDOMOptions, MaskInputFn, MaskTextFn } from 'rrweb-snapshot';
import { PackFn, UnpackFn } from './packer/base';
import { IframeManager } from './record/iframe-manager';