about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-11-17 18:22:37 +0100
committerbptato <nincsnevem662@gmail.com>2024-11-17 18:22:37 +0100
commit1fc73e25913ba098ab61d101857b5824baa61d10 (patch)
treea70cfd6810001d27048ee4f6bb21c5d7b8aef91c /src
parentbc472dc214eb525f811beed470b8de2cd1a520cb (diff)
downloadchawan-1fc73e25913ba098ab61d101857b5824baa61d10.tar.gz
buffer: fix hover text not showing up with pseudo elements
This was a bit annoying because it triggered the popup warning on
cursorNextLink -> click with form buttons (as the square brackets around
[button] are implemented with ::before/::after.)

Notably, this also removes the visibility check from isClickable.
I suspect there was a good reason why I added it, but I can't remember
why, and it's incompatible with what desktop browsers do on:

<a href="https://asdf.com" style="visibility: hidden">
<div style="visibility: visible">
test
</div>
</div>

So if it's ever added back, then this case should be respected too.
Diffstat (limited to 'src')
-rw-r--r--src/html/dom.nim5
-rw-r--r--src/server/buffer.nim139
2 files changed, 66 insertions, 78 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 558777c5..074e099d 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -1186,6 +1186,11 @@ iterator branch*(node: Node): Node {.inline.} =
     yield node
     node = node.parentNode
 
+iterator branchElems*(node: Node): Element {.inline.} =
+  for node in node.branch:
+    if node of Element:
+      yield Element(node)
+
 # Returns the node's descendants
 iterator descendants*(node: Node): Node {.inline.} =
   var stack: seq[Node] = @[]
diff --git a/src/server/buffer.nim b/src/server/buffer.nim
index 21b061d9..9ded8fbd 100644
--- a/src/server/buffer.nim
+++ b/src/server/buffer.nim
@@ -13,7 +13,6 @@ import chame/tags
 import config/config
 import css/box
 import css/cascade
-import css/cssvalues
 import css/layout
 import css/lunit
 import css/render
@@ -97,13 +96,13 @@ type
     needsBOMSniff: bool
     outputId: int
     pollData: PollData
+    prevHover: Element
     prevStyled: StyledNode
-    prevnode: StyledNode
-    rootBox: BlockBox
     pstream: SocketStream # control stream
     quirkstyle: CSSStylesheet
     reportedBytesRead: int
     rfd: int # file descriptor of command pipe
+    rootBox: BlockBox
     savetask: bool
     ssock: ServerSocket
     state: BufferState
@@ -140,6 +139,10 @@ type
 
   GetValueProc = proc(iface: BufferInterface; promise: EmptyPromise) {.nimcall.}
 
+# Forward declarations
+proc submitForm(buffer: Buffer; form: HTMLFormElement; submitter: Element):
+  Request
+
 proc getFromStream[T](iface: BufferInterface; promise: EmptyPromise) =
   if iface.len != 0:
     let promise = Promise[T](promise)
@@ -314,48 +317,38 @@ macro task(fun: typed) =
   pfun.istask = true
   fun
 
-func getTitleAttr(buffer: Buffer; node: StyledNode): string =
-  if node == nil:
-    return ""
-  if node.t == stElement and node.node != nil:
-    let element = Element(node.node)
-    if element.attrb(satTitle):
-      return element.attr(satTitle)
-  if node.node != nil:
-    var node = node.node
-    for element in node.ancestors:
+func getTitleAttr(buffer: Buffer; element: Element): string =
+  if element != nil:
+    for element in element.branchElems:
       if element.attrb(satTitle):
         return element.attr(satTitle)
-  #TODO pseudo-elements
   return ""
 
 const ClickableElements = {
   TAG_A, TAG_INPUT, TAG_OPTION, TAG_BUTTON, TAG_TEXTAREA, TAG_LABEL
 }
 
-proc isClickable(styledNode: StyledNode): bool =
-  if styledNode.t != stElement or styledNode.node == nil:
-    return false
-  if styledNode.computed{"visibility"} != VisibilityVisible:
-    return false
-  let element = Element(styledNode.node)
+proc isClickable(element: Element): bool =
   if element of HTMLAnchorElement:
     return HTMLAnchorElement(element).reinitURL().isSome
   if element.isButton() and FormAssociatedElement(element).form == nil:
     return false
   return element.tagType in ClickableElements
 
+proc getClickable(element: Element): Element =
+  for element in element.branchElems:
+    if element.isClickable():
+      return element
+  return nil
+
 proc getClickable(styledNode: StyledNode): Element =
   var styledNode = styledNode
   while styledNode != nil:
-    if styledNode.isClickable():
-      return Element(styledNode.node)
+    if styledNode.node of Element:
+      return Element(styledNode.node).getClickable()
     styledNode = styledNode.parent
   return nil
 
-proc submitForm(buffer: Buffer; form: HTMLFormElement; submitter: Element):
-  Request
-
 func canSubmitOnClick(fae: FormAssociatedElement): bool =
   if fae.form == nil:
     return false
@@ -368,8 +361,8 @@ func canSubmitOnClick(fae: FormAssociatedElement): bool =
     return true
   return false
 
-proc getClickHover(buffer: Buffer; styledNode: StyledNode): string =
-  let clickable = styledNode.getClickable()
+proc getClickHover(buffer: Buffer; element: Element): string =
+  let clickable = element.getClickable()
   if clickable != nil:
     if clickable of HTMLAnchorElement:
       let url = HTMLAnchorElement(clickable).reinitURL()
@@ -387,39 +380,36 @@ proc getClickHover(buffer: Buffer; styledNode: StyledNode): string =
       return "<option>"
   ""
 
-proc getImageHover(buffer: Buffer; styledNode: StyledNode): string =
-  var styledNode = styledNode
-  while styledNode != nil:
-    if styledNode.t == stElement:
-      if styledNode.node of HTMLImageElement:
-        let image = HTMLImageElement(styledNode.node)
-        let src = image.attr(satSrc)
-        if src != "":
-          let url = image.document.parseURL(src)
-          if url.isSome:
-            return $url.get
-      elif styledNode.node of HTMLVideoElement:
-        let video = HTMLVideoElement(styledNode.node)
-        let src = video.getSrc()
-        if src != "":
-          let url = video.document.parseURL(src)
-          if url.isSome:
-            return $url.get
-      elif styledNode.node of HTMLAudioElement:
-        let audio = HTMLAudioElement(styledNode.node)
-        let src = audio.getSrc()
-        if src != "":
-          let url = audio.document.parseURL(src)
-          if url.isSome:
-            return $url.get
-    styledNode = styledNode.parent
+proc getImageHover(buffer: Buffer; element: Element): string =
+  for element in element.branchElems:
+    if element of HTMLImageElement:
+      let image = HTMLImageElement(element)
+      let src = image.attr(satSrc)
+      if src != "":
+        let url = image.document.parseURL(src)
+        if url.isSome:
+          return $url.get
+    elif element of HTMLVideoElement:
+      let video = HTMLVideoElement(element)
+      let src = video.getSrc()
+      if src != "":
+        let url = video.document.parseURL(src)
+        if url.isSome:
+          return $url.get
+    elif element of HTMLAudioElement:
+      let audio = HTMLAudioElement(element)
+      let src = audio.getSrc()
+      if src != "":
+        let url = audio.document.parseURL(src)
+        if url.isSome:
+          return $url.get
   ""
 
 func getCursorStyledNode(buffer: Buffer; cursorx, cursory: int): StyledNode =
   let i = buffer.lines[cursory].findFormatN(cursorx) - 1
   if i >= 0:
     return buffer.lines[cursory].formats[i].node
-  nil
+  return nil
 
 func getCursorElement(buffer: Buffer; cursorx, cursory: int): Element =
   let styledNode = buffer.getCursorStyledNode(cursorx, cursory)
@@ -434,9 +424,9 @@ func getCursorElement(buffer: Buffer; cursorx, cursory: int): Element =
   return nil
 
 proc getCursorClickable(buffer: Buffer; cursorx, cursory: int): Element =
-  let styledNode = buffer.getCursorStyledNode(cursorx, cursory)
-  if styledNode != nil:
-    return styledNode.getClickable()
+  let element = buffer.getCursorElement(cursorx, cursory)
+  if element != nil:
+    return element.getClickable()
   return nil
 
 func cursorBytes(buffer: Buffer; y, cc: int): int =
@@ -892,35 +882,28 @@ proc updateHover*(buffer: Buffer; cursorx, cursory: int): UpdateHoverResult
     {.proxy.} =
   if cursory >= buffer.lines.len:
     return UpdateHoverResult()
-  var thisnode: StyledNode = nil
-  let i = buffer.lines[cursory].findFormatN(cursorx) - 1
-  if i >= 0:
-    thisnode = buffer.lines[cursory].formats[i].node
+  let thisNode = buffer.getCursorElement(cursorx, cursory)
   var hover: seq[tuple[t: HoverType, s: string]] = @[]
   var repaint = false
-  let prevnode = buffer.prevnode
-  if thisnode != prevnode and (thisnode == nil or prevnode == nil or
-      thisnode.node != prevnode.node):
-    for styledNode in prevnode.branch:
-      if styledNode.t == stElement and styledNode.node != nil:
-        let elem = Element(styledNode.node)
-        if elem.hover:
-          elem.setHover(false)
-          repaint = true
+  let prevNode = buffer.prevHover
+  if thisNode != prevNode and (thisNode == nil or prevNode == nil or
+      thisNode != prevNode):
+    for element in prevNode.branchElems:
+      if element.hover:
+        element.setHover(false)
+        repaint = true
     for ht in HoverType:
-      let s = HoverFun[ht](buffer, thisnode)
+      let s = HoverFun[ht](buffer, thisNode)
       if buffer.hoverText[ht] != s:
         hover.add((ht, s))
         buffer.hoverText[ht] = s
-    for styledNode in thisnode.branch:
-      if styledNode.t == stElement and styledNode.node != nil:
-        let elem = Element(styledNode.node)
-        if not elem.hover:
-          elem.setHover(true)
-          repaint = true
+    for element in thisNode.branchElems:
+      if not element.hover:
+        element.setHover(true)
+        repaint = true
   if repaint:
     buffer.reshape()
-  buffer.prevnode = thisnode
+  buffer.prevHover = thisNode
   return UpdateHoverResult(repaint: repaint, hover: hover)
 
 proc loadResources(buffer: Buffer): EmptyPromise =