new sandbox strategy
In this commit we switched the sandbox strategy to use iframe's sandbox attribute. Indeed we do not need delegate event anymore, but need to add some styles into the iframe. The details were documented in the sandbox part of internal design.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { rebuild, buildNodeWithSN } from 'rrweb-snapshot';
|
||||
import * as mittProxy from 'mitt';
|
||||
import { on, off } from 'delegated-events';
|
||||
import Timer from './timer';
|
||||
import {
|
||||
EventType,
|
||||
@@ -18,6 +17,7 @@ import {
|
||||
actionWithDelay,
|
||||
} from '../types';
|
||||
import { mirror } from '../utils';
|
||||
import injectStyleRules from './styles/inject-style';
|
||||
import './styles/style.css';
|
||||
|
||||
// https://github.com/rollup/rollup/issues/1267#issuecomment-296395734
|
||||
@@ -138,6 +138,7 @@ export class Replayer {
|
||||
this.wrapper.appendChild(this.mouse);
|
||||
|
||||
this.iframe = document.createElement('iframe');
|
||||
this.iframe.setAttribute('sandbox', 'allow-same-origin');
|
||||
this.wrapper.appendChild(this.iframe);
|
||||
}
|
||||
|
||||
@@ -208,21 +209,13 @@ export class Replayer {
|
||||
event: fullSnapshotEvent & { timestamp: number },
|
||||
) {
|
||||
mirror.map = rebuild(event.data.node, this.iframe.contentDocument!)[1];
|
||||
const styleEl = document.createElement('style');
|
||||
const { documentElement, head } = this.iframe.contentDocument!;
|
||||
documentElement!.insertBefore(styleEl, head);
|
||||
for (let idx = 0; idx < injectStyleRules.length; idx++) {
|
||||
(styleEl.sheet! as CSSStyleSheet).insertRule(injectStyleRules[idx], idx);
|
||||
}
|
||||
this.waitForStylesheetLoad();
|
||||
// avoid form submit to refresh the iframe
|
||||
off('submit', 'form', this.preventDefault, {
|
||||
document: this.iframe.contentDocument!,
|
||||
});
|
||||
on('submit', 'form', this.preventDefault, {
|
||||
document: this.iframe.contentDocument!,
|
||||
});
|
||||
// avoid a link click to refresh the iframe
|
||||
off('click', 'a', this.preventDefault, {
|
||||
document: this.iframe.contentDocument!,
|
||||
});
|
||||
on('click', 'a', this.preventDefault, {
|
||||
document: this.iframe.contentDocument!,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,10 +255,6 @@ export class Replayer {
|
||||
}
|
||||
}
|
||||
|
||||
private preventDefault(evt: Event) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
private applyIncremental(d: incrementalData, isSync: boolean) {
|
||||
switch (d.source) {
|
||||
case IncrementalSource.Mutation: {
|
||||
@@ -377,13 +366,24 @@ export class Replayer {
|
||||
}
|
||||
const event = new Event(MouseInteractions[d.type].toLowerCase());
|
||||
const target = (mirror.getNode(d.id) as Node) as HTMLElement;
|
||||
target.dispatchEvent(event);
|
||||
if (d.type === MouseInteractions.Blur) {
|
||||
target.blur();
|
||||
} else if (d.type === MouseInteractions.Click) {
|
||||
target.click();
|
||||
/**
|
||||
* Click has no visual impact when replaying and may
|
||||
* trigger navigation when apply to an <a> link.
|
||||
* So we will not call click(), instead we add an
|
||||
* animation to the mouse element which indicate user
|
||||
* clicked at this moment.
|
||||
*/
|
||||
this.mouse.classList.remove('active');
|
||||
// tslint:disable-next-line
|
||||
void this.mouse.offsetWidth;
|
||||
this.mouse.classList.add('active');
|
||||
} else if (d.type === MouseInteractions.Focus) {
|
||||
target.focus();
|
||||
} else {
|
||||
target.dispatchEvent(event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
6
src/replay/styles/inject-style.ts
Normal file
6
src/replay/styles/inject-style.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
const rules: string[] = [
|
||||
'iframe { background: #ccc }',
|
||||
'noscript { display: none !important; }',
|
||||
];
|
||||
|
||||
export default rules;
|
||||
@@ -3,8 +3,12 @@
|
||||
}
|
||||
.replayer-mouse {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-size: contain;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGRhdGEtbmFtZT0iTGF5ZXIgMSIgdmlld0JveD0iMCAwIDUwIDUwIiB4PSIwcHgiIHk9IjBweCI+PHRpdGxlPkRlc2lnbl90bnA8L3RpdGxlPjxwYXRoIGQ9Ik00OC43MSw0Mi45MUwzNC4wOCwyOC4yOSw0NC4zMywxOEExLDEsMCwwLDAsNDQsMTYuMzlMMi4zNSwxLjA2QTEsMSwwLDAsMCwxLjA2LDIuMzVMMTYuMzksNDRhMSwxLDAsMCwwLDEuNjUuMzZMMjguMjksMzQuMDgsNDIuOTEsNDguNzFhMSwxLDAsMCwwLDEuNDEsMGw0LjM4LTQuMzhBMSwxLDAsMCwwLDQ4LjcxLDQyLjkxWm0tNS4wOSwzLjY3TDI5LDMyYTEsMSwwLDAsMC0xLjQxLDBsLTkuODUsOS44NUwzLjY5LDMuNjlsMzguMTIsMTRMMzIsMjcuNThBMSwxLDAsMCwwLDMyLDI5TDQ2LjU5LDQzLjYyWiI+PC9wYXRoPjwvc3ZnPg==');
|
||||
}
|
||||
.replayer-mouse::after {
|
||||
content: '';
|
||||
@@ -12,7 +16,27 @@
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
background: thistle;
|
||||
background: rgb(73, 80, 246);
|
||||
transform: translate(-10px, -10px);
|
||||
opacity: 0.5;
|
||||
opacity: 0.3;
|
||||
}
|
||||
.replayer-mouse.active::after {
|
||||
animation: click 0.3s ease-in-out 1;
|
||||
}
|
||||
|
||||
@keyframes click {
|
||||
0% {
|
||||
opacity: 0.3;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
transform: translate(-10px, -10px);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
transform: translate(-5px, -5px);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user