Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monkeypatch each iframe #716

Merged
merged 9 commits into from
Oct 1, 2021
15 changes: 11 additions & 4 deletions packages/rrweb/src/plugins/console/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type LogRecordOptions = {
level?: LogLevel[];
lengthThreshold?: number;
stringifyOptions?: StringifyOptions;
logger?: Logger;
logger?: Logger | string;
};

const defaultLogOptions: LogRecordOptions = {
Expand All @@ -48,7 +48,7 @@ const defaultLogOptions: LogRecordOptions = {
'warn',
],
lengthThreshold: 1000,
logger: console,
logger: 'console',
};

export type LogData = {
Expand Down Expand Up @@ -106,12 +106,19 @@ export type Logger = {

function initLogObserver(
cb: logCallback,
win: Window, // top window or in an iframe
logOptions: LogRecordOptions,
): listenerHandler {
const logger = logOptions.logger;
if (!logger) {
const loggerType = logOptions.logger;
if (!loggerType) {
return () => {};
}
let logger: Logger;
if (typeof loggerType === 'string') {
logger = (win as any)[loggerType];
} else {
logger = loggerType;
}
let logCount = 0;
const cancelHandlers: listenerHandler[] = [];
// add listener to thrown errors
Expand Down
87 changes: 54 additions & 33 deletions packages/rrweb/src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,10 +519,11 @@ function getNestedCSSRulePositions(rule: CSSRule): number[] {

function initStyleSheetObserver(
cb: styleSheetRuleCallback,
win: Window,
mirror: Mirror,
): listenerHandler {
const insertRule = CSSStyleSheet.prototype.insertRule;
CSSStyleSheet.prototype.insertRule = function (rule: string, index?: number) {
const insertRule = (win as any).CSSStyleSheet.prototype.insertRule;
(win as any).CSSStyleSheet.prototype.insertRule = function (rule: string, index?: number) {
const id = mirror.getId(this.ownerNode as INode);
if (id !== -1) {
cb({
Expand All @@ -533,8 +534,8 @@ function initStyleSheetObserver(
return insertRule.apply(this, arguments);
};

const deleteRule = CSSStyleSheet.prototype.deleteRule;
CSSStyleSheet.prototype.deleteRule = function (index: number) {
const deleteRule = (win as any).CSSStyleSheet.prototype.deleteRule;
(win as any).CSSStyleSheet.prototype.deleteRule = function (index: number) {
const id = mirror.getId(this.ownerNode as INode);
if (id !== -1) {
cb({
Expand All @@ -549,20 +550,20 @@ function initStyleSheetObserver(
[key: string]: GroupingCSSRuleTypes;
} = {};
if (isCSSGroupingRuleSupported) {
supportedNestedCSSRuleTypes['CSSGroupingRule'] = CSSGroupingRule;
supportedNestedCSSRuleTypes['CSSGroupingRule'] = (win as any).CSSGroupingRule;
} else {
// Some browsers (Safari) don't support CSSGroupingRule
// https://caniuse.com/?search=cssgroupingrule
// fall back to monkey patching classes that would have inherited from CSSGroupingRule

if (isCSSMediaRuleSupported) {
supportedNestedCSSRuleTypes['CSSMediaRule'] = CSSMediaRule;
supportedNestedCSSRuleTypes['CSSMediaRule'] = (win as any).CSSMediaRule;
}
if (isCSSConditionRuleSupported) {
supportedNestedCSSRuleTypes['CSSConditionRule'] = CSSConditionRule;
supportedNestedCSSRuleTypes['CSSConditionRule'] = (win as any).CSSConditionRule;
}
if (isCSSSupportsRuleSupported) {
supportedNestedCSSRuleTypes['CSSSupportsRule'] = CSSSupportsRule;
supportedNestedCSSRuleTypes['CSSSupportsRule'] = (win as any).CSSSupportsRule;
}
}

Expand Down Expand Up @@ -611,8 +612,8 @@ function initStyleSheetObserver(
});

return () => {
CSSStyleSheet.prototype.insertRule = insertRule;
CSSStyleSheet.prototype.deleteRule = deleteRule;
(win as any).CSSStyleSheet.prototype.insertRule = insertRule;
(win as any).CSSStyleSheet.prototype.deleteRule = deleteRule;
Object.entries(supportedNestedCSSRuleTypes).forEach(([typeKey, type]) => {
type.prototype.insertRule = unmodifiedFunctions[typeKey].insertRule;
type.prototype.deleteRule = unmodifiedFunctions[typeKey].deleteRule;
Expand All @@ -622,14 +623,15 @@ function initStyleSheetObserver(

function initStyleDeclarationObserver(
cb: styleDeclarationCallback,
win: Window,
mirror: Mirror,
): listenerHandler {
const setProperty = CSSStyleDeclaration.prototype.setProperty;
CSSStyleDeclaration.prototype.setProperty = function (
const setProperty = (win as any).CSSStyleDeclaration.prototype.setProperty;
(win as any).CSSStyleDeclaration.prototype.setProperty = function (
this: CSSStyleDeclaration,
property,
value,
priority,
property: string,
value: string,
priority: string,
) {
const id = mirror.getId(
(this.parentRule?.parentStyleSheet?.ownerNode as unknown) as INode,
Expand All @@ -648,10 +650,10 @@ function initStyleDeclarationObserver(
return setProperty.apply(this, arguments);
};

const removeProperty = CSSStyleDeclaration.prototype.removeProperty;
CSSStyleDeclaration.prototype.removeProperty = function (
const removeProperty = (win as any).CSSStyleDeclaration.prototype.removeProperty;
(win as any).CSSStyleDeclaration.prototype.removeProperty = function (
this: CSSStyleDeclaration,
property,
property: string,
) {
const id = mirror.getId(
(this.parentRule?.parentStyleSheet?.ownerNode as unknown) as INode,
Expand All @@ -669,8 +671,8 @@ function initStyleDeclarationObserver(
};

return () => {
CSSStyleDeclaration.prototype.setProperty = setProperty;
CSSStyleDeclaration.prototype.removeProperty = removeProperty;
(win as any).CSSStyleDeclaration.prototype.setProperty = setProperty;
(win as any).CSSStyleDeclaration.prototype.removeProperty = removeProperty;
};
}

Expand Down Expand Up @@ -702,22 +704,23 @@ function initMediaInteractionObserver(

function initCanvasMutationObserver(
cb: canvasMutationCallback,
win: Window,
blockClass: blockClass,
mirror: Mirror,
): listenerHandler {
const props = Object.getOwnPropertyNames(CanvasRenderingContext2D.prototype);
const props = Object.getOwnPropertyNames((win as any).CanvasRenderingContext2D.prototype);
const handlers: listenerHandler[] = [];
for (const prop of props) {
try {
if (
typeof CanvasRenderingContext2D.prototype[
typeof (win as any).CanvasRenderingContext2D.prototype[
prop as keyof CanvasRenderingContext2D
] !== 'function'
) {
continue;
}
const restoreHandler = patch(
CanvasRenderingContext2D.prototype,
(win as any).CanvasRenderingContext2D.prototype,
prop,
function (original) {
return function (
Expand Down Expand Up @@ -758,7 +761,7 @@ function initCanvasMutationObserver(
handlers.push(restoreHandler);
} catch {
const hookHandler = hookSetter<CanvasRenderingContext2D>(
CanvasRenderingContext2D.prototype,
(win as any).CanvasRenderingContext2D.prototype,
prop,
{
set(v) {
Expand All @@ -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 fontMap = new WeakMap<FontFace, fontParam>();

const originalFontFace = FontFace;
const originalFontFace = (win as any).FontFace;
// tslint:disable-next-line: no-any
(window as any).FontFace = function FontFace(
(win as any).FontFace = function FontFace(
family: string,
source: string | ArrayBufferView,
descriptors?: FontFaceDescriptors,
Expand All @@ -805,7 +813,7 @@ function initFontObserver(cb: fontCallback): listenerHandler {
return fontFace;
};

const restoreHandler = patch(document.fonts, 'add', function (original) {
const restoreHandler = patch(doc.fonts, 'add', function (original) {
return function (this: FontFaceSet, fontFace: FontFace) {
setTimeout(() => {
const p = fontMap.get(fontFace);
Expand All @@ -820,7 +828,7 @@ function initFontObserver(cb: fontCallback): listenerHandler {

handlers.push(() => {
// tslint:disable-next-line: no-any
(window as any).FontFace = originalFontFace;
(win as any).FontFace = originalFontFace;
});
handlers.push(restoreHandler);

Expand Down Expand Up @@ -971,22 +979,35 @@ export function initObservers(
o.blockClass,
o.mirror,
);

const currentWindow = o.doc.defaultView as Window; // basically document.window

const styleSheetObserver = initStyleSheetObserver(
o.styleSheetRuleCb,
currentWindow,
o.mirror,
);
const styleDeclarationObserver = initStyleDeclarationObserver(
o.styleDeclarationCb,
currentWindow,
o.mirror,
);
const canvasMutationObserver = o.recordCanvas
? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass, o.mirror)
: () => {};
const fontObserver = o.collectFonts ? initFontObserver(o.fontCb) : () => {};
? initCanvasMutationObserver(
o.canvasMutationCb,
currentWindow,
o.blockClass,
o.mirror,
) : () => {};
const fontObserver = o.collectFonts ? initFontObserver(o.fontCb, o.doc) : () => {};
// plugins
const pluginHandlers: listenerHandler[] = [];
for (const plugin of o.plugins) {
pluginHandlers.push(plugin.observer(plugin.callback, plugin.options));
pluginHandlers.push(plugin.observer(
plugin.callback,
currentWindow,
plugin.options,
));
}

return () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/rrweb/src/replay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ export class Replayer {
const head = this.iframe.contentDocument?.head;
if (head) {
const unloadSheets: Set<HTMLLinkElement> = new Set();
let timer: number;
let timer: ReturnType<typeof setTimeout> | -1;
let beforeLoadState = this.service.state;
const stateHandler = () => {
beforeLoadState = this.service.state;
Expand All @@ -784,7 +784,7 @@ export class Replayer {
}
this.emitter.emit(ReplayerEvents.LoadStylesheetEnd);
if (timer) {
window.clearTimeout(timer);
clearTimeout(timer);
}
unsubscribe();
}
Expand All @@ -796,7 +796,7 @@ export class Replayer {
// find some unload sheets after iterate
this.service.send({ type: 'PAUSE' });
this.emitter.emit(ReplayerEvents.LoadStylesheetStart);
timer = window.setTimeout(() => {
timer = setTimeout(() => {
if (beforeLoadState.matches('playing')) {
this.play(this.getCurrentTime());
}
Expand Down
2 changes: 1 addition & 1 deletion packages/rrweb/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export type SamplingStrategy = Partial<{

export type RecordPlugin<TOptions = unknown> = {
name: string;
observer: (cb: Function, options: TOptions) => listenerHandler;
observer: (cb: Function, win: Window, options: TOptions) => listenerHandler;
options: TOptions;
};

Expand Down
6 changes: 3 additions & 3 deletions packages/rrweb/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export function throttle<T>(
wait: number,
options: throttleOptions = {},
) {
let timeout: number | null = null;
let timeout: ReturnType<typeof setTimeout> | null = null;
let previous = 0;
// tslint:disable-next-line: only-arrow-functions
return function (arg: T) {
Expand All @@ -124,13 +124,13 @@ export function throttle<T>(
let args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
window.clearTimeout(timeout);
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout && options.trailing !== false) {
timeout = window.setTimeout(() => {
timeout = setTimeout(() => {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
func.apply(context, args);
Expand Down
Loading