openreplay/frontend/app/player/MessageDistributor/managers/DOM/StylesManager.ts
2022-09-12 12:18:55 +02:00

105 lines
3.4 KiB
TypeScript

import type StatedScreen from '../../StatedScreen';
import type { CssInsertRule, CssDeleteRule, ReplaceVcss } from '../../messages';
type CSSRuleMessage = CssInsertRule | CssDeleteRule | ReplaceVcss;
import ListWalker from '../ListWalker';
const HOVER_CN = "-openreplay-hover";
const HOVER_SELECTOR = `.${HOVER_CN}`;
// Doesn't work with css files (hasOwnProperty)
export function rewriteNodeStyleSheet(doc: Document, node: HTMLLinkElement | HTMLStyleElement) {
const ss = Object.values(doc.styleSheets).find(s => s.ownerNode === node);
if (!ss || !ss.hasOwnProperty('rules')) { return; }
for(let i = 0; i < ss.rules.length; i++){
const r = ss.rules[i]
if (r instanceof CSSStyleRule) {
r.selectorText = r.selectorText.replace('/\:hover/g', HOVER_SELECTOR)
}
}
}
export default class StylesManager extends ListWalker<CSSRuleMessage> {
private linkLoadingCount: number = 0;
private linkLoadPromises: Array<Promise<void>> = [];
private skipCSSLinks: Array<string> = []; // should be common for all pages
constructor(private readonly screen: StatedScreen) {
super();
}
reset():void {
super.reset();
this.linkLoadingCount = 0;
this.linkLoadPromises = [];
//cancel all promises? tothinkaboutit
}
setStyleHandlers(node: HTMLLinkElement, value: string): void {
let timeoutId: ReturnType<typeof setTimeout> | undefined;
const promise = new Promise<void>((resolve) => {
if (this.skipCSSLinks.includes(value)) resolve();
this.linkLoadingCount++;
this.screen.setCSSLoading(true);
const addSkipAndResolve = () => {
this.skipCSSLinks.push(value); // watch out
resolve()
}
timeoutId = setTimeout(addSkipAndResolve, 4000);
node.onload = () => {
const doc = this.screen.document;
doc && rewriteNodeStyleSheet(doc, node);
resolve();
}
node.onerror = addSkipAndResolve;
}).then(() => {
node.onload = null;
node.onerror = null;
clearTimeout(timeoutId);
this.linkLoadingCount--;
if (this.linkLoadingCount === 0) {
this.screen.setCSSLoading(false);
}
});
this.linkLoadPromises.push(promise);
}
private manageRule = (msg: CSSRuleMessage):void => {
// if (msg.tp === "css_insert_rule") {
// let styleSheet = this.#screen.document.styleSheets[ msg.stylesheetID ];
// if (!styleSheet) {
// logger.log("No stylesheet with corresponding ID found: ", msg)
// styleSheet = this.#screen.document.styleSheets[0];
// if (!styleSheet) {
// return;
// }
// }
// try {
// styleSheet.insertRule(msg.rule, msg.index);
// } catch (e) {
// logger.log(e, msg)
// //const index = Math.min(msg.index, styleSheet.cssRules.length);
// styleSheet.insertRule(msg.rule, styleSheet.cssRules.length);
// //styleSheet.ownerNode.innerHTML += msg.rule;
// }
// }
// if (msg.tp === "css_delete_rule") {
// // console.warn('Warning: STYLESHEET_DELETE_RULE msg')
// const styleSheet = this.#screen.document.styleSheets[msg.stylesheetID];
// if (!styleSheet) {
// logger.log("No stylesheet with corresponding ID found: ", msg)
// return;
// }
// styleSheet.deleteRule(msg.index);
// }
}
moveReady(t: number): Promise<void> {
return Promise.all(this.linkLoadPromises)
.then(() => this.moveApply(t, this.manageRule));
}
}