impl #309 observe font face set changes
This commit is contained in:
@@ -58,6 +58,7 @@
|
|||||||
"typescript": "^3.9.5"
|
"typescript": "^3.9.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/css-font-loading-module": "0.0.4",
|
||||||
"@xstate/fsm": "^1.4.0",
|
"@xstate/fsm": "^1.4.0",
|
||||||
"mitt": "^1.1.3",
|
"mitt": "^1.1.3",
|
||||||
"pako": "^1.0.11",
|
"pako": "^1.0.11",
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ function getCode(): string {
|
|||||||
window.__IS_RECORDING__ = true
|
window.__IS_RECORDING__ = true
|
||||||
rrweb.record({
|
rrweb.record({
|
||||||
emit: event => window._replLog(event),
|
emit: event => window._replLog(event),
|
||||||
recordCanvas: true
|
recordCanvas: true,
|
||||||
|
collectFonts: true
|
||||||
});
|
});
|
||||||
`);
|
`);
|
||||||
page.on('framenavigated', async () => {
|
page.on('framenavigated', async () => {
|
||||||
@@ -105,7 +106,8 @@ function getCode(): string {
|
|||||||
window.__IS_RECORDING__ = true
|
window.__IS_RECORDING__ = true
|
||||||
rrweb.record({
|
rrweb.record({
|
||||||
emit: event => window._replLog(event),
|
emit: event => window._replLog(event),
|
||||||
recordCanvas: true
|
recordCanvas: true,
|
||||||
|
collectFonts: true
|
||||||
});
|
});
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ function record<T = eventWithTime>(
|
|||||||
sampling = {},
|
sampling = {},
|
||||||
mousemoveWait,
|
mousemoveWait,
|
||||||
recordCanvas = false,
|
recordCanvas = false,
|
||||||
|
collectFonts = false,
|
||||||
} = options;
|
} = options;
|
||||||
// runtime checks for user options
|
// runtime checks for user options
|
||||||
if (!emit) {
|
if (!emit) {
|
||||||
@@ -256,12 +257,23 @@ function record<T = eventWithTime>(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
fontCb: (p) =>
|
||||||
|
wrappedEmit(
|
||||||
|
wrapEvent({
|
||||||
|
type: EventType.IncrementalSnapshot,
|
||||||
|
data: {
|
||||||
|
source: IncrementalSource.Font,
|
||||||
|
...p,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
blockClass,
|
blockClass,
|
||||||
ignoreClass,
|
ignoreClass,
|
||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
inlineStylesheet,
|
inlineStylesheet,
|
||||||
sampling,
|
sampling,
|
||||||
recordCanvas,
|
recordCanvas,
|
||||||
|
collectFonts,
|
||||||
},
|
},
|
||||||
hooks,
|
hooks,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { INode, MaskInputOptions } from 'rrweb-snapshot';
|
import { INode, MaskInputOptions } from 'rrweb-snapshot';
|
||||||
|
import { FontFaceDescriptors, FontFaceSet } from 'css-font-loading-module';
|
||||||
import {
|
import {
|
||||||
mirror,
|
mirror,
|
||||||
throttle,
|
throttle,
|
||||||
@@ -32,6 +33,8 @@ import {
|
|||||||
MediaInteractions,
|
MediaInteractions,
|
||||||
SamplingStrategy,
|
SamplingStrategy,
|
||||||
canvasMutationCallback,
|
canvasMutationCallback,
|
||||||
|
fontCallback,
|
||||||
|
fontParam,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import MutationBuffer from './mutation';
|
import MutationBuffer from './mutation';
|
||||||
|
|
||||||
@@ -432,6 +435,56 @@ function initCanvasMutationObserver(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initFontObserver(cb: fontCallback): listenerHandler {
|
||||||
|
const handlers: listenerHandler[] = [];
|
||||||
|
|
||||||
|
const fontMap = new WeakMap<FontFace, fontParam>();
|
||||||
|
|
||||||
|
const originalFontFace = FontFace;
|
||||||
|
// tslint:disable-next-line: no-any
|
||||||
|
(window as any).FontFace = function FontFace(
|
||||||
|
family: string,
|
||||||
|
source: string | ArrayBufferView,
|
||||||
|
descriptors?: FontFaceDescriptors,
|
||||||
|
) {
|
||||||
|
const fontFace = new originalFontFace(family, source, descriptors);
|
||||||
|
fontMap.set(fontFace, {
|
||||||
|
family,
|
||||||
|
buffer: typeof source !== 'string',
|
||||||
|
descriptors,
|
||||||
|
fontSource:
|
||||||
|
typeof source === 'string'
|
||||||
|
? source
|
||||||
|
: // tslint:disable-next-line: no-any
|
||||||
|
JSON.stringify(Array.from(new Uint8Array(source as any))),
|
||||||
|
});
|
||||||
|
return fontFace;
|
||||||
|
};
|
||||||
|
|
||||||
|
const restoreHandler = patch(document.fonts, 'add', function (original) {
|
||||||
|
return function (this: FontFaceSet, fontFace: FontFace) {
|
||||||
|
setTimeout(() => {
|
||||||
|
const p = fontMap.get(fontFace);
|
||||||
|
if (p) {
|
||||||
|
cb(p);
|
||||||
|
fontMap.delete(fontFace);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
return original.apply(this, [fontFace]);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
handlers.push(() => {
|
||||||
|
// tslint:disable-next-line: no-any
|
||||||
|
(window as any).FonFace = originalFontFace;
|
||||||
|
});
|
||||||
|
handlers.push(restoreHandler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
handlers.forEach((h) => h());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function mergeHooks(o: observerParam, hooks: hooksParam) {
|
function mergeHooks(o: observerParam, hooks: hooksParam) {
|
||||||
const {
|
const {
|
||||||
mutationCb,
|
mutationCb,
|
||||||
@@ -443,6 +496,7 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
|
|||||||
mediaInteractionCb,
|
mediaInteractionCb,
|
||||||
styleSheetRuleCb,
|
styleSheetRuleCb,
|
||||||
canvasMutationCb,
|
canvasMutationCb,
|
||||||
|
fontCb,
|
||||||
} = o;
|
} = o;
|
||||||
o.mutationCb = (...p: Arguments<mutationCallBack>) => {
|
o.mutationCb = (...p: Arguments<mutationCallBack>) => {
|
||||||
if (hooks.mutation) {
|
if (hooks.mutation) {
|
||||||
@@ -498,6 +552,12 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
|
|||||||
}
|
}
|
||||||
canvasMutationCb(...p);
|
canvasMutationCb(...p);
|
||||||
};
|
};
|
||||||
|
o.fontCb = (...p: Arguments<fontCallback>) => {
|
||||||
|
if (hooks.font) {
|
||||||
|
hooks.font(...p);
|
||||||
|
}
|
||||||
|
fontCb(...p);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function initObservers(
|
export default function initObservers(
|
||||||
@@ -539,6 +599,7 @@ export default function initObservers(
|
|||||||
const canvasMutationObserver = o.recordCanvas
|
const canvasMutationObserver = o.recordCanvas
|
||||||
? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass)
|
? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass)
|
||||||
: () => {};
|
: () => {};
|
||||||
|
const fontObserver = o.collectFonts ? initFontObserver(o.fontCb) : () => {};
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
mutationObserver.disconnect();
|
mutationObserver.disconnect();
|
||||||
@@ -550,5 +611,6 @@ export default function initObservers(
|
|||||||
mediaInteractionHandler();
|
mediaInteractionHandler();
|
||||||
styleSheetObserver();
|
styleSheetObserver();
|
||||||
canvasMutationObserver();
|
canvasMutationObserver();
|
||||||
|
fontObserver();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -813,6 +813,22 @@ export class Replayer {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.warnCanvasMutationFailed(d, d.id, error);
|
this.warnCanvasMutationFailed(d, d.id, error);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IncrementalSource.Font: {
|
||||||
|
try {
|
||||||
|
const fontFace = new FontFace(
|
||||||
|
d.family,
|
||||||
|
d.buffer ? new Uint8Array(JSON.parse(d.fontSource)) : d.fontSource,
|
||||||
|
d.descriptors,
|
||||||
|
);
|
||||||
|
this.iframe.contentDocument?.fonts.add(fontFace);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.config.showWarning) {
|
||||||
|
console.warn(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|||||||
22
src/types.ts
22
src/types.ts
@@ -5,6 +5,7 @@ import {
|
|||||||
MaskInputOptions,
|
MaskInputOptions,
|
||||||
} from 'rrweb-snapshot';
|
} from 'rrweb-snapshot';
|
||||||
import { PackFn, UnpackFn } from './packer/base';
|
import { PackFn, UnpackFn } from './packer/base';
|
||||||
|
import { FontFaceDescriptors } from 'css-font-loading-module';
|
||||||
|
|
||||||
export enum EventType {
|
export enum EventType {
|
||||||
DomContentLoaded,
|
DomContentLoaded,
|
||||||
@@ -71,6 +72,7 @@ export enum IncrementalSource {
|
|||||||
MediaInteraction,
|
MediaInteraction,
|
||||||
StyleSheetRule,
|
StyleSheetRule,
|
||||||
CanvasMutation,
|
CanvasMutation,
|
||||||
|
Font,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type mutationData = {
|
export type mutationData = {
|
||||||
@@ -111,6 +113,10 @@ export type canvasMutationData = {
|
|||||||
source: IncrementalSource.CanvasMutation;
|
source: IncrementalSource.CanvasMutation;
|
||||||
} & canvasMutationParam;
|
} & canvasMutationParam;
|
||||||
|
|
||||||
|
export type fontData = {
|
||||||
|
source: IncrementalSource.Font;
|
||||||
|
} & fontParam;
|
||||||
|
|
||||||
export type incrementalData =
|
export type incrementalData =
|
||||||
| mutationData
|
| mutationData
|
||||||
| mousemoveData
|
| mousemoveData
|
||||||
@@ -120,7 +126,8 @@ export type incrementalData =
|
|||||||
| inputData
|
| inputData
|
||||||
| mediaInteractionData
|
| mediaInteractionData
|
||||||
| styleSheetRuleData
|
| styleSheetRuleData
|
||||||
| canvasMutationData;
|
| canvasMutationData
|
||||||
|
| fontData;
|
||||||
|
|
||||||
export type event =
|
export type event =
|
||||||
| domContentLoadedEvent
|
| domContentLoadedEvent
|
||||||
@@ -172,6 +179,7 @@ export type recordOptions<T> = {
|
|||||||
packFn?: PackFn;
|
packFn?: PackFn;
|
||||||
sampling?: SamplingStrategy;
|
sampling?: SamplingStrategy;
|
||||||
recordCanvas?: boolean;
|
recordCanvas?: boolean;
|
||||||
|
collectFonts?: boolean;
|
||||||
// departed, please use sampling options
|
// departed, please use sampling options
|
||||||
mousemoveWait?: number;
|
mousemoveWait?: number;
|
||||||
};
|
};
|
||||||
@@ -190,8 +198,10 @@ export type observerParam = {
|
|||||||
inlineStylesheet: boolean;
|
inlineStylesheet: boolean;
|
||||||
styleSheetRuleCb: styleSheetRuleCallback;
|
styleSheetRuleCb: styleSheetRuleCallback;
|
||||||
canvasMutationCb: canvasMutationCallback;
|
canvasMutationCb: canvasMutationCallback;
|
||||||
|
fontCb: fontCallback;
|
||||||
sampling: SamplingStrategy;
|
sampling: SamplingStrategy;
|
||||||
recordCanvas: boolean;
|
recordCanvas: boolean;
|
||||||
|
collectFonts: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type hooksParam = {
|
export type hooksParam = {
|
||||||
@@ -204,6 +214,7 @@ export type hooksParam = {
|
|||||||
mediaInteaction?: mediaInteractionCallback;
|
mediaInteaction?: mediaInteractionCallback;
|
||||||
styleSheetRule?: styleSheetRuleCallback;
|
styleSheetRule?: styleSheetRuleCallback;
|
||||||
canvasMutation?: canvasMutationCallback;
|
canvasMutation?: canvasMutationCallback;
|
||||||
|
font?: fontCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://dom.spec.whatwg.org/#interface-mutationrecord
|
// https://dom.spec.whatwg.org/#interface-mutationrecord
|
||||||
@@ -328,6 +339,15 @@ export type canvasMutationParam = {
|
|||||||
setter?: true;
|
setter?: true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type fontParam = {
|
||||||
|
family: string;
|
||||||
|
fontSource: string;
|
||||||
|
buffer: boolean;
|
||||||
|
descriptors?: FontFaceDescriptors;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type fontCallback = (p: fontParam) => void;
|
||||||
|
|
||||||
export type viewportResizeDimention = {
|
export type viewportResizeDimention = {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
|||||||
@@ -14,5 +14,9 @@
|
|||||||
},
|
},
|
||||||
"compileOnSave": true,
|
"compileOnSave": true,
|
||||||
"exclude": ["test"],
|
"exclude": ["test"],
|
||||||
"include": ["src", "test.d.ts"]
|
"include": [
|
||||||
|
"src",
|
||||||
|
"test.d.ts",
|
||||||
|
"node_modules/@types/css-font-loading-module/index.d.ts"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user