diff options
author | bptato <nincsnevem662@gmail.com> | 2024-07-03 17:21:14 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-07-03 17:21:14 +0200 |
commit | d0c43d69f5c64d01230c79458f473eb9ecd5a7e1 (patch) | |
tree | 6409d32007555c56d93d5f95b921bfcd977fefce | |
parent | 1eb05b007a71dbd6589e722d2aeefdf6747da022 (diff) | |
download | chawan-d0c43d69f5c64d01230c79458f473eb9ecd5a7e1.tar.gz |
css, html: fix CSS dependency invalidation
-rw-r--r-- | src/css/cascade.nim | 9 | ||||
-rw-r--r-- | src/css/stylednode.nim | 46 | ||||
-rw-r--r-- | src/html/chadombuilder.nim | 2 | ||||
-rw-r--r-- | src/html/dom.nim | 162 | ||||
-rw-r--r-- | src/server/buffer.nim | 14 |
5 files changed, 121 insertions, 112 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim index bfb5b172..6f62b68d 100644 --- a/src/css/cascade.nim +++ b/src/css/cascade.nim @@ -332,7 +332,7 @@ proc applyDeclarations(styledNode: StyledNode; parent: CSSComputedValues; rules[coAuthor].add(rule[peNone]) if styledNode.node != nil: let element = Element(styledNode.node) - let style = element.style_cached + let style = element.cachedStyle if style != nil: for decl in style.decls: let vals = parseComputedValues(decl.name, decl.value) @@ -610,11 +610,12 @@ proc applyRules(document: Document; ua, user: CSSStylesheet; cachedChild: cachedTree )] var root: StyledNode + var toReset: seq[Element] = @[] while styledStack.len > 0: var frame = styledStack.pop() var declmap: RuleListMap let styledParent = frame.styledParent - let valid = frame.cachedChild != nil and frame.cachedChild.isValid() + let valid = frame.cachedChild != nil and frame.cachedChild.isValid(toReset) let styledChild = if valid: frame.applyRulesFrameValid() else: @@ -627,8 +628,10 @@ proc applyRules(document: Document; ua, user: CSSStylesheet; # Root element root = styledChild if styledChild.t == stElement and styledChild.node != nil: - styledChild.applyDependValues() styledStack.appendChildren(frame, styledChild, declmap) + for element in toReset: + element.invalid = true + element.invalidDeps = {} return root proc applyStylesheets*(document: Document; uass, userss: CSSStylesheet; diff --git a/src/css/stylednode.nim b/src/css/stylednode.nim index a5d2b44b..0fcdb6ad 100644 --- a/src/css/stylednode.nim +++ b/src/css/stylednode.nim @@ -1,4 +1,3 @@ -import chame/tags import css/cssvalues import css/selectorparser import html/dom @@ -87,15 +86,14 @@ iterator elementList_rev*(node: StyledNode): StyledNode {.inline.} = for i in countdown(node.children.high, 0): yield node.children[i] -func findElement*(root: StyledNode; elem: Element): StyledNode = - var stack: seq[StyledNode] +func findElement*(root: StyledNode; element: Element): StyledNode = + var stack: seq[StyledNode] = @[] for child in root.elementList_rev: if child.t == stElement and child.pseudo == peNone: stack.add(child) - let en = Node(elem) while stack.len > 0: let node = stack.pop() - if node.node == en: + if node.node == element: return node for child in node.elementList_rev: if child.t == stElement and child.pseudo == peNone: @@ -108,41 +106,23 @@ func isDomElement*(styledNode: StyledNode): bool {.inline.} = func parentElement*(node: StyledNode): StyledNode {.inline.} = node.parent -func checked(element: Element): bool = - if element.tagType == TAG_INPUT: - let input = HTMLInputElement(element) - result = input.checked - -func isValid*(styledNode: StyledNode): bool = +proc isValid*(styledNode: StyledNode; toReset: var seq[Element]): bool = if styledNode.t == stText: return true if styledNode.t == stReplacement: return true - if styledNode.node != nil and Element(styledNode.node).invalid: - return false + if styledNode.node != nil: + let element = Element(styledNode.node) + if element.invalid: + toReset.add(element) + return false for d in DependencyType: - for elem in styledNode.depends[d]: - case d - of dtHover: - if elem.prev[d] != elem.hover: - return false - of dtChecked: - if elem.prev[d] != elem.checked: - return false - of dtFocus: - let focus = elem.document.focus == elem - if elem.prev[d] != focus: - return false + for dep in styledNode.depends[d]: + if d in dep.invalidDeps: + toReset.add(dep) + return false return true -proc applyDependValues*(styledNode: StyledNode) = - let elem = Element(styledNode.node) - elem.prev[dtHover] = elem.hover - elem.prev[dtChecked] = elem.checked - let focus = elem.document.focus == elem - elem.prev[dtFocus] = focus - elem.invalid = false - proc addDependency*(styledNode: StyledNode; dep: Element; t: DependencyType) = if dep notin styledNode.depends[t]: styledNode.depends[t].add(dep) diff --git a/src/html/chadombuilder.nim b/src/html/chadombuilder.nim index afa0362d..2bb9bec7 100644 --- a/src/html/chadombuilder.nim +++ b/src/html/chadombuilder.nim @@ -91,7 +91,7 @@ proc restart*(wrapper: HTML5ParserWrapper; charset: Charset) = wrapper.parser = initHTML5Parser(builder, wrapper.opts) proc setQuirksModeImpl(builder: ChaDOMBuilder; quirksMode: QuirksMode) = - if not builder.document.parser_cannot_change_the_mode_flag: + if not builder.document.parserCannotChangeModeFlag: builder.document.mode = quirksMode proc setEncodingImpl(builder: ChaDOMBuilder; encoding: string): diff --git a/src/html/dom.nim b/src/html/dom.nim index 0fbdda7d..7b0a866c 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -144,8 +144,8 @@ type # nodes they refer to. These are removed when the collection is destroyed, # and invalidated when the owner node's children or attributes change. liveCollections: HashSet[pointer] - childNodes_cached: NodeList - document_internal: Document # not nil + cachedChildNodes: NodeList + internalDocument: Document # not nil Attr* = ref object of Node dataIdx: int @@ -180,19 +180,17 @@ type scriptsToExecOnLoad*: Deque[HTMLScriptElement] parserBlockingScript*: HTMLScriptElement - parser_cannot_change_the_mode_flag*: bool - is_iframe_srcdoc*: bool - focus*: Element + parserCannotChangeModeFlag*: bool + internalFocus: Element contentType* {.jsget.}: string - renderBlockingElements: seq[Element] invalidCollections: HashSet[pointer] # pointers to Collection objects - all_cached: HTMLAllCollection + cachedAll: HTMLAllCollection cachedSheets: seq[CSSStylesheet] cachedSheetsInvalid*: bool - children_cached: HTMLCollection + cachedChildren: HTMLCollection #TODO I hate this but I really don't want to put chadombuilder into dom too parser*: pointer @@ -210,7 +208,7 @@ type DocumentFragment* = ref object of Node host*: Element - children_cached*: HTMLCollection + cachedChildren*: HTMLCollection DocumentType* = ref object of Node name*: string @@ -235,14 +233,13 @@ type classList* {.jsget.}: DOMTokenList attrs: seq[AttrData] # sorted by int(qualifiedName) attributesInternal: NamedNodeMap - hover*: bool + internalHover: bool invalid*: bool - style_cached*: CSSStyleDeclaration - children_cached: HTMLCollection - + cachedStyle*: CSSStyleDeclaration + cachedChildren: HTMLCollection # The owner StyledNode is marked as invalid when one of these no longer # matches the DOM value. - prev*: array[DependencyType, bool] + invalidDeps*: set[DependencyType] AttrDummyElement = ref object of Element @@ -260,7 +257,7 @@ type HTMLInputElement* = ref object of FormAssociatedElement inputType*: InputType value* {.jsget.}: string - checked* {.jsget.}: bool + internalChecked {.jsget: "checked".}: bool xcoord*: int ycoord*: int file*: WebFile @@ -652,7 +649,7 @@ proc fillText(ctx: CanvasRenderingContext2D; text: string; x, y: float64) if classify(v) in {fcInf, fcNegInf, fcNan}: return #TODO should not be loaded here... - ctx.canvas.document_internal.window.loadUnifont() + ctx.canvas.internalDocument.window.loadUnifont() let vec = ctx.transform(Vector2D(x: x, y: y)) ctx.bitmap.fillText(text, vec.x, vec.y, ctx.state.fillStyle, ctx.state.textAlign) @@ -664,7 +661,7 @@ proc strokeText(ctx: CanvasRenderingContext2D; text: string; x, y: float64) if classify(v) in {fcInf, fcNegInf, fcNan}: return #TODO should not be loaded here... - ctx.canvas.document_internal.window.loadUnifont() + ctx.canvas.internalDocument.window.loadUnifont() let vec = ctx.transform(Vector2D(x: x, y: y)) ctx.bitmap.strokeText(text, vec.x, vec.y, ctx.state.strokeStyle, ctx.state.textAlign) @@ -892,12 +889,12 @@ proc attr*(element: Element; name: CAtom; value: string) proc attr*(element: Element; name: StaticAtom; value: string) func baseURL*(document: Document): URL proc delAttr(element: Element; i: int; keep = false) -proc reflectAttrs(element: Element; name: CAtom; value: string) +proc reflectAttr(element: Element; name: CAtom; value: Option[string]) func document*(node: Node): Document = if node of Document: return Document(node) - return node.document_internal + return node.internalDocument proc toAtom*(document: Document; s: string): CAtom = return document.factory.toAtom(s) @@ -1200,14 +1197,14 @@ func isElement(node: Node): bool = return node of Element template parentNodeChildrenImpl(parentNode: typed) = - if parentNode.children_cached == nil: - parentNode.children_cached = newCollection[HTMLCollection]( + if parentNode.cachedChildren == nil: + parentNode.cachedChildren = newCollection[HTMLCollection]( root = parentNode, match = isElement, islive = true, childonly = true ) - return parentNode.children_cached + return parentNode.cachedChildren func children(parentNode: Document): HTMLCollection {.jsfget.} = parentNodeChildrenImpl(parentNode) @@ -1219,14 +1216,14 @@ func children(parentNode: Element): HTMLCollection {.jsfget.} = parentNodeChildrenImpl(parentNode) func childNodes(node: Node): NodeList {.jsfget.} = - if node.childNodes_cached == nil: - node.childNodes_cached = newCollection[NodeList]( + if node.cachedChildNodes == nil: + node.cachedChildNodes = newCollection[NodeList]( root = node, match = nil, islive = true, childonly = true ) - return node.childNodes_cached + return node.cachedChildNodes # DOMTokenList func length(tokenList: DOMTokenList): uint32 {.jsfget.} = @@ -1490,14 +1487,14 @@ func names(ctx: JSContext; collection: HTMLAllCollection): JSPropertyEnumList return list proc all(document: Document): HTMLAllCollection {.jsfget.} = - if document.all_cached == nil: - document.all_cached = newCollection[HTMLAllCollection]( + if document.cachedAll == nil: + document.cachedAll = newCollection[HTMLAllCollection]( root = document, match = isElement, islive = true, childonly = false ) - return document.all_cached + return document.cachedAll # Location proc newLocation*(window: Window): Location = @@ -1678,7 +1675,7 @@ proc getAttr(map: NamedNodeMap; dataIdx: int): Attr = if i != -1: return map.attrlist[i] let attr = Attr( - document_internal: map.element.document, + internalDocument: map.element.document, index: -1, dataIdx: dataIdx, ownerElement: map.element @@ -1700,7 +1697,7 @@ func attributes(element: Element): NamedNodeMap {.jsfget.} = element.attributesInternal = NamedNodeMap(element: element) for i, attr in element.attrs: element.attributesInternal.attrlist.add(Attr( - document_internal: element.document, + internalDocument: element.document, index: -1, dataIdx: i, ownerElement: element @@ -2270,6 +2267,13 @@ proc sheets*(document: Document): seq[CSSStylesheet] = document.cachedSheetsInvalid = false return document.cachedSheets +func checked*(input: HTMLInputElement): bool {.inline.} = + return input.internalChecked + +proc setChecked*(input: HTMLInputElement; b: bool) {.inline.} = + input.invalidDeps.incl(dtChecked) + input.internalChecked = b + func inputString*(input: HTMLInputElement): string = case input.inputType of itCheckbox, itRadio: @@ -2378,6 +2382,23 @@ func findAnchor*(document: Document; id: string): Element = return child return nil +func focus*(document: Document): Element = + return document.internalFocus + +proc setFocus*(document: Document; element: Element) = + if document.focus != nil: + document.focus.invalidDeps.incl(dtFocus) + document.internalFocus = element + if element != nil: + element.invalidDeps.incl(dtFocus) + +func hover*(element: Element): bool = + return element.internalHover + +proc setHover*(element: Element; hover: bool) = + element.invalidDeps.incl(dtHover) + element.internalHover = hover + func findAutoFocus*(document: Document): Element = for child in document.elements: if child.attrb(satAutofocus): @@ -2518,7 +2539,7 @@ func getSrc*(this: HTMLVideoElement|HTMLAudioElement): string = func newText*(document: Document; data: string): Text = return Text( - document_internal: document, + internalDocument: document, data: data, index: -1 ) @@ -2529,7 +2550,7 @@ func newText(ctx: JSContext; data = ""): Text {.jsctor.} = func newCDATASection(document: Document; data: string): CDATASection = return CDATASection( - document_internal: document, + internalDocument: document, data: data, index: -1 ) @@ -2537,7 +2558,7 @@ func newCDATASection(document: Document; data: string): CDATASection = func newProcessingInstruction(document: Document; target, data: string): ProcessingInstruction = return ProcessingInstruction( - document_internal: document, + internalDocument: document, target: target, data: data, index: -1 @@ -2545,7 +2566,7 @@ func newProcessingInstruction(document: Document; target, data: string): func newDocumentFragment(document: Document): DocumentFragment = return DocumentFragment( - document_internal: document, + internalDocument: document, index: -1 ) @@ -2555,7 +2576,7 @@ func newDocumentFragment(ctx: JSContext): DocumentFragment {.jsctor.} = func newComment(document: Document; data: string): Comment = return Comment( - document_internal: document, + internalDocument: document, data: data, index: -1 ) @@ -2610,10 +2631,7 @@ proc newHTMLElement*(document: Document; localName: CAtom; result = form of TAG_TEMPLATE: result = HTMLTemplateElement( - content: DocumentFragment( - document_internal: document, - host: result - ) + content: DocumentFragment(internalDocument: document, host: result) ) of TAG_UNKNOWN: result = HTMLUnknownElement() @@ -2646,7 +2664,7 @@ proc newHTMLElement*(document: Document; localName: CAtom; result.localName = localName result.namespace = namespace result.namespacePrefix = prefix - result.document_internal = document + result.internalDocument = document let localName = document.toAtom(satClassList) result.classList = DOMTokenList(element: result, localName: localName) result.index = -1 @@ -2676,7 +2694,7 @@ func newDocument(ctx: JSContext): Document {.jsctor.} = func newDocumentType*(document: Document; name, publicId, systemId: string): DocumentType = return DocumentType( - document_internal: document, + internalDocument: document, name: name, publicId: publicId, systemId: systemId, @@ -2766,13 +2784,13 @@ proc delAttr(element: Element; i: int; keep = false) = let attr = map.attrlist[j] let data = attr.data attr.ownerElement = AttrDummyElement( - document_internal: attr.ownerElement.document, + internalDocument: attr.ownerElement.document, index: -1, attrs: @[data] ) attr.dataIdx = 0 map.attrlist.del(j) # ordering does not matter - element.reflectAttrs(name, "") + element.reflectAttr(name, none(string)) element.invalidateCollections() element.invalid = true @@ -2858,9 +2876,9 @@ proc setter[T: uint32|string](this: CSSStyleDeclaration; u: T; this.element.attr(satStyle, $this.decls) proc style*(element: Element): CSSStyleDeclaration {.jsfget.} = - if element.style_cached == nil: - element.style_cached = CSSStyleDeclaration(element: element) - return element.style_cached + if element.cachedStyle == nil: + element.cachedStyle = CSSStyleDeclaration(element: element) + return element.cachedStyle # Forward declaration hack var appliesFwdDecl*: proc(mqlist: MediaQueryList; window: Window): bool @@ -3017,15 +3035,18 @@ proc reflectEvent(element: Element; target: EventTarget; name: StaticAtom; doAssert ctx.addEventListener(target, ctype, fun).isSome JS_FreeValue(ctx, fun) -proc reflectAttrs(element: Element; name: CAtom; value: string) = +proc reflectAttr(element: Element; name: CAtom; value: Option[string]) = let name = element.document.toStaticAtom(name) template reflect_str(element: Element; n: StaticAtom; val: untyped) = if name == n: - element.val = value + element.val = value.get("") return template reflect_atom(element: Element; n: StaticAtom; val: untyped) = if name == n: - element.val = element.document.toAtom(value) + if value.isSome: + element.val = element.document.toAtom(value.get) + else: + element.val = CAtomNull return template reflect_bool(element: Element; n: StaticAtom; val: untyped) = if name == n: @@ -3033,11 +3054,12 @@ proc reflectAttrs(element: Element; name: CAtom; value: string) = return template reflect_domtoklist0(element: Element; val: untyped) = element.val.toks.setLen(0) - for x in value.split(AsciiWhitespace): - if x != "": - let a = element.document.toAtom(x) - if a notin element.val: - element.val.toks.add(a) + if value.isSome: + for x in value.get.split(AsciiWhitespace): + if x != "": + let a = element.document.toAtom(x) + if a notin element.val: + element.val.toks.add(a) template reflect_domtoklist(element: Element; n: StaticAtom; val: untyped) = if name == n: element.reflect_domtoklist0 val @@ -3047,22 +3069,26 @@ proc reflectAttrs(element: Element; name: CAtom; value: string) = element.reflect_domtoklist satClass, classList #TODO internalNonce if name == satStyle: - element.style_cached = newCSSStyleDeclaration(element, value) + if value.isSome: + element.cachedStyle = newCSSStyleDeclaration(element, value.get) + else: + element.cachedStyle = nil return if name == satOnclick and element.scriptingEnabled: - element.reflectEvent(element, name, "click", value) + element.reflectEvent(element, name, "click", value.get("")) return case element.tagType of TAG_BODY: if name == satOnload and element.scriptingEnabled: - element.reflectEvent(element.document.window, name, "load", value) + element.reflectEvent(element.document.window, name, "load", value.get("")) return of TAG_INPUT: let input = HTMLInputElement(element) input.reflect_str satValue, value - input.reflect_bool satChecked, checked - if name == satType: - input.inputType = parseEnumNoCase[InputType](value).get(itText) + if name == satChecked: + input.setChecked(value.isSome) + elif name == satType: + input.inputType = parseEnumNoCase[InputType](value.get("")).get(itText) of TAG_OPTION: let option = HTMLOptionElement(element) option.reflect_bool satSelected, selected @@ -3070,7 +3096,7 @@ proc reflectAttrs(element: Element; name: CAtom; value: string) = let button = HTMLButtonElement(element) button.reflect_str satValue, value if name == satType: - button.ctype = parseEnumNoCase[ButtonType](value).get(btSubmit) + button.ctype = parseEnumNoCase[ButtonType](value.get("")).get(btSubmit) of TAG_LINK: let link = HTMLLinkElement(element) if name == satRel: @@ -3130,7 +3156,7 @@ proc attr*(element: Element; name: CAtom; value: string) = localName: name, value: value ), -(i + 1)) - element.reflectAttrs(name, value) + element.reflectAttr(name, some(value)) proc attr*(element: Element; name: StaticAtom; value: string) = element.attr(element.document.toAtom(name), value) @@ -3163,7 +3189,7 @@ proc attrns*(element: Element; localName: CAtom; prefix: NamespacePrefix; namespace: namespace, value: value ), element.attrs.upperBound(qualifiedName, cmpAttrName)) - element.reflectAttrs(qualifiedName, value) + element.reflectAttr(qualifiedName, some(value)) proc attrl(element: Element; name: StaticAtom; value: int32) = element.attr(name, $value) @@ -3319,10 +3345,10 @@ proc adopt(document: Document; node: Node) = if oldDocument != document: #TODO shadow root for desc in node.descendants: - desc.document_internal = document + desc.internalDocument = document if desc of Element: for attr in Element(desc).attributes.attrlist: - attr.document_internal = document + attr.internalDocument = document #TODO custom elements #..adopting steps @@ -3332,7 +3358,7 @@ proc resetElement*(element: Element) = let input = HTMLInputElement(element) case input.inputType of itCheckbox, itRadio: - input.checked = input.attrb(satChecked) + input.setChecked(input.attrb(satChecked)) of itFile: input.file = nil else: @@ -4031,7 +4057,7 @@ proc clone(node: Node; document = none(Document), deep = false): Node = let element = HTMLInputElement(element) x.value = element.value #TODO dirty value flag - x.checked = element.checked + x.setChecked(element.checked) #TODO dirty checkedness flag Node(x) elif node of Attr: @@ -4039,7 +4065,7 @@ proc clone(node: Node; document = none(Document), deep = false): Node = let data = attr.data let x = Attr( ownerElement: AttrDummyElement( - document_internal: attr.ownerElement.document, + internalDocument: attr.ownerElement.document, index: -1, attrs: @[data] ), diff --git a/src/server/buffer.nim b/src/server/buffer.nim index af14634a..4c01e3d9 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -821,7 +821,7 @@ proc updateHover*(buffer: Buffer; cursorx, cursory: int): UpdateHoverResult if styledNode.t == stElement and styledNode.node != nil: let elem = Element(styledNode.node) if not elem.hover: - elem.hover = true + elem.setHover(true) repaint = true for ht in HoverType: let s = HoverFun[ht](buffer, thisnode) @@ -832,7 +832,7 @@ proc updateHover*(buffer: Buffer; cursorx, cursory: int): UpdateHoverResult if styledNode.t == stElement and styledNode.node != nil: let elem = Element(styledNode.node) if elem.hover: - elem.hover = false + elem.setHover(false) repaint = true if repaint: buffer.do_reshape() @@ -1384,13 +1384,13 @@ proc submitForm(buffer: Buffer; form: HTMLFormElement; submitter: Element): Requ proc setFocus(buffer: Buffer; e: Element): bool = if buffer.document.focus != e: - buffer.document.focus = e + buffer.document.setFocus(e) buffer.do_reshape() return true proc restoreFocus(buffer: Buffer): bool = if buffer.document.focus != nil: - buffer.document.focus = nil + buffer.document.setFocus(nil) buffer.do_reshape() return true @@ -1577,15 +1577,15 @@ proc click(buffer: Buffer; input: HTMLInputElement): ClickResult = readline: some(ReadLineResult(t: rltFile)) ) of itCheckbox: - input.checked = not input.checked + input.setChecked(not input.checked) input.invalid = true buffer.do_reshape() return ClickResult(repaint: true) of itRadio: for radio in input.radiogroup: - radio.checked = false + radio.setChecked(false) radio.invalid = true - input.checked = true + input.setChecked(true) input.invalid = true buffer.do_reshape() return ClickResult(repaint: true) |