about summary refs log tree commit diff stats
path: root/src/css
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2024-06-02 18:54:55 +0200
committerbptato <nincsnevem662@gmail.com>2024-06-02 19:17:52 +0200
commit284df8430542a49c2affb8912e3fe9a077e9634e (patch)
treee51b88a8784dde60d1bea80877a5371a39a64c90 /src/css
parente1a03329f113a30c092d4f1dcd8d99f6ed4ec335 (diff)
downloadchawan-284df8430542a49c2affb8912e3fe9a077e9634e.tar.gz
css: slightly optimize cascade
Parse rule values in sheet addRule, not during cascade.
Diffstat (limited to 'src/css')
-rw-r--r--src/css/cascade.nim150
-rw-r--r--src/css/cssparser.nim19
-rw-r--r--src/css/cssvalues.nim76
-rw-r--r--src/css/sheet.nim36
4 files changed, 145 insertions, 136 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index cd80fb96..4fb69de8 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -20,12 +20,12 @@ import types/opt
 import chame/tags
 
 type
-  DeclarationList* = array[PseudoElem, seq[CSSDeclaration]]
+  RuleList* = array[PseudoElem, seq[CSSRuleDef]]
 
-  DeclarationListMap* = ref object
-    ua: DeclarationList # user agent
-    user: DeclarationList
-    author: seq[DeclarationList]
+  RuleListMap* = ref object
+    ua: RuleList # user agent
+    user: RuleList
+    author: seq[RuleList]
 
 func appliesLR(feature: MediaFeature; window: Window;
     n: LayoutUnit): bool =
@@ -86,24 +86,24 @@ func applies*(mqlist: MediaQueryList; window: Window): bool =
 appliesFwdDecl = applies
 
 type
-  ToSorts = array[PseudoElem, seq[(int, seq[CSSDeclaration])]]
+  ToSorts = array[PseudoElem, seq[(int, CSSRuleDef)]]
 
 proc calcRule(tosorts: var ToSorts; styledNode: StyledNode; rule: CSSRuleDef) =
   for sel in rule.sels:
     if styledNode.selectorsMatch(sel):
       let spec = getSpecificity(sel)
-      tosorts[sel.pseudo].add((spec, rule.decls))
+      tosorts[sel.pseudo].add((spec, rule))
 
-func calcRules(styledNode: StyledNode; sheet: CSSStylesheet): DeclarationList =
+func calcRules(styledNode: StyledNode; sheet: CSSStylesheet): RuleList =
   var tosorts: ToSorts
   let elem = Element(styledNode.node)
   for rule in sheet.genRules(elem.localName, elem.id, elem.classList.toks):
     tosorts.calcRule(styledNode, rule)
   for i in PseudoElem:
-    tosorts[i].sort((proc(x, y: (int, seq[CSSDeclaration])): int =
+    tosorts[i].sort((proc(x, y: (int, CSSRuleDef)): int =
       cmp(x[0], y[0])
     ), order = Ascending)
-    result[i] = newSeqOfCap[CSSDeclaration](tosorts[i].len)
+    result[i] = newSeqOfCap[CSSRuleDef](tosorts[i].len)
     for item in tosorts[i]:
       result[i].add(item[1])
 
@@ -257,58 +257,126 @@ func calcPresentationalHints(element: Element): CSSComputedValues =
     map_list_type_ul
   else: discard
 
+type
+  CSSValueEntryObj = object
+    normal: seq[CSSComputedEntry]
+    important: seq[CSSComputedEntry]
+
+  CSSValueEntryMap = array[CSSOrigin, CSSValueEntryObj]
+
+func buildComputedValues(rules: CSSValueEntryMap; presHints, parent:
+    CSSComputedValues): CSSComputedValues =
+  new(result)
+  var previousOrigins: array[CSSOrigin, CSSComputedValues]
+  for entry in rules[coUserAgent].normal: # user agent
+    result.applyValue(entry, parent, nil)
+  previousOrigins[coUserAgent] = result.copyProperties()
+  # Presentational hints override user agent style, but respect user/author
+  # style.
+  if presHints != nil:
+    for prop in CSSPropertyType:
+      if presHints[prop] != nil:
+        result[prop] = presHints[prop]
+  for entry in rules[coUser].normal: # user
+    result.applyValue(entry, parent, previousOrigins[coUserAgent])
+  # save user origins so author can use them
+  previousOrigins[coUser] = result.copyProperties()
+  for entry in rules[coAuthor].normal: # author
+    result.applyValue(entry, parent, previousOrigins[coUser])
+  # no need to save user origins
+  for entry in rules[coAuthor].important: # author important
+    result.applyValue(entry, parent, previousOrigins[coUser])
+  # important, so no need to save origins
+  for entry in rules[coUser].important: # user important
+    result.applyValue(entry, parent, previousOrigins[coUserAgent])
+  # important, so no need to save origins
+  for entry in rules[coUserAgent].important: # user agent important
+    result.applyValue(entry, parent, nil)
+  # important, so no need to save origins
+  # set defaults
+  for prop in CSSPropertyType:
+    if result[prop] == nil:
+      if prop.inherited and parent != nil and parent[prop] != nil:
+        result[prop] = parent[prop]
+      else:
+        result[prop] = getDefault(prop)
+  if result{"float"} != FloatNone:
+    #TODO it may be better to handle this in layout
+    let display = result{"display"}.blockify()
+    if display != result{"display"}:
+      result{"display"} = display
+
+proc add(map: var CSSValueEntryObj; rules: seq[CSSRuleDef]) =
+  for rule in rules:
+    map.normal.add(rule.normalVals)
+    map.important.add(rule.importantVals)
+
 proc applyDeclarations(styledNode: StyledNode; parent: CSSComputedValues;
-    map: DeclarationListMap) =
-  let pseudo = peNone
-  var builder = CSSComputedValuesBuilder(parent: parent)
-  builder.addValues(map.ua[pseudo], coUserAgent)
-  builder.addValues(map.user[pseudo], coUser)
+    map: RuleListMap) =
+  var rules: CSSValueEntryMap
+  var presHints: CSSComputedValues = nil
+  rules[coUserAgent].add(map.ua[peNone])
+  rules[coUser].add(map.user[peNone])
   for rule in map.author:
-    builder.addValues(rule[pseudo], coAuthor)
+    rules[coAuthor].add(rule[peNone])
   if styledNode.node != nil:
     let element = Element(styledNode.node)
     let style = element.style_cached
     if style != nil:
-      builder.addValues(style.decls, coAuthor)
-    builder.preshints = element.calcPresentationalHints()
-  styledNode.computed = builder.buildComputedValues()
+      for decl in style.decls:
+        let vals = parseComputedValues(decl.name, decl.value)
+        if decl.important:
+          rules[coAuthor].important.add(vals)
+        else:
+          rules[coAuthor].normal.add(vals)
+    presHints = element.calcPresentationalHints()
+  styledNode.computed = rules.buildComputedValues(presHints, parent)
+
+func hasValues(rules: CSSValueEntryMap): bool =
+  for origin in CSSOrigin:
+    if rules[origin].normal.len > 0 or rules[origin].important.len > 0:
+      return true
+  return false
 
 # Either returns a new styled node or nil.
 proc applyDeclarations(pseudo: PseudoElem; styledParent: StyledNode;
-    map: DeclarationListMap): StyledNode =
-  var builder = CSSComputedValuesBuilder(parent: styledParent.computed)
-  builder.addValues(map.ua[pseudo], coUserAgent)
-  builder.addValues(map.user[pseudo], coUser)
+    map: RuleListMap): StyledNode =
+  var rules: CSSValueEntryMap
+  rules[coUserAgent].add(map.ua[pseudo])
+  rules[coUser].add(map.user[pseudo])
   for rule in map.author:
-    builder.addValues(rule[pseudo], coAuthor)
-  if builder.hasValues():
-    let cvals = builder.buildComputedValues()
-    result = styledParent.newStyledElement(pseudo, cvals)
+    rules[coAuthor].add(rule[pseudo])
+  if rules.hasValues():
+    let cvals = rules.buildComputedValues(nil, styledParent.computed)
+    return styledParent.newStyledElement(pseudo, cvals)
+  return nil
 
 func applyMediaQuery(ss: CSSStylesheet; window: Window): CSSStylesheet =
-  if ss == nil: return nil
-  new(result)
-  result[] = ss[]
+  if ss == nil:
+    return nil
+  var res = CSSStylesheet()
+  res[] = ss[]
   for mq in ss.mqList:
     if mq.query.applies(window):
-      result.add(mq.children.applyMediaQuery(window))
+      res.add(mq.children.applyMediaQuery(window))
+  return res
 
 func calcRules(styledNode: StyledNode; ua, user: CSSStylesheet;
-    author: seq[CSSStylesheet]): DeclarationListMap =
+    author: seq[CSSStylesheet]): RuleListMap =
   let uadecls = calcRules(styledNode, ua)
-  var userdecls: DeclarationList
+  var userdecls: RuleList
   if user != nil:
     userdecls = calcRules(styledNode, user)
-  var authordecls: seq[DeclarationList]
+  var authordecls: seq[RuleList]
   for rule in author:
     authordecls.add(calcRules(styledNode, rule))
-  return DeclarationListMap(
+  return RuleListMap(
     ua: uadecls,
     user: userdecls,
     author: authordecls
   )
 
-proc applyStyle(parent, styledNode: StyledNode; map: DeclarationListMap) =
+proc applyStyle(parent, styledNode: StyledNode; map: RuleListMap) =
   let parentComputed = if parent != nil:
     parent.computed
   else:
@@ -320,7 +388,7 @@ type CascadeFrame = object
   child: Node
   pseudo: PseudoElem
   cachedChild: StyledNode
-  parentDeclMap: DeclarationListMap
+  parentDeclMap: RuleListMap
 
 proc getAuthorSheets(document: Document): seq[CSSStylesheet] =
   var author: seq[CSSStylesheet]
@@ -350,7 +418,7 @@ proc applyRulesFrameValid(frame: CascadeFrame): StyledNode =
   return styledChild
 
 proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet;
-    author: seq[CSSStylesheet]; declmap: var DeclarationListMap): StyledNode =
+    author: seq[CSSStylesheet]; declmap: var RuleListMap): StyledNode =
   var styledChild: StyledNode
   let pseudo = frame.pseudo
   let styledParent = frame.styledParent
@@ -460,7 +528,7 @@ proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
 
 proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
     styledParent: StyledNode; pseudo: PseudoElem; i: var int;
-    parentDeclMap: DeclarationListMap = nil) =
+    parentDeclMap: RuleListMap = nil) =
   if frame.cachedChild != nil:
     var cached: StyledNode
     let oldi = i
@@ -494,7 +562,7 @@ proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
 
 # Append children to styledChild.
 proc appendChildren(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
-    styledChild: StyledNode; parentDeclMap: DeclarationListMap) =
+    styledChild: StyledNode; parentDeclMap: RuleListMap) =
   # i points to the child currently being inspected.
   var idx = if frame.cachedChild != nil:
     frame.cachedChild.children.len - 1
@@ -532,7 +600,7 @@ proc applyRules(document: Document; ua, user: CSSStylesheet;
   var root: StyledNode
   while styledStack.len > 0:
     var frame = styledStack.pop()
-    var declmap: DeclarationListMap
+    var declmap: RuleListMap
     let styledParent = frame.styledParent
     let valid = frame.cachedChild != nil and frame.cachedChild.isValid()
     let styledChild = if valid:
diff --git a/src/css/cssparser.nim b/src/css/cssparser.nim
index 121175c9..0d71e6d0 100644
--- a/src/css/cssparser.nim
+++ b/src/css/cssparser.nim
@@ -253,9 +253,8 @@ proc consumeEscape(state: var CSSTokenizerState): string =
     return $c #NOTE this assumes the caller doesn't care about non-ascii
 
 proc consumeString(state: var CSSTokenizerState): CSSToken =
-  var s: string
+  var s = ""
   let ending = state.curr
-
   while state.has():
     let c = state.consume()
     case c
@@ -276,32 +275,31 @@ proc consumeString(state: var CSSTokenizerState): CSSToken =
   return CSSToken(tokenType: cttString, value: s)
 
 proc consumeIdentSequence(state: var CSSTokenizerState): string =
+  var s = ""
   while state.has():
     let c = state.consume()
     if state.isValidEscape():
-      result &= state.consumeEscape()
+      s &= state.consumeEscape()
     elif c in Ident:
-      result &= c
+      s &= c
     else:
       state.reconsume()
-      return result
+      return s
+  return s
 
 proc consumeNumber(state: var CSSTokenizerState): (tflagb, float64) =
   var t = tflagbInteger
-  var repr: string
+  var repr = ""
   if state.has() and state.peek() in {'+', '-'}:
     repr &= state.consume()
-
   while state.has() and state.peek() in AsciiDigit:
     repr &= state.consume()
-
   if state.has(1) and state.peek() == '.' and state.peek(1) in AsciiDigit:
     repr &= state.consume()
     repr &= state.consume()
     t = tflagbNumber
     while state.has() and state.peek() in AsciiDigit:
       repr &= state.consume()
-
   if state.has(1) and state.peek() in {'E', 'e'} and
         state.peek(1) in AsciiDigit or
       state.has(2) and state.peek() in {'E', 'e'} and
@@ -315,8 +313,7 @@ proc consumeNumber(state: var CSSTokenizerState): (tflagb, float64) =
     t = tflagbNumber
     while state.has() and state.peek() in AsciiDigit:
       repr &= state.consume()
-
-  let val = parseFloat64($repr)
+  let val = parseFloat64(repr)
   return (t, val)
 
 proc consumeNumericToken(state: var CSSTokenizerState): CSSToken =
diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim
index 010634be..e3a09a39 100644
--- a/src/css/cssvalues.nim
+++ b/src/css/cssvalues.nim
@@ -404,14 +404,6 @@ type
     val: CSSComputedValue
     global: CSSGlobalType
 
-  CSSComputedEntries = seq[CSSComputedEntry]
-
-  CSSComputedValuesBuilder* = object
-    parent*: CSSComputedValues
-    normalProperties: array[CSSOrigin, CSSComputedEntries]
-    importantProperties: array[CSSOrigin, CSSComputedEntries]
-    preshints*: CSSComputedValues
-
 const ShorthandNames = block:
   var tab = initTable[string, CSSShorthandType]()
   for t in CSSShorthandType:
@@ -575,7 +567,7 @@ macro `{}=`*(vals: CSSComputedValues; s: static string, val: typed) =
       `vs`: `val`
     )
 
-func inherited(t: CSSPropertyType): bool =
+func inherited*(t: CSSPropertyType): bool =
   return t in InheritedProperties
 
 func em_to_px(em: float64; window: WindowAttributes): LayoutUnit =
@@ -1350,7 +1342,7 @@ func getInitialTable(): array[CSSPropertyType, CSSComputedValue] =
 
 let defaultTable = getInitialTable()
 
-template getDefault(t: CSSPropertyType): CSSComputedValue =
+template getDefault*(t: CSSPropertyType): CSSComputedValue =
   {.cast(noSideEffect).}:
     defaultTable[t]
 
@@ -1538,17 +1530,8 @@ proc parseComputedValues*(name: string; value: seq[CSSComponentValue]):
     return res
   return @[]
 
-proc addValues*(builder: var CSSComputedValuesBuilder;
-    decls: seq[CSSDeclaration]; origin: CSSOrigin) =
-  for decl in decls:
-    let vals = parseComputedValues(decl.name, decl.value)
-    if decl.important:
-      builder.importantProperties[origin].add(vals)
-    else:
-      builder.normalProperties[origin].add(vals)
-
-proc applyValue(vals: CSSComputedValues; entry: CSSComputedEntry;
-    parent: CSSComputedValues; previousOrigin: CSSComputedValues) =
+proc applyValue*(vals: CSSComputedValues; entry: CSSComputedEntry;
+    parent, previousOrigin: CSSComputedValues) =
   let parentVal = if parent != nil:
     parent[entry.t]
   else:
@@ -1621,54 +1604,3 @@ func splitTable*(computed: CSSComputedValues):
       innerComputed[prop] = computed[prop]
   outerComputed{"display"} = computed{"display"}
   return (outerComputed, innerComputed)
-
-func hasValues*(builder: CSSComputedValuesBuilder): bool =
-  for origin in CSSOrigin:
-    if builder.normalProperties[origin].len > 0:
-      return true
-    if builder.importantProperties[origin].len > 0:
-      return true
-  return false
-
-func buildComputedValues*(builder: CSSComputedValuesBuilder):
-    CSSComputedValues =
-  new(result)
-  var previousOrigins: array[CSSOrigin, CSSComputedValues]
-  for entry in builder.normalProperties[coUserAgent]: # user agent
-    result.applyValue(entry, builder.parent, nil)
-  previousOrigins[coUserAgent] = result.copyProperties()
-  # Presentational hints override user agent style, but respect user/author
-  # style.
-  if builder.preshints != nil:
-    for prop in CSSPropertyType:
-      if builder.preshints[prop] != nil:
-        result[prop] = builder.preshints[prop]
-  for entry in builder.normalProperties[coUser]: # user
-    result.applyValue(entry, builder.parent, previousOrigins[coUserAgent])
-  # save user origins so author can use them
-  previousOrigins[coUser] = result.copyProperties()
-  for entry in builder.normalProperties[coAuthor]: # author
-    result.applyValue(entry, builder.parent, previousOrigins[coUser])
-  # no need to save user origins
-  for entry in builder.importantProperties[coAuthor]: # author important
-    result.applyValue(entry, builder.parent, previousOrigins[coUser])
-  # important, so no need to save origins
-  for entry in builder.importantProperties[coUser]: # user important
-    result.applyValue(entry, builder.parent, previousOrigins[coUserAgent])
-  # important, so no need to save origins
-  for entry in builder.importantProperties[coUserAgent]: # user agent important
-    result.applyValue(entry, builder.parent, nil)
-  # important, so no need to save origins
-  # set defaults
-  for prop in CSSPropertyType:
-    if result[prop] == nil:
-      if inherited(prop) and builder.parent != nil and
-          builder.parent[prop] != nil:
-        result[prop] = builder.parent[prop]
-      else:
-        result[prop] = getDefault(prop)
-  if result{"float"} != FloatNone:
-    #TODO it may be better to handle this in layout
-    let display = result{"display"}.blockify()
-    if display != result{"display"}:
-      result{"display"} = display
diff --git a/src/css/sheet.nim b/src/css/sheet.nim
index 11c5cb36..eb11854e 100644
--- a/src/css/sheet.nim
+++ b/src/css/sheet.nim
@@ -2,6 +2,7 @@ import std/algorithm
 import std/tables
 
 import css/cssparser
+import css/cssvalues
 import css/mediaquery
 import css/selectorparser
 import html/catom
@@ -11,7 +12,8 @@ type
 
   CSSRuleDef* = ref object of CSSRuleBase
     sels*: SelectorList
-    decls*: seq[CSSDeclaration]
+    normalVals*: seq[CSSComputedEntry]
+    importantVals*: seq[CSSComputedEntry]
     # Absolute position in the stylesheet; used for sorting rules after
     # retrieval from the cache.
     idx: int
@@ -137,7 +139,7 @@ iterator genRules*(sheet: CSSStylesheet; tag, id: CAtom; classes: seq[CAtom]):
   for rule in rules:
     yield rule
 
-proc add(sheet: var CSSStylesheet; rule: CSSRuleDef) =
+proc add(sheet: CSSStylesheet; rule: CSSRuleDef) =
   var hashes: SelectorHashes
   for cxsel in rule.sels:
     hashes.getSelectorIds(cxsel)
@@ -159,7 +161,7 @@ proc add(sheet: var CSSStylesheet; rule: CSSRuleDef) =
     else:
       sheet.generalList.add(rule)
 
-proc add*(sheet: var CSSStylesheet; sheet2: CSSStylesheet) =
+proc add*(sheet, sheet2: CSSStylesheet) =
   sheet.generalList.add(sheet2.generalList)
   for key, value in sheet2.tagTable.pairs:
     sheet.tagTable.withValue(key, p):
@@ -177,18 +179,27 @@ proc add*(sheet: var CSSStylesheet; sheet2: CSSStylesheet) =
     do:
       sheet.classTable[key] = value
 
-proc addRule(stylesheet: var CSSStylesheet; rule: CSSQualifiedRule) =
+proc addRule(stylesheet: CSSStylesheet; rule: CSSQualifiedRule) =
   let sels = parseSelectors(rule.prelude, stylesheet.factory)
   if sels.len > 0:
-    let r = CSSRuleDef(
+    var normalVals: seq[CSSComputedEntry] = @[]
+    var importantVals: seq[CSSComputedEntry] = @[]
+    let decls = rule.oblock.value.parseDeclarations2()
+    for decl in decls:
+      let vals = parseComputedValues(decl.name, decl.value)
+      if decl.important:
+        importantVals.add(vals)
+      else:
+        normalVals.add(vals)
+    stylesheet.add(CSSRuleDef(
       sels: sels,
-      decls: rule.oblock.value.parseDeclarations2(),
+      normalVals: normalVals,
+      importantVals: importantVals,
       idx: stylesheet.len
-    )
-    stylesheet.add(r)
+    ))
     inc stylesheet.len
 
-proc addAtRule(stylesheet: var CSSStylesheet; atrule: CSSAtRule) =
+proc addAtRule(stylesheet: CSSStylesheet; atrule: CSSAtRule) =
   case atrule.name
   of "media":
     if atrule.oblock == nil:
@@ -212,9 +223,10 @@ proc addAtRule(stylesheet: var CSSStylesheet; atrule: CSSAtRule) =
 
 proc parseStylesheet*(ibuf: string; factory: CAtomFactory): CSSStylesheet =
   let raw = parseStylesheet(ibuf)
-  result = newStylesheet(raw.value.len, factory)
+  let sheet = newStylesheet(raw.value.len, factory)
   for v in raw.value:
     if v of CSSAtRule:
-      result.addAtRule(CSSAtRule(v))
+      sheet.addAtRule(CSSAtRule(v))
     else:
-      result.addRule(CSSQualifiedRule(v))
+      sheet.addRule(CSSQualifiedRule(v))
+  return sheet