-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Buffer modifications to virtual stylesheets (#618)
* Fix sheet insertion Restore skip duration Use virtualStyleRulesMap to re-populate stylesheet on Flush event Clear virtualStyleRulesMap after flush applied * Support rule deletion in virtual processing * Simply restoreNodeSheet with early aborts * Encountered a bug where firstFullSnapshot was played twice because timer was immediately started and reached the snapshot before the setTimeout returned * Ignoring a FullSnapshot needs to be a one-time only thing, as otherwise we'll ignore it after scrubbing (restarting play head at a particular time). This is a problem if mutations have altered the player state, and we try to replay those mutations, so we e.g. try to remove an element that has already been removed because we haven't reset the FullSnapshot state * Some `npm run typings` related fixups * add basic html snapshot functionality * move restoreNodeSheet to it's own module * Refactor virtual style rules to buffer changes. Only applies changes on flush. `virtualStyleRulesMap` now works with strings instead of CSSRules. CSSRules can only be via made `.insertRule` on CSSStyleSheet in most browsers. And `new CSSStyleSheet()` only works in Chrome currently. * remove unused code * move VirtualStyleRules from CSSRule to string in tests * correct paths for tests * naming * create and restore style snapshots for virtual nodes * update replayer snapshot * move storeCSSRules to virtual-styles.ts * try/catch access to .sheet in case of access errors * clean up tests Co-authored-by: Vladimir Milenko <vladimir.milenko@uber.com> Co-authored-by: Eoghan Murray <eoghan@getthere.ie>
- Loading branch information
1 parent
7a0e04c
commit 39c8ba1
Showing
9 changed files
with
1,851 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
import { INode } from 'rrweb-snapshot'; | ||
|
||
export enum StyleRuleType { | ||
Insert, | ||
Remove, | ||
Snapshot, | ||
} | ||
|
||
type InsertRule = { | ||
cssText: string; | ||
type: StyleRuleType.Insert; | ||
index?: number; | ||
}; | ||
type RemoveRule = { | ||
type: StyleRuleType.Remove; | ||
index: number; | ||
}; | ||
type SnapshotRule = { | ||
type: StyleRuleType.Snapshot; | ||
cssTexts: string[]; | ||
}; | ||
|
||
export type VirtualStyleRules = Array<InsertRule | RemoveRule | SnapshotRule>; | ||
export type VirtualStyleRulesMap = Map<INode, VirtualStyleRules>; | ||
|
||
export function applyVirtualStyleRulesToNode( | ||
storedRules: VirtualStyleRules, | ||
styleNode: HTMLStyleElement, | ||
) { | ||
storedRules.forEach((rule) => { | ||
if (rule.type === StyleRuleType.Insert) { | ||
try { | ||
styleNode.sheet?.insertRule(rule.cssText, rule.index); | ||
} catch (e) { | ||
/** | ||
* sometimes we may capture rules with browser prefix | ||
* insert rule with prefixs in other browsers may cause Error | ||
*/ | ||
} | ||
} else if (rule.type === StyleRuleType.Remove) { | ||
try { | ||
styleNode.sheet?.deleteRule(rule.index); | ||
} catch (e) { | ||
/** | ||
* accessing styleSheet rules may cause SecurityError | ||
* for specific access control settings | ||
*/ | ||
} | ||
} else if (rule.type === StyleRuleType.Snapshot) { | ||
restoreSnapshotOfStyleRulesToNode(rule.cssTexts, styleNode); | ||
} | ||
}); | ||
} | ||
|
||
function restoreSnapshotOfStyleRulesToNode( | ||
cssTexts: string[], | ||
styleNode: HTMLStyleElement, | ||
) { | ||
try { | ||
const existingRules = Array.from(styleNode.sheet?.cssRules || []).map( | ||
(rule) => rule.cssText, | ||
); | ||
const existingRulesReversed = Object.entries(existingRules).reverse(); | ||
let lastMatch = existingRules.length; | ||
existingRulesReversed.forEach(([index, rule]) => { | ||
const indexOf = cssTexts.indexOf(rule); | ||
if (indexOf === -1 || indexOf > lastMatch) { | ||
try { | ||
styleNode.sheet?.deleteRule(Number(index)); | ||
} catch (e) { | ||
/** | ||
* accessing styleSheet rules may cause SecurityError | ||
* for specific access control settings | ||
*/ | ||
} | ||
} | ||
lastMatch = indexOf; | ||
}); | ||
cssTexts.forEach((cssText, index) => { | ||
try { | ||
if (styleNode.sheet?.cssRules[index]?.cssText !== cssText) { | ||
styleNode.sheet?.insertRule(cssText, index); | ||
} | ||
} catch (e) { | ||
/** | ||
* sometimes we may capture rules with browser prefix | ||
* insert rule with prefixs in other browsers may cause Error | ||
*/ | ||
} | ||
}); | ||
} catch (e) { | ||
/** | ||
* accessing styleSheet rules may cause SecurityError | ||
* for specific access control settings | ||
*/ | ||
} | ||
} | ||
|
||
export function storeCSSRules( | ||
parentElement: HTMLStyleElement, | ||
virtualStyleRulesMap: VirtualStyleRulesMap, | ||
) { | ||
try { | ||
const cssTexts = Array.from( | ||
(parentElement as HTMLStyleElement).sheet?.cssRules || [], | ||
).map((rule) => rule.cssText); | ||
virtualStyleRulesMap.set((parentElement as unknown) as INode, [ | ||
{ | ||
type: StyleRuleType.Snapshot, | ||
cssTexts, | ||
}, | ||
]); | ||
} catch (e) { | ||
/** | ||
* accessing styleSheet rules may cause SecurityError | ||
* for specific access control settings | ||
*/ | ||
} | ||
} |
Oops, something went wrong.