about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-07-03 17:21:14 +0200
committerbptato <nincsnevem662@gmail.com>2024-07-03 17:21:14 +0200
commitd0c43d69f5c64d01230c79458f473eb9ecd5a7e1 (patch)
tree6409d32007555c56d93d5f95b921bfcd977fefce
parent1eb05b007a71dbd6589e722d2aeefdf6747da022 (diff)
downloadchawan-d0c43d69f5c64d01230c79458f473eb9ecd5a7e1.tar.gz
css, html: fix CSS dependency invalidation
-rw-r--r--src/css/cascade.nim9
-rw-r--r--src/css/stylednode.nim46
-rw-r--r--src/html/chadombuilder.nim2
-rw-r--r--src/html/dom.nim162
-rw-r--r--src/server/buffer.nim14
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)