Skip to content

Commit

Permalink
impl #650, CSS declaration observer
Browse files Browse the repository at this point in the history
  • Loading branch information
Yuyz0112 committed Aug 18, 2021
1 parent 997e960 commit 60fe27a
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 13 deletions.
10 changes: 10 additions & 0 deletions packages/rrweb/src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,16 @@ function record<T = eventWithTime>(
},
}),
),
styleDeclarationCb: (r) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.StyleDeclaration,
...r,
},
}),
),
canvasMutationCb: (p) =>
wrappedEmit(
wrapEvent({
Expand Down
85 changes: 77 additions & 8 deletions packages/rrweb/src/record/observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
fontCallback,
fontParam,
Mirror,
styleDeclarationCallback,
} from '../types';
import MutationBuffer from './mutation';
import { IframeManager } from './iframe-manager';
Expand Down Expand Up @@ -472,16 +473,18 @@ function initInputObserver(
};
}

function getNestedCSSRulePositions(rule: CSSStyleRule): number[] {
const positions: Array<number> = [];
function recurse(rule: CSSRule, pos: number[]) {
if (rule.parentRule instanceof CSSGroupingRule) {
const rules = Array.from((rule.parentRule as CSSGroupingRule).cssRules);
const index = rules.indexOf(rule);
function getNestedCSSRulePositions(rule: CSSRule): number[] {
const positions: number[] = [];
function recurse(childRule: CSSRule, pos: number[]) {
if (childRule.parentRule instanceof CSSGroupingRule) {
const rules = Array.from(
(childRule.parentRule as CSSGroupingRule).cssRules,
);
const index = rules.indexOf(childRule);
pos.unshift(index);
} else {
const rules = Array.from(rule.parentStyleSheet!.cssRules);
const index = rules.indexOf(rule);
const rules = Array.from(childRule.parentStyleSheet!.cssRules);
const index = rules.indexOf(childRule);
pos.unshift(index);
}
return pos;
Expand Down Expand Up @@ -560,6 +563,60 @@ function initStyleSheetObserver(
};
}

function initStyleDeclarationObserver(
cb: styleDeclarationCallback,
mirror: Mirror,
): listenerHandler {
const setProperty = CSSStyleDeclaration.prototype.setProperty;
CSSStyleDeclaration.prototype.setProperty = function (
this: CSSStyleDeclaration,
property,
value,
priority,
) {
const id = mirror.getId(
(this.parentRule?.parentStyleSheet?.ownerNode as unknown) as INode,
);
if (id !== -1) {
cb({
id,
set: {
property,
value,
priority,
},
index: getNestedCSSRulePositions(this.parentRule!),
});
}
return setProperty.apply(this, arguments);
};

const removeProperty = CSSStyleDeclaration.prototype.removeProperty;
CSSStyleDeclaration.prototype.removeProperty = function (
this: CSSStyleDeclaration,
property,
) {
const id = mirror.getId(
(this.parentRule?.parentStyleSheet?.ownerNode as unknown) as INode,
);
if (id !== -1) {
cb({
id,
remove: {
property,
},
index: getNestedCSSRulePositions(this.parentRule!),
});
}
return removeProperty.apply(this, arguments);
};

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

function initMediaInteractionObserver(
mediaInteractionCb: mediaInteractionCallback,
blockClass: blockClass,
Expand Down Expand Up @@ -725,6 +782,7 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
inputCb,
mediaInteractionCb,
styleSheetRuleCb,
styleDeclarationCb,
canvasMutationCb,
fontCb,
} = o;
Expand Down Expand Up @@ -776,6 +834,12 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
}
styleSheetRuleCb(...p);
};
o.styleDeclarationCb = (...p: Arguments<styleDeclarationCallback>) => {
if (hooks.styleDeclaration) {
hooks.styleDeclaration(...p);
}
styleDeclarationCb(...p);
};
o.canvasMutationCb = (...p: Arguments<canvasMutationCallback>) => {
if (hooks.canvasMutation) {
hooks.canvasMutation(...p);
Expand Down Expand Up @@ -854,6 +918,10 @@ export function initObservers(
o.styleSheetRuleCb,
o.mirror,
);
const styleDeclarationObserver = initStyleDeclarationObserver(
o.styleDeclarationCb,
o.mirror,
);
const canvasMutationObserver = o.recordCanvas
? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass, o.mirror)
: () => {};
Expand All @@ -873,6 +941,7 @@ export function initObservers(
inputHandler();
mediaInteractionHandler();
styleSheetObserver();
styleDeclarationObserver();
canvasMutationObserver();
fontObserver();
pluginHandlers.forEach((h) => h());
Expand Down
62 changes: 59 additions & 3 deletions packages/rrweb/src/replay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -989,9 +989,9 @@ export class Replayer {
nestedRule.insertRule(rule, insertAt);
} else {
const _index =
index === undefined
? undefined
: Math.min(index, styleSheet.cssRules.length);
index === undefined
? undefined
: Math.min(index, styleSheet.cssRules.length);
styleSheet.insertRule(rule, _index);
}
} catch (e) {
Expand Down Expand Up @@ -1037,6 +1037,62 @@ export class Replayer {
}
break;
}
case IncrementalSource.StyleDeclaration: {
// same with StyleSheetRule
const target = this.mirror.getNode(d.id);
if (!target) {
return this.debugNodeNotFound(d, d.id);
}

const styleEl = (target as Node) as HTMLStyleElement;
const parent = (target.parentNode as unknown) as INode;
const usingVirtualParent = this.fragmentParentMap.has(parent);

const styleSheet = usingVirtualParent ? null : styleEl.sheet;
let rules: VirtualStyleRules = [];

if (!styleSheet) {
if (this.virtualStyleRulesMap.has(target)) {
rules = this.virtualStyleRulesMap.get(target) as VirtualStyleRules;
} else {
rules = [];
this.virtualStyleRulesMap.set(target, rules);
}
}

if (d.set) {
if (styleSheet) {
const rule = (getNestedRule(
styleSheet.rules,
d.index,
) as unknown) as CSSStyleRule;
rule.style.setProperty(d.set.property, d.set.value, d.set.priority);
} else {
rules.push({
type: StyleRuleType.SetProperty,
index: d.index,
...d.set,
});
}
}

if (d.remove) {
if (styleSheet) {
const rule = (getNestedRule(
styleSheet.rules,
d.index,
) as unknown) as CSSStyleRule;
rule.style.removeProperty(d.remove.property);
} else {
rules.push({
type: StyleRuleType.RemoveProperty,
index: d.index,
...d.remove,
});
}
}
break;
}
case IncrementalSource.CanvasMutation: {
if (!this.config.UNSAFE_replayCanvas) {
return;
Expand Down
30 changes: 29 additions & 1 deletion packages/rrweb/src/replay/virtual-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export enum StyleRuleType {
Insert,
Remove,
Snapshot,
SetProperty,
RemoveProperty,
}

type InsertRule = {
Expand All @@ -19,8 +21,22 @@ type SnapshotRule = {
type: StyleRuleType.Snapshot;
cssTexts: string[];
};
type SetPropertyRule = {
type: StyleRuleType.SetProperty;
index: number[];
property: string;
value: string | null;
priority: string | undefined;
};
type RemovePropertyRule = {
type: StyleRuleType.RemoveProperty;
index: number[];
property: string;
};

export type VirtualStyleRules = Array<InsertRule | RemoveRule | SnapshotRule>;
export type VirtualStyleRules = Array<
InsertRule | RemoveRule | SnapshotRule | SetPropertyRule | RemovePropertyRule
>;
export type VirtualStyleRulesMap = Map<INode, VirtualStyleRules>;

export function getNestedRule(
Expand Down Expand Up @@ -84,6 +100,18 @@ export function applyVirtualStyleRulesToNode(
}
} else if (rule.type === StyleRuleType.Snapshot) {
restoreSnapshotOfStyleRulesToNode(rule.cssTexts, styleNode);
} else if (rule.type === StyleRuleType.SetProperty) {
const nativeRule = (getNestedRule(
styleNode.sheet!.cssRules,
rule.index,
) as unknown) as CSSStyleRule;
nativeRule.style.setProperty(rule.property, rule.value, rule.priority);
} else if (rule.type === StyleRuleType.RemoveProperty) {
const nativeRule = (getNestedRule(
styleNode.sheet!.cssRules,
rule.index,
) as unknown) as CSSStyleRule;
nativeRule.style.removeProperty(rule.property);
}
});
}
Expand Down
25 changes: 24 additions & 1 deletion packages/rrweb/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export enum IncrementalSource {
Font,
Log,
Drag,
StyleDeclaration,
}

export type mutationData = {
Expand Down Expand Up @@ -129,6 +130,10 @@ export type styleSheetRuleData = {
source: IncrementalSource.StyleSheetRule;
} & styleSheetRuleParam;

export type styleDeclarationData = {
source: IncrementalSource.StyleDeclaration;
} & styleDeclarationParam;

export type canvasMutationData = {
source: IncrementalSource.CanvasMutation;
} & canvasMutationParam;
Expand All @@ -147,7 +152,8 @@ export type incrementalData =
| mediaInteractionData
| styleSheetRuleData
| canvasMutationData
| fontData;
| fontData
| styleDeclarationData;

export type event =
| domContentLoadedEvent
Expand Down Expand Up @@ -244,6 +250,7 @@ export type observerParam = {
maskTextFn?: MaskTextFn;
inlineStylesheet: boolean;
styleSheetRuleCb: styleSheetRuleCallback;
styleDeclarationCb: styleDeclarationCallback;
canvasMutationCb: canvasMutationCallback;
fontCb: fontCallback;
sampling: SamplingStrategy;
Expand Down Expand Up @@ -271,6 +278,7 @@ export type hooksParam = {
input?: inputCallback;
mediaInteaction?: mediaInteractionCallback;
styleSheetRule?: styleSheetRuleCallback;
styleDeclaration?: styleDeclarationCallback;
canvasMutation?: canvasMutationCallback;
font?: fontCallback;
};
Expand Down Expand Up @@ -399,6 +407,21 @@ export type styleSheetRuleParam = {

export type styleSheetRuleCallback = (s: styleSheetRuleParam) => void;

export type styleDeclarationParam = {
id: number;
index: number[];
set?: {
property: string;
value: string | null;
priority: string | undefined;
};
remove?: {
property: string;
};
};

export type styleDeclarationCallback = (s: styleDeclarationParam) => void;

export type canvasMutationCallback = (p: canvasMutationParam) => void;

export type canvasMutationParam = {
Expand Down

0 comments on commit 60fe27a

Please sign in to comment.