impl #650, CSS declaration observer (#671)

This commit is contained in:
yz-yu
2021-08-23 12:22:23 +08:00
committed by GitHub
parent 9e226b593f
commit 5dcbfa530f
5 changed files with 217 additions and 18 deletions

View File

@@ -352,6 +352,16 @@ function record<T = eventWithTime>(
}, },
}), }),
), ),
styleDeclarationCb: (r) =>
wrappedEmit(
wrapEvent({
type: EventType.IncrementalSnapshot,
data: {
source: IncrementalSource.StyleDeclaration,
...r,
},
}),
),
canvasMutationCb: (p) => canvasMutationCb: (p) =>
wrappedEmit( wrappedEmit(
wrapEvent({ wrapEvent({

View File

@@ -43,6 +43,7 @@ import {
fontCallback, fontCallback,
fontParam, fontParam,
Mirror, Mirror,
styleDeclarationCallback,
} from '../types'; } from '../types';
import MutationBuffer from './mutation'; import MutationBuffer from './mutation';
import { IframeManager } from './iframe-manager'; import { IframeManager } from './iframe-manager';
@@ -472,16 +473,18 @@ function initInputObserver(
}; };
} }
function getNestedCSSRulePositions(rule: CSSStyleRule): number[] { function getNestedCSSRulePositions(rule: CSSRule): number[] {
const positions: Array<number> = []; const positions: number[] = [];
function recurse(rule: CSSRule, pos: number[]) { function recurse(childRule: CSSRule, pos: number[]) {
if (rule.parentRule instanceof CSSGroupingRule) { if (childRule.parentRule instanceof CSSGroupingRule) {
const rules = Array.from((rule.parentRule as CSSGroupingRule).cssRules); const rules = Array.from(
const index = rules.indexOf(rule); (childRule.parentRule as CSSGroupingRule).cssRules,
);
const index = rules.indexOf(childRule);
pos.unshift(index); pos.unshift(index);
} else { } else {
const rules = Array.from(rule.parentStyleSheet!.cssRules); const rules = Array.from(childRule.parentStyleSheet!.cssRules);
const index = rules.indexOf(rule); const index = rules.indexOf(childRule);
pos.unshift(index); pos.unshift(index);
} }
return pos; return pos;
@@ -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( function initMediaInteractionObserver(
mediaInteractionCb: mediaInteractionCallback, mediaInteractionCb: mediaInteractionCallback,
blockClass: blockClass, blockClass: blockClass,
@@ -725,6 +782,7 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
inputCb, inputCb,
mediaInteractionCb, mediaInteractionCb,
styleSheetRuleCb, styleSheetRuleCb,
styleDeclarationCb,
canvasMutationCb, canvasMutationCb,
fontCb, fontCb,
} = o; } = o;
@@ -776,6 +834,12 @@ function mergeHooks(o: observerParam, hooks: hooksParam) {
} }
styleSheetRuleCb(...p); styleSheetRuleCb(...p);
}; };
o.styleDeclarationCb = (...p: Arguments<styleDeclarationCallback>) => {
if (hooks.styleDeclaration) {
hooks.styleDeclaration(...p);
}
styleDeclarationCb(...p);
};
o.canvasMutationCb = (...p: Arguments<canvasMutationCallback>) => { o.canvasMutationCb = (...p: Arguments<canvasMutationCallback>) => {
if (hooks.canvasMutation) { if (hooks.canvasMutation) {
hooks.canvasMutation(...p); hooks.canvasMutation(...p);
@@ -854,6 +918,10 @@ export function initObservers(
o.styleSheetRuleCb, o.styleSheetRuleCb,
o.mirror, o.mirror,
); );
const styleDeclarationObserver = initStyleDeclarationObserver(
o.styleDeclarationCb,
o.mirror,
);
const canvasMutationObserver = o.recordCanvas const canvasMutationObserver = o.recordCanvas
? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass, o.mirror) ? initCanvasMutationObserver(o.canvasMutationCb, o.blockClass, o.mirror)
: () => {}; : () => {};
@@ -873,6 +941,7 @@ export function initObservers(
inputHandler(); inputHandler();
mediaInteractionHandler(); mediaInteractionHandler();
styleSheetObserver(); styleSheetObserver();
styleDeclarationObserver();
canvasMutationObserver(); canvasMutationObserver();
fontObserver(); fontObserver();
pluginHandlers.forEach((h) => h()); pluginHandlers.forEach((h) => h());

View File

@@ -79,10 +79,12 @@ const defaultMouseTailConfig = {
} as const; } as const;
function indicatesTouchDevice(e: eventWithTime) { function indicatesTouchDevice(e: eventWithTime) {
return e.type == EventType.IncrementalSnapshot && return (
e.type == EventType.IncrementalSnapshot &&
(e.data.source == IncrementalSource.TouchMove || (e.data.source == IncrementalSource.TouchMove ||
(e.data.source == IncrementalSource.MouseInteraction && (e.data.source == IncrementalSource.MouseInteraction &&
e.data.type == MouseInteractions.TouchStart)); e.data.type == MouseInteractions.TouchStart))
);
} }
export class Replayer { export class Replayer {
@@ -266,7 +268,6 @@ export class Replayer {
if (this.service.state.context.events.find(indicatesTouchDevice)) { if (this.service.state.context.events.find(indicatesTouchDevice)) {
this.mouse.classList.add('touch-device'); this.mouse.classList.add('touch-device');
} }
} }
public on(event: string, handler: Handler) { public on(event: string, handler: Handler) {
@@ -489,7 +490,13 @@ export class Replayer {
castFn(); castFn();
} }
if (this.mousePos) { if (this.mousePos) {
this.moveAndHover(this.mousePos.x, this.mousePos.y, this.mousePos.id, true, this.mousePos.debugData); this.moveAndHover(
this.mousePos.x,
this.mousePos.y,
this.mousePos.id,
true,
this.mousePos.debugData,
);
} }
this.mousePos = null; this.mousePos = null;
if (this.touchActive === true) { if (this.touchActive === true) {
@@ -952,14 +959,14 @@ export class Replayer {
void this.mouse.offsetWidth; void this.mouse.offsetWidth;
this.mouse.classList.add('active'); this.mouse.classList.add('active');
} else if (d.type === MouseInteractions.TouchStart) { } else if (d.type === MouseInteractions.TouchStart) {
void this.mouse.offsetWidth; // needed for the position update of moveAndHover to apply without the .touch-active transition void this.mouse.offsetWidth; // needed for the position update of moveAndHover to apply without the .touch-active transition
this.mouse.classList.add('touch-active'); this.mouse.classList.add('touch-active');
} else if (d.type === MouseInteractions.TouchEnd) { } else if (d.type === MouseInteractions.TouchEnd) {
this.mouse.classList.remove('touch-active'); this.mouse.classList.remove('touch-active');
} }
} }
break; break;
case MouseInteractions.TouchCancel: case MouseInteractions.TouchCancel:
if (isSync) { if (isSync) {
this.touchActive = false; this.touchActive = false;
} else { } else {
@@ -1138,6 +1145,62 @@ export class Replayer {
} }
break; 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: { case IncrementalSource.CanvasMutation: {
if (!this.config.UNSAFE_replayCanvas) { if (!this.config.UNSAFE_replayCanvas) {
return; return;
@@ -1581,7 +1644,13 @@ export class Replayer {
} }
} }
private moveAndHover(x: number, y: number, id: number, isSync: boolean, debugData: incrementalData) { private moveAndHover(
x: number,
y: number,
id: number,
isSync: boolean,
debugData: incrementalData,
) {
const target = this.mirror.getNode(id); const target = this.mirror.getNode(id);
if (!target) { if (!target) {
return this.debugNodeNotFound(debugData, id); return this.debugNodeNotFound(debugData, id);

View File

@@ -4,6 +4,8 @@ export enum StyleRuleType {
Insert, Insert,
Remove, Remove,
Snapshot, Snapshot,
SetProperty,
RemoveProperty,
} }
type InsertRule = { type InsertRule = {
@@ -19,8 +21,22 @@ type SnapshotRule = {
type: StyleRuleType.Snapshot; type: StyleRuleType.Snapshot;
cssTexts: string[]; 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 type VirtualStyleRulesMap = Map<INode, VirtualStyleRules>;
export function getNestedRule( export function getNestedRule(
@@ -88,6 +104,18 @@ export function applyVirtualStyleRulesToNode(
} }
} else if (rule.type === StyleRuleType.Snapshot) { } else if (rule.type === StyleRuleType.Snapshot) {
restoreSnapshotOfStyleRulesToNode(rule.cssTexts, styleNode); 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);
} }
}); });
} }

View File

@@ -90,6 +90,7 @@ export enum IncrementalSource {
Font, Font,
Log, Log,
Drag, Drag,
StyleDeclaration,
} }
export type mutationData = { export type mutationData = {
@@ -129,6 +130,10 @@ export type styleSheetRuleData = {
source: IncrementalSource.StyleSheetRule; source: IncrementalSource.StyleSheetRule;
} & styleSheetRuleParam; } & styleSheetRuleParam;
export type styleDeclarationData = {
source: IncrementalSource.StyleDeclaration;
} & styleDeclarationParam;
export type canvasMutationData = { export type canvasMutationData = {
source: IncrementalSource.CanvasMutation; source: IncrementalSource.CanvasMutation;
} & canvasMutationParam; } & canvasMutationParam;
@@ -147,7 +152,8 @@ export type incrementalData =
| mediaInteractionData | mediaInteractionData
| styleSheetRuleData | styleSheetRuleData
| canvasMutationData | canvasMutationData
| fontData; | fontData
| styleDeclarationData;
export type event = export type event =
| domContentLoadedEvent | domContentLoadedEvent
@@ -244,6 +250,7 @@ export type observerParam = {
maskTextFn?: MaskTextFn; maskTextFn?: MaskTextFn;
inlineStylesheet: boolean; inlineStylesheet: boolean;
styleSheetRuleCb: styleSheetRuleCallback; styleSheetRuleCb: styleSheetRuleCallback;
styleDeclarationCb: styleDeclarationCallback;
canvasMutationCb: canvasMutationCallback; canvasMutationCb: canvasMutationCallback;
fontCb: fontCallback; fontCb: fontCallback;
sampling: SamplingStrategy; sampling: SamplingStrategy;
@@ -271,6 +278,7 @@ export type hooksParam = {
input?: inputCallback; input?: inputCallback;
mediaInteaction?: mediaInteractionCallback; mediaInteaction?: mediaInteractionCallback;
styleSheetRule?: styleSheetRuleCallback; styleSheetRule?: styleSheetRuleCallback;
styleDeclaration?: styleDeclarationCallback;
canvasMutation?: canvasMutationCallback; canvasMutation?: canvasMutationCallback;
font?: fontCallback; font?: fontCallback;
}; };
@@ -407,6 +415,21 @@ export type styleSheetRuleParam = {
export type styleSheetRuleCallback = (s: styleSheetRuleParam) => void; 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 canvasMutationCallback = (p: canvasMutationParam) => void;
export type canvasMutationParam = { export type canvasMutationParam = {