about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/css/cascade.nim215
-rw-r--r--src/css/cssvalues.nim105
-rw-r--r--src/css/selectorparser.nim76
-rw-r--r--src/css/sheet.nim46
-rw-r--r--src/css/stylednode.nim8
-rw-r--r--src/html/dom.nim10
6 files changed, 258 insertions, 202 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index aaf68677..b2eef00e 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -21,23 +21,33 @@ import types/opt
 import types/winattrs
 
 type
-  RuleList* = array[PseudoElem, seq[CSSRuleDef]]
+  RuleListEntry = object
+    normal: seq[CSSComputedEntry]
+    important: seq[CSSComputedEntry]
+
+  RuleList = array[CSSOrigin, RuleListEntry]
+
+  RuleListMap = ref object
+    rules: array[PseudoElement, RuleList]
 
-  RuleListMap* = ref object
-    ua: RuleList # user agent
-    user: RuleList
-    author: seq[RuleList]
+  RulePair = tuple
+    specificity: int
+    rule: CSSRuleDef
 
-  ToSorts = array[PseudoElem, seq[(int, CSSRuleDef)]]
+  ToSorts = array[PseudoElement, seq[RulePair]]
 
 proc calcRule(tosorts: var ToSorts; element: Element;
     depends: var DependencyInfo; rule: CSSRuleDef) =
   for sel in rule.sels:
     if element.matches(sel, depends):
-      let spec = getSpecificity(sel)
-      tosorts[sel.pseudo].add((spec, rule))
+      if tosorts[sel.pseudo].len > 0 and tosorts[sel.pseudo][^1].rule == rule:
+        tosorts[sel.pseudo][^1].specificity =
+          max(tosorts[sel.pseudo][^1].specificity, sel.specificity)
+      else:
+        tosorts[sel.pseudo].add((sel.specificity, rule))
 
-func calcRules(styledNode: StyledNode; sheet: CSSStylesheet): RuleList =
+func calcRules(map: RuleListMap; styledNode: StyledNode; sheet: CSSStylesheet;
+    origin: CSSOrigin) =
   var tosorts = ToSorts.default
   let element = Element(styledNode.node)
   var rules: seq[CSSRuleDef] = @[]
@@ -54,27 +64,28 @@ func calcRules(styledNode: StyledNode; sheet: CSSStylesheet): RuleList =
       rules.add(v[])
   for rule in sheet.generalList:
     rules.add(rule)
-  rules.sort(ruleDefCmp, order = Ascending)
   for rule in rules:
     tosorts.calcRule(element, styledNode.depends, rule)
-  for i in PseudoElem:
-    tosorts[i].sort((proc(x, y: (int, CSSRuleDef)): int =
-      cmp(x[0], y[0])
-    ), order = Ascending)
-    result[i] = newSeqOfCap[CSSRuleDef](tosorts[i].len)
-    for item in tosorts[i]:
-      result[i].add(item[1])
+  for pseudo, it in tosorts.mpairs:
+    it.sort(proc(x, y: (int, CSSRuleDef)): int =
+      let n = cmp(x[0], y[0])
+      if n != 0:
+        return n
+      return cmp(x[1].idx, y[1].idx), order = Ascending)
+    for item in it:
+      map.rules[pseudo][origin].normal.add(item[1].normalVals)
+      map.rules[pseudo][origin].important.add(item[1].importantVals)
 
 proc applyPresHints(computed: CSSValues; element: Element;
     attrs: WindowAttributes; initMap: var InitMap) =
   template set_cv(t, x, b: untyped) =
-    computed.applyValue(makeEntry(t, CSSValueWord(x: b)), nil, nil, initMap,
-      itUserAgent)
+    computed.applyValue(makeEntry(t, CSSValueWord(x: b)), nil, nil, nil,
+      initMap, itUserAgent)
     initMap[t].incl(itUser)
   template set_cv_new(t, x, b: untyped) =
     const v = valueType(t)
     let val = CSSValue(v: v, x: b)
-    computed.applyValue(makeEntry(t, val), nil, nil, initMap, itUserAgent)
+    computed.applyValue(makeEntry(t, val), nil, nil, nil, initMap, itUserAgent)
     initMap[t].incl(itUser)
   template map_width =
     let s = parseDimensionValues(element.attr(satWidth))
@@ -129,7 +140,7 @@ proc applyPresHints(computed: CSSValues; element: Element;
   template set_bgcolor_is_canvas =
     let t = cptBgcolorIsCanvas
     let val = CSSValueBit(bgcolorIsCanvas: true)
-    computed.applyValue(makeEntry(t, val), nil, nil, initMap, itUserAgent)
+    computed.applyValue(makeEntry(t, val), nil, nil, nil, initMap, itUserAgent)
     initMap[t].incl(itUser)
   template map_cellspacing =
     let s = element.attrul(satCellspacing)
@@ -185,42 +196,34 @@ proc applyPresHints(computed: CSSValues; element: Element;
       set_cv cptHeight, length, resolveLength(cuEm, float32(size), attrs)
   else: discard
 
-type
-  CSSValueEntryObj = object
-    normal: seq[CSSComputedEntry]
-    important: seq[CSSComputedEntry]
-
-  CSSValueEntryMap = array[CSSOrigin, CSSValueEntryObj]
-
-# element and attrsp are nil if called for a pseudo element.
-func buildComputedValues(rules: CSSValueEntryMap; parent: CSSValues;
-    element: Element; attrsp: ptr WindowAttributes): CSSValues =
+func applyDeclarations0(rules: RuleList; parent: CSSValues; element: Element;
+    window: Window): CSSValues =
   result = CSSValues()
   var initMap = InitMap.default
   for entry in rules[coUserAgent].normal: # user agent
-    result.applyValue(entry, parent, nil, initMap, itOther)
+    result.applyValue(entry, nil, parent, nil, initMap, itOther)
     initMap[entry.t] = {itUserAgent, itUser}
   let uaProperties = result.copyProperties()
   # Presentational hints override user agent style, but respect user/author
   # style.
   if element != nil:
-    result.applyPresHints(element, attrsp[], initMap)
+    result.applyPresHints(element, window.attrsp[], initMap)
   for entry in rules[coUser].normal: # user
-    result.applyValue(entry, parent, uaProperties, initMap, itUserAgent)
+    result.applyValue(entry, nil, parent, uaProperties, initMap, itUserAgent)
     initMap[entry.t].incl(itUser)
   # save user properties so author can use them
   let userProperties = result.copyProperties()
   for entry in rules[coAuthor].normal: # author
-    result.applyValue(entry, parent, userProperties, initMap, itUser)
+    result.applyValue(entry, nil, parent, userProperties, initMap, itUser)
     initMap[entry.t].incl(itOther)
   for entry in rules[coAuthor].important: # author important
-    result.applyValue(entry, parent, userProperties, initMap, itUser)
+    result.applyValue(entry, nil, parent, userProperties, initMap, itUser)
     initMap[entry.t].incl(itOther)
   for entry in rules[coUser].important: # user important
-    result.applyValue(entry, parent, uaProperties, initMap, itUserAgent)
+    result.applyValue(entry, nil, parent, uaProperties, initMap, itUserAgent)
     initMap[entry.t].incl(itOther)
   for entry in rules[coUserAgent].important: # user agent important
-    result.applyValue(entry, parent, nil, initMap, itUserAgent)
+    result.applyValue(entry, nil, parent, nil, initMap, itUserAgent)
     initMap[entry.t].incl(itOther)
   # set defaults
   for t in CSSPropertyType:
@@ -240,53 +243,21 @@ func buildComputedValues(rules: CSSValueEntryMap; parent: CSSValues;
     result{"overflow-x"} = result{"overflow-x"}.bfcify()
     result{"overflow-y"} = result{"overflow-y"}.bfcify()
 
-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: CSSValues;
-    map: RuleListMap; window: Window) =
-  var rules: CSSValueEntryMap
-  rules[coUserAgent].add(map.ua[peNone])
-  rules[coUser].add(map.user[peNone])
-  for rule in map.author:
-    rules[coAuthor].add(rule[peNone])
-  var element: Element = nil
-  if styledNode.node != nil:
-    element = Element(styledNode.node)
-    let style = element.cachedStyle
-    if window.styling and style != nil:
-      for decl in style.decls:
-        let vals = parseComputedValues(decl.name, decl.value, window.attrsp[])
-        if decl.important:
-          rules[coAuthor].important.add(vals)
-        else:
-          rules[coAuthor].normal.add(vals)
-  styledNode.computed = rules.buildComputedValues(parent, element,
-    window.attrsp)
+    map: RuleListMap; window: Window; pseudo = peNone) =
+  let element = Element(styledNode.node)
+  styledNode.computed = map.rules[pseudo].applyDeclarations0(parent, element,
+    window)
   if element != nil and window.settings.scripting == smApp:
     element.computed = styledNode.computed
 
-func hasValues(rules: CSSValueEntryMap): bool =
-  for origin in CSSOrigin:
-    if rules[origin].normal.len > 0 or rules[origin].important.len > 0:
-      return true
+func hasValues(map: RuleListMap; pseudo: PseudoElement): bool =
+  for x in map.rules:
+    for y in x:
+      if y.normal.len > 0 or y.important.len > 0:
+        return true
   return false
 
-# Either returns a new styled node or nil.
-proc applyDeclarations(pseudo: PseudoElem; styledParent: StyledNode;
-    map: RuleListMap): StyledNode =
-  var rules: CSSValueEntryMap
-  rules[coUserAgent].add(map.ua[pseudo])
-  rules[coUser].add(map.user[pseudo])
-  for rule in map.author:
-    rules[coAuthor].add(rule[pseudo])
-  if rules.hasValues():
-    let cvals = rules.buildComputedValues(styledParent.computed, nil, nil)
-    return styledParent.newStyledElement(pseudo, cvals)
-  return nil
-
 func applyMediaQuery(ss: CSSStylesheet; window: Window): CSSStylesheet =
   if ss == nil:
     return nil
@@ -298,35 +269,32 @@ func applyMediaQuery(ss: CSSStylesheet; window: Window): CSSStylesheet =
   return res
 
 func calcRules(styledNode: StyledNode; ua, user: CSSStylesheet;
-    author: seq[CSSStylesheet]): RuleListMap =
-  let uadecls = calcRules(styledNode, ua)
-  var userdecls = RuleList.default
+    author: seq[CSSStylesheet]; window: Window): RuleListMap =
+  let map = RuleListMap()
+  map.calcRules(styledNode, ua, coUserAgent)
   if user != nil:
-    userdecls = calcRules(styledNode, user)
-  var authordecls: seq[RuleList] = @[]
+    map.calcRules(styledNode, user, coUser)
   for rule in author:
-    authordecls.add(calcRules(styledNode, rule))
-  return RuleListMap(
-    ua: uadecls,
-    user: userdecls,
-    author: authordecls
-  )
-
-proc applyStyle(parent, styledNode: StyledNode; map: RuleListMap;
-    window: Window) =
-  let parentComputed = if parent != nil:
-    parent.computed
-  else:
-    rootProperties()
-  styledNode.applyDeclarations(parentComputed, map, window)
+    map.calcRules(styledNode, rule, coAuthor)
+  if styledNode.node != nil:
+    let style = Element(styledNode.node).cachedStyle
+    if window.styling and style != nil:
+      for decl in style.decls:
+        let vals = parseComputedValues(decl.name, decl.value, window.attrsp[],
+          window.factory)
+        if decl.important:
+          map.rules[peNone][coAuthor].important.add(vals)
+        else:
+          map.rules[peNone][coAuthor].normal.add(vals)
+  return map
 
 type CascadeFrame = object
   styledParent: StyledNode
   child: Node
-  pseudo: PseudoElem
+  pseudo: PseudoElement
   cachedChild: StyledNode
   cachedChildren: seq[StyledNode]
-  parentDeclMap: RuleListMap
+  parentMap: RuleListMap
 
 proc getAuthorSheets(document: Document): seq[CSSStylesheet] =
   var author: seq[CSSStylesheet] = @[]
@@ -350,7 +318,7 @@ proc applyRulesFrameValid(frame: var CascadeFrame): StyledNode =
   return cachedChild
 
 proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet;
-    author: seq[CSSStylesheet]; declmap: var RuleListMap; window: Window):
+    author: seq[CSSStylesheet]; map: var RuleListMap; window: Window):
     StyledNode =
   var styledChild: StyledNode = nil
   let pseudo = frame.pseudo
@@ -359,13 +327,15 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet;
   if frame.pseudo != peNone:
     case pseudo
     of peBefore, peAfter:
-      let declmap = frame.parentDeclMap
-      let styledPseudo = pseudo.applyDeclarations(styledParent, declmap)
-      if styledPseudo != nil and styledPseudo.computed{"content"}.len > 0:
-        for content in styledPseudo.computed{"content"}:
-          let child = styledPseudo.newStyledReplacement(content, peNone)
-          styledPseudo.children.add(child)
-        styledParent.children.add(styledPseudo)
+      let map = frame.parentMap
+      if map.hasValues(pseudo):
+        let styledPseudo = styledParent.newStyledElement(pseudo)
+        styledPseudo.applyDeclarations(styledParent.computed, map, nil, pseudo)
+        if styledPseudo.computed{"content"}.len > 0:
+          for content in styledPseudo.computed{"content"}:
+            let child = styledPseudo.newStyledReplacement(content, peNone)
+            styledPseudo.children.add(child)
+          styledParent.children.add(styledPseudo)
     of peInputText:
       let s = HTMLInputElement(styledParent.node).inputString()
       if s.len > 0:
@@ -429,8 +399,8 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet;
         let element = Element(child)
         styledChild = styledParent.newStyledElement(element)
         styledParent.children.add(styledChild)
-        declmap = styledChild.calcRules(ua, user, author)
-        styledParent.applyStyle(styledChild, declmap, window)
+        map = styledChild.calcRules(ua, user, author, window)
+        styledChild.applyDeclarations(styledParent.computed, map, window)
       elif child of Text:
         let text = Text(child)
         styledChild = styledParent.newStyledText(text)
@@ -439,8 +409,8 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet;
       # Root element
       let element = Element(child)
       styledChild = newStyledElement(element)
-      declmap = styledChild.calcRules(ua, user, author)
-      styledParent.applyStyle(styledChild, declmap, window)
+      map = styledChild.calcRules(ua, user, author, window)
+      styledChild.applyDeclarations(rootProperties(), map, window)
   return styledChild
 
 proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
@@ -461,8 +431,8 @@ proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
   ))
 
 proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
-    styledParent: StyledNode; pseudo: PseudoElem; i: var int;
-    parentDeclMap: RuleListMap = nil) =
+    styledParent: StyledNode; pseudo: PseudoElement; i: var int;
+    parentMap: RuleListMap = nil) =
   # Can't check for cachedChildren.len here, because we assume that we only have
   # cached pseudo elems when the parent is also cached.
   if frame.cachedChild != nil:
@@ -483,25 +453,25 @@ proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
         styledParent: styledParent,
         pseudo: pseudo,
         cachedChild: cached,
-        parentDeclMap: parentDeclMap
+        parentMap: parentMap
       ))
   else:
     styledStack.add(CascadeFrame(
       styledParent: styledParent,
       pseudo: pseudo,
       cachedChild: nil,
-      parentDeclMap: parentDeclMap
+      parentMap: parentMap
     ))
 
 # Append children to styledChild.
 proc appendChildren(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
-    styledChild: StyledNode; parentDeclMap: RuleListMap) =
+    styledChild: StyledNode; parentMap: RuleListMap) =
   # i points to the child currently being inspected.
   var idx = frame.cachedChildren.len - 1
   let element = Element(styledChild.node)
   # reset invalid flag here to avoid a type conversion above
   element.invalid = false
-  styledStack.stackAppend(frame, styledChild, peAfter, idx, parentDeclMap)
+  styledStack.stackAppend(frame, styledChild, peAfter, idx, parentMap)
   case element.tagType
   of TAG_TEXTAREA:
     styledStack.stackAppend(frame, styledChild, peTextareaText, idx)
@@ -520,7 +490,7 @@ proc appendChildren(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
         styledStack.stackAppend(frame, styledChild, child, idx)
     if element.tagType == TAG_INPUT:
       styledStack.stackAppend(frame, styledChild, peInputText, idx)
-  styledStack.stackAppend(frame, styledChild, peBefore, idx, parentDeclMap)
+  styledStack.stackAppend(frame, styledChild, peBefore, idx, parentMap)
 
 # Builds a StyledNode tree, optionally based on a previously cached version.
 proc applyRules(document: Document; ua, user: CSSStylesheet;
@@ -538,7 +508,7 @@ proc applyRules(document: Document; ua, user: CSSStylesheet;
   var toReset: seq[Element] = @[]
   while styledStack.len > 0:
     var frame = styledStack.pop()
-    var declmap: RuleListMap
+    var map: RuleListMap = nil
     let styledParent = frame.styledParent
     let valid = frame.cachedChild != nil and frame.cachedChild.isValid(toReset)
     let styledChild = if valid:
@@ -547,15 +517,14 @@ proc applyRules(document: Document; ua, user: CSSStylesheet;
       # From here on, computed values of this node's children are invalid
       # because of property inheritance.
       frame.cachedChild = nil
-      frame.applyRulesFrameInvalid(ua, user, author, declmap,
-        document.window)
+      frame.applyRulesFrameInvalid(ua, user, author, map, document.window)
     if styledChild != nil:
       if styledParent == nil:
         # Root element
         root = styledChild
       if styledChild.t == stElement and styledChild.node != nil:
         # note: following resets styledChild.node's invalid flag
-        styledStack.appendChildren(frame, styledChild, declmap)
+        styledStack.appendChildren(frame, styledChild, map)
   for element in toReset:
     element.invalidDeps = {}
   return root
diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim
index fa24b39b..a2c661d6 100644
--- a/src/css/cssvalues.nim
+++ b/src/css/cssvalues.nim
@@ -7,14 +7,13 @@ import std/tables
 import css/cssparser
 import css/lunit
 import css/selectorparser
+import html/catom
 import types/bitmap
 import types/color
 import types/opt
 import types/winattrs
 import utils/twtstr
 
-export selectorparser.PseudoElem
-
 type
   CSSPropertyType* = enum
     # primitive/enum properties: stored as byte
@@ -410,13 +409,26 @@ type
     coUser
     coAuthor
 
+  CSSEntryType* = enum
+    ceBit, ceObject, ceWord, ceVar
+
   CSSComputedEntry* = object
     t*: CSSPropertyType
     global*: CSSGlobalType
-    #TODO case on t?
-    bit*: uint8
-    word*: CSSValueWord
-    obj*: CSSValue
+    case et*: CSSEntryType
+    of ceBit:
+      bit*: uint8
+    of ceWord:
+      word*: CSSValueWord
+    of ceObject:
+      obj*: CSSValue
+    of ceVar:
+      cvar*: CAtom
+
+  CSSVariable* = ref object
+    name*: CAtom
+    cvals*: seq[CSSComponentValue]
+    resolved: seq[tuple[v: CSSValueType; entry: CSSComputedEntry]]
 
 static:
   doAssert sizeof(CSSValueBit) == 1
@@ -1247,34 +1259,66 @@ func cssNumber(cval: CSSComponentValue; positive: bool): Opt[float32] =
 
 proc makeEntry*(t: CSSPropertyType; obj: CSSValue; global = cgtNone):
     CSSComputedEntry =
-  return CSSComputedEntry(t: t, obj: obj, global: global)
+  return CSSComputedEntry(et: ceObject, t: t, obj: obj, global: global)
 
 proc makeEntry*(t: CSSPropertyType; word: CSSValueWord; global = cgtNone):
     CSSComputedEntry =
-  return CSSComputedEntry(t: t, word: word, global: global)
+  return CSSComputedEntry(et: ceWord, t: t, word: word, global: global)
 
 proc makeEntry*(t: CSSPropertyType; bit: CSSValueBit; global = cgtNone):
     CSSComputedEntry =
-  return CSSComputedEntry(t: t, bit: bit.dummy, global: global)
+  return CSSComputedEntry(et: ceBit, t: t, bit: bit.dummy, global: global)
 
 proc makeEntry*(t: CSSPropertyType; global: CSSGlobalType): CSSComputedEntry =
-  return CSSComputedEntry(t: t, global: global)
+  return CSSComputedEntry(et: ceObject, t: t, global: global)
+
+proc parseVar(fun: CSSFunction; entry: var CSSComputedEntry;
+    attrs: WindowAttributes; factory: CAtomFactory): Opt[void] =
+  let i = fun.value.skipBlanks(0)
+  if i >= fun.value.len or fun.value.skipBlanks(i + 1) < fun.value.len:
+    return err()
+  let cval = fun.value[i]
+  if not (cval of CSSToken):
+    return err()
+  let tok = CSSToken(fun.value[i])
+  if tok.t != cttIdent:
+    return err()
+  entry = CSSComputedEntry(
+    et: ceVar,
+    cvar: factory.toAtom(tok.value.substr(2))
+  )
+  return ok()
 
 proc parseValue(cvals: openArray[CSSComponentValue];
-    entry: var CSSComputedEntry; attrs: WindowAttributes): Opt[void] =
+    entry: var CSSComputedEntry; attrs: WindowAttributes;
+    factory: CAtomFactory): Opt[void] =
   var i = cvals.skipBlanks(0)
   if i >= cvals.len:
     return err()
   let cval = cvals[i]
-  let t = entry.t
   inc i
+  if cval of CSSFunction:
+    let fun = CSSFunction(cval)
+    if fun.name.equalsIgnoreCase("var"):
+      if cvals.skipBlanks(i) < cvals.len:
+        return err()
+      return fun.parseVar(entry, attrs, factory)
+  let t = entry.t
   let v = valueType(t)
   template set_new(prop, val: untyped) =
-    entry.obj = CSSValue(v: v, prop: val)
+    entry = CSSComputedEntry(
+      t: entry.t,
+      et: ceObject,
+      obj: CSSValue(v: v, prop: val)
+    )
   template set_word(prop, val: untyped) =
-    entry.word = CSSValueWord(prop: val)
+    entry = CSSComputedEntry(
+      t: entry.t,
+      et: ceWord,
+      word: CSSValueWord(prop: val)
+    )
   template set_bit(prop, val: untyped) =
-    entry.bit = cast[uint8](val)
+    entry = CSSComputedEntry(t: entry.t, et: ceBit, bit: cast[uint8](val))
   case v
   of cvtDisplay: set_bit display, ?parseIdent[CSSDisplay](cval)
   of cvtWhiteSpace: set_bit whiteSpace, ?parseIdent[CSSWhiteSpace](cval)
@@ -1426,7 +1470,8 @@ const PropertyPaddingSpec = [
 ]
 
 proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
-    cvals: openArray[CSSComponentValue]; attrs: WindowAttributes): Err[void] =
+    cvals: openArray[CSSComponentValue]; attrs: WindowAttributes;
+    factory: CAtomFactory): Err[void] =
   var i = cvals.skipBlanks(0)
   if i >= cvals.len:
     return err()
@@ -1439,8 +1484,12 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
       if global != cgtNone:
         res.add(makeEntry(t, global))
       else:
-        var entry = CSSComputedEntry(t: t)
-        ?cvals.parseValue(entry, attrs)
+        let et = case t.reprType
+        of cprtBit: ceBit
+        of cprtObject: ceObject
+        of cprtWord: ceWord
+        var entry = CSSComputedEntry(t: t, et: et)
+        ?cvals.parseValue(entry, attrs, factory)
         res.add(entry)
   of cstAll:
     if global == cgtNone:
@@ -1561,9 +1610,9 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
   return ok()
 
 proc parseComputedValues*(name: string; value: seq[CSSComponentValue];
-    attrs: WindowAttributes): seq[CSSComputedEntry] =
+    attrs: WindowAttributes; factory: CAtomFactory): seq[CSSComputedEntry] =
   var res: seq[CSSComputedEntry] = @[]
-  if res.parseComputedValues(name, value, attrs).isSome:
+  if res.parseComputedValues(name, value, attrs, factory).isSome:
     return res
   return @[]
 
@@ -1591,8 +1640,13 @@ type
 
   InitMap* = array[CSSPropertyType, set[InitType]]
 
+  CSSVariableMap* = ref object
+    parent*: CSSVariableMap
+    vars*: seq[CSSVariable]
+
 proc applyValue*(vals: CSSValues; entry: CSSComputedEntry;
-    parent, previousOrigin: CSSValues; inited: InitMap; initType: InitType) =
+    varMap: CSSVariableMap; parent, previousOrigin: CSSValues; inited: InitMap;
+    initType: InitType) =
   case entry.global
   of cgtInherit:
     if parent != nil:
@@ -1609,10 +1663,11 @@ proc applyValue*(vals: CSSValues; entry: CSSComputedEntry;
     else:
       vals.initialOrInheritFrom(parent, entry.t)
   of cgtNone:
-    case entry.t.reprType
-    of cprtBit: vals.bits[entry.t].dummy = entry.bit
-    of cprtWord: vals.words[entry.t] = entry.word
-    of cprtObject: vals.objs[entry.t] = entry.obj
+    case entry.et
+    of ceBit: vals.bits[entry.t].dummy = entry.bit
+    of ceWord: vals.words[entry.t] = entry.word
+    of ceObject: vals.objs[entry.t] = entry.obj
+    of ceVar: discard #eprint "TODO"
 
 func inheritProperties*(parent: CSSValues): CSSValues =
   result = CSSValues()
diff --git a/src/css/selectorparser.nim b/src/css/selectorparser.nim
index c66a1b43..a927b9be 100644
--- a/src/css/selectorparser.nim
+++ b/src/css/selectorparser.nim
@@ -9,7 +9,7 @@ type
   SelectorType* = enum
     stType, stId, stAttr, stClass, stUniversal, stPseudoClass, stPseudoElement
 
-  PseudoElem* = enum
+  PseudoElement* = enum
     peNone = "-cha-none"
     peBefore = "before"
     peAfter = "after"
@@ -91,7 +91,7 @@ type
     of stPseudoClass:
       pseudo*: PseudoData
     of stPseudoElement:
-      elem*: PseudoElem
+      elem*: PseudoElement
 
   PseudoData* = object
     case t*: PseudoClass
@@ -108,10 +108,19 @@ type
     ct*: CombinatorType # relation to the next entry in a ComplexSelector.
     sels*: seq[Selector]
 
-  ComplexSelector* = seq[CompoundSelector]
+  ComplexSelector* = object
+    specificity*: int
+    pseudo*: PseudoElement
+    csels: seq[CompoundSelector]
 
   SelectorList* = seq[ComplexSelector]
 
+# Forward declarations
+proc parseSelectorList(cvals: seq[CSSComponentValue]; factory: CAtomFactory;
+  nested, forgiving: bool): SelectorList
+proc parseComplexSelector(state: var SelectorParser): ComplexSelector
+func `$`*(cxsel: ComplexSelector): string
+
 iterator items*(sels: CompoundSelector): Selector {.inline.} =
   for it in sels.sels:
     yield it
@@ -128,7 +137,26 @@ func len*(sels: CompoundSelector): int {.inline.} =
 proc add*(sels: var CompoundSelector; sel: Selector) {.inline.} =
   sels.sels.add(sel)
 
-func `$`*(cxsel: ComplexSelector): string
+func `[]`*(cxsel: ComplexSelector; i: int): lent CompoundSelector {.inline.} =
+  return cxsel.csels[i]
+
+func `[]`*(cxsel: ComplexSelector; i: BackwardsIndex): CompoundSelector
+    {.inline.} =
+  return cxsel.csels[i]
+
+func `[]`*(cxsel: var ComplexSelector; i: BackwardsIndex): var CompoundSelector
+    {.inline.} =
+  return cxsel.csels[i]
+
+func len*(cxsel: ComplexSelector): int {.inline.} =
+  return cxsel.csels.len
+
+func high*(cxsel: ComplexSelector): int {.inline.} =
+  return cxsel.csels.high
+
+iterator items*(cxsel: ComplexSelector): lent CompoundSelector {.inline.} =
+  for it in cxsel.csels:
+    yield it
 
 func `$`*(sel: Selector): string =
   case sel.t
@@ -212,8 +240,6 @@ func `$`*(slist: SelectorList): string =
     result &= $cxsel
     s = true
 
-func getSpecificity*(cxsel: ComplexSelector): int
-
 func getSpecificity(sel: Selector): int =
   case sel.t
   of stId:
@@ -225,7 +251,7 @@ func getSpecificity(sel: Selector): int =
     of pcIs, pcNot:
       var best = 0
       for child in sel.pseudo.fsels:
-        let s = getSpecificity(child)
+        let s = child.specificity
         if s > best:
           best = s
       result += best
@@ -233,7 +259,7 @@ func getSpecificity(sel: Selector): int =
       if sel.pseudo.ofsels.len != 0:
         var best = 0
         for child in sel.pseudo.ofsels:
-          let s = getSpecificity(child)
+          let s = child.specificity
           if s > best:
             best = s
         result += best
@@ -245,19 +271,11 @@ func getSpecificity(sel: Selector): int =
   of stUniversal:
     discard
 
-func getSpecificity*(sels: CompoundSelector): int =
+func getSpecificity(sels: CompoundSelector): int =
+  result= 0
   for sel in sels:
     result += getSpecificity(sel)
 
-func getSpecificity*(cxsel: ComplexSelector): int =
-  for sels in cxsel:
-    result += getSpecificity(sels)
-
-func pseudo*(cxsel: ComplexSelector): PseudoElem =
-  if cxsel[^1][^1].t == stPseudoElement:
-    return cxsel[^1][^1].elem
-  return peNone
-
 proc consume(state: var SelectorParser): CSSComponentValue =
   result = state.cvals[state.at]
   inc state.at
@@ -277,9 +295,6 @@ template get_tok(cval: CSSComponentValue): CSSToken =
   if not (c of CSSToken): fail
   CSSToken(c)
 
-proc parseSelectorList(cvals: seq[CSSComponentValue]; factory: CAtomFactory;
-  nested, forgiving: bool): SelectorList
-
 # Functions that may contain other selectors, functions, etc.
 proc parseRecursiveSelectorFunction(state: var SelectorParser;
     class: PseudoClass; body: seq[CSSComponentValue]; forgiving: bool):
@@ -353,7 +368,7 @@ proc parsePseudoSelector(state: var SelectorParser): Selector =
   if not state.has(): fail
   let cval = state.consume()
   if cval of CSSToken:
-    template add_pseudo_element(element: PseudoElem) =
+    template add_pseudo_element(element: PseudoElement) =
       state.skipWhitespace()
       if state.nested or state.has() and state.peek() != cttComma: fail
       return Selector(t: stPseudoElement, elem: element)
@@ -391,8 +406,6 @@ proc parsePseudoSelector(state: var SelectorParser): Selector =
     return state.parseSelectorFunction(CSSFunction(cval))
   else: fail
 
-proc parseComplexSelector(state: var SelectorParser): ComplexSelector
-
 proc parseAttributeSelector(state: var SelectorParser;
     cssblock: CSSSimpleBlock): Selector =
   if cssblock.token.t != cttLbracket: fail
@@ -438,10 +451,7 @@ proc parseAttributeSelector(state: var SelectorParser;
     t: stAttr,
     attr: state.factory.toAtom(attr.value),
     value: value.value,
-    rel: SelectorRelation(
-      t: rel,
-      flag: flag
-    )
+    rel: SelectorRelation(t: rel, flag: flag)
   )
 
 proc parseClassSelector(state: var SelectorParser): Selector =
@@ -504,10 +514,15 @@ proc parseCompoundSelector(state: var SelectorParser): CompoundSelector =
       fail
 
 proc parseComplexSelector(state: var SelectorParser): ComplexSelector =
+  result = ComplexSelector()
   while true:
     state.skipWhitespace()
     let sels = state.parseCompoundSelector()
-    result.add(sels)
+    if state.failed:
+      break
+    #TODO propagate from parser
+    result.specificity += sels.getSpecificity()
+    result.csels.add(sels)
     if sels.len == 0: fail
     if not state.has():
       break # finish
@@ -526,6 +541,9 @@ proc parseComplexSelector(state: var SelectorParser): ComplexSelector =
     else: fail
   if result.len == 0 or result[^1].ct != ctNone:
     fail
+  if result[^1][^1].t == stPseudoElement:
+    #TODO move pseudo check here?
+    result.pseudo = result[^1][^1].elem
 
 proc parseSelectorList(cvals: seq[CSSComponentValue]; factory: CAtomFactory;
     nested, forgiving: bool): SelectorList =
diff --git a/src/css/sheet.nim b/src/css/sheet.nim
index ee92198c..7d1359f9 100644
--- a/src/css/sheet.nim
+++ b/src/css/sheet.nim
@@ -1,4 +1,5 @@
 import std/options
+import std/strutils
 import std/tables
 
 import css/cssparser
@@ -6,6 +7,7 @@ import css/cssvalues
 import css/mediaquery
 import css/selectorparser
 import html/catom
+import types/opt
 import types/url
 import types/winattrs
 import utils/twtstr
@@ -15,11 +17,14 @@ type
 
   CSSRuleDef* = ref object of CSSRuleBase
     sels*: SelectorList
+    specificity*: int
     normalVals*: seq[CSSComputedEntry]
     importantVals*: seq[CSSComputedEntry]
+    normalVars*: seq[CSSVariable]
+    importantVars*: seq[CSSVariable]
     # Absolute position in the stylesheet; used for sorting rules after
     # retrieval from the cache.
-    idx: int
+    idx*: int
 
   CSSMediaQueryDef* = ref object of CSSRuleBase
     children*: CSSStylesheet
@@ -130,9 +135,6 @@ proc getSelectorIds(hashes: var SelectorHashes; sel: Selector): bool =
     return hashes.tag != CAtomNull or hashes.id != CAtomNull or
       hashes.class != CAtomNull
 
-proc ruleDefCmp*(a, b: CSSRuleDef): int =
-  cmp(a.idx, b.idx)
-
 proc add(sheet: CSSStylesheet; rule: CSSRuleDef) =
   var hashes = SelectorHashes()
   for cxsel in rule.sels:
@@ -179,23 +181,35 @@ proc add*(sheet, sheet2: CSSStylesheet) =
       sheet.attrTable[key] = value
 
 proc addRule(sheet: CSSStylesheet; rule: CSSQualifiedRule) =
-  let sels = parseSelectors(rule.prelude, sheet.factory)
+  var sels = parseSelectors(rule.prelude, sheet.factory)
   if sels.len > 0:
-    var normalVals: seq[CSSComputedEntry] = @[]
-    var importantVals: seq[CSSComputedEntry] = @[]
     let decls = rule.oblock.value.parseDeclarations()
+    var pass2 = newSeqOfCap[CSSDeclaration](decls.len)
+    let rule = CSSRuleDef(sels: move(sels), idx: sheet.len)
     for decl in decls:
-      let vals = parseComputedValues(decl.name, decl.value, sheet.attrs[])
+      if decl.name.startsWith("--"):
+        let cvar = CSSVariable(
+          # case sensitive, it seems
+          name: sheet.factory.toAtom(decl.name.substr(2)),
+          cvals: move(decl.value)
+        )
+        if decl.important:
+          rule.importantVars.add(cvar)
+        else:
+          rule.normalVars.add(cvar)
+      pass2.add(decl)
+    for decl in pass2:
       if decl.important:
-        importantVals.add(vals)
+        let olen = rule.importantVals.len
+        if rule.importantVals.parseComputedValues(decl.name, decl.value,
+            sheet.attrs[], sheet.factory).isNone:
+          rule.importantVals.setLen(olen)
       else:
-        normalVals.add(vals)
-    sheet.add(CSSRuleDef(
-      sels: sels,
-      normalVals: normalVals,
-      importantVals: importantVals,
-      idx: sheet.len
-    ))
+        let olen = rule.normalVals.len
+        if rule.normalVals.parseComputedValues(decl.name, decl.value,
+            sheet.attrs[], sheet.factory).isNone:
+          rule.normalVals.setLen(olen)
+    sheet.add(rule)
     inc sheet.len
 
 proc addAtRule(sheet: CSSStylesheet; atrule: CSSAtRule; base: URL) =
diff --git a/src/css/stylednode.nim b/src/css/stylednode.nim
index 46df5ff0..c1a4cbc8 100644
--- a/src/css/stylednode.nim
+++ b/src/css/stylednode.nim
@@ -47,7 +47,7 @@ type
   StyledNode* = ref object
     parent*: StyledNode
     node*: Node
-    pseudo*: PseudoElem
+    pseudo*: PseudoElement
     case t*: StyledType
     of stText:
       discard
@@ -108,11 +108,9 @@ func newStyledElement*(parent: StyledNode; element: Element): StyledNode =
 func newStyledElement*(element: Element): StyledNode =
   return StyledNode(t: stElement, node: element)
 
-func newStyledElement*(parent: StyledNode; pseudo: PseudoElem;
-    computed: CSSValues): StyledNode =
+func newStyledElement*(parent: StyledNode; pseudo: PseudoElement): StyledNode =
   return StyledNode(
     t: stElement,
-    computed: computed,
     pseudo: pseudo,
     parent: parent
   )
@@ -124,7 +122,7 @@ func newStyledText*(text: string): StyledNode =
   return StyledNode(t: stText, node: CharacterData(data: text))
 
 func newStyledReplacement*(parent: StyledNode; content: CSSContent;
-    pseudo: PseudoElem): StyledNode =
+    pseudo: PseudoElement): StyledNode =
   return StyledNode(
     t: stReplacement,
     parent: parent,
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 49233e87..83ad9025 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -3796,8 +3796,9 @@ proc setValue(this: CSSStyleDeclaration; i: int; cvals: seq[CSSComponentValue]):
   if i notin 0 .. this.decls.high:
     return err()
   # dummyAttrs can be safely used because the result is discarded.
-  var dummy: seq[CSSComputedEntry]
-  ?parseComputedValues(dummy, this.decls[i].name, cvals, dummyAttrs)
+  var dummy: seq[CSSComputedEntry] = @[]
+  ?dummy.parseComputedValues(this.decls[i].name, cvals, dummyAttrs,
+    this.element.document.factory)
   this.decls[i].value = cvals
   return ok()
 
@@ -3831,8 +3832,9 @@ proc setProperty(this: CSSStyleDeclaration; name, value: string):
       # not err! this does not throw.
       return ok()
   else:
-    var dummy: seq[CSSComputedEntry]
-    let val0 = parseComputedValues(dummy, name, cvals, dummyAttrs)
+    var dummy: seq[CSSComputedEntry] = @[]
+    let val0 = dummy.parseComputedValues(name, cvals, dummyAttrs,
+      this.element.document.factory)
     if val0.isNone:
       return ok()
     this.decls.add(CSSDeclaration(name: name, value: cvals))