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:
@@ -22,7 +22,7 @@ type LogRecordOptions = {
|
|||||||
level?: LogLevel[];
|
level?: LogLevel[];
|
||||||
lengthThreshold?: number;
|
lengthThreshold?: number;
|
||||||
stringifyOptions?: StringifyOptions;
|
stringifyOptions?: StringifyOptions;
|
||||||
logger?: Logger;
|
logger?: Logger | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultLogOptions: LogRecordOptions = {
|
const defaultLogOptions: LogRecordOptions = {
|
||||||
@@ -48,7 +48,7 @@ const defaultLogOptions: LogRecordOptions = {
|
|||||||
'warn',
|
'warn',
|
||||||
],
|
],
|
||||||
lengthThreshold: 1000,
|
lengthThreshold: 1000,
|
||||||
logger: console,
|
logger: 'console',
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LogData = {
|
export type LogData = {
|
||||||
@@ -106,12 +106,19 @@ export type Logger = {
|
|||||||
|
|
||||||
function initLogObserver(
|
function initLogObserver(
|
||||||
cb: logCallback,
|
cb: logCallback,
|
||||||
|
win: Window, // top window or in an iframe
|
||||||
logOptions: LogRecordOptions,
|
logOptions: LogRecordOptions,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
const logger = logOptions.logger;
|
const loggerType = logOptions.logger;
|
||||||
if (!logger) {
|
if (!loggerType) {
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
let logger: Logger;
|
||||||
|
if (typeof loggerType === 'string') {
|
||||||
|
logger = (win as any)[loggerType];
|
||||||
|
} else {
|
||||||
|
logger = loggerType;
|
||||||
|
}
|
||||||
let logCount = 0;
|
let logCount = 0;
|
||||||
const cancelHandlers: listenerHandler[] = [];
|
const cancelHandlers: listenerHandler[] = [];
|
||||||
// add listener to thrown errors
|
// add listener to thrown errors
|
||||||
|
|||||||
@@ -519,10 +519,11 @@ function getNestedCSSRulePositions(rule: CSSRule): number[] {
|
|||||||
|
|
||||||
function initStyleSheetObserver(
|
function initStyleSheetObserver(
|
||||||
cb: styleSheetRuleCallback,
|
cb: styleSheetRuleCallback,
|
||||||
|
win: Window,
|
||||||
mirror: Mirror,
|
mirror: Mirror,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
const insertRule = CSSStyleSheet.prototype.insertRule;
|
const insertRule = (win as any).CSSStyleSheet.prototype.insertRule;
|
||||||
CSSStyleSheet.prototype.insertRule = function (rule: string, index?: number) {
|
(win as any).CSSStyleSheet.prototype.insertRule = function (rule: string, index?: number) {
|
||||||
const id = mirror.getId(this.ownerNode as INode);
|
const id = mirror.getId(this.ownerNode as INode);
|
||||||
if (id !== -1) {
|
if (id !== -1) {
|
||||||
cb({
|
cb({
|
||||||
@@ -533,8 +534,8 @@ function initStyleSheetObserver(
|
|||||||
return insertRule.apply(this, arguments);
|
return insertRule.apply(this, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteRule = CSSStyleSheet.prototype.deleteRule;
|
const deleteRule = (win as any).CSSStyleSheet.prototype.deleteRule;
|
||||||
CSSStyleSheet.prototype.deleteRule = function (index: number) {
|
(win as any).CSSStyleSheet.prototype.deleteRule = function (index: number) {
|
||||||
const id = mirror.getId(this.ownerNode as INode);
|
const id = mirror.getId(this.ownerNode as INode);
|
||||||
if (id !== -1) {
|
if (id !== -1) {
|
||||||
cb({
|
cb({
|
||||||
@@ -549,20 +550,20 @@ function initStyleSheetObserver(
|
|||||||
[key: string]: GroupingCSSRuleTypes;
|
[key: string]: GroupingCSSRuleTypes;
|
||||||
} = {};
|
} = {};
|
||||||
if (isCSSGroupingRuleSupported) {
|
if (isCSSGroupingRuleSupported) {
|
||||||
supportedNestedCSSRuleTypes['CSSGroupingRule'] = CSSGroupingRule;
|
supportedNestedCSSRuleTypes['CSSGroupingRule'] = (win as any).CSSGroupingRule;
|
||||||
} else {
|
} else {
|
||||||
// Some browsers (Safari) don't support CSSGroupingRule
|
// Some browsers (Safari) don't support CSSGroupingRule
|
||||||
// https://caniuse.com/?search=cssgroupingrule
|
// https://caniuse.com/?search=cssgroupingrule
|
||||||
// fall back to monkey patching classes that would have inherited from CSSGroupingRule
|
// fall back to monkey patching classes that would have inherited from CSSGroupingRule
|
||||||
|
|
||||||
if (isCSSMediaRuleSupported) {
|
if (isCSSMediaRuleSupported) {
|
||||||
supportedNestedCSSRuleTypes['CSSMediaRule'] = CSSMediaRule;
|
supportedNestedCSSRuleTypes['CSSMediaRule'] = (win as any).CSSMediaRule;
|
||||||
}
|
}
|
||||||
if (isCSSConditionRuleSupported) {
|
if (isCSSConditionRuleSupported) {
|
||||||
supportedNestedCSSRuleTypes['CSSConditionRule'] = CSSConditionRule;
|
supportedNestedCSSRuleTypes['CSSConditionRule'] = (win as any).CSSConditionRule;
|
||||||
}
|
}
|
||||||
if (isCSSSupportsRuleSupported) {
|
if (isCSSSupportsRuleSupported) {
|
||||||
supportedNestedCSSRuleTypes['CSSSupportsRule'] = CSSSupportsRule;
|
supportedNestedCSSRuleTypes['CSSSupportsRule'] = (win as any).CSSSupportsRule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,8 +612,8 @@ function initStyleSheetObserver(
|
|||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
CSSStyleSheet.prototype.insertRule = insertRule;
|
(win as any).CSSStyleSheet.prototype.insertRule = insertRule;
|
||||||
CSSStyleSheet.prototype.deleteRule = deleteRule;
|
(win as any).CSSStyleSheet.prototype.deleteRule = deleteRule;
|
||||||
Object.entries(supportedNestedCSSRuleTypes).forEach(([typeKey, type]) => {
|
Object.entries(supportedNestedCSSRuleTypes).forEach(([typeKey, type]) => {
|
||||||
type.prototype.insertRule = unmodifiedFunctions[typeKey].insertRule;
|
type.prototype.insertRule = unmodifiedFunctions[typeKey].insertRule;
|
||||||
type.prototype.deleteRule = unmodifiedFunctions[typeKey].deleteRule;
|
type.prototype.deleteRule = unmodifiedFunctions[typeKey].deleteRule;
|
||||||
@@ -622,14 +623,15 @@ function initStyleSheetObserver(
|
|||||||
|
|
||||||
function initStyleDeclarationObserver(
|
function initStyleDeclarationObserver(
|
||||||
cb: styleDeclarationCallback,
|
cb: styleDeclarationCallback,
|
||||||
|
win: Window,
|
||||||
mirror: Mirror,
|
mirror: Mirror,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
const setProperty = CSSStyleDeclaration.prototype.setProperty;
|
const setProperty = (win as any).CSSStyleDeclaration.prototype.setProperty;
|
||||||
CSSStyleDeclaration.prototype.setProperty = function (
|
(win as any).CSSStyleDeclaration.prototype.setProperty = function (
|
||||||
this: CSSStyleDeclaration,
|
this: CSSStyleDeclaration,
|
||||||
property,
|
property: string,
|
||||||
value,
|
value: string,
|
||||||
priority,
|
priority: string,
|
||||||
) {
|
) {
|
||||||
const id = mirror.getId(
|
const id = mirror.getId(
|
||||||
(this.parentRule?.parentStyleSheet?.ownerNode as unknown) as INode,
|
(this.parentRule?.parentStyleSheet?.ownerNode as unknown) as INode,
|
||||||
@@ -648,10 +650,10 @@ function initStyleDeclarationObserver(
|
|||||||
return setProperty.apply(this, arguments);
|
return setProperty.apply(this, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeProperty = CSSStyleDeclaration.prototype.removeProperty;
|
const removeProperty = (win as any).CSSStyleDeclaration.prototype.removeProperty;
|
||||||
CSSStyleDeclaration.prototype.removeProperty = function (
|
(win as any).CSSStyleDeclaration.prototype.removeProperty = function (
|
||||||
this: CSSStyleDeclaration,
|
this: CSSStyleDeclaration,
|
||||||
property,
|
property: string,
|
||||||
) {
|
) {
|
||||||
const id = mirror.getId(
|
const id = mirror.getId(
|
||||||
(this.parentRule?.parentStyleSheet?.ownerNode as unknown) as INode,
|
(this.parentRule?.parentStyleSheet?.ownerNode as unknown) as INode,
|
||||||
@@ -669,8 +671,8 @@ function initStyleDeclarationObserver(
|
|||||||
};
|
};
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
CSSStyleDeclaration.prototype.setProperty = setProperty;
|
(win as any).CSSStyleDeclaration.prototype.setProperty = setProperty;
|
||||||
CSSStyleDeclaration.prototype.removeProperty = removeProperty;
|
(win as any).CSSStyleDeclaration.prototype.removeProperty = removeProperty;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -702,22 +704,23 @@ function initMediaInteractionObserver(
|
|||||||
|
|
||||||
function initCanvasMutationObserver(
|
function initCanvasMutationObserver(
|
||||||
cb: canvasMutationCallback,
|
cb: canvasMutationCallback,
|
||||||
|
win: Window,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
mirror: Mirror,
|
mirror: Mirror,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
const props = Object.getOwnPropertyNames(CanvasRenderingContext2D.prototype);
|
const props = Object.getOwnPropertyNames((win as any).CanvasRenderingContext2D.prototype);
|
||||||
const handlers: listenerHandler[] = [];
|
const handlers: listenerHandler[] = [];
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
typeof CanvasRenderingContext2D.prototype[
|
typeof (win as any).CanvasRenderingContext2D.prototype[
|
||||||
prop as keyof CanvasRenderingContext2D
|
prop as keyof CanvasRenderingContext2D
|
||||||
] !== 'function'
|
] !== 'function'
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const restoreHandler = patch(
|
const restoreHandler = patch(
|
||||||
CanvasRenderingContext2D.prototype,
|
(win as any).CanvasRenderingContext2D.prototype,
|
||||||
prop,
|
prop,
|
||||||
function (original) {
|
function (original) {
|
||||||
return function (
|
return function (
|
||||||
@@ -758,7 +761,7 @@ function initCanvasMutationObserver(
|
|||||||
handlers.push(restoreHandler);
|
handlers.push(restoreHandler);
|
||||||
} catch {
|
} catch {
|
||||||
const hookHandler = hookSetter<CanvasRenderingContext2D>(
|
const hookHandler = hookSetter<CanvasRenderingContext2D>(
|
||||||
CanvasRenderingContext2D.prototype,
|
(win as any).CanvasRenderingContext2D.prototype,
|
||||||
prop,
|
prop,
|
||||||
{
|
{
|
||||||
set(v) {
|
set(v) {
|
||||||
@@ -779,14 +782,19 @@ function initCanvasMutationObserver(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function initFontObserver(cb: fontCallback): listenerHandler {
|
function initFontObserver(
|
||||||
|
cb: fontCallback,
|
||||||
|
doc: Document,
|
||||||
|
): listenerHandler {
|
||||||
|
const win = doc.defaultView;
|
||||||
|
|
||||||
const handlers: listenerHandler[] = [];
|
const handlers: listenerHandler[] = [];
|
||||||
|
|
||||||
const fontMap = new WeakMap<FontFace, fontParam>();
|
const fontMap = new WeakMap<FontFace, fontParam>();
|
||||||
|
|
||||||
const originalFontFace = FontFace;
|
const originalFontFace = (win as any).FontFace;
|
||||||
// tslint:disable-next-line: no-any
|
// tslint:disable-next-line: no-any
|
||||||
(window as any).FontFace = function FontFace(
|
(win as any).FontFace = function FontFace(
|
||||||
family: string,
|
family: string,
|
||||||
source: string | ArrayBufferView,
|
source: string | ArrayBufferView,
|
||||||
descriptors?: FontFaceDescriptors,
|
descriptors?: FontFaceDescriptors,
|
||||||
@@ -805,7 +813,7 @@ function initFontObserver(cb: fontCallback): listenerHandler {
|
|||||||
return fontFace;
|
return fontFace;
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreHandler = patch(document.fonts, 'add', function (original) {
|
const restoreHandler = patch(doc.fonts, 'add', function (original) {
|
||||||
return function (this: FontFaceSet, fontFace: FontFace) {
|
return function (this: FontFaceSet, fontFace: FontFace) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const p = fontMap.get(fontFace);
|
const p = fontMap.get(fontFace);
|
||||||
@@ -820,7 +828,7 @@ function initFontObserver(cb: fontCallback): listenerHandler {
|
|||||||
|
|
||||||
handlers.push(() => {
|
handlers.push(() => {
|
||||||
// tslint:disable-next-line: no-any
|
// tslint:disable-next-line: no-any
|
||||||
(window as any).FontFace = originalFontFace;
|
(win as any).FontFace = originalFontFace;
|
||||||
});
|
});
|
||||||
handlers.push(restoreHandler);
|
handlers.push(restoreHandler);
|
||||||
|
|
||||||
@@ -971,22 +979,35 @@ export function initObservers(
|
|||||||
o.blockClass,
|
o.blockClass,
|
||||||
o.mirror,
|
o.mirror,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const currentWindow = o.doc.defaultView as Window; // basically document.window
|
||||||
|
|
||||||
const styleSheetObserver = initStyleSheetObserver(
|
const styleSheetObserver = initStyleSheetObserver(
|
||||||
o.styleSheetRuleCb,
|
o.styleSheetRuleCb,
|
||||||
|
currentWindow,
|
||||||
o.mirror,
|
o.mirror,
|
||||||
);
|
);
|
||||||
const styleDeclarationObserver = initStyleDeclarationObserver(
|
const styleDeclarationObserver = initStyleDeclarationObserver(
|
||||||
o.styleDeclarationCb,
|
o.styleDeclarationCb,
|
||||||
|
currentWindow,
|
||||||
o.mirror,
|
o.mirror,
|
||||||
);
|
);
|
||||||
const canvasMutationObserver = o.recordCanvas
|
const canvasMutationObserver = o.recordCanvas
|
||||||
? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass, o.mirror)
|
? initCanvasMutationObserver(
|
||||||
: () => {};
|
o.canvasMutationCb,
|
||||||
const fontObserver = o.collectFonts ? initFontObserver(o.fontCb) : () => {};
|
currentWindow,
|
||||||
|
o.blockClass,
|
||||||
|
o.mirror,
|
||||||
|
) : () => {};
|
||||||
|
const fontObserver = o.collectFonts ? initFontObserver(o.fontCb, o.doc) : () => {};
|
||||||
// plugins
|
// plugins
|
||||||
const pluginHandlers: listenerHandler[] = [];
|
const pluginHandlers: listenerHandler[] = [];
|
||||||
for (const plugin of o.plugins) {
|
for (const plugin of o.plugins) {
|
||||||
pluginHandlers.push(plugin.observer(plugin.callback, plugin.options));
|
pluginHandlers.push(plugin.observer(
|
||||||
|
plugin.callback,
|
||||||
|
currentWindow,
|
||||||
|
plugin.options,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -759,7 +759,7 @@ export class Replayer {
|
|||||||
const head = this.iframe.contentDocument?.head;
|
const head = this.iframe.contentDocument?.head;
|
||||||
if (head) {
|
if (head) {
|
||||||
const unloadSheets: Set<HTMLLinkElement> = new Set();
|
const unloadSheets: Set<HTMLLinkElement> = new Set();
|
||||||
let timer: number;
|
let timer: ReturnType<typeof setTimeout> | -1;
|
||||||
let beforeLoadState = this.service.state;
|
let beforeLoadState = this.service.state;
|
||||||
const stateHandler = () => {
|
const stateHandler = () => {
|
||||||
beforeLoadState = this.service.state;
|
beforeLoadState = this.service.state;
|
||||||
@@ -784,7 +784,7 @@ export class Replayer {
|
|||||||
}
|
}
|
||||||
this.emitter.emit(ReplayerEvents.LoadStylesheetEnd);
|
this.emitter.emit(ReplayerEvents.LoadStylesheetEnd);
|
||||||
if (timer) {
|
if (timer) {
|
||||||
window.clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
}
|
}
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
}
|
}
|
||||||
@@ -796,7 +796,7 @@ export class Replayer {
|
|||||||
// find some unload sheets after iterate
|
// find some unload sheets after iterate
|
||||||
this.service.send({ type: 'PAUSE' });
|
this.service.send({ type: 'PAUSE' });
|
||||||
this.emitter.emit(ReplayerEvents.LoadStylesheetStart);
|
this.emitter.emit(ReplayerEvents.LoadStylesheetStart);
|
||||||
timer = window.setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
if (beforeLoadState.matches('playing')) {
|
if (beforeLoadState.matches('playing')) {
|
||||||
this.play(this.getCurrentTime());
|
this.play(this.getCurrentTime());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export type SamplingStrategy = Partial<{
|
|||||||
|
|
||||||
export type RecordPlugin<TOptions = unknown> = {
|
export type RecordPlugin<TOptions = unknown> = {
|
||||||
name: string;
|
name: string;
|
||||||
observer: (cb: Function, options: TOptions) => listenerHandler;
|
observer: (cb: Function, win: Window, options: TOptions) => listenerHandler;
|
||||||
options: TOptions;
|
options: TOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ export function throttle<T>(
|
|||||||
wait: number,
|
wait: number,
|
||||||
options: throttleOptions = {},
|
options: throttleOptions = {},
|
||||||
) {
|
) {
|
||||||
let timeout: number | null = null;
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
||||||
let previous = 0;
|
let previous = 0;
|
||||||
// tslint:disable-next-line: only-arrow-functions
|
// tslint:disable-next-line: only-arrow-functions
|
||||||
return function (arg: T) {
|
return function (arg: T) {
|
||||||
@@ -124,13 +124,13 @@ export function throttle<T>(
|
|||||||
let args = arguments;
|
let args = arguments;
|
||||||
if (remaining <= 0 || remaining > wait) {
|
if (remaining <= 0 || remaining > wait) {
|
||||||
if (timeout) {
|
if (timeout) {
|
||||||
window.clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
timeout = null;
|
timeout = null;
|
||||||
}
|
}
|
||||||
previous = now;
|
previous = now;
|
||||||
func.apply(context, args);
|
func.apply(context, args);
|
||||||
} else if (!timeout && options.trailing !== false) {
|
} else if (!timeout && options.trailing !== false) {
|
||||||
timeout = window.setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
previous = options.leading === false ? 0 : Date.now();
|
previous = options.leading === false ? 0 : Date.now();
|
||||||
timeout = null;
|
timeout = null;
|
||||||
func.apply(context, args);
|
func.apply(context, args);
|
||||||
|
|||||||
@@ -3347,6 +3347,88 @@ exports[`log 1`] = `
|
|||||||
\\"payload\\": []
|
\\"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`] = `
|
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`] = `
|
exports[`stylesheet-rules 1`] = `
|
||||||
"[
|
"[
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -471,8 +471,15 @@ describe('record integration tests', function (this: ISuite) {
|
|||||||
console.trace('trace');
|
console.trace('trace');
|
||||||
console.warn('warn');
|
console.warn('warn');
|
||||||
console.clear();
|
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');
|
const snapshots = await page.evaluate('window.snapshots');
|
||||||
assertSnapshot(snapshots, __filename, 'log');
|
assertSnapshot(snapshots, __filename, 'log');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -220,9 +220,11 @@ describe('record', function (this: ISuite) {
|
|||||||
document.head.appendChild(styleElement);
|
document.head.appendChild(styleElement);
|
||||||
|
|
||||||
const styleSheet = <CSSStyleSheet>styleElement.sheet;
|
const styleSheet = <CSSStyleSheet>styleElement.sheet;
|
||||||
|
// begin: pre-serialization
|
||||||
const ruleIdx0 = styleSheet.insertRule('body { background: #000; }');
|
const ruleIdx0 = styleSheet.insertRule('body { background: #000; }');
|
||||||
const ruleIdx1 = styleSheet.insertRule('body { background: #111; }');
|
const ruleIdx1 = styleSheet.insertRule('body { background: #111; }');
|
||||||
styleSheet.deleteRule(ruleIdx1);
|
styleSheet.deleteRule(ruleIdx1);
|
||||||
|
// end: pre-serialization
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
styleSheet.insertRule('body { color: #fff; }');
|
styleSheet.insertRule('body { color: #fff; }');
|
||||||
}, 0);
|
}, 0);
|
||||||
@@ -239,14 +241,15 @@ describe('record', function (this: ISuite) {
|
|||||||
e.type === EventType.IncrementalSnapshot &&
|
e.type === EventType.IncrementalSnapshot &&
|
||||||
e.data.source === IncrementalSource.StyleSheetRule,
|
e.data.source === IncrementalSource.StyleSheetRule,
|
||||||
);
|
);
|
||||||
const addRuleCount = styleSheetRuleEvents.filter((e) =>
|
const addRules = styleSheetRuleEvents.filter((e) =>
|
||||||
Boolean((e.data as styleSheetRuleData).adds),
|
Boolean((e.data as styleSheetRuleData).adds),
|
||||||
).length;
|
);
|
||||||
const removeRuleCount = styleSheetRuleEvents.filter((e) =>
|
const removeRuleCount = styleSheetRuleEvents.filter((e) =>
|
||||||
Boolean((e.data as styleSheetRuleData).removes),
|
Boolean((e.data as styleSheetRuleData).removes),
|
||||||
).length;
|
).length;
|
||||||
// sync insert/delete should be ignored
|
// pre-serialization insert/delete should be ignored
|
||||||
expect(addRuleCount).to.equal(2);
|
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);
|
expect(removeRuleCount).to.equal(1);
|
||||||
assertSnapshot(this.events, __filename, 'stylesheet-rules');
|
assertSnapshot(this.events, __filename, 'stylesheet-rules');
|
||||||
});
|
});
|
||||||
@@ -311,6 +314,30 @@ describe('record', function (this: ISuite) {
|
|||||||
});
|
});
|
||||||
it('captures nested stylesheet rules', captureNestedStylesheetRulesTest);
|
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) {
|
describe('record iframes', function (this: ISuite) {
|
||||||
@@ -350,4 +377,60 @@ describe('record iframes', function (this: ISuite) {
|
|||||||
EventType.IncrementalSnapshot,
|
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');
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ declare type LogRecordOptions = {
|
|||||||
level?: LogLevel[];
|
level?: LogLevel[];
|
||||||
lengthThreshold?: number;
|
lengthThreshold?: number;
|
||||||
stringifyOptions?: StringifyOptions;
|
stringifyOptions?: StringifyOptions;
|
||||||
logger?: Logger;
|
logger?: Logger | string;
|
||||||
};
|
};
|
||||||
export declare type LogData = {
|
export declare type LogData = {
|
||||||
level: LogLevel;
|
level: LogLevel;
|
||||||
|
|||||||
2
packages/rrweb/typings/types.d.ts
vendored
2
packages/rrweb/typings/types.d.ts
vendored
@@ -126,7 +126,7 @@ export declare type SamplingStrategy = Partial<{
|
|||||||
}>;
|
}>;
|
||||||
export declare type RecordPlugin<TOptions = unknown> = {
|
export declare type RecordPlugin<TOptions = unknown> = {
|
||||||
name: string;
|
name: string;
|
||||||
observer: (cb: Function, options: TOptions) => listenerHandler;
|
observer: (cb: Function, win: Window, options: TOptions) => listenerHandler;
|
||||||
options: TOptions;
|
options: TOptions;
|
||||||
};
|
};
|
||||||
export declare type recordOptions<T> = {
|
export declare type recordOptions<T> = {
|
||||||
|
|||||||
Reference in New Issue
Block a user