about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/css/cascade.nim45
-rw-r--r--src/css/cssvalues.nim156
-rw-r--r--src/css/layout.nim167
-rw-r--r--src/css/mediaquery.nim22
-rw-r--r--src/css/render.nim4
-rw-r--r--src/css/sheet.nim42
-rw-r--r--src/html/dom.nim21
-rw-r--r--src/server/buffer.nim8
8 files changed, 237 insertions, 228 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index c2ac7f9a..9a49cf48 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -17,6 +17,7 @@ import html/enums
 import types/color
 import types/jscolor
 import types/opt
+import types/winattrs
 
 type
   RuleList* = array[PseudoElem, seq[CSSRuleDef]]
@@ -26,9 +27,9 @@ type
     user: RuleList
     author: seq[RuleList]
 
-func appliesLR(feature: MediaFeature; window: Window; n: LayoutUnit): bool =
-  let a = feature.lengthrange.s.a.px(window.attrs, 0)
-  let b = feature.lengthrange.s.b.px(window.attrs, 0)
+func appliesLR(feature: MediaFeature; window: Window; n: float64): bool =
+  let a = feature.lengthrange.s.a.num
+  let b = feature.lengthrange.s.b.num
   return (feature.lengthrange.aeq and a == n or a < n) and
     (feature.lengthrange.beq and b == n or n < b)
 
@@ -43,9 +44,9 @@ func applies(feature: MediaFeature; window: Window): bool =
   of mftPrefersColorScheme:
     return feature.b == window.attrs.prefersDark
   of mftWidth:
-    return feature.appliesLR(window, window.attrs.widthPx.toLayoutUnit)
+    return feature.appliesLR(window, float64(window.attrs.widthPx))
   of mftHeight:
-    return feature.appliesLR(window, window.attrs.heightPx.toLayoutUnit)
+    return feature.appliesLR(window, float64(window.attrs.heightPx))
   of mftScripting:
     return feature.b == window.settings.scripting
 
@@ -111,7 +112,8 @@ func calcRules(styledNode: StyledNode; sheet: CSSStylesheet): RuleList =
     for item in tosorts[i]:
       result[i].add(item[1])
 
-func calcPresHints(element: Element): seq[CSSComputedEntry] =
+func calcPresHints(element: Element; attrs: WindowAttributes):
+    seq[CSSComputedEntry] =
   result = @[]
   template set_cv(t, x, b: untyped) =
     const v = valueType(t)
@@ -141,7 +143,7 @@ func calcPresHints(element: Element): seq[CSSComputedEntry] =
   template map_size =
     let s = element.attrul(satSize)
     if s.isSome:
-      set_cv cptWidth, length, CSSLength(num: float64(s.get), u: cuCh)
+      set_cv cptWidth, length, resolveLength(cuCh, float64(s.get), attrs)
   template map_text =
     let s = element.attr(satText)
     if s != "":
@@ -173,9 +175,8 @@ func calcPresHints(element: Element): seq[CSSComputedEntry] =
   template map_cellspacing =
     let s = element.attrul(satCellspacing)
     if s.isSome:
-      set_cv cptBorderSpacing, length2, CSSLength2(
-        a: CSSLength(num: float64(s.get), u: cuPx)
-      )
+      let n = float64(s.get)
+      set_cv cptBorderSpacing, length2, CSSLength2(a: cssLength(n))
 
   case element.tagType
   of TAG_TABLE:
@@ -210,8 +211,8 @@ func calcPresHints(element: Element): seq[CSSComputedEntry] =
     let textarea = HTMLTextAreaElement(element)
     let cols = textarea.attrul(satCols).get(20)
     let rows = textarea.attrul(satRows).get(1)
-    set_cv cptWidth, length, CSSLength(u: cuCh, num: float64(cols))
-    set_cv cptHeight, length, CSSLength(u: cuEm, num: float64(rows))
+    set_cv cptWidth, length, resolveLength(cuCh, float64(cols), attrs)
+    set_cv cptHeight, length, resolveLength(cuEm, float64(rows), attrs)
   of TAG_FONT:
     map_color
   of TAG_INPUT:
@@ -280,7 +281,7 @@ proc add(map: var CSSValueEntryObj; rules: seq[CSSRuleDef]) =
     map.important.add(rule.importantVals)
 
 proc applyDeclarations(styledNode: StyledNode; parent: CSSValues;
-    map: RuleListMap; styling: bool) =
+    map: RuleListMap; window: Window) =
   var rules: CSSValueEntryMap
   var presHints: seq[CSSComputedEntry] = @[]
   rules[coUserAgent].add(map.ua[peNone])
@@ -290,14 +291,14 @@ proc applyDeclarations(styledNode: StyledNode; parent: CSSValues;
   if styledNode.node != nil:
     let element = Element(styledNode.node)
     let style = element.cachedStyle
-    if styling and style != nil:
+    if window.styling and style != nil:
       for decl in style.decls:
-        let vals = parseComputedValues(decl.name, decl.value)
+        let vals = parseComputedValues(decl.name, decl.value, window.attrs)
         if decl.important:
           rules[coAuthor].important.add(vals)
         else:
           rules[coAuthor].normal.add(vals)
-    presHints = element.calcPresHints()
+    presHints = element.calcPresHints(window.attrs)
   styledNode.computed = rules.buildComputedValues(presHints, parent)
 
 func hasValues(rules: CSSValueEntryMap): bool =
@@ -345,12 +346,12 @@ func calcRules(styledNode: StyledNode; ua, user: CSSStylesheet;
   )
 
 proc applyStyle(parent, styledNode: StyledNode; map: RuleListMap;
-    styling: bool) =
+    window: Window) =
   let parentComputed = if parent != nil:
     parent.computed
   else:
     rootProperties()
-  styledNode.applyDeclarations(parentComputed, map, styling)
+  styledNode.applyDeclarations(parentComputed, map, window)
 
 type CascadeFrame = object
   styledParent: StyledNode
@@ -382,7 +383,7 @@ proc applyRulesFrameValid(frame: var CascadeFrame): StyledNode =
   return cachedChild
 
 proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet;
-    author: seq[CSSStylesheet]; declmap: var RuleListMap; styling: bool):
+    author: seq[CSSStylesheet]; declmap: var RuleListMap; window: Window):
     StyledNode =
   var styledChild: StyledNode = nil
   let pseudo = frame.pseudo
@@ -451,7 +452,7 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet;
         styledChild = styledParent.newStyledElement(element)
         styledParent.children.add(styledChild)
         declmap = styledChild.calcRules(ua, user, author)
-        styledParent.applyStyle(styledChild, declmap, styling)
+        styledParent.applyStyle(styledChild, declmap, window)
       elif child of Text:
         let text = Text(child)
         styledChild = styledParent.newStyledText(text)
@@ -461,7 +462,7 @@ proc applyRulesFrameInvalid(frame: CascadeFrame; ua, user: CSSStylesheet;
       let element = Element(child)
       styledChild = newStyledElement(element)
       declmap = styledChild.calcRules(ua, user, author)
-      styledParent.applyStyle(styledChild, declmap, styling)
+      styledParent.applyStyle(styledChild, declmap, window)
   return styledChild
 
 proc stackAppend(styledStack: var seq[CascadeFrame]; frame: CascadeFrame;
@@ -566,7 +567,7 @@ proc applyRules(document: Document; ua, user: CSSStylesheet;
       # because of property inheritance.
       frame.cachedChild = nil
       frame.applyRulesFrameInvalid(ua, user, author, declmap,
-        document.window.styling)
+        document.window)
     if styledChild != nil:
       if styledParent == nil:
         # Root element
diff --git a/src/css/cssvalues.nim b/src/css/cssvalues.nim
index 581c008a..d22fd414 100644
--- a/src/css/cssvalues.nim
+++ b/src/css/cssvalues.nim
@@ -311,14 +311,19 @@ type
     OverflowAuto = "auto"
 
 type
+  CSSLengthType* = enum
+    clPx = "px"
+    clAuto = "auto"
+    clPerc = "%"
+
   CSSLength* = object
-    u*: CSSUnit
+    u*: CSSLengthType
     num*: float64
 
   CSSVerticalAlign* = object
     keyword*: CSSVerticalAlign2
     # inlined CSSLength so that this object fits into 2 words
-    u*: CSSUnit
+    u*: CSSLengthType
     num*: float64
 
   CSSContent* = object
@@ -489,7 +494,7 @@ func isSupportedProperty*(s: string): bool =
 
 when defined(debug):
   func `$`*(length: CSSLength): string =
-    if length.u == cuAuto:
+    if length.u == clAuto:
       return "auto"
     return $length.num & $length.u
 
@@ -553,41 +558,6 @@ macro `{}=`*(vals: CSSValues; s: static string, val: typed) =
 func inherited*(t: CSSPropertyType): bool =
   return t in InheritedProperties
 
-func em_to_px(em: float64; window: WindowAttributes): LayoutUnit =
-  (em * float64(window.ppl)).toLayoutUnit()
-
-func ch_to_px(ch: float64; window: WindowAttributes): LayoutUnit =
-  (ch * float64(window.ppc)).toLayoutUnit()
-
-# 水 width, we assume it's 2 chars
-func ic_to_px(ic: float64; window: WindowAttributes): LayoutUnit =
-  (ic * float64(window.ppc) * 2).toLayoutUnit()
-
-# x-letter height, we assume it's em/2
-func ex_to_px(ex: float64; window: WindowAttributes): LayoutUnit =
-  (ex * float64(window.ppc) / 2).toLayoutUnit()
-
-func px*(l: CSSLength; window: WindowAttributes; p: LayoutUnit): LayoutUnit =
-  return case l.u
-  of cuAuto: LayoutUnit(0)
-  of cuEm, cuRem: em_to_px(l.num, window)
-  of cuCh: ch_to_px(l.num, window)
-  of cuIc: ic_to_px(l.num, window)
-  of cuEx: ex_to_px(l.num, window)
-  of cuPerc: toLayoutUnit(toFloat64(p) * l.num / 100)
-  of cuPx: toLayoutUnit(l.num)
-  of cuCm: toLayoutUnit(l.num * 37.8)
-  of cuMm: toLayoutUnit(l.num * 3.78)
-  of cuIn: toLayoutUnit(l.num * 96)
-  of cuPc: toLayoutUnit(l.num * 16)
-  of cuPt: toLayoutUnit(l.num * 4 / 3)
-  of cuVw: toLayoutUnit(float64(window.widthPx) * l.num / 100)
-  of cuVh: toLayoutUnit(float64(window.heightPx) * l.num / 100)
-  of cuVmin:
-    toLayoutUnit(min(window.widthPx, window.heightPx) / 100 * l.num)
-  of cuVmax:
-    toLayoutUnit(max(window.widthPx, window.heightPx) / 100 * l.num)
-
 func blockify*(display: CSSDisplay): CSSDisplay =
   case display
   of DisplayBlock, DisplayTable, DisplayListItem, DisplayNone, DisplayFlowRoot,
@@ -775,11 +745,35 @@ func parseIdent[T: enum](cval: CSSComponentValue): Opt[T] =
     return ok(T(i))
   return err()
 
-func cssLength(val: float64; u: string): Opt[CSSLength] =
+template cssLength*(n: float64): CSSLength =
+  CSSLength(u: clPx, num: n)
+
+func resolveLength*(u: CSSUnit; val: float64; attrs: WindowAttributes):
+    CSSLength =
+  return case u
+  of cuAuto: CSSLength(u: clAuto)
+  of cuEm, cuRem: cssLength(val * float64(attrs.ppl))
+  of cuCh: cssLength(val * float64(attrs.ppc))
+  of cuIc: cssLength(val * float64(attrs.ppc) * 2)
+  of cuEx: cssLength(val * float64(attrs.ppc) / 2)
+  of cuPerc: CSSLength(u: clPerc, num: val)
+  of cuPx: cssLength(val)
+  of cuCm: cssLength(val * 37.8)
+  of cuMm: cssLength(val * 3.78)
+  of cuIn: cssLength(val * 96)
+  of cuPc: cssLength(val * 16)
+  of cuPt: cssLength(val * 4 / 3)
+  of cuVw: cssLength(float64(attrs.widthPx) * val / 100)
+  of cuVh: cssLength(float64(attrs.heightPx) * val / 100)
+  of cuVmin: cssLength(min(attrs.widthPx, attrs.heightPx) / 100 * val)
+  of cuVmax: cssLength(max(attrs.widthPx, attrs.heightPx) / 100 * val)
+
+func parseLength(val: float64; u: string; attrs: WindowAttributes):
+    Opt[CSSLength] =
   let u = ?parseEnumNoCase[CSSUnit](u)
-  return ok(CSSLength(num: val, u: u))
+  return ok(resolveLength(u, val, attrs))
 
-const CSSLengthAuto* = CSSLength(u: cuAuto)
+const CSSLengthAuto* = CSSLength(u: clAuto)
 
 func parseDimensionValues*(s: string): Option[CSSLength] =
   var i = s.skipBlanks(0)
@@ -791,19 +785,19 @@ func parseDimensionValues*(s: string): Option[CSSLength] =
     n += float64(decValue(s[i]))
     inc i
     if i >= s.len:
-      return some(CSSLength(num: n, u: cuPx))
+      return some(cssLength(n))
   if s[i] == '.':
     inc i
     if i >= s.len:
-      return some(CSSLength(num: n, u: cuPx))
+      return some(cssLength(n))
     var d = 1
     while i < s.len and s[i] in AsciiDigit:
       n += float64(decValue(s[i])) / float64(d)
       inc d
       inc i
   if i < s.len and s[i] == '%':
-    return some(CSSLength(num: n, u: cuPerc))
-  return some(CSSLength(num: n, u: cuPx))
+    return some(CSSLength(num: n, u: clPerc))
+  return some(cssLength(n))
 
 func skipWhitespace*(vals: openArray[CSSComponentValue]; i: var int) =
   while i < vals.len:
@@ -917,38 +911,39 @@ func cssColor*(val: CSSComponentValue): Opt[CSSColor] =
       return parseANSI(f.value)
   return err()
 
-func cssLength*(val: CSSComponentValue; hasAuto = true; allowNegative = true):
-    Opt[CSSLength] =
+func parseLength*(val: CSSComponentValue; attrs: WindowAttributes;
+    hasAuto = true; allowNegative = true): Opt[CSSLength] =
   if val of CSSToken:
     let tok = CSSToken(val)
     case tok.t
     of cttNumber:
       if tok.nvalue == 0:
-        return ok(CSSLength(num: 0, u: cuPx))
+        return ok(cssLength(0))
     of cttPercentage:
       if not allowNegative and tok.nvalue < 0:
         return err()
-      return cssLength(tok.nvalue, "%")
+      return parseLength(tok.nvalue, "%", attrs)
     of cttDimension:
       if not allowNegative and tok.nvalue < 0:
         return err()
-      return cssLength(tok.nvalue, tok.unit)
+      return parseLength(tok.nvalue, tok.unit, attrs)
     of cttIdent:
       if hasAuto and tok.value.equalsIgnoreCase("auto"):
         return ok(CSSLengthAuto)
     else: discard
   return err()
 
-func cssAbsoluteLength(val: CSSComponentValue): Opt[CSSLength] =
+func cssAbsoluteLength(val: CSSComponentValue; attrs: WindowAttributes):
+    Opt[CSSLength] =
   if val of CSSToken:
     let tok = CSSToken(val)
     case tok.t
     of cttNumber:
       if tok.nvalue == 0:
-        return ok(CSSLength(num: 0, u: cuPx))
+        return ok(cssLength(0))
     of cttDimension:
       if tok.nvalue >= 0:
-        return cssLength(tok.nvalue, tok.unit)
+        return parseLength(tok.nvalue, tok.unit, attrs)
     else: discard
   return err()
 
@@ -1044,14 +1039,15 @@ func cssTextDecoration(cvals: openArray[CSSComponentValue]):
       s.incl(td)
   return ok(s)
 
-func cssVerticalAlign(cval: CSSComponentValue): Opt[CSSVerticalAlign] =
+func cssVerticalAlign(cval: CSSComponentValue; attrs: WindowAttributes):
+    Opt[CSSVerticalAlign] =
   if cval of CSSToken:
     let tok = CSSToken(cval)
     if tok.t == cttIdent:
       let va2 = ?parseIdent[CSSVerticalAlign2](cval)
       return ok(CSSVerticalAlign(keyword: va2))
     else:
-      let length = ?cssLength(tok, hasAuto = false)
+      let length = ?parseLength(tok, attrs, hasAuto = false)
       return ok(CSSVerticalAlign(
         keyword: VerticalAlignBaseline,
         u: length.u,
@@ -1086,7 +1082,8 @@ func cssCounterReset(cvals: openArray[CSSComponentValue]):
         die
   return ok(res)
 
-func cssMaxSize(cval: CSSComponentValue): Opt[CSSLength] =
+func cssMaxSize(cval: CSSComponentValue; attrs: WindowAttributes):
+    Opt[CSSLength] =
   if cval of CSSToken:
     let tok = CSSToken(cval)
     case tok.t
@@ -1094,7 +1091,7 @@ func cssMaxSize(cval: CSSComponentValue): Opt[CSSLength] =
       if tok.value.equalsIgnoreCase("none"):
         return ok(CSSLengthAuto)
     of cttNumber, cttDimension, cttPercentage:
-      return cssLength(tok, allowNegative = false)
+      return parseLength(tok, attrs, allowNegative = false)
     else: discard
   return err()
 
@@ -1163,7 +1160,7 @@ proc makeEntry*(t: CSSPropertyType; global: CSSGlobalType): CSSComputedEntry =
   return CSSComputedEntry(t: t, global: global)
 
 proc parseValue(cvals: openArray[CSSComponentValue];
-    entry: var CSSComputedEntry): Opt[void] =
+    entry: var CSSComputedEntry; attrs: WindowAttributes): Opt[void] =
   var i = 0
   cvals.skipWhitespace(i)
   if i >= cvals.len:
@@ -1187,14 +1184,14 @@ proc parseValue(cvals: openArray[CSSComponentValue];
   of cvtLength:
     case t
     of cptMinWidth, cptMinHeight:
-      set_new length, ?cssLength(cval, allowNegative = false)
+      set_new length, ?parseLength(cval, attrs, allowNegative = false)
     of cptMaxWidth, cptMaxHeight:
-      set_new length, ?cssMaxSize(cval)
+      set_new length, ?cssMaxSize(cval, attrs)
     of cptPaddingLeft, cptPaddingRight, cptPaddingTop, cptPaddingBottom:
-      set_new length, ?cssLength(cval, hasAuto = false)
+      set_new length, ?parseLength(cval, attrs, hasAuto = false)
     #TODO content for flex-basis
     else:
-      set_new length, ?cssLength(cval)
+      set_new length, ?parseLength(cval, attrs)
   of cvtContent: set_new content, cssContent(cvals)
   of cvtInteger:
     case t
@@ -1204,7 +1201,7 @@ proc parseValue(cvals: openArray[CSSComponentValue];
     of cptZIndex: set_new integer, ?cssInteger(cval, -65534 .. 65534)
     else: assert false
   of cvtTextDecoration: set_new textdecoration, ?cssTextDecoration(cvals)
-  of cvtVerticalAlign: set_new verticalAlign, ?cssVerticalAlign(cval)
+  of cvtVerticalAlign: set_new verticalAlign, ?cssVerticalAlign(cval, attrs)
   of cvtTextAlign: set_bit textAlign, ?parseIdent[CSSTextAlign](cval)
   of cvtListStylePosition:
     set_bit listStylePosition, ?parseIdent[CSSListStylePosition](cval)
@@ -1213,9 +1210,9 @@ proc parseValue(cvals: openArray[CSSComponentValue];
   of cvtBorderCollapse:
     set_bit borderCollapse, ?parseIdent[CSSBorderCollapse](cval)
   of cvtLength2:
-    let a = ?cssAbsoluteLength(cval)
+    let a = ?cssAbsoluteLength(cval, attrs)
     cvals.skipWhitespace(i)
-    let b = if i >= cvals.len: a else: ?cssAbsoluteLength(cvals[i])
+    let b = if i >= cvals.len: a else: ?cssAbsoluteLength(cvals[i], attrs)
     set_new length2, CSSLength2(a: a, b: b)
   of cvtQuotes: set_new quotes, ?cssQuotes(cvals)
   of cvtCounterReset: set_new counterReset, ?cssCounterReset(cvals)
@@ -1246,7 +1243,7 @@ func getInitialLength(t: CSSPropertyType): CSSLength =
       cptMaxHeight, cptMinWidth, cptMinHeight, cptFlexBasis:
     return CSSLengthAuto
   else:
-    return CSSLength(u: cuPx, num: 0)
+    return cssLength(0)
 
 func getInitialInteger(t: CSSPropertyType): int =
   case t
@@ -1283,8 +1280,8 @@ template getDefault*(t: CSSPropertyType): CSSValue =
     defaultTable[t]
 
 func lengthShorthand(cvals: openArray[CSSComponentValue];
-    props: array[4, CSSPropertyType]; global: CSSGlobalType; hasAuto = true):
-    Opt[seq[CSSComputedEntry]] =
+    props: array[4, CSSPropertyType]; global: CSSGlobalType;
+    attrs: WindowAttributes; hasAuto = true): Opt[seq[CSSComputedEntry]] =
   var res: seq[CSSComputedEntry] = @[]
   if global != cgtNone:
     for t in props:
@@ -1294,7 +1291,7 @@ func lengthShorthand(cvals: openArray[CSSComponentValue];
   var i = 0
   while i < cvals.len:
     cvals.skipWhitespace(i)
-    let length = ?cssLength(cvals[i], hasAuto = hasAuto)
+    let length = ?parseLength(cvals[i], attrs, hasAuto = hasAuto)
     let val = CSSValue(v: cvtLength, length: length)
     lengths.add(val)
     inc i
@@ -1330,7 +1327,7 @@ const PropertyPaddingSpec = [
 ]
 
 proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
-    cvals: openArray[CSSComponentValue]): Err[void] =
+    cvals: openArray[CSSComponentValue]; attrs: WindowAttributes): Err[void] =
   var i = 0
   cvals.skipWhitespace(i)
   if i >= cvals.len:
@@ -1343,7 +1340,7 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
       res.add(makeEntry(t, global))
     else:
       var entry = CSSComputedEntry(t: t)
-      ?cvals.parseValue(entry)
+      ?cvals.parseValue(entry, attrs)
       res.add(entry)
   of cstAll:
     if global == cgtNone:
@@ -1351,9 +1348,9 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
     for t in CSSPropertyType:
       res.add(makeEntry(t, global))
   of cstMargin:
-    res.add(?lengthShorthand(cvals, PropertyMarginSpec, global))
+    res.add(?lengthShorthand(cvals, PropertyMarginSpec, global, attrs))
   of cstPadding:
-    res.add(?lengthShorthand(cvals, PropertyPaddingSpec, global,
+    res.add(?lengthShorthand(cvals, PropertyPaddingSpec, global, attrs,
       hasAuto = false))
   of cstBackground:
     var bgcolorval = getDefault(cptBackgroundColor)
@@ -1422,13 +1419,10 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
         res.add(makeEntry(cptFlexShrink, val))
       if i < cvals.len:
         # flex-basis
-        let val = CSSValue(v: cvtLength, length: ?cssLength(cvals[i]))
+        let val = CSSValue(v: cvtLength, length: ?parseLength(cvals[i], attrs))
         res.add(makeEntry(cptFlexBasis, val))
       else: # omitted, default to 0px
-        let val = CSSValue(
-          v: cvtLength,
-          length: CSSLength(u: cuPx, num: 0)
-        )
+        let val = CSSValue(v: cvtLength, length: cssLength(0))
         res.add(makeEntry(cptFlexBasis, val, global))
     else:
       res.add(makeEntry(cptFlexGrow, global))
@@ -1457,10 +1451,10 @@ proc parseComputedValues*(res: var seq[CSSComputedEntry]; name: string;
       res.add(makeEntry(cptFlexWrap, global))
   return ok()
 
-proc parseComputedValues*(name: string; value: seq[CSSComponentValue]):
-    seq[CSSComputedEntry] =
+proc parseComputedValues*(name: string; value: seq[CSSComponentValue];
+    attrs: WindowAttributes): seq[CSSComputedEntry] =
   var res: seq[CSSComputedEntry] = @[]
-  if res.parseComputedValues(name, value).isSome:
+  if res.parseComputedValues(name, value, attrs).isSome:
     return res
   return @[]
 
diff --git a/src/css/layout.nim b/src/css/layout.nim
index c737f9f8..ef712b8a 100644
--- a/src/css/layout.nim
+++ b/src/css/layout.nim
@@ -139,29 +139,24 @@ func isDefinite(sc: SizeConstraint): bool =
   return sc.t in {scStretch, scFitContent}
 
 # 2nd pass: layout
-func px(l: CSSLength; lctx: LayoutContext; p: LayoutUnit = 0):
-    LayoutUnit {.inline.} =
-  return px(l, lctx.attrs, p)
-
 func canpx(l: CSSLength; sc: SizeConstraint): bool =
-  return l.u != cuAuto and (l.u != cuPerc or sc.t == scStretch)
-
-# Note: for margins only
-# For percentages, use 0 for indefinite, and containing box's size for
-# definite.
-func px(l: CSSLength; lctx: LayoutContext; p: SizeConstraint): LayoutUnit =
-  if l.u == cuPerc:
-    case p.t
-    of scMinContent, scMaxContent:
-      return 0
-    of scStretch, scFitContent:
-      return l.px(lctx, p.u)
-  return px(l, lctx.attrs, 0)
+  return l.u != clAuto and (l.u != clPerc or sc.t == scStretch)
+
+func px(l: CSSLength; p: LayoutUnit): LayoutUnit {.inline.} =
+  if l.u != clPerc:
+    return l.num.toLayoutUnit()
+  return (p.toFloat64() * l.num / 100).toLayoutUnit()
 
-func stretchOrMaxContent(l: CSSLength; lctx: LayoutContext; sc: SizeConstraint):
-    SizeConstraint =
+func px(l: CSSLength; p: SizeConstraint): LayoutUnit {.inline.} =
+  if l.u != clPerc:
+    return l.num.toLayoutUnit()
+  if p.t in {scStretch, scFitContent}:
+    return (p.u.toFloat64() * l.num / 100).toLayoutUnit()
+  return 0
+
+func stretchOrMaxContent(l: CSSLength; sc: SizeConstraint): SizeConstraint =
   if l.canpx(sc):
-    return stretch(l.px(lctx, sc))
+    return stretch(l.px(sc))
   return maxContent()
 
 func applySizeConstraint(u: LayoutUnit; availableSize: SizeConstraint):
@@ -664,7 +659,7 @@ func getBaseline(ictx: InlineContext; iastate: InlineAtomState;
   return case iastate.vertalign.keyword
   of VerticalAlignBaseline:
     let length = CSSLength(u: iastate.vertalign.u, num: iastate.vertalign.num)
-    let len = length.px(ictx.lctx, ictx.cellHeight)
+    let len = length.px(ictx.cellHeight)
     iastate.baseline + len
   of VerticalAlignTop, VerticalAlignBottom:
     atom.size.h
@@ -899,9 +894,9 @@ proc layoutText(ictx: var InlineContext; state: var InlineState; s: string) =
     else: ""
     ictx.layoutTextLoop(state, s)
 
-func spx(l: CSSLength; lctx: LayoutContext; p: SizeConstraint;
-    computed: CSSValues; padding: LayoutUnit): LayoutUnit =
-  let u = l.px(lctx, p)
+func spx(l: CSSLength; p: SizeConstraint; computed: CSSValues;
+    padding: LayoutUnit): LayoutUnit =
+  let u = l.px(p)
   if computed{"box-sizing"} == BoxSizingBorderBox:
     return max(u - padding, 0)
   return max(u, 0)
@@ -921,14 +916,14 @@ proc resolveContentWidth(sizes: var ResolvedSizes; widthpx: LayoutUnit;
     else:
       sizes.margin[dtHorizontal].send += underflow
   elif underflow > 0:
-    if computed{"margin-left"}.u != cuAuto and
-        computed{"margin-right"}.u != cuAuto:
+    if computed{"margin-left"}.u != clAuto and
+        computed{"margin-right"}.u != clAuto:
       sizes.margin[dtHorizontal].send += underflow
-    elif computed{"margin-left"}.u != cuAuto and
-        computed{"margin-right"}.u == cuAuto:
+    elif computed{"margin-left"}.u != clAuto and
+        computed{"margin-right"}.u == clAuto:
       sizes.margin[dtHorizontal].send = underflow
-    elif computed{"margin-left"}.u == cuAuto and
-        computed{"margin-right"}.u != cuAuto:
+    elif computed{"margin-left"}.u == clAuto and
+        computed{"margin-right"}.u != clAuto:
       sizes.margin[dtHorizontal].start = underflow
     else:
       sizes.margin[dtHorizontal].start = underflow div 2
@@ -939,12 +934,12 @@ proc resolveMargins(lctx: LayoutContext; availableWidth: SizeConstraint;
   # Note: we use availableWidth for percentage resolution intentionally.
   return [
     dtHorizontal: Span(
-      start: computed{"margin-left"}.px(lctx, availableWidth),
-      send: computed{"margin-right"}.px(lctx, availableWidth),
+      start: computed{"margin-left"}.px(availableWidth),
+      send: computed{"margin-right"}.px(availableWidth),
     ),
     dtVertical: Span(
-      start: computed{"margin-top"}.px(lctx, availableWidth),
-      send: computed{"margin-bottom"}.px(lctx, availableWidth),
+      start: computed{"margin-top"}.px(availableWidth),
+      send: computed{"margin-bottom"}.px(availableWidth),
     )
   ]
 
@@ -953,12 +948,12 @@ proc resolvePadding(lctx: LayoutContext; availableWidth: SizeConstraint;
   # Note: we use availableWidth for percentage resolution intentionally.
   return [
     dtHorizontal: Span(
-      start: computed{"padding-left"}.px(lctx, availableWidth),
-      send: computed{"padding-right"}.px(lctx, availableWidth)
+      start: computed{"padding-left"}.px(availableWidth),
+      send: computed{"padding-right"}.px(availableWidth)
     ),
     dtVertical: Span(
-      start: computed{"padding-top"}.px(lctx, availableWidth),
-      send: computed{"padding-bottom"}.px(lctx, availableWidth),
+      start: computed{"padding-top"}.px(availableWidth),
+      send: computed{"padding-bottom"}.px(availableWidth),
     )
   ]
 
@@ -968,12 +963,12 @@ func resolvePositioned(lctx: LayoutContext; size: Size;
   # (unlike with margin/padding)
   return [
     dtHorizontal: Span(
-      start: computed{"left"}.px(lctx, size.w),
-      send: computed{"right"}.px(lctx, size.w)
+      start: computed{"left"}.px(size.w),
+      send: computed{"right"}.px(size.w)
     ),
     dtVertical: Span(
-      start: computed{"top"}.px(lctx, size.h),
-      send: computed{"bottom"}.px(lctx, size.h),
+      start: computed{"top"}.px(size.h),
+      send: computed{"bottom"}.px(size.h),
     )
   ]
 
@@ -989,21 +984,21 @@ func resolveBounds(lctx: LayoutContext; space: AvailableSpace; padding: Size;
     let sc = space.w
     let padding = padding[dtHorizontal]
     if computed{"min-width"}.canpx(sc):
-      let px = computed{"min-width"}.spx(lctx, sc, computed, padding)
+      let px = computed{"min-width"}.spx(sc, computed, padding)
       res.a[dtHorizontal].start = px
       res.minClamp[dtHorizontal] = px
     if computed{"max-width"}.canpx(sc):
-      let px = computed{"max-width"}.spx(lctx, sc, computed, padding)
+      let px = computed{"max-width"}.spx(sc, computed, padding)
       res.a[dtHorizontal].send = px
   block:
     let sc = space.h
     let padding = padding[dtHorizontal]
     if computed{"min-height"}.canpx(sc):
-      let px = computed{"min-height"}.spx(lctx, sc, computed, padding)
+      let px = computed{"min-height"}.spx(sc, computed, padding)
       res.a[dtVertical].start = px
       res.minClamp[dtVertical] = px
     if computed{"max-height"}.canpx(sc):
-      let px = computed{"max-height"}.spx(lctx, sc, computed, padding)
+      let px = computed{"max-height"}.spx(sc, computed, padding)
       res.a[dtVertical].send = px
   return res
 
@@ -1012,9 +1007,9 @@ const CvalSizeMap = [dtHorizontal: cptWidth, dtVertical: cptHeight]
 proc resolveAbsoluteWidth(sizes: var ResolvedSizes; size: Size;
     positioned: RelativeRect; computed: CSSValues;
     lctx: LayoutContext) =
-  if computed{"width"}.u == cuAuto:
+  if computed{"width"}.u == clAuto:
     let u = max(size.w - positioned[dtHorizontal].sum(), 0)
-    if computed{"left"}.u != cuAuto and computed{"right"}.u != cuAuto:
+    if computed{"left"}.u != clAuto and computed{"right"}.u != clAuto:
       # Both left and right are known, so we can calculate the width.
       sizes.space.w = stretch(u)
     else:
@@ -1022,15 +1017,15 @@ proc resolveAbsoluteWidth(sizes: var ResolvedSizes; size: Size;
       sizes.space.w = fitContent(u)
   else:
     let padding = sizes.padding[dtHorizontal].sum()
-    let sizepx = computed{"width"}.spx(lctx, stretch(size.w), computed, padding)
+    let sizepx = computed{"width"}.spx(stretch(size.w), computed, padding)
     sizes.space.w = stretch(sizepx)
 
 proc resolveAbsoluteHeight(sizes: var ResolvedSizes; size: Size;
     positioned: RelativeRect; computed: CSSValues;
     lctx: LayoutContext) =
-  if computed{"height"}.u == cuAuto:
+  if computed{"height"}.u == clAuto:
     let u = max(size.w - positioned[dtVertical].sum(), 0)
-    if computed{"top"}.u != cuAuto and computed{"bottom"}.u != cuAuto:
+    if computed{"top"}.u != clAuto and computed{"bottom"}.u != clAuto:
       # Both top and bottom are known, so we can calculate the height.
       sizes.space.h = stretch(u)
     else:
@@ -1038,7 +1033,7 @@ proc resolveAbsoluteHeight(sizes: var ResolvedSizes; size: Size;
       sizes.space.h = maxContent()
   else:
     let padding = sizes.padding[dtVertical].sum()
-    let sizepx = computed{"height"}.spx(lctx, stretch(size.h), computed,
+    let sizepx = computed{"height"}.spx(stretch(size.h), computed,
       padding)
     sizes.space.h = stretch(sizepx)
 
@@ -1071,7 +1066,7 @@ proc resolveFloatSizes(lctx: LayoutContext; space: AvailableSpace;
   for dim in DimensionType:
     let length = computed.objs[CvalSizeMap[dim]].length
     if length.canpx(space[dim]):
-      let u = length.spx(lctx, space[dim], computed, paddingSum[dim])
+      let u = length.spx(space[dim], computed, paddingSum[dim])
       sizes.space[dim] = stretch(minClamp(u, sizes.bounds.a[dim]))
     elif sizes.space[dim].isDefinite():
       let u = sizes.space[dim].u - sizes.margin[dim].sum() - paddingSum[dim]
@@ -1092,7 +1087,7 @@ proc resolveFlexItemSizes(lctx: LayoutContext; space: AvailableSpace;
     sizes.space.h = maxContent()
   let length = computed.objs[CvalSizeMap[dim]].length
   if length.canpx(space[dim]):
-    let u = length.spx(lctx, space[dim], computed, paddingSum[dim])
+    let u = length.spx(space[dim], computed, paddingSum[dim])
     sizes.space[dim] = SizeConstraint(
       t: sizes.space[dim].t,
       u: minClamp(u, sizes.bounds.a[dim])
@@ -1108,7 +1103,7 @@ proc resolveFlexItemSizes(lctx: LayoutContext; space: AvailableSpace;
   let odim = dim.opposite()
   let olength = computed.objs[CvalSizeMap[odim]].length
   if olength.canpx(space[odim]):
-    let u = olength.spx(lctx, space[odim], computed, paddingSum[odim])
+    let u = olength.spx(space[odim], computed, paddingSum[odim])
     sizes.space[odim] = stretch(minClamp(u, sizes.bounds.a[odim]))
     sizes.bounds.minClamp[odim] = min(sizes.space[odim].u,
       sizes.bounds.minClamp[odim])
@@ -1128,10 +1123,10 @@ proc resolveBlockWidth(sizes: var ResolvedSizes; parentWidth: SizeConstraint;
   let width = computed{"width"}
   var widthpx: LayoutUnit = 0
   if width.canpx(parentWidth):
-    widthpx = width.spx(lctx, parentWidth, computed, inlinePadding)
+    widthpx = width.spx(parentWidth, computed, inlinePadding)
     sizes.space.w = stretch(widthpx)
     sizes.bounds.minClamp[dtHorizontal] = widthpx
-  sizes.resolveContentWidth(widthpx, parentWidth, computed, width.u == cuAuto)
+  sizes.resolveContentWidth(widthpx, parentWidth, computed, width.u == clAuto)
   if sizes.space.w.isDefinite() and sizes.maxWidth < sizes.space.w.u or
       sizes.maxWidth < LayoutUnit.high and sizes.space.w.t == scMaxContent:
     if sizes.space.w.t == scStretch:
@@ -1156,7 +1151,7 @@ proc resolveBlockHeight(sizes: var ResolvedSizes; parentHeight: SizeConstraint;
     lctx: LayoutContext) =
   let height = computed{"height"}
   if height.canpx(parentHeight):
-    let heightpx = height.spx(lctx, parentHeight, computed, blockPadding)
+    let heightpx = height.spx(parentHeight, computed, blockPadding)
     sizes.space.h = stretch(heightpx)
     sizes.bounds.minClamp[dtVertical] = heightpx
   if sizes.space.h.isDefinite() and sizes.maxHeight < sizes.space.h.u or
@@ -1314,15 +1309,15 @@ proc popPositioned(lctx: LayoutContext; overflow: var Overflow; size: Size) =
       # the available width, and we must re-layout.
       sizes.space.w = stretch(child.state.xminwidth)
       marginBottom = lctx.layoutRootBlock(child, it.offset, sizes)
-    if child.computed{"left"}.u != cuAuto:
+    if child.computed{"left"}.u != clAuto:
       child.state.offset.x = positioned.left + sizes.margin.left
-    elif child.computed{"right"}.u != cuAuto:
+    elif child.computed{"right"}.u != clAuto:
       child.state.offset.x = size.w - positioned.right - child.state.size.w -
         sizes.margin.right
     # margin.left is added in layoutRootBlock
-    if child.computed{"top"}.u != cuAuto:
+    if child.computed{"top"}.u != clAuto:
       child.state.offset.y = positioned.top + sizes.margin.top
-    elif child.computed{"bottom"}.u != cuAuto:
+    elif child.computed{"bottom"}.u != clAuto:
       child.state.offset.y = size.h - positioned.bottom - child.state.size.h -
         sizes.margin.bottom
     else:
@@ -1617,25 +1612,24 @@ proc addInlineImage(ictx: var InlineContext; state: var InlineState;
     size: size(w = bmp.width, h = bmp.height) #TODO overflow
   )
   let computed = state.fragment.computed
-  let lctx = ictx.lctx
   let hasWidth = computed{"width"}.canpx(ictx.space.w)
   let hasHeight = computed{"height"}.canpx(ictx.space.h)
   let osize = atom.size
   if hasWidth:
-    atom.size.w = computed{"width"}.spx(lctx, ictx.space.w, computed, padding)
+    atom.size.w = computed{"width"}.spx(ictx.space.w, computed, padding)
   if hasHeight:
-    atom.size.h = computed{"height"}.spx(lctx, ictx.space.h, computed, padding)
+    atom.size.h = computed{"height"}.spx(ictx.space.h, computed, padding)
   if computed{"max-width"}.canpx(ictx.space.w):
-    let w = computed{"max-width"}.spx(lctx, ictx.space.w, computed, padding)
+    let w = computed{"max-width"}.spx(ictx.space.w, computed, padding)
     atom.size.w = min(atom.size.w, w)
   if computed{"min-width"}.canpx(ictx.space.w):
-    let w = computed{"min-width"}.spx(lctx, ictx.space.w, computed, padding)
+    let w = computed{"min-width"}.spx(ictx.space.w, computed, padding)
     atom.size.w = max(atom.size.w, w)
   if computed{"max-height"}.canpx(ictx.space.h):
-    let h = computed{"max-height"}.spx(lctx, ictx.space.h, computed, padding)
+    let h = computed{"max-height"}.spx(ictx.space.h, computed, padding)
     atom.size.h = min(atom.size.h, h)
   if computed{"min-height"}.canpx(ictx.space.h):
-    let h = computed{"min-height"}.spx(lctx, ictx.space.h, computed, padding)
+    let h = computed{"min-height"}.spx(ictx.space.h, computed, padding)
     atom.size.h = max(atom.size.h, h)
   if not hasWidth and ictx.space.w.isDefinite():
     atom.size.w = min(ictx.space.w.u, atom.size.w)
@@ -1663,8 +1657,8 @@ proc addInlineImage(ictx: var InlineContext; state: var InlineState;
     # parent size yet. e.g. <img width=100% ...> with an indefinite containing
     # size (i.e. the first table cell pass) would resolve to an xminwidth of
     # image.width, stretching out the table to an uncomfortably large size.
-    if ictx.space.w.isDefinite() or computed{"width"}.u != cuPerc and
-        computed{"min-width"}.u != cuPerc:
+    if ictx.space.w.isDefinite() or computed{"width"}.u != clPerc and
+        computed{"min-width"}.u != clPerc:
       ictx.state.xminwidth = max(ictx.state.xminwidth, atom.size.w)
 
 proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) =
@@ -1672,12 +1666,12 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) =
   let computed = fragment.computed
   var padding = Span()
   if stSplitStart in fragment.splitType:
-    let w = computed{"margin-left"}.px(lctx, ictx.space.w)
+    let w = computed{"margin-left"}.px(ictx.space.w)
     ictx.lbstate.size.w += w
     ictx.lbstate.widthAfterWhitespace += w
     padding = Span(
-      start: computed{"padding-left"}.px(lctx, ictx.space.w),
-      send: computed{"padding-right"}.px(lctx, ictx.space.w)
+      start: computed{"padding-left"}.px(ictx.space.w),
+      send: computed{"padding-right"}.px(ictx.space.w)
     )
   fragment.state = InlineFragmentState()
   if padding.start != 0:
@@ -1713,7 +1707,7 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) =
     ictx.lbstate.paddingTodo.add((fragment, fragment.state.areas.high))
   if stSplitEnd in fragment.splitType:
     ictx.lbstate.size.w += padding.send
-    ictx.lbstate.size.w += computed{"margin-right"}.px(lctx, ictx.space.w)
+    ictx.lbstate.size.w += computed{"margin-right"}.px(ictx.space.w)
   if fragment.t != iftParent:
     if not ictx.textFragmentSeen:
       ictx.textFragmentSeen = true
@@ -1732,14 +1726,14 @@ proc layoutInline(ictx: var InlineContext; fragment: InlineFragment) =
 
 proc positionRelative(lctx: LayoutContext; parent, box: BlockBox) =
   let positioned = lctx.resolvePositioned(parent.state.size, box.computed)
-  if box.computed{"left"}.u != cuAuto:
+  if box.computed{"left"}.u != clAuto:
     box.state.offset.x += positioned.left
-  elif box.computed{"right"}.u != cuAuto:
+  elif box.computed{"right"}.u != clAuto:
     box.state.offset.x += parent.state.size.w - box.state.size.w -
       positioned.right
-  if box.computed{"top"}.u != cuAuto:
+  if box.computed{"top"}.u != clAuto:
     box.state.offset.y += positioned.top
-  elif box.computed{"bottom"}.u != cuAuto:
+  elif box.computed{"bottom"}.u != clAuto:
     box.state.offset.y += parent.state.size.h - box.state.size.h -
       positioned.bottom
 
@@ -1871,8 +1865,8 @@ proc preLayoutTableRow(pctx: var TableContext; row, parent: BlockBox;
     let cw = box.computed{"width"}
     let ch = box.computed{"height"}
     let space = availableSpace(
-      w = cw.stretchOrMaxContent(pctx.lctx, pctx.space.w),
-      h = ch.stretchOrMaxContent(pctx.lctx, pctx.space.h)
+      w = cw.stretchOrMaxContent(pctx.space.w),
+      h = ch.stretchOrMaxContent(pctx.space.h)
     )
     #TODO specified table height should be distributed among rows.
     # Allow the table cell to use its specified width.
@@ -2091,7 +2085,7 @@ func needsRedistribution(tctx: TableContext; computed: CSSValues):
   of scStretch:
     return tctx.space.w.u != tctx.maxwidth
   of scFitContent:
-    return tctx.space.w.u > tctx.maxwidth and computed{"width"}.u != cuAuto or
+    return tctx.space.w.u > tctx.maxwidth and computed{"width"}.u != clAuto or
         tctx.space.w.u < tctx.maxwidth
 
 proc redistributeWidth(tctx: var TableContext) =
@@ -2194,12 +2188,11 @@ proc layoutTable(tctx: var TableContext; table: BlockBox;
     sizes: ResolvedSizes) =
   if tctx.space.w.t == scStretch:
     table.state.xminwidth = tctx.space.w.u
-  let lctx = tctx.lctx
   if table.computed{"border-collapse"} != BorderCollapseCollapse:
     let spc = table.computed{"border-spacing"}
     if spc != nil:
-      tctx.inlineSpacing = table.computed{"border-spacing"}.a.px(lctx)
-      tctx.blockSpacing = table.computed{"border-spacing"}.b.px(lctx)
+      tctx.inlineSpacing = table.computed{"border-spacing"}.a.px(0)
+      tctx.blockSpacing = table.computed{"border-spacing"}.b.px(0)
   tctx.preLayoutTableRows(table) # first pass
   if tctx.needsRedistribution(table.computed):
     tctx.redistributeWidth()
@@ -2412,11 +2405,11 @@ proc layoutFlex(bctx: var BlockContext; box: BlockBox; sizes: ResolvedSizes) =
     var childSizes = lctx.resolveFlexItemSizes(sizes.space, dim, child.computed)
     let flexBasis = child.computed{"flex-basis"}
     lctx.layoutFlexChild(child, childSizes)
-    if flexBasis.u != cuAuto and sizes.space[dim].isDefinite:
+    if flexBasis.u != clAuto and sizes.space[dim].isDefinite:
       # we can't skip this pass; it is needed to calculate the minimum
       # height.
       let minu = child.state.minFlexItemSize(dim)
-      childSizes.space[dim] = stretch(flexBasis.spx(lctx, sizes.space[dim],
+      childSizes.space[dim] = stretch(flexBasis.spx(sizes.space[dim],
         child.computed, childSizes.padding[dim].sum()))
       if minu > childSizes.space[dim].u:
         # First pass gave us a box that is thinner than the minimum
diff --git a/src/css/mediaquery.nim b/src/css/mediaquery.nim
index 756a67e4..5cb1ad98 100644
--- a/src/css/mediaquery.nim
+++ b/src/css/mediaquery.nim
@@ -3,12 +3,14 @@ import std/options
 import css/cssparser
 import css/cssvalues
 import types/opt
+import types/winattrs
 import utils/twtstr
 
 type
   MediaQueryParser = object
     at: int
     cvals: seq[CSSComponentValue]
+    attrs: ptr WindowAttributes
 
   MediaType* = enum
     mtAll = "all"
@@ -241,16 +243,19 @@ proc parseIntRange(parser: var MediaQueryParser; ismin, ismax: bool):
 
 proc parseLength(parser: var MediaQueryParser): Opt[CSSLength] =
   let cval = parser.consume()
-  return cssLength(cval)
+  let len = ?parseLength(cval, parser.attrs[])
+  if len.u != clPx:
+    return err()
+  return ok(len)
 
 proc parseLengthRange(parser: var MediaQueryParser; ismin, ismax: bool):
     Opt[LengthRange] =
   if ismin:
     let a = ?parser.parseLength()
-    let b = CSSLength(num: Inf, u: cuPx)
+    let b = cssLength(Inf)
     return ok(LengthRange(s: a .. b, aeq: true, beq: false))
   if ismax:
-    let a = CSSLength(num: 0, u: cuPx)
+    let a = cssLength(0)
     let b = ?parser.parseLength()
     return ok(LengthRange(s: a .. b, aeq: false, beq: true))
   let comparison = ?parser.parseComparison()
@@ -260,10 +265,10 @@ proc parseLengthRange(parser: var MediaQueryParser; ismin, ismax: bool):
   of mqcEq:
     return ok(LengthRange(s: len .. len, aeq: true, beq: true))
   of mqcGt, mqcGe:
-    let b = CSSLength(num: Inf, u: cuPx)
+    let b = cssLength(Inf)
     return ok(LengthRange(s: len .. b, aeq: comparison == mqcGe, beq: false))
   of mqcLt, mqcLe:
-    let a = CSSLength(num: 0, u: cuPx)
+    let a = cssLength(0)
     return ok(LengthRange(s: a .. len, aeq: false, beq: comparison == mqcLe))
 
 proc parseFeature0(parser: var MediaQueryParser; t: MediaFeatureType;
@@ -312,7 +317,7 @@ proc parseMediaInParens(parser: var MediaQueryParser): Opt[MediaQuery] =
   let sb = ?parser.consumeSimpleBlock()
   if sb.token.t != cttLparen:
     return err()
-  var fparser = MediaQueryParser(cvals: sb.value)
+  var fparser = MediaQueryParser(cvals: sb.value, attrs: parser.attrs)
   fparser.skipBlanks()
   let tok = ?fparser.consumeIdent()
   fparser.skipBlanks()
@@ -397,11 +402,12 @@ proc parseMediaQuery(parser: var MediaQueryParser): Opt[MediaQuery] =
   else:
     return err()
 
-proc parseMediaQueryList*(cvals: seq[CSSComponentValue]): MediaQueryList =
+proc parseMediaQueryList*(cvals: seq[CSSComponentValue];
+    attrs: ptr WindowAttributes): MediaQueryList =
   result = @[]
   let cseplist = cvals.parseCommaSepComponentValues()
   for list in cseplist:
-    var parser = MediaQueryParser(cvals: list)
+    var parser = MediaQueryParser(cvals: list, attrs: attrs)
     let query = parser.parseMediaQuery()
     if query.isSome:
       result.add(query.get)
diff --git a/src/css/render.nim b/src/css/render.nim
index 10045683..06fa02a7 100644
--- a/src/css/render.nim
+++ b/src/css/render.nim
@@ -402,9 +402,9 @@ proc renderBlockBox(grid: var FlexibleGrid; state: var RenderState;
     return
   var offset = offset
   if position in {PositionAbsolute, PositionFixed}:
-    if box.computed{"left"}.u != cuAuto or box.computed{"right"}.u != cuAuto:
+    if box.computed{"left"}.u != clAuto or box.computed{"right"}.u != clAuto:
       offset.x = state.absolutePos[^1].x
-    if box.computed{"top"}.u != cuAuto or box.computed{"bottom"}.u != cuAuto:
+    if box.computed{"top"}.u != clAuto or box.computed{"bottom"}.u != clAuto:
       offset.y = state.absolutePos[^1].y
   offset += box.state.offset
   box.render.offset = offset
diff --git a/src/css/sheet.nim b/src/css/sheet.nim
index 0793e903..818e30f8 100644
--- a/src/css/sheet.nim
+++ b/src/css/sheet.nim
@@ -7,6 +7,7 @@ import css/mediaquery
 import css/selectorparser
 import html/catom
 import types/url
+import types/winattrs
 import utils/twtstr
 
 type
@@ -36,6 +37,7 @@ type
     importList*: seq[URL]
     len: int
     factory*: CAtomFactory
+    attrs: ptr WindowAttributes
 
 type SelectorHashes = object
   tag: CAtom
@@ -43,7 +45,8 @@ type SelectorHashes = object
   class: CAtom
   attr: CAtom
 
-func newStylesheet*(cap: int; factory: CAtomFactory): CSSStylesheet =
+func newStylesheet*(cap: int; factory: CAtomFactory;
+    attrs: ptr WindowAttributes): CSSStylesheet =
   let bucketsize = cap div 2
   return CSSStylesheet(
     tagTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
@@ -51,7 +54,8 @@ func newStylesheet*(cap: int; factory: CAtomFactory): CSSStylesheet =
     classTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
     attrTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
     generalList: newSeqOfCap[CSSRuleDef](bucketsize),
-    factory: factory
+    factory: factory,
+    attrs: attrs
   )
 
 proc getSelectorIds(hashes: var SelectorHashes; sel: Selector): bool
@@ -176,29 +180,29 @@ proc add*(sheet, sheet2: CSSStylesheet) =
     do:
       sheet.attrTable[key] = value
 
-proc addRule(stylesheet: CSSStylesheet; rule: CSSQualifiedRule) =
-  let sels = parseSelectors(rule.prelude, stylesheet.factory)
+proc addRule(sheet: CSSStylesheet; rule: CSSQualifiedRule) =
+  let sels = parseSelectors(rule.prelude, sheet.factory)
   if sels.len > 0:
     var normalVals: seq[CSSComputedEntry] = @[]
     var importantVals: seq[CSSComputedEntry] = @[]
     let decls = rule.oblock.value.parseDeclarations()
     for decl in decls:
-      let vals = parseComputedValues(decl.name, decl.value)
+      let vals = parseComputedValues(decl.name, decl.value, sheet.attrs[])
       if decl.important:
         importantVals.add(vals)
       else:
         normalVals.add(vals)
-    stylesheet.add(CSSRuleDef(
+    sheet.add(CSSRuleDef(
       sels: sels,
       normalVals: normalVals,
       importantVals: importantVals,
-      idx: stylesheet.len
+      idx: sheet.len
     ))
-    inc stylesheet.len
+    inc sheet.len
 
-proc addAtRule(stylesheet: CSSStylesheet; atrule: CSSAtRule; base: URL) =
+proc addAtRule(sheet: CSSStylesheet; atrule: CSSAtRule; base: URL) =
   if atrule.name.equalsIgnoreCase("import"):
-    if stylesheet.len == 0 and base != nil:
+    if sheet.len == 0 and base != nil:
       var i = 0
       atrule.prelude.skipWhitespace(i)
       # Warning: this is a tracking vector minefield. If you implement
@@ -212,28 +216,28 @@ proc addAtRule(stylesheet: CSSStylesheet; atrule: CSSAtRule; base: URL) =
             atrule.prelude.skipWhitespace(i)
             # check if there are really no media queries/layers/etc
             if i == atrule.prelude.len:
-              stylesheet.importList.add(url.get)
+              sheet.importList.add(url.get)
   elif atrule.name.equalsIgnoreCase("media"):
     if atrule.oblock != nil:
-      let query = parseMediaQueryList(atrule.prelude)
+      let query = parseMediaQueryList(atrule.prelude, sheet.attrs)
       let rules = atrule.oblock.value.parseListOfRules()
       if rules.len > 0:
         var media = CSSMediaQueryDef()
-        media.children = newStylesheet(rules.len, stylesheet.factory)
-        media.children.len = stylesheet.len
+        media.children = newStylesheet(rules.len, sheet.factory, sheet.attrs)
+        media.children.len = sheet.len
         media.query = query
         for rule in rules:
           if rule of CSSAtRule:
             media.children.addAtRule(CSSAtRule(rule), nil)
           else:
             media.children.addRule(CSSQualifiedRule(rule))
-        stylesheet.mqList.add(media)
-        stylesheet.len = media.children.len
+        sheet.mqList.add(media)
+        sheet.len = media.children.len
 
-proc parseStylesheet*(ibuf: string; factory: CAtomFactory; base: URL):
-    CSSStylesheet =
+proc parseStylesheet*(ibuf: string; factory: CAtomFactory; base: URL;
+    attrs: ptr WindowAttributes): CSSStylesheet =
   let raw = parseStylesheet(ibuf)
-  let sheet = newStylesheet(raw.value.len, factory)
+  let sheet = newStylesheet(raw.value.len, factory, attrs)
   for v in raw.value:
     if v of CSSAtRule:
       sheet.addAtRule(CSSAtRule(v), base)
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 41f37744..fdadd5f7 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -2543,8 +2543,8 @@ proc sheets*(document: Document): seq[CSSStylesheet] =
     for elem in document.documentElement.descendants:
       if elem of HTMLStyleElement:
         let style = HTMLStyleElement(elem)
-        style.sheet = parseStylesheet(style.textContent, document.factory,
-          document.baseURL)
+        style.sheet = style.textContent.parseStylesheet(document.factory,
+          document.baseURL, addr document.window.attrs)
         document.cachedSheets.add(style.sheet)
       elif elem of HTMLLinkElement:
         let link = HTMLLinkElement(elem)
@@ -3288,12 +3288,21 @@ proc getter(ctx: JSContext; this: CSSStyleDeclaration; atom: JSAtom):
     return ctx.toJS(this.getPropertyValue(s))
   return JS_UNINITIALIZED
 
+template dummyWindow(): WindowAttributes = WindowAttributes(
+  width: 80,
+  height: 24,
+  ppc: 9,
+  ppl: 18,
+  widthPx: 80 * 9,
+  heightPx: 24 * 18
+)
+
 proc setValue(this: CSSStyleDeclaration; i: int; cvals: seq[CSSComponentValue]):
     Err[void] =
   if i notin 0 .. this.decls.high:
     return err()
   var dummy: seq[CSSComputedEntry]
-  ?parseComputedValues(dummy, this.decls[i].name, cvals)
+  ?parseComputedValues(dummy, this.decls[i].name, cvals, dummyWindow())
   this.decls[i].value = cvals
   return ok()
 
@@ -3313,7 +3322,7 @@ proc setter(ctx: JSContext; this: CSSStyleDeclaration; atom: JSAtom;
         return ok()
     else:
       var dummy: seq[CSSComputedEntry]
-      let val0 = parseComputedValues(dummy, s, cvals)
+      let val0 = parseComputedValues(dummy, s, cvals, dummyWindow())
       if val0.isNone:
         return ok()
       this.decls.add(CSSDeclaration(name: s, value: cvals))
@@ -3343,7 +3352,7 @@ proc loadSheet(window: Window; link: HTMLLinkElement; url: URL; applies: bool) =
   ).then(proc(s: JSResult[string]) =
     # Check applies here, to avoid leaking the window size.
     if s.isSome:
-      let sheet = s.get.parseStylesheet(window.factory, url)
+      let sheet = s.get.parseStylesheet(window.factory, url, addr window.attrs)
       if applies:
         # Note: we intentionally load all sheets to prevent media query
         # based tracking.
@@ -3372,7 +3381,7 @@ proc loadResource(window: Window; link: HTMLLinkElement) =
     var applies = true
     if media != "":
       let cvals = parseComponentValues(media)
-      let media = parseMediaQueryList(cvals)
+      let media = parseMediaQueryList(cvals, addr window.attrs)
       applies = media.appliesImpl(window)
     window.loadSheet(link, url, applies)
 
diff --git a/src/server/buffer.nim b/src/server/buffer.nim
index f57a9184..da9b8bb5 100644
--- a/src/server/buffer.nim
+++ b/src/server/buffer.nim
@@ -1949,9 +1949,11 @@ proc launchBuffer*(config: BufferConfig; url: URL; attrs: WindowAttributes;
   const css = staticRead"res/ua.css"
   const quirk = css & staticRead"res/quirk.css"
   buffer.initDecoder()
-  buffer.uastyle = css.parseStylesheet(factory, nil)
-  buffer.quirkstyle = quirk.parseStylesheet(factory, nil)
-  buffer.userstyle = buffer.config.userstyle.parseStylesheet(factory, nil)
+  let attrsp = addr buffer.attrs
+  buffer.uastyle = css.parseStylesheet(factory, nil, attrsp)
+  buffer.quirkstyle = quirk.parseStylesheet(factory, nil, attrsp)
+  buffer.userstyle = buffer.config.userstyle.parseStylesheet(factory, nil,
+    attrsp)
   buffer.htmlParser = newHTML5ParserWrapper(
     buffer.window,
     buffer.url,