From dabe7f0e444441eede2f9bc5b2ecaae3977373f5 Mon Sep 17 00:00:00 2001 From: Alex Kaminskii Date: Wed, 27 Jul 2022 17:28:51 +0200 Subject: [PATCH] fix (frontend/player): instant insertion of style elements & correct children deletion --- .../managers/DOM/DOMManager.ts | 15 ++++- .../managers/DOM/VirtualDOM.ts | 63 +++++++++++-------- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/frontend/app/player/MessageDistributor/managers/DOM/DOMManager.ts b/frontend/app/player/MessageDistributor/managers/DOM/DOMManager.ts index 005713e15..19e35d8f6 100644 --- a/frontend/app/player/MessageDistributor/managers/DOM/DOMManager.ts +++ b/frontend/app/player/MessageDistributor/managers/DOM/DOMManager.ts @@ -14,6 +14,16 @@ type HTMLElementWithValue = HTMLInputElement | HTMLTextAreaElement | HTMLSelectE const IGNORED_ATTRS = [ "autocomplete", "name" ]; const ATTR_NAME_REGEXP = /([^\t\n\f \/>"'=]+)/; // regexp costs ~ + +// TODO: filter out non-relevant prefixes +// function replaceCSSPrefixes(css: string) { +// return css +// .replace(/\-ms\-/g, "") +// .replace(/\-webkit\-/g, "") +// .replace(/\-moz\-/g, "") +// .replace(/\-webkit\-/g, "") +// } + export default class DOMManager extends ListWalker { private vTexts: Map = new Map() // map vs object here? private vElements: Map = new Map() @@ -144,6 +154,9 @@ export default class DOMManager extends ListWalker { this.insertNode(msg) this.removeBodyScroll(msg.id, vn) this.removeAutocomplete(element) + if (['STYLE', 'style', 'link'].includes(msg.tag)) { + vn.enforceInsertion() + } return case "move_node": this.insertNode(msg); @@ -287,7 +300,7 @@ export default class DOMManager extends ListWalker { // @ts-ignore this.vElements.get(0).applyChanges() - this.vRoots.forEach(rt => rt.applyChanges()) + this.vRoots.forEach(rt => rt.applyChanges()) // MBTODO (optimisation): affected set // Thinkabout (read): css preload // What if we go back before it is ready? We'll have two handlres? diff --git a/frontend/app/player/MessageDistributor/managers/DOM/VirtualDOM.ts b/frontend/app/player/MessageDistributor/managers/DOM/VirtualDOM.ts index 12009c21c..c25f07caf 100644 --- a/frontend/app/player/MessageDistributor/managers/DOM/VirtualDOM.ts +++ b/frontend/app/player/MessageDistributor/managers/DOM/VirtualDOM.ts @@ -17,6 +17,7 @@ abstract class VParent { this.children = this.children.filter(ch => ch !== child) child.parentNode = null } + applyChanges() { const node = this.node if (!node) { @@ -25,17 +26,20 @@ abstract class VParent { return } const realChildren = node.childNodes - for (let i = 0; i < this.children.length; i++) { - const ch = this.children[i] - ch.applyChanges() - if (ch.node.parentNode !== node) { - const nextSibling = realChildren[i] - node.insertBefore(ch.node, nextSibling || null) - } - if (realChildren[i] !== ch.node) { - node.removeChild(realChildren[i]) + let i: number + // apply correct children order + for (i = 0; i < this.children.length; i++) { + const child = this.children[i] + child.applyChanges() + //while (realChildren[i] shouldn't be there) remove it //optimal way + if (realChildren[i] !== child.node) { + node.insertBefore(child.node, realChildren[i] || null) } } + // remove rest + while(realChildren[i]) { + node.removeChild(realChildren[i]) + } } } @@ -52,7 +56,9 @@ export class VDocument extends VParent { // iframe not mounted yet return } - const htmlNode = this.children[0].node + const child = this.children[0] + child.applyChanges() + const htmlNode = child.node if (htmlNode.parentNode !== this.node) { this.node.replaceChild(htmlNode, this.node.documentElement) } @@ -66,7 +72,6 @@ export class VFragment extends VParent { export class VElement extends VParent { parentNode: VParent | null = null private newAttributes: Map = new Map() - //private props: Record constructor(public readonly node: Element) { super() } setAttribute(name: string, value: string) { this.newAttributes.set(name, value) @@ -75,6 +80,14 @@ export class VElement extends VParent { this.newAttributes.set(name, false) } + enforceInsertion() { + let vNode: VElement = this + while (vNode.parentNode instanceof VElement) { + vNode = vNode.parentNode + } + (vNode.parentNode || vNode).applyChanges() + } + applyChanges() { this.newAttributes.forEach((value, key) => { if (value === false) { @@ -96,31 +109,31 @@ export class VElement extends VParent { type StyleSheetCallback = (s: CSSStyleSheet) => void export type StyleElement = HTMLStyleElement | SVGStyleElement export class VStyleElement extends VElement { - private loaded = false + // private loaded = false private stylesheetCallbacks: StyleSheetCallback[] = [] constructor(public readonly node: StyleElement) { super(node) // Is it compiled correctly or with 2 node assignments? - node.onload = () => { - const sheet = node.sheet - if (sheet) { - this.stylesheetCallbacks.forEach(cb => cb(sheet)) - } else { - console.warn("Style onload: sheet is null") - } - this.loaded = true - } + // node.onload = () => { + // const sheet = node.sheet + // if (sheet) { + // this.stylesheetCallbacks.forEach(cb => cb(sheet)) + // } else { + // console.warn("Style onload: sheet is null") + // } + // this.loaded = true + // } } onStyleSheet(cb: StyleSheetCallback) { - if (this.loaded) { + // if (this.loaded) { if (!this.node.sheet) { console.warn("Style tag is loaded, but sheet is null") return } cb(this.node.sheet) - } else { - this.stylesheetCallbacks.push(cb) - } + // } else { + // this.stylesheetCallbacks.push(cb) + // } } }