fix (frontend/player): instant insertion of style elements & correct children deletion

This commit is contained in:
Alex Kaminskii 2022-07-27 17:28:51 +02:00
parent 683b0b3ceb
commit dabe7f0e44
2 changed files with 52 additions and 26 deletions

View file

@ -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<Message> {
private vTexts: Map<number, VText> = new Map() // map vs object here?
private vElements: Map<number, VElement> = new Map()
@ -144,6 +154,9 @@ export default class DOMManager extends ListWalker<Message> {
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<Message> {
// @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?

View file

@ -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<string, string | false> = new Map()
//private props: Record<string, string | number | boolean>
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)
// }
}
}