feat(player/VirtualDOM): OnloadVRoot & OnloadStyleSheet for lazy iframe innerContent initialisation & elimination of forceInsertion requirement in this case;; few renamings
This commit is contained in:
parent
076e54573f
commit
57e4648e5e
2 changed files with 185 additions and 125 deletions
|
|
@ -9,13 +9,14 @@ import FocusManager from './FocusManager';
|
|||
import SelectionManager from './SelectionManager';
|
||||
import type { StyleElement } from './VirtualDOM';
|
||||
import {
|
||||
PostponedStyleSheet,
|
||||
OnloadStyleSheet,
|
||||
VDocument,
|
||||
VElement,
|
||||
VHTMLElement,
|
||||
VNode,
|
||||
VShadowRoot,
|
||||
VText,
|
||||
OnloadVRoot,
|
||||
} from './VirtualDOM';
|
||||
import { deleteRule, insertRule } from './safeCSSRules';
|
||||
|
||||
|
|
@ -27,11 +28,13 @@ const ATTR_NAME_REGEXP = /([^\t\n\f \/>"'=]+)/; // regexp costs ~
|
|||
export default class DOMManager extends ListWalker<Message> {
|
||||
private readonly vTexts: Map<number, VText> = new Map() // map vs object here?
|
||||
private readonly vElements: Map<number, VElement> = new Map()
|
||||
private readonly vRoots: Map<number, VShadowRoot | VDocument> = new Map()
|
||||
private styleSheets: Map<number, CSSStyleSheet> = new Map()
|
||||
private ppStyleSheets: Map<number, PostponedStyleSheet> = new Map()
|
||||
private readonly olVRoots: Map<number, OnloadVRoot> = new Map()
|
||||
/** Constructed StyleSheets https://developer.mozilla.org/en-US/docs/Web/API/Document/adoptedStyleSheets
|
||||
* as well as <style> tag owned StyleSheets
|
||||
* */
|
||||
private olStyleSheets: Map<number, OnloadStyleSheet> = new Map()
|
||||
/** @depreacted since tracker 4.0.2 Mapping by nodeID */
|
||||
private ppStyleSheetsDeprecated: Map<number, PostponedStyleSheet> = new Map()
|
||||
private olStyleSheetsDeprecated: Map<number, OnloadStyleSheet> = new Map()
|
||||
private stringDict: Record<number,string> = {}
|
||||
private attrsBacktrack: Message[] = []
|
||||
|
||||
|
|
@ -107,9 +110,9 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
logger.error("Insert error. Node not found", id);
|
||||
return;
|
||||
}
|
||||
const parent = this.vElements.get(parentID) || this.vRoots.get(parentID)
|
||||
const parent = this.vElements.get(parentID) || this.olVRoots.get(parentID)
|
||||
if (!parent) {
|
||||
logger.error("Insert error. Parent vNode not found", parentID, this.vElements, this.vRoots);
|
||||
logger.error("Insert error. Parent vNode not found", parentID, this.vElements, this.olVRoots);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -177,10 +180,10 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
const vHTMLElement = new VHTMLElement(fRoot)
|
||||
this.vElements.clear()
|
||||
this.vElements.set(0, vHTMLElement)
|
||||
const vDoc = new VDocument(() => doc as Document)
|
||||
const vDoc = OnloadVRoot.fromDocumentNode(doc)
|
||||
vDoc.insertChildAt(vHTMLElement, 0)
|
||||
this.vRoots.clear()
|
||||
this.vRoots.set(0, vDoc) // watchout: id==0 for both Document and documentElement
|
||||
this.olVRoots.clear()
|
||||
this.olVRoots.set(0, vDoc) // watchout: id==0 for both Document and documentElement
|
||||
// this is done for the AdoptedCSS logic
|
||||
// todo: start from 0-node (sync logic with tracker)
|
||||
this.vTexts.clear()
|
||||
|
|
@ -201,7 +204,7 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
this.removeBodyScroll(msg.id, vElem)
|
||||
this.removeAutocomplete(vElem)
|
||||
if (['STYLE', 'style', 'LINK'].includes(msg.tag)) { // Styles in priority
|
||||
vElem.enforceInsertion()
|
||||
vElem.enforceInsertion() //TODOTODO priority mounting instead
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -273,7 +276,7 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
case MType.SetNodeData:
|
||||
case MType.SetCssData: {
|
||||
const vText = this.vTexts.get(msg.id)
|
||||
if (!vText) { logger.error("SetCssData: Node not found", msg); return }
|
||||
if (!vText) { logger.error("SetNodeData/SetCssData: Node not found", msg); return }
|
||||
vText.setData(msg.data)
|
||||
|
||||
if (msg.tp === MType.SetCssData) { //TODOTODO
|
||||
|
|
@ -286,19 +289,19 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
* since 4.0.2 in favor of AdoptedSsInsertRule/DeleteRule + AdoptedSsAddOwner as a common case for StyleSheets
|
||||
*/
|
||||
case MType.CssInsertRule: {
|
||||
let styleSheet = this.ppStyleSheetsDeprecated.get(msg.id)
|
||||
let styleSheet = this.olStyleSheetsDeprecated.get(msg.id)
|
||||
if (!styleSheet) {
|
||||
const vElem = this.vElements.get(msg.id)
|
||||
if (!vElem) { logger.error("CssInsertRule: Node not found", msg); return }
|
||||
if (vElem.tagName.toLowerCase() !== "style") { logger.error("CssInsertRule: Non-style elemtn", msg); return }
|
||||
styleSheet = new PostponedStyleSheet(vElem.node as StyleElement)
|
||||
this.ppStyleSheetsDeprecated.set(msg.id, styleSheet)
|
||||
styleSheet = OnloadStyleSheet.fromStyleElement(vElem.node as StyleElement)
|
||||
this.olStyleSheetsDeprecated.set(msg.id, styleSheet)
|
||||
}
|
||||
styleSheet.insertRule(msg.rule, msg.index)
|
||||
return
|
||||
}
|
||||
case MType.CssDeleteRule: {
|
||||
const styleSheet = this.ppStyleSheetsDeprecated.get(msg.id)
|
||||
const styleSheet = this.olStyleSheetsDeprecated.get(msg.id)
|
||||
if (!styleSheet) { logger.error("CssDeleteRule: StyleSheet was not created", msg); return }
|
||||
styleSheet.deleteRule(msg.index)
|
||||
return
|
||||
|
|
@ -307,33 +310,13 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
case MType.CreateIFrameDocument: {
|
||||
const vElem = this.vElements.get(msg.frameID)
|
||||
if (!vElem) { logger.error("CreateIFrameDocument: Node not found", msg); return }
|
||||
vElem.enforceInsertion() //TODOTODO
|
||||
const host = vElem.node
|
||||
if (host instanceof HTMLIFrameElement) {
|
||||
const doc = host.contentDocument
|
||||
if (!doc) {
|
||||
logger.warn("No default iframe doc", msg, host)
|
||||
return
|
||||
}
|
||||
|
||||
const vDoc = new VDocument(() => doc)
|
||||
this.vRoots.set(msg.id, vDoc)
|
||||
return;
|
||||
} else if (host instanceof Element) { // shadow DOM
|
||||
try {
|
||||
const shadowRoot = host.attachShadow({ mode: 'open' })
|
||||
const vRoot = new VShadowRoot(() => shadowRoot)
|
||||
this.vRoots.set(msg.id, vRoot)
|
||||
} catch(e) {
|
||||
logger.warn("Can not attach shadow dom", e, msg)
|
||||
}
|
||||
} else {
|
||||
logger.warn("Context message host is not Element", msg)
|
||||
}
|
||||
const vRoot = OnloadVRoot.fromVElement(vElem)
|
||||
vRoot.catch(e => logger.warn(e, msg))
|
||||
this.olVRoots.set(msg.id, vRoot)
|
||||
return
|
||||
}
|
||||
case MType.AdoptedSsInsertRule: {
|
||||
const styleSheet = this.styleSheets.get(msg.sheetID) || this.ppStyleSheets.get(msg.sheetID)
|
||||
const styleSheet = this.olStyleSheets.get(msg.sheetID)
|
||||
if (!styleSheet) {
|
||||
logger.warn("No stylesheet was created for ", msg)
|
||||
return
|
||||
|
|
@ -342,7 +325,7 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
return
|
||||
}
|
||||
case MType.AdoptedSsDeleteRule: {
|
||||
const styleSheet = this.styleSheets.get(msg.sheetID) || this.ppStyleSheets.get(msg.sheetID)
|
||||
const styleSheet = this.olStyleSheets.get(msg.sheetID)
|
||||
if (!styleSheet) {
|
||||
logger.warn("No stylesheet was created for ", msg)
|
||||
return
|
||||
|
|
@ -351,7 +334,7 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
return
|
||||
}
|
||||
case MType.AdoptedSsReplace: {
|
||||
const styleSheet = this.styleSheets.get(msg.sheetID)
|
||||
const styleSheet = this.olStyleSheets.get(msg.sheetID)
|
||||
if (!styleSheet) {
|
||||
logger.warn("No stylesheet was created for ", msg)
|
||||
return
|
||||
|
|
@ -361,58 +344,62 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
return
|
||||
}
|
||||
case MType.AdoptedSsAddOwner: {
|
||||
const vRoot = this.vRoots.get(msg.id)
|
||||
const vRoot = this.olVRoots.get(msg.id)
|
||||
if (!vRoot) {
|
||||
/* <style> tag case */
|
||||
const vElem = this.vElements.get(msg.id)
|
||||
if (!vElem) { logger.error("AdoptedSsAddOwner: Node not found", msg); return }
|
||||
if (vElem.tagName.toLowerCase() !== "style") { logger.error("Non-style owner", msg); return }
|
||||
this.ppStyleSheets.set(msg.sheetID, new PostponedStyleSheet(vElem.node as StyleElement))
|
||||
this.olStyleSheets.set(msg.sheetID, OnloadStyleSheet.fromStyleElement(vElem.node as StyleElement))
|
||||
return
|
||||
}
|
||||
/* Constructed StyleSheet case */
|
||||
let styleSheet = this.styleSheets.get(msg.sheetID)
|
||||
if (!styleSheet) {
|
||||
let context: typeof globalThis | null
|
||||
if (vRoot instanceof VDocument) {
|
||||
context = vRoot.node.defaultView
|
||||
} else {
|
||||
context = vRoot.node.ownerDocument.defaultView
|
||||
}
|
||||
if (!context) { logger.error("AdoptedSsAddOwner: Root node default view not found", msg); return }
|
||||
styleSheet = new context.CSSStyleSheet() /* a StyleSheet from another Window context won't work */
|
||||
this.styleSheets.set(msg.sheetID, styleSheet)
|
||||
let olStyleSheet = this.olStyleSheets.get(msg.sheetID)
|
||||
if (!olStyleSheet) {
|
||||
olStyleSheet = OnloadStyleSheet.fromVRootContext(vRoot)
|
||||
this.olStyleSheets.set(msg.sheetID, olStyleSheet)
|
||||
}
|
||||
// @ts-ignore
|
||||
vRoot.node.adoptedStyleSheets = [...vRoot.node.adoptedStyleSheets, styleSheet]
|
||||
olStyleSheet.doNext(styleSheet => {
|
||||
vRoot.onNode(node => {
|
||||
// @ts-ignore
|
||||
node.adoptedStyleSheets = [...node.adoptedStyleSheets, styleSheet]
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
case MType.AdoptedSsRemoveOwner: {
|
||||
const styleSheet = this.styleSheets.get(msg.sheetID)
|
||||
if (!styleSheet) {
|
||||
logger.warn("No stylesheet was created for ", msg)
|
||||
const olStyleSheet = this.olStyleSheets.get(msg.sheetID)
|
||||
if (!olStyleSheet) {
|
||||
logger.warn("AdoptedSsRemoveOwner: No stylesheet was created for ", msg)
|
||||
return
|
||||
}
|
||||
const vRoot = this.vRoots.get(msg.id)
|
||||
if (!vRoot) { logger.error("AdoptedSsRemoveOwner: Node not found", msg); return }
|
||||
//@ts-ignore
|
||||
vRoot.node.adoptedStyleSheets = [...vRoot.node.adoptedStyleSheets].filter(s => s !== styleSheet)
|
||||
const vRoot = this.olVRoots.get(msg.id)
|
||||
if (!vRoot) { logger.error("AdoptedSsRemoveOwner: Owner node not found", msg); return }
|
||||
olStyleSheet.doNext(styleSheet => {
|
||||
vRoot.onNode(node => {
|
||||
// @ts-ignore
|
||||
node.adoptedStyleSheets = [...vRoot.node.adoptedStyleSheets].filter(s => s !== styleSheet)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
case MType.LoadFontFace: {
|
||||
const vRoot = this.vRoots.get(msg.parentID)
|
||||
const vRoot = this.olVRoots.get(msg.parentID)
|
||||
if (!vRoot) { logger.error("LoadFontFace: Node not found", msg); return }
|
||||
if (vRoot instanceof VShadowRoot) { logger.error(`Node ${vRoot} expected to be a Document`, msg); return }
|
||||
let descr: Object | undefined
|
||||
try {
|
||||
descr = JSON.parse(msg.descriptors)
|
||||
descr = typeof descr === 'object' ? descr : undefined
|
||||
} catch {
|
||||
logger.warn("Can't parse font-face descriptors: ", msg)
|
||||
}
|
||||
const ff = new FontFace(msg.family, msg.source, descr)
|
||||
vRoot.node.fonts.add(ff)
|
||||
return ff.load()
|
||||
vRoot.doNext(vNode => {
|
||||
if (vNode instanceof VShadowRoot) { logger.error(`Node ${vNode} expected to be a Document`, msg); return }
|
||||
let descr: Object | undefined
|
||||
try {
|
||||
descr = JSON.parse(msg.descriptors)
|
||||
descr = typeof descr === 'object' ? descr : undefined
|
||||
} catch {
|
||||
logger.warn("Can't parse font-face descriptors: ", msg)
|
||||
}
|
||||
const ff = new FontFace(msg.family, msg.source, descr)
|
||||
vNode.node.fonts.add(ff)
|
||||
ff.load() // TODO: wait for this one in StylesManager in a common way with styles
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -445,12 +432,13 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves and applies all the messages from the current (or from the beginning, if t < current.time)
|
||||
* to the one with msg.time >= `t`
|
||||
*
|
||||
* This function autoresets pointer if necessary (better name?)
|
||||
* */
|
||||
async moveReady(t: number): Promise<void> {
|
||||
// MBTODO (back jump optimisation):
|
||||
// - store intemediate virtual dom state
|
||||
// - cancel previous moveReady tasks (is it possible?) if new timestamp is less
|
||||
// This function autoresets pointer if necessary (better name?)
|
||||
|
||||
/**
|
||||
* Basically just skipping all set attribute with attrs being "href" if user is 'jumping'
|
||||
* to the other point of replay to save time on NOT downloading any resources before the dom tree changes
|
||||
|
|
@ -466,7 +454,7 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
})
|
||||
this.attrsBacktrack = []
|
||||
|
||||
this.vRoots.forEach(rt => rt.applyChanges()) // MBTODO (optimisation): affected set
|
||||
this.olVRoots.forEach(rt => rt.applyChanges())
|
||||
// Thinkabout (read): css preload
|
||||
// What if we go back before it is ready? We'll have two handlres?
|
||||
return this.stylesManager.moveReady(t).then(() => {
|
||||
|
|
@ -477,12 +465,16 @@ export default class DOMManager extends ListWalker<Message> {
|
|||
this.nodeScrollManagers.forEach(manager => {
|
||||
const msg = manager.moveGetLast(t)
|
||||
if (msg) {
|
||||
let vNode: VElement | VDocument | VShadowRoot | undefined
|
||||
if (vNode = this.vElements.get(msg.id)) {
|
||||
vNode.node.scrollLeft = msg.x
|
||||
vNode.node.scrollTop = msg.y
|
||||
} else if ((vNode = this.vRoots.get(msg.id)) && vNode instanceof VDocument){
|
||||
vNode.node.defaultView?.scrollTo(msg.x, msg.y)
|
||||
let scrollVHost: VElement | OnloadVRoot | undefined
|
||||
if (scrollVHost = this.vElements.get(msg.id)) {
|
||||
scrollVHost.node.scrollLeft = msg.x
|
||||
scrollVHost.node.scrollTop = msg.y
|
||||
} else if ((scrollVHost = this.olVRoots.get(msg.id))) {
|
||||
scrollVHost.doNext(vNode => {
|
||||
if (vNode instanceof VDocument) {
|
||||
vNode.node.defaultView?.scrollTo(msg.x, msg.y)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -41,20 +41,20 @@ export abstract class VNode<T extends Node = Node> {
|
|||
type VChild = VElement | VText
|
||||
abstract class VParent<T extends Node = Node> extends VNode<T>{
|
||||
protected children: VChild[] = []
|
||||
private insertedChildren: Set<VChild> = new Set()
|
||||
private recentlyInserted: Set<VChild> = new Set()
|
||||
|
||||
insertChildAt(child: VChild, index: number) {
|
||||
if (child.parentNode) {
|
||||
child.parentNode.removeChild(child)
|
||||
}
|
||||
this.children.splice(index, 0, child)
|
||||
this.insertedChildren.add(child)
|
||||
this.recentlyInserted.add(child)
|
||||
child.parentNode = this
|
||||
}
|
||||
|
||||
removeChild(child: VChild) {
|
||||
this.children = this.children.filter(ch => ch !== child)
|
||||
this.insertedChildren.delete(child)
|
||||
this.recentlyInserted.delete(child)
|
||||
child.parentNode = null
|
||||
}
|
||||
|
||||
|
|
@ -63,13 +63,13 @@ abstract class VParent<T extends Node = Node> extends VNode<T>{
|
|||
/* Inserting */
|
||||
for (let i = this.children.length-1; i >= 0; i--) {
|
||||
const child = this.children[i]
|
||||
child.applyChanges()
|
||||
if (this.insertedChildren.has(child)) {
|
||||
child.applyChanges() /* Building the sub-tree in memory first */
|
||||
if (this.recentlyInserted.has(child)) {
|
||||
const nextVSibling = this.children[i+1]
|
||||
node.insertBefore(child.node, nextVSibling ? nextVSibling.node : null)
|
||||
}
|
||||
}
|
||||
this.insertedChildren.clear()
|
||||
this.recentlyInserted.clear()
|
||||
/* Removing in-between */
|
||||
const realChildren = node.childNodes
|
||||
for(let j = 0; j < this.children.length; j++) {
|
||||
|
|
@ -104,15 +104,17 @@ export class VShadowRoot extends VParent<ShadowRoot> {
|
|||
constructor(protected readonly createNode: () => ShadowRoot) { super() }
|
||||
}
|
||||
|
||||
export type VRoot = VDocument | VShadowRoot
|
||||
|
||||
export class VElement extends VParent<Element> {
|
||||
parentNode: VParent | null = null
|
||||
private newAttributes: Map<string, string | false> = new Map()
|
||||
|
||||
constructor(readonly tagName: string, readonly isSVG = false) { super() }
|
||||
protected createNode(){
|
||||
protected createNode() {
|
||||
return this.isSVG
|
||||
? document.createElementNS('http://www.w3.org/2000/svg', this.tagName)
|
||||
: document.createElement(this.tagName)
|
||||
? document.createElementNS('http://www.w3.org/2000/svg', this.tagName)
|
||||
: document.createElement(this.tagName)
|
||||
}
|
||||
setAttribute(name: string, value: string) {
|
||||
this.newAttributes.set(name, value)
|
||||
|
|
@ -176,42 +178,108 @@ export class VText extends VNode<Text> {
|
|||
}
|
||||
}
|
||||
|
||||
export type StyleElement = HTMLStyleElement | SVGStyleElement
|
||||
|
||||
export class PostponedStyleSheet {
|
||||
private loaded = false
|
||||
private stylesheetCallbacks: Callback<CSSStyleSheet>[] = []
|
||||
|
||||
constructor(private readonly node: StyleElement) { //TODO: use virtual DOM + onNode callback for better lazy node init
|
||||
node.addEventListener("load", () => {
|
||||
const sheet = node.sheet
|
||||
if (sheet) {
|
||||
this.stylesheetCallbacks.forEach(cb => cb(sheet))
|
||||
this.stylesheetCallbacks = []
|
||||
} else {
|
||||
console.warn("Style node onload: sheet is null")
|
||||
}
|
||||
this.loaded = true
|
||||
class PromiseQueue<T> {
|
||||
constructor(private promise: Promise<T>) {}
|
||||
doNext(cb: Callback<T>) { // Doing this with callbacks list instead might be more efficient. TODO: research
|
||||
this.promise = this.promise.then(vRoot => {
|
||||
cb(vRoot)
|
||||
return vRoot
|
||||
})
|
||||
}
|
||||
catch(cb: Parameters<Promise<T>['catch']>[0]) {
|
||||
this.promise.catch(cb)
|
||||
}
|
||||
}
|
||||
|
||||
private applyCallback(cb: Callback<CSSStyleSheet>) {
|
||||
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)
|
||||
}
|
||||
/**
|
||||
* VRoot wrapper that allows to defer all the API calls till the moment
|
||||
* when VNode CAN be created (for example, on <iframe> mount&load)
|
||||
* */
|
||||
export class OnloadVRoot extends PromiseQueue<VRoot> {
|
||||
static fromDocumentNode(doc: Document): OnloadVRoot {
|
||||
return new OnloadVRoot(Promise.resolve(new VDocument(() => doc)))
|
||||
}
|
||||
static fromVElement(vElem: VElement): OnloadVRoot {
|
||||
return new OnloadVRoot(new Promise((resolve, reject) => {
|
||||
vElem.onNode(host => {
|
||||
if (host instanceof HTMLIFrameElement) {
|
||||
/* IFrame case: creating Document */
|
||||
const doc = host.contentDocument
|
||||
if (doc) {
|
||||
resolve(new VDocument(() => doc))
|
||||
} else {
|
||||
host.addEventListener('load', () => {
|
||||
const doc = host.contentDocument
|
||||
if (doc) {
|
||||
resolve(new VDocument(() => doc))
|
||||
} else {
|
||||
reject("No default Document found on iframe load") // Send `host` for logging as well
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
/* ShadowDom case */
|
||||
try {
|
||||
const shadowRoot = host.attachShadow({ mode: 'open' })
|
||||
resolve(new VShadowRoot(() => shadowRoot))
|
||||
} catch(e) {
|
||||
reject(e) // "Can not attach shadow dom"
|
||||
}
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
onNode(cb: Callback<Document | ShadowRoot>) {
|
||||
this.doNext(vRoot => vRoot.onNode(cb))
|
||||
}
|
||||
applyChanges() {
|
||||
this.doNext(vRoot => vRoot.applyChanges())
|
||||
}
|
||||
insertChildAt(...args: Parameters<VParent['insertChildAt']>) {
|
||||
this.doNext(vRoot => vRoot.insertChildAt(...args))
|
||||
}
|
||||
}
|
||||
|
||||
export type StyleElement = HTMLStyleElement | SVGStyleElement
|
||||
|
||||
/**
|
||||
* CSSStyleSheet wrapper that collects all the insertRule/deleteRule calls
|
||||
* and then applies them when the sheet is ready
|
||||
* */
|
||||
export class OnloadStyleSheet extends PromiseQueue<CSSStyleSheet> {
|
||||
static fromStyleElement(node: StyleElement) {
|
||||
return new OnloadStyleSheet(new Promise((resolve, reject) => {
|
||||
node.addEventListener("load", () => {
|
||||
const sheet = node.sheet
|
||||
if (sheet) {
|
||||
resolve(sheet)
|
||||
} else {
|
||||
reject("Style node onload: sheet is null")
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
static fromVRootContext(vRoot: OnloadVRoot) {
|
||||
return new OnloadStyleSheet(new Promise((resolve, reject) =>
|
||||
vRoot.onNode(node => {
|
||||
let context: typeof globalThis | null
|
||||
if (node instanceof Document) {
|
||||
context = node.defaultView
|
||||
} else {
|
||||
context = node.ownerDocument.defaultView
|
||||
}
|
||||
if (!context) { reject("Root node default view not found"); return }
|
||||
/* a StyleSheet from another Window context won't work */
|
||||
resolve(new context.CSSStyleSheet())
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
insertRule(rule: string, index: number) {
|
||||
this.applyCallback(s => insertRule(s, { rule, index }))
|
||||
this.doNext(s => insertRule(s, { rule, index }))
|
||||
}
|
||||
|
||||
deleteRule(index: number) {
|
||||
this.applyCallback(s => deleteRule(s, { index }))
|
||||
this.doNext(s => deleteRule(s, { index }))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue