about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/buffer/buffer.nim10
-rw-r--r--src/css/cascade.nim58
-rw-r--r--src/css/mediaquery.nim205
-rw-r--r--src/css/values.nim3
-rw-r--r--src/html/dom.nim2
-rw-r--r--src/html/env.nim4
-rw-r--r--src/utils/twtstr.nim3
7 files changed, 234 insertions, 51 deletions
diff --git a/src/buffer/buffer.nim b/src/buffer/buffer.nim
index 77772532..8775e0e6 100644
--- a/src/buffer/buffer.nim
+++ b/src/buffer/buffer.nim
@@ -558,7 +558,7 @@ proc loadResource(buffer: Buffer, document: Document, elem: HTMLLinkElement): Em
       let media = elem.media
       if media != "":
         let media = parseMediaQueryList(parseListOfComponentValues(newStringStream(media)))
-        if not media.applies(): return
+        if not media.applies(document.window): return
       return buffer.loader.fetch(newRequest(url)).then(proc(res: Response) =
         if res.contenttype == "text/css":
           elem.sheet = parseStylesheet(res.body))
@@ -652,7 +652,8 @@ proc finishLoad(buffer: Buffer): EmptyPromise =
     buffer.sstream.setPosition(0)
     buffer.available = 0
     if buffer.window == nil:
-      buffer.window = newWindow(buffer.config.scripting, buffer.selector)
+      buffer.window = newWindow(buffer.config.scripting, buffer.selector,
+        buffer.attrs)
     let doc = parseHTML(buffer.sstream, charsets = buffer.charsets,
       window = buffer.window, url = buffer.url)
     buffer.document = doc
@@ -745,7 +746,8 @@ proc cancel*(buffer: Buffer): int {.proxy.} =
     buffer.sstream.setPosition(0)
     buffer.available = 0
     if buffer.window == nil:
-      buffer.window = newWindow(buffer.config.scripting, buffer.selector)
+      buffer.window = newWindow(buffer.config.scripting, buffer.selector,
+        buffer.attrs)
     buffer.document = parseHTML(buffer.sstream,
       charsets = buffer.charsets, window = buffer.window,
       url = buffer.url, canReinterpret = false)
@@ -1195,7 +1197,7 @@ proc launchBuffer*(config: BufferConfig, source: BufferSource,
   buffer.srenderer = newStreamRenderer(buffer.sstream, buffer.charsets)
   if buffer.config.scripting:
     buffer.window = newWindow(buffer.config.scripting, buffer.selector,
-      some(buffer.loader))
+      buffer.attrs, some(buffer.loader))
   let socks = connectSocketStream(mainproc, false)
   socks.swrite(getpid())
   buffer.pstream = socks
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
index 7aac213f..acfbdcb9 100644
--- a/src/css/cascade.nim
+++ b/src/css/cascade.nim
@@ -18,7 +18,26 @@ import types/color
 type
   DeclarationList* = array[PseudoElem, seq[CSSDeclaration]]
 
-func applies(mq: MediaQuery): bool =
+func applies(feature: MediaFeature, window: Window): bool =
+  case feature.t
+  of FEATURE_COLOR:
+    return 8 in feature.range
+  of FEATURE_GRID:
+    return feature.b
+  of FEATURE_HOVER:
+    return feature.b
+  of FEATURE_PREFERS_COLOR_SCHEME:
+    return feature.b
+  of FEATURE_WIDTH:
+    let a = px(feature.lengthrange.a, window.attrs, 0)
+    let b = px(feature.lengthrange.b, window.attrs, 0)
+    return window.attrs.ppc * window.attrs.width in a .. b
+  of FEATURE_HEIGHT:
+    let a = px(feature.lengthrange.a, window.attrs, 0)
+    let b = px(feature.lengthrange.b, window.attrs, 0)
+    return window.attrs.ppl * window.attrs.height in a .. b
+
+func applies(mq: MediaQuery, window: Window): bool =
   case mq.t
   of CONDITION_MEDIA:
     case mq.media
@@ -29,25 +48,17 @@ func applies(mq: MediaQuery): bool =
     of MEDIA_TYPE_TTY: return true
     of MEDIA_TYPE_UNKNOWN: return false
   of CONDITION_NOT:
-    return not mq.n.applies()
+    return not mq.n.applies(window)
   of CONDITION_AND:
-    return mq.anda.applies() and mq.andb.applies()
+    return mq.anda.applies(window) and mq.andb.applies(window)
   of CONDITION_OR:
-    return mq.ora.applies() or mq.orb.applies()
+    return mq.ora.applies(window) or mq.orb.applies(window)
   of CONDITION_FEATURE:
-    case mq.feature.t
-    of FEATURE_COLOR:
-      return true #TODO
-    of FEATURE_GRID:
-      return mq.feature.b
-    of FEATURE_HOVER:
-      return mq.feature.b
-    of FEATURE_PREFERS_COLOR_SCHEME:
-      return mq.feature.b
-
-func applies*(mqlist: MediaQueryList): bool =
+    return mq.feature.applies(window)
+
+func applies*(mqlist: MediaQueryList, window: Window): bool =
   for mq in mqlist:
-    if mq.applies():
+    if mq.applies(window):
       return true
   return false
 
@@ -209,12 +220,12 @@ proc applyDeclarations(pseudo: PseudoElem, styledParent: StyledNode, ua,
   if builder.hasValues():
     result = styledParent.newStyledElement(pseudo, builder.buildComputedValues())
 
-func applyMediaQuery(ss: CSSStylesheet): CSSStylesheet =
+func applyMediaQuery(ss: CSSStylesheet, window: Window): CSSStylesheet =
   if ss == nil: return nil
   result = ss
   for mq in ss.mq_list:
-    if mq.query.applies():
-      result.add(mq.children.applyMediaQuery())
+    if mq.query.applies(window):
+      result.add(mq.children.applyMediaQuery(window))
 
 func calcRules(styledNode: StyledNode, ua, user: CSSStylesheet, author: seq[CSSStylesheet]): tuple[uadecls, userdecls: DeclarationList, authordecls: seq[DeclarationList]] =
   result.uadecls = calcRules(styledNode, ua)
@@ -245,7 +256,7 @@ proc applyRules(document: Document, ua, user: CSSStylesheet, cachedTree: StyledN
 
   var author: seq[CSSStylesheet]
   for sheet in document.sheets():
-    author.add(sheet.applyMediaQuery())
+    author.add(sheet.applyMediaQuery(document.window))
 
   var styledStack: seq[CascadeLevel]
   styledStack.add((nil, document.html, PSEUDO_NONE, cachedTree))
@@ -393,7 +404,8 @@ proc applyRules(document: Document, ua, user: CSSStylesheet, cachedTree: StyledN
 
       stack_append styledChild, PSEUDO_BEFORE
 
-proc applyStylesheets*(document: Document, uass, userss: CSSStylesheet, previousStyled: StyledNode): StyledNode =
-  let uass = uass.applyMediaQuery()
-  let userss = userss.applyMediaQuery()
+proc applyStylesheets*(document: Document, uass, userss: CSSStylesheet,
+    previousStyled: StyledNode): StyledNode =
+  let uass = uass.applyMediaQuery(document.window)
+  let userss = userss.applyMediaQuery(document.window)
   return document.applyRules(uass, userss, previousStyled)
diff --git a/src/css/mediaquery.nim b/src/css/mediaquery.nim
index 2292a27c..8d75be2a 100644
--- a/src/css/mediaquery.nim
+++ b/src/css/mediaquery.nim
@@ -1,6 +1,9 @@
+import strutils
 import tables
 
 import css/cssparser
+import css/values
+import utils/twtstr
 
 type
   MediaQueryParser = object
@@ -16,14 +19,19 @@ type
     CONDITION_MEDIA
 
   MediaFeatureType* = enum
-    FEATURE_COLOR, FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME
+    FEATURE_COLOR, FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME,
+    FEATURE_WIDTH, FEATURE_HEIGHT
 
   MediaFeature* = object
     case t*: MediaFeatureType
     of FEATURE_COLOR:
-      color*: Slice[int]
+      range*: Slice[int]
     of FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME:
       b*: bool
+    of FEATURE_WIDTH, FEATURE_HEIGHT:
+      lengthrange*: Slice[CSSLength]
+      lengthaeq*: bool
+      lengthbeq*: bool
 
   MediaQuery* = ref object
     case t*: MediaConditionType
@@ -42,6 +50,9 @@ type
 
   MediaQueryList* = seq[MediaQuery]
 
+  MediaQueryComparison = enum
+    COMPARISON_EQ, COMPARISON_GT, COMPARISON_LT, COMPARISON_GE, COMPARISON_LE
+
 const MediaTypes = {
   "all": MEDIA_TYPE_ALL,
   "print": MEDIA_TYPE_PRINT,
@@ -50,6 +61,8 @@ const MediaTypes = {
   "tty": MEDIA_TYPE_TTY
 }.toTable()
 
+const RangeFeatures = {FEATURE_COLOR, FEATURE_WIDTH, FEATURE_HEIGHT}
+
 proc has(parser: MediaQueryParser, i = 0): bool {.inline.} =
   return parser.cvals.len > parser.at + i
 
@@ -77,7 +90,13 @@ proc getBoolFeature(feature: MediaFeatureType): MediaQuery =
   of FEATURE_GRID, FEATURE_HOVER, FEATURE_PREFERS_COLOR_SCHEME:
     result.feature = MediaFeature(t: feature, b: true)
   of FEATURE_COLOR:
-    result.feature = MediaFeature(t: feature, color: 1..high(int))
+    result.feature = MediaFeature(t: feature, range: 1..high(int))
+  else:
+    return nil
+
+template skip_has(): bool =
+  parser.skipBlanks()
+  parser.has()
 
 template get_tok(tok: untyped) =
   if not (cval of CSSToken): return nil
@@ -87,20 +106,33 @@ template get_idtok(tok: untyped) =
   get_tok(tok)
   if tok.tokenType != CSS_IDENT_TOKEN: return nil
 
-template expect_mq_int(b: bool, ifalse: int, itrue: int) =
+template consume_token(): CSSToken =
+  let cval = parser.consume()
+  if not (cval of CSSToken): return nil
+  CSSToken(cval)
+
+template skip_consume(): CSSToken =
+  parser.skipBlanks()
+  consume_token()
+
+template expect_int(i: var int) =
   let cval = parser.consume()
   if not (cval of CSSToken): return nil
   let tok = CSSToken(cval)
-  if tok.tokenType != CSS_NUMBER_TOKEN: return nil
-  let i = int(tok.nvalue)
+  if tok.tokenType == CSS_NUMBER_TOKEN and tok.tflagb == TFLAGB_INTEGER:
+    i = int(tok.nvalue)
+  else:
+    return nil
+
+template expect_mq_int(b: bool, ifalse: int, itrue: int) =
+  var i: int
+  expect_int(i)
   if i == ifalse: b = false
   elif i == itrue: b = true
   else: return nil
 
 template expect_bool(b: bool, sfalse: string, strue: string) =
-  let cval = parser.consume()
-  if not (cval of CSSToken): return nil
-  let tok = CSSToken(cval)
+  let tok = consume_token()
   if tok.tokenType != CSS_IDENT_TOKEN: return nil
   let s = tok.value
   case s
@@ -108,31 +140,154 @@ template expect_bool(b: bool, sfalse: string, strue: string) =
   of sfalse: b = false
   else: return nil
 
-proc parseFeature(parser: var MediaQueryParser, feature: MediaFeatureType): MediaQuery =
-  if not parser.has(): return getBoolFeature(feature)
+template expect_comparison(comparison: var MediaQueryComparison) =
+  let tok = consume_token()
+  if tok != CSS_DELIM_TOKEN: return nil
+  if tok.rvalue.isAscii(): return nil
+  let c = char(tok.rvalue)
+  if c notin {'=', '<', '>'}: return nil
+  block parse:
+    case char(tok.rvalue)
+    of '<':
+      if parser.has():
+        let tok = skip_consume()
+        if tok == CSS_DELIM_TOKEN and tok.rvalue == '=':
+          comparison = COMPARISON_LE
+          break parse
+        parser.reconsume()
+      comparison = COMPARISON_LT
+    of '>':
+      if parser.has():
+        let tok = skip_consume()
+        if tok == CSS_DELIM_TOKEN and tok.rvalue == '=':
+          comparison = COMPARISON_GE
+          break parse
+        parser.reconsume()
+      comparison = COMPARISON_GT
+    of '=':
+      comparison = COMPARISON_EQ
+    else: return nil
+
+template expect_int_range(range: var Slice[int], ismin, ismax: bool) =
+  if ismin:
+    expect_int(range.a)
+  elif ismax:
+    expect_int(range.b)
+  else:
+    let tok = consume_token
+    parser.reconsume()
+    if tok.tokenType == CSS_DELIM_TOKEN:
+      var comparison: MediaQueryComparison
+      expect_comparison(comparison)
+      if not skip_has: return nil
+      case comparison
+      of COMPARISON_EQ:
+        expect_int(range.a) #TODO should be >= 0 (for color at least)
+        range.b = range.a
+      of COMPARISON_GT:
+        expect_int(range.a)
+        range.b = high(int)
+      of COMPARISON_GE:
+        expect_int(range.a)
+        range.b = high(int)
+      of COMPARISON_LT:
+        expect_int(range.b)
+      of COMPARISON_LE:
+        expect_int(range.b)
+    else:
+      return nil
+
+template expect_length(length: var CSSLength) =
+  let cval = parser.consume()
+  try:
+    length = cssLength(cval)
+  except CSSValueError:
+    return nil
+
+template expect_length_range(range: var Slice[CSSLength], lengthaeq, lengthbeq:
+    var bool, ismin, ismax: bool) =
+  if ismin:
+    expect_length(range.a)
+    range.b = CSSLength(num: Inf, unit: UNIT_PX)
+    lengthaeq = true
+  elif ismax:
+    range.a = CSSLength(num: 0, unit: UNIT_PX)
+    expect_length(range.b)
+    lengthbeq = true
+  else:
+    let tok = consume_token
+    parser.reconsume()
+    if tok.tokenType == CSS_DELIM_TOKEN:
+      var comparison: MediaQueryComparison
+      expect_comparison(comparison)
+      if not skip_has: return nil
+      expect_length(range.a)
+      expect_length(range.b)
+      case comparison
+      of COMPARISON_EQ:
+        expect_length(range.a)
+        range.b = range.a
+        lengthaeq = true
+        lengthbeq = true
+      of COMPARISON_GT:
+        expect_length(range.a)
+        range.b = CSSLength(num: Inf, unit: UNIT_PX)
+      of COMPARISON_GE:
+        expect_length(range.a)
+        range.b = CSSLength(num: Inf, unit: UNIT_PX)
+        lengthaeq = true
+      of COMPARISON_LT:
+        range.a = CSSLength(num: 0, unit: UNIT_PX)
+        expect_length(range.b)
+      of COMPARISON_LE:
+        range.a = CSSLength(num: 0, unit: UNIT_PX)
+        expect_length(range.b)
+        lengthbeq = true
+    else:
+      return nil
+
+proc parseFeature(parser: var MediaQueryParser, t: MediaFeatureType,
+    ismin, ismax: bool): MediaQuery =
+  if not parser.has(): return getBoolFeature(t)
   let cval = parser.consume()
   var tok: CSSToken
   get_tok(tok)
   if tok.tokenType != CSS_COLON_TOKEN: return nil
   parser.skipBlanks()
-  case feature
+  if (ismin or ismax) and t notin RangeFeatures:
+    return nil
+  let feature = case t
   of FEATURE_GRID:
     var b: bool
     expect_mq_int(b, 0, 1)
-    result = MediaQuery(t: CONDITION_FEATURE, feature: MediaFeature(t: feature, b: b))
+    MediaFeature(t: t, b: b)
   of FEATURE_HOVER:
     var b: bool
     expect_bool(b, "none", "hover")
-    result = MediaQuery(t: CONDITION_FEATURE, feature: MediaFeature(t: feature, b: b))
+    MediaFeature(t: t, b: b)
   of FEATURE_PREFERS_COLOR_SCHEME:
     var b: bool
     expect_bool(b, "light", "dark")
-    result = MediaQuery(t: CONDITION_FEATURE, feature: MediaFeature(t: feature, b: b))
-  else: return nil
-
+    MediaFeature(t: t, b: b)
+  of FEATURE_COLOR:
+    var range: Slice[int]
+    expect_int_range(range, ismin, ismax)
+    MediaFeature(t: t, range: range)
+  of FEATURE_WIDTH, FEATURE_HEIGHT:
+    var range: Slice[CSSLength]
+    var lengthaeq: bool
+    var lengthbeq: bool
+    expect_length_range(range, lengthaeq, lengthbeq, ismin, ismax)
+    MediaFeature(
+      t: t,
+      lengthrange: range,
+      lengthaeq: lengthaeq,
+      lengthbeq: lengthbeq
+    )
   parser.skipBlanks()
   if parser.has():
     return nil
+  return MediaQuery(t: CONDITION_FEATURE, feature: feature)
 
 proc parseMediaCondition(parser: var MediaQueryParser, non = false, noor = false): MediaQuery
 
@@ -154,18 +309,24 @@ proc parseMediaInParens(parser: var MediaQueryParser): MediaQuery =
     get_tok(tok)
     fparser.skipBlanks()
     if tok.tokenType == CSS_IDENT_TOKEN:
-      let tokval = tok.value
+      var tokval = tok.value
+      let ismin = tokval.startsWith("min-")
+      let ismax = tokval.startsWith("max-")
+      if ismin or ismax:
+        tokval = tokval.substr(4)
       case tokval
       of "not":
         return fparser.parseMediaCondition(true)
       of "color":
-        return fparser.parseFeature(FEATURE_COLOR)
+        return fparser.parseFeature(FEATURE_COLOR, ismin, ismax)
+      of "width":
+        return fparser.parseFeature(FEATURE_WIDTH, ismin, ismax)
       of "grid":
-        return fparser.parseFeature(FEATURE_GRID)
+        return fparser.parseFeature(FEATURE_GRID, ismin, ismax)
       of "hover":
-        return fparser.parseFeature(FEATURE_HOVER)
+        return fparser.parseFeature(FEATURE_HOVER, ismin, ismax)
       of "prefers-color-scheme":
-        return fparser.parseFeature(FEATURE_PREFERS_COLOR_SCHEME)
+        return fparser.parseFeature(FEATURE_PREFERS_COLOR_SCHEME, ismin, ismax)
       else: discard
   return nil
 
diff --git a/src/css/values.nim b/src/css/values.nim
index dd3f2df4..2204e6ab 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -552,7 +552,8 @@ func cssColor*(val: CSSComponentValue): RGBAColor =
 
 func isToken(cval: CSSComponentValue): bool {.inline.} = cval of CSSToken
 
-func cssLength(val: CSSComponentValue, has_auto: static bool = true, allow_negative: static bool = true): CSSLength =
+func cssLength*(val: CSSComponentValue, has_auto: static bool = true,
+    allow_negative: static bool = true): CSSLength =
   block nofail:
     if val of CSSToken:
       let tok = CSSToken(val)
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 78184540..190dd479 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -19,6 +19,7 @@ import img/png
 import img/path
 import io/loader
 import io/request
+import io/window
 import js/javascript
 import js/timeout
 import types/blob
@@ -88,6 +89,7 @@ type
 
 type
   Window* = ref object
+    attrs*: WindowAttributes
     console* {.jsget.}: console
     navigator* {.jsget.}: Navigator
     settings*: EnvironmentSettings
diff --git a/src/html/env.nim b/src/html/env.nim
index 8b558b8a..ca7e497d 100644
--- a/src/html/env.nim
+++ b/src/html/env.nim
@@ -6,6 +6,7 @@ import html/htmlparser
 import io/loader
 import io/promise
 import io/request
+import io/window
 import js/intl
 import js/javascript
 import js/timeout
@@ -124,8 +125,9 @@ proc runJSJobs*(window: Window) =
   window.jsrt.runJSJobs(window.console.err)
 
 proc newWindow*(scripting: bool, selector: Selector[int],
-    loader = none(FileLoader)): Window =
+    attrs: WindowAttributes, loader = none(FileLoader)): Window =
   let window = Window(
+    attrs: attrs,
     console: console(err: newFileStream(stderr)),
     navigator: Navigator(),
     loader: loader,
diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim
index 93fce06f..c41a7e57 100644
--- a/src/utils/twtstr.nim
+++ b/src/utils/twtstr.nim
@@ -130,6 +130,9 @@ func normalizeLocale*(s: string): string =
 func isAscii*(r: Rune): bool =
   return cast[uint32](r) < 128
 
+func `==`*(r: Rune, c: char): bool {.inline.} =
+  return Rune(c) == r
+
 func startsWithNoCase*(str, prefix: string): bool =
   if str.len < prefix.len: return false
   # prefix.len is always lower