about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/css/match.nim108
-rw-r--r--src/css/stylednode.nim9
2 files changed, 78 insertions, 39 deletions
diff --git a/src/css/match.nim b/src/css/match.nim
index e1b3b832..5a4f81d2 100644
--- a/src/css/match.nim
+++ b/src/css/match.nim
@@ -8,6 +8,22 @@ import html/catom
 import html/dom
 import utils/twtstr
 
+# We use three match types.
+# "mtTrue" and "mtFalse" are self-explanatory.
+# "mtContinue" is like "mtFalse", but also modifies "depends".  This
+# "depends" change is only propagated at the end if no selector before
+# the pseudo element matches the element, and the last match was
+# "mtContinue".
+#
+# Since style is only recomputed (e.g. when the hovered element changes)
+# for elements that are included in "depends", this has the effect of
+# minimizing such recomputations to cases where it's really necessary.
+type MatchType = enum
+  mtFalse, mtTrue, mtContinue
+
+converter toMatchType(b: bool): MatchType =
+  return MatchType(b)
+
 #TODO rfNone should match insensitively for certain properties
 func matchesAttr(element: Element; sel: Selector): bool =
   case sel.rel.t
@@ -69,7 +85,7 @@ func matches(element: Element; slist: SelectorList;
   return false
 
 func matches(element: Element; pseudo: PseudoData;
-    depends: var DependencyInfo): bool =
+    depends: var DependencyInfo): MatchType =
   case pseudo.t
   of pcFirstChild: return element.parentNode.firstElementChild == element
   of pcLastChild: return element.parentNode.lastElementChild == element
@@ -79,14 +95,10 @@ func matches(element: Element; pseudo: PseudoData;
     return element.parentNode.firstElementChild == element and
       element.parentNode.lastElementChild == element
   of pcHover:
-    #TODO this is somewhat problematic.
-    # e.g. if there is a rule like ".class :hover", then you set
-    # dtHover for basically every element, even if most of them are not
-    # a .class descendant.
-    # Ideally we should try to match the rest of the selector before
-    # attaching dependencies in general.
     depends.add(element, dtHover)
-    return element.hover
+    if element.hover:
+      return mtTrue
+    return mtContinue
   of pcRoot: return element == element.document.documentElement
   of pcNthChild:
     let A = pseudo.anb.A # step
@@ -111,7 +123,7 @@ func matches(element: Element; pseudo: PseudoData;
           return j >= 0 and j mod A == 0
         if child.matches(pseudo.ofsels, depends):
           inc i
-    return false
+    return mtFalse
   of pcNthLastChild:
     let A = pseudo.anb.A # step
     let B = pseudo.anb.B # start
@@ -136,20 +148,27 @@ func matches(element: Element; pseudo: PseudoData;
           return j >= 0 and j mod A == 0
         if child.matches(pseudo.ofsels, depends):
           inc i
-    return false
+    return mtFalse
   of pcChecked:
-    depends.add(element, dtChecked)
     if element.tagType == TAG_INPUT:
-      return HTMLInputElement(element).checked
+      depends.add(element, dtChecked)
+      if HTMLInputElement(element).checked:
+        return mtTrue
     elif element.tagType == TAG_OPTION:
-      return HTMLOptionElement(element).selected
-    return false
+      depends.add(element, dtChecked)
+      if HTMLOptionElement(element).selected:
+        return mtTrue
+    return mtContinue
   of pcFocus:
     depends.add(element, dtFocus)
-    return element.document.focus == element
+    if element.document.focus == element:
+      return mtTrue
+    return mtContinue
   of pcTarget:
     depends.add(element, dtTarget)
-    return element.document.target == element
+    if element.document.target == element:
+      return mtTrue
+    return mtContinue
   of pcNot:
     return not element.matches(pseudo.fsels, depends)
   of pcIs, pcWhere:
@@ -159,10 +178,10 @@ func matches(element: Element; pseudo: PseudoData;
   of pcLink:
     return element.tagType in {TAG_A, TAG_AREA} and element.attrb(satHref)
   of pcVisited:
-    return false
+    return mtFalse
 
 func matches(element: Element; sel: Selector; depends: var DependencyInfo):
-    bool =
+    MatchType =
   case sel.t
   of stType:
     return element.localName == sel.tag
@@ -170,8 +189,8 @@ func matches(element: Element; sel: Selector; depends: var DependencyInfo):
     let factory = element.document.factory
     for it in element.classList.toks:
       if sel.class == factory.toLowerAscii(it):
-        return true
-    return false
+        return mtTrue
+    return mtFalse
   of stId:
     return sel.id == element.document.factory.toLowerAscii(element.id)
   of stAttr:
@@ -179,54 +198,71 @@ func matches(element: Element; sel: Selector; depends: var DependencyInfo):
   of stPseudoClass:
     return element.matches(sel.pseudo, depends)
   of stPseudoElement:
-    return true
+    return mtTrue
   of stUniversal:
-    return true
+    return mtTrue
 
 func matches(element: Element; sels: CompoundSelector;
-    depends: var DependencyInfo): bool =
+    depends: var DependencyInfo): MatchType =
+  var res = mtTrue
   for sel in sels:
-    if not element.matches(sel, depends):
-      return false
-  return true
+    case element.matches(sel, depends)
+    of mtFalse: return mtFalse
+    of mtTrue: discard
+    of mtContinue: res = mtContinue
+  return res
 
 # Note: this modifies "depends".
 func matches*(element: Element; cxsel: ComplexSelector;
     depends: var DependencyInfo): bool =
   var e = element
+  var pmatch = mtFalse
+  var mdepends = DependencyInfo()
   for i in countdown(cxsel.high, 0):
-    var match = false
+    var match = mtFalse
     case cxsel[i].ct
     of ctNone:
-      match = e.matches(cxsel[i], depends)
+      match = e.matches(cxsel[i], mdepends)
     of ctDescendant:
       e = e.parentElement
       while e != nil:
-        if e.matches(cxsel[i], depends):
-          match = true
+        case e.matches(cxsel[i], mdepends)
+        of mtFalse: discard
+        of mtTrue:
+          match = mtTrue
           break
+        of mtContinue: match = mtContinue # keep looking
         e = e.parentElement
     of ctChild:
       e = e.parentElement
       if e != nil:
-        match = e.matches(cxsel[i], depends)
+        match = e.matches(cxsel[i], mdepends)
     of ctNextSibling:
       let prev = e.previousElementSibling
       if prev != nil:
         e = prev
-        match = e.matches(cxsel[i], depends)
+        match = e.matches(cxsel[i], mdepends)
     of ctSubsequentSibling:
       let parent = e.parentNode
       for j in countdown(e.index - 1, 0):
         let child = parent.childList[j]
         if child of Element:
           let child = Element(child)
-          if child.matches(cxsel[i], depends):
+          case child.matches(cxsel[i], mdepends)
+          of mtTrue:
             e = child
-            match = true
+            match = mtTrue
             break
-    if not match:
-      return false
+          of mtFalse: discard
+          of mtContinue: match = mtContinue # keep looking
+    if match == mtFalse:
+      return false # we can discard depends.
+    if pmatch == mtContinue and match == mtTrue or e == nil:
+      break # we must update depends.
+    pmatch = match
+  depends.merge(mdepends)
+  if pmatch == mtContinue:
+    return false
   return true
 
 # Forward declaration hack
diff --git a/src/css/stylednode.nim b/src/css/stylednode.nim
index 5e060142..46df5ff0 100644
--- a/src/css/stylednode.nim
+++ b/src/css/stylednode.nim
@@ -94,9 +94,12 @@ proc isValid*(styledNode: StyledNode; toReset: var seq[Element]): bool =
   return true
 
 proc add*(depends: var DependencyInfo; element: Element; t: DependencyType) =
-  let it = DependencyInfoItem(t: t, element: element)
-  if it notin depends.items:
-    depends.items.add(it)
+  depends.items.add(DependencyInfoItem(t: t, element: element))
+
+proc merge*(a: var DependencyInfo; b: DependencyInfo) =
+  for it in b.items:
+    if it notin a.items:
+      a.items.add(it)
 
 func newStyledElement*(parent: StyledNode; element: Element): StyledNode =
   return StyledNode(t: stElement, node: element, parent: parent)