Gitweb links:

...log 
http://git.netsurf-browser.org/libcss.git/shortlog/ace5978891ce0c2e1700945a296881fc62109701
...commit 
http://git.netsurf-browser.org/libcss.git/commit/ace5978891ce0c2e1700945a296881fc62109701
...tree 
http://git.netsurf-browser.org/libcss.git/tree/ace5978891ce0c2e1700945a296881fc62109701

The branch, master has been updated
       via  ace5978891ce0c2e1700945a296881fc62109701 (commit)
       via  44feffba4ab7178cca1dbdb4e218d6a93861f804 (commit)
       via  9f526a19e9f6085cd0bda8b45b7f2dc347ca5a5b (commit)
       via  5d1a706bfc5d25ea0fc2060772e44222ea711df5 (commit)
       via  d6126aa77eb442f446d28b6dfb15a884f209a341 (commit)
       via  171cb33082983c145ac9818b94e38f486c5f407b (commit)
       via  c0ce0471b979d6fe39a16c7aed4f183922219e2e (commit)
       via  f6bc2001832f1941ea40d52ab1333f3ceb76348a (commit)
       via  27657ff21c4dfff87ef1830cecdbef5dffec9910 (commit)
       via  958136f0ae2544ad6f82a89e9b14cd96fbe5339d (commit)
       via  e42684ed2ee84301a0a458fe846e8e107d19bf96 (commit)
       via  0da84ebf34d68cc1f89d057086fdd0f24747b258 (commit)
       via  74ecce7e2dd5272a9978970307d288613d1d171e (commit)
       via  30bc166207d3fa672a961729e6fe057a77c85db0 (commit)
       via  45c847d897e36d30c8d95b7729251454328cbd4b (commit)
       via  d0aefa94e80b0f2b2ad449d281d0fa9a68ab7d39 (commit)
       via  965117e95ba32a4261b1eeaa1fd9a70f541e7e1b (commit)
       via  06278f34e47a257a1c8cc2a1ca053c330b1e34d5 (commit)
       via  221e1d2f9b82d0833b81b23f083c96991d8dd805 (commit)
       via  32221bc146be61fba1296c58ed072cdf84b4de6f (commit)
       via  861681b2717ed605f26af326d82d9b2009714c2e (commit)
       via  db3834aa96dc06f4be51d936246e2a297e02fecf (commit)
       via  76474f669d55122d3ddc5d7898ea753312c116fd (commit)
       via  3342c8e3bafba86ff1154db940891a3405cb1c30 (commit)
       via  9eea40513329fbd10d494f5a2d29664847043c4c (commit)
       via  f95b1cc7a0d2574ec813176c60874e1b8063c56b (commit)
       via  e3f8652f083435f6c51409c29ec5a41acaca0c70 (commit)
       via  3a5b9a97c45580e938396b46567af580a7e8048e (commit)
       via  4ddb36bdf12d754b1e7d49060a19e4886d190640 (commit)
       via  55c0ff7fb7faa1383f5fbda7261d7e1be6249220 (commit)
       via  55c5cb22e615ed91098a5ace3de29e17c5dc509b (commit)
       via  960f641a354b638795084df3475ffac191ef3abf (commit)
       via  f435c611b52bee6ca4375a59a63d3dc9e924519b (commit)
       via  7c38f6c3ce7622ae2e12589a70579b440992efcb (commit)
       via  819a78a0eb742c04ec3fdfeaeba97ad4962c8eaa (commit)
       via  0311677f5676e2f6213a774938dbdc893897cebb (commit)
       via  52131876ea0a87f94963d7d00d04582ce379cdcb (commit)
       via  f6be004fab719765c08f89d9bddbd0585c4632ab (commit)
       via  df9f2c91ff5c2f117fcd34317f865463e70b4063 (commit)
       via  6b6c2eee7f9845f563a008fa2d78667ddaf781bc (commit)
       via  c1f3546185fefe10c4e6585d8aa772a8bea3f26a (commit)
       via  e90c1223068c6c18e91ed9c0f572c65d50f96bc3 (commit)
       via  e1fb3f9af1b953651f2c90ac3001564f03e867bd (commit)
       via  0b5d61d41b786ff6c081e5f44d12b061cf453caa (commit)
       via  38cad9c5adc597448c9ea73ece67d36c012a2cd8 (commit)
       via  e47d8dcc95f8fbe830a2b7852ba0af0d60f1a29b (commit)
       via  a1347619c5f9ad96d2462a568e625fec567594e2 (commit)
       via  dc5dc8aaba115bb678be2a5a59a6054027bf28c1 (commit)
       via  f8c906e4c6ff36cefe0803cba6b2a083efe5bcb6 (commit)
      from  4591e4851068ab16afdecbaef5eccacc71344ffa (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commitdiff 
http://git.netsurf-browser.org/libcss.git/commit/?id=ace5978891ce0c2e1700945a296881fc62109701
commit ace5978891ce0c2e1700945a296881fc62109701
Merge: 4591e48 44feffb
Author: Michael Drake <Michael Drake [email protected]>
Commit: Michael Drake <Michael Drake [email protected]>

    Merge branch 'tlsa/jmb/mq2'



-----------------------------------------------------------------------

Summary of changes:
 docs/Bytecode                       |    9 +
 examples/example1.c                 |    7 +-
 include/libcss/select.h             |    8 +-
 include/libcss/stylesheet.h         |    5 +-
 include/libcss/types.h              |  137 ++++-
 src/bytecode/bytecode.h             |    8 +-
 src/parse/Makefile                  |    2 +-
 src/parse/language.c                |  112 +---
 src/parse/mq.c                      | 1150 +++++++++++++++++++++++++++++++++++
 src/parse/mq.h                      |  100 +++
 src/parse/parse.c                   |  204 ++++---
 src/parse/parse.h                   |    2 +
 src/parse/properties/utils.c        |    8 +
 src/parse/propstrings.c             |    4 +
 src/parse/propstrings.h             |    2 +-
 src/select/computed.c               |    2 +-
 src/select/hash.h                   |    2 +-
 src/select/mq.h                     |   53 +-
 src/select/select.c                 |   60 +-
 src/select/select.h                 |    2 +-
 src/stylesheet.c                    |   19 +-
 src/stylesheet.h                    |    9 +-
 test/css21.c                        |    4 +-
 test/data/parse2/illegal-values.dat |    2 +-
 test/parse-auto.c                   |    4 +-
 test/select.c                       |   27 +-
 26 files changed, 1698 insertions(+), 244 deletions(-)
 create mode 100644 src/parse/mq.c
 create mode 100644 src/parse/mq.h

diff --git a/docs/Bytecode b/docs/Bytecode
index f64656a..dd0f424 100644
--- a/docs/Bytecode
+++ b/docs/Bytecode
@@ -69,6 +69,7 @@ Length is a 32bit numeric value (as described above) and unit 
is as follows:
                        00000000 => deg
                        00000001 => grad
                        00000010 => rad
+                       00000011 => turn
 
        bit 10 set => time unit
                bits 11-31: MBZ
@@ -84,6 +85,14 @@ Length is a 32bit numeric value (as described above) and 
unit is as follows:
                        00000000 => Hz
                        00000001 => kHz
 
+       bit 12 set => resolution unit
+               bits 13-31: MBZ
+               bits 8-11 : MBZ
+               bits 0-7  :
+                       00000000 => dpi
+                       00000001 => dpcm
+                       00000010 => dppx
+
 CSS colours are stored as one 32bit value. See "Colour" for their format.
 
 Shorthand properties
diff --git a/examples/example1.c b/examples/example1.c
index 1c0dcf9..c36a94d 100644
--- a/examples/example1.c
+++ b/examples/example1.c
@@ -161,6 +161,9 @@ int main(int argc, char **argv)
        uint32_t count;
        unsigned int hh;
        css_stylesheet_params params;
+       css_media media = {
+               .type = CSS_MEDIA_SCREEN,
+       };
 
        UNUSED(argc);
        UNUSED(argv);
@@ -210,7 +213,7 @@ int main(int argc, char **argv)
        if (code != CSS_OK)
                die("css_select_ctx_create", code);
        code = css_select_ctx_append_sheet(select_ctx, sheet, CSS_ORIGIN_AUTHOR,
-                       CSS_MEDIA_ALL);
+                       NULL);
        if (code != CSS_OK)
                die("css_select_ctx_append_sheet", code);
        code = css_select_ctx_count_sheets(select_ctx, &count);
@@ -234,7 +237,7 @@ int main(int argc, char **argv)
                lwc_intern_string(element, strlen(element), &element_name);
 
                code = css_select_style(select_ctx, element_name,
-                               CSS_MEDIA_SCREEN, NULL,
+                               &media, NULL,
                                &select_handler, 0,
                                &style);
                if (code != CSS_OK)
diff --git a/include/libcss/select.h b/include/libcss/select.h
index f1de409..ca57456 100644
--- a/include/libcss/select.h
+++ b/include/libcss/select.h
@@ -206,10 +206,10 @@ css_error css_select_ctx_destroy(css_select_ctx *ctx);
 
 css_error css_select_ctx_append_sheet(css_select_ctx *ctx,
                const css_stylesheet *sheet,
-               css_origin origin, uint64_t media);
+               css_origin origin, const char *media);
 css_error css_select_ctx_insert_sheet(css_select_ctx *ctx,
                const css_stylesheet *sheet, uint32_t index,
-               css_origin origin, uint64_t media);
+               css_origin origin, const char *media);
 css_error css_select_ctx_remove_sheet(css_select_ctx *ctx,
                const css_stylesheet *sheet);
 
@@ -221,13 +221,13 @@ css_error css_select_default_style(css_select_ctx *ctx,
                css_select_handler *handler, void *pw,
                css_computed_style **style);
 css_error css_select_style(css_select_ctx *ctx, void *node,
-               uint64_t media, const css_stylesheet *inline_style,
+               const css_media *media, const css_stylesheet *inline_style,
                css_select_handler *handler, void *pw,
                css_select_results **result);
 css_error css_select_results_destroy(css_select_results *results);
 
 css_error css_select_font_faces(css_select_ctx *ctx,
-               uint64_t media, lwc_string *font_family,
+               const css_media *media, lwc_string *font_family,
                css_select_font_faces_results **result);
 css_error css_select_font_faces_results_destroy(
                css_select_font_faces_results *results);
diff --git a/include/libcss/stylesheet.h b/include/libcss/stylesheet.h
index 68c4dfc..542f199 100644
--- a/include/libcss/stylesheet.h
+++ b/include/libcss/stylesheet.h
@@ -36,7 +36,6 @@ typedef css_error (*css_url_resolution_fn)(void *pw,
  * \param pw      Client data
  * \param parent  Stylesheet requesting the import
  * \param url     URL of the imported sheet
- * \param media   Applicable media for the imported sheet
  * \return CSS_OK on success, appropriate error otherwise
  *
  * \note This function will be invoked for notification purposes
@@ -46,7 +45,7 @@ typedef css_error (*css_url_resolution_fn)(void *pw,
  *       registration API.
  */
 typedef css_error (*css_import_notification_fn)(void *pw,
-               css_stylesheet *parent, lwc_string *url, uint64_t media);
+               css_stylesheet *parent, lwc_string *url);
 
 /**
  * Callback use to resolve system colour names to RGB values
@@ -145,7 +144,7 @@ css_error css_stylesheet_append_data(css_stylesheet *sheet,
 css_error css_stylesheet_data_done(css_stylesheet *sheet);
 
 css_error css_stylesheet_next_pending_import(css_stylesheet *parent,
-               lwc_string **url, uint64_t *media);
+               lwc_string **url);
 css_error css_stylesheet_register_import(css_stylesheet *parent,
                css_stylesheet *child);
 
diff --git a/include/libcss/types.h b/include/libcss/types.h
index 4f35737..44aef12 100644
--- a/include/libcss/types.h
+++ b/include/libcss/types.h
@@ -60,10 +60,10 @@ typedef enum css_media_type {
        CSS_MEDIA_TTY               = (1 << 8),
        CSS_MEDIA_TV                = (1 << 9),
        CSS_MEDIA_ALL               = CSS_MEDIA_AURAL | CSS_MEDIA_BRAILLE |
-                                      CSS_MEDIA_EMBOSSED | CSS_MEDIA_HANDHELD |
-                                      CSS_MEDIA_PRINT | CSS_MEDIA_PROJECTION |
-                                      CSS_MEDIA_SCREEN | CSS_MEDIA_SPEECH |
-                                      CSS_MEDIA_TTY | CSS_MEDIA_TV
+                                     CSS_MEDIA_EMBOSSED | CSS_MEDIA_HANDHELD |
+                                     CSS_MEDIA_PRINT | CSS_MEDIA_PROJECTION |
+                                     CSS_MEDIA_SCREEN | CSS_MEDIA_SPEECH |
+                                     CSS_MEDIA_TTY | CSS_MEDIA_TV
 } css_media_type;
 
 /**
@@ -116,6 +116,135 @@ typedef enum css_unit {
 } css_unit;
 
 /**
+ * Media orienations
+ */
+typedef enum css_media_orientation {
+       CSS_MEDIA_ORIENTATION_PORTRAIT  = 0,
+       CSS_MEDIA_ORIENTATION_LANDSCAPE = 1
+} css_media_orientation;
+
+/**
+ * Media scans
+ */
+typedef enum css_media_scan {
+       CSS_MEDIA_SCAN_PROGRESSIVE = 0,
+       CSS_MEDIA_SCAN_INTERLACE   = 1
+} css_media_scan;
+
+/**
+ * Media update-frequencies
+ */
+typedef enum css_media_update_frequency {
+       CSS_MEDIA_UPDATE_FREQUENCY_NORMAL = 0,
+       CSS_MEDIA_UPDATE_FREQUENCY_SLOW   = 1,
+       CSS_MEDIA_UPDATE_FREQUENCY_NONE   = 2
+} css_media_update_frequency;
+
+/**
+ * Media block overflows
+ */
+typedef enum css_media_overflow_block {
+       CSS_MEDIA_OVERFLOW_BLOCK_NONE           = 0,
+       CSS_MEDIA_OVERFLOW_BLOCK_SCROLL         = 1,
+       CSS_MEDIA_OVERFLOW_BLOCK_OPTIONAL_PAGED = 2,
+       CSS_MEDIA_OVERFLOW_BLOCK_PAGED          = 3
+} css_media_overflow_block;
+
+/**
+ * Media inline overflows
+ */
+typedef enum css_media_overflow_inline {
+       CSS_MEDIA_OVERFLOW_INLINE_NONE   = 0,
+       CSS_MEDIA_OVERFLOW_INLINE_SCROLL = 1
+} css_media_overflow_inline;
+
+/**
+ * Media pointers
+ */
+typedef enum css_media_pointer {
+       CSS_MEDIA_POINTER_NONE   = 0,
+       CSS_MEDIA_POINTER_COARSE = 1,
+       CSS_MEDIA_POINTER_FINE   = 2
+} css_media_pointer;
+
+/**
+ * Media hovers
+ */
+typedef enum css_media_hover {
+       CSS_MEDIA_HOVER_NONE      = 0,
+       CSS_MEDIA_HOVER_ON_DEMAND = 1,
+       CSS_MEDIA_HOVER_HOVER     = 2
+} css_media_hover;
+
+/**
+ * Media light-levels
+ */
+typedef enum css_media_light_level {
+       CSS_MEDIA_LIGHT_LEVEL_NORMAL = 0,
+       CSS_MEDIA_LIGHT_LEVEL_DIM    = 1,
+       CSS_MEDIA_LIGHT_LEVEL_WASHED = 2
+} css_media_light_level;
+
+/**
+ * Media scriptings
+ */
+typedef enum css_media_scripting {
+       CSS_MEDIA_SCRIPTING_NONE         = 0,
+       CSS_MEDIA_SCRIPTING_INITIAL_ONLY = 1,
+       CSS_MEDIA_SCRIPTING_ENABLED      = 2
+} css_media_scripting;
+
+typedef struct css_media_length {
+       css_fixed value;
+       css_unit unit;
+} css_media_length;
+
+typedef struct css_media_resolution {
+       css_fixed value;
+       css_unit unit;
+} css_media_resolution;
+
+/**
+ * Media specification
+ */
+typedef struct css_media {
+       /* Media type */
+       css_media_type        type;
+
+       /* Screen / Device media features */
+       css_media_length      width;
+       css_media_length      height;
+       css_fixed             aspect_ratio;
+       css_media_orientation orientation;
+
+       /* Display quality media features */
+       css_media_resolution       resolution;
+       css_media_scan             scan;
+       css_fixed                  grid; /** boolean: {0|1} */
+       css_media_update_frequency update;
+       css_media_overflow_block   overflow_block;
+       css_media_overflow_inline  overflow_inline;
+
+       /* Color media features */
+       css_fixed color;      /* colour bpp (0 for monochrome) */
+       css_fixed color_index;
+       css_fixed monochrome; /* monochrome bpp (0 for colour) */
+       css_fixed inverted_colors; /** boolean: {0|1} */
+
+       /* Interaction media features */
+       css_media_pointer pointer;
+       css_media_pointer any_pointer;
+       css_media_hover   hover;
+       css_media_hover   any_hover;
+
+       /* Environmental media features */
+       css_media_light_level light_level;
+
+       /* Scripting media features */
+       css_media_scripting scripting;
+} css_media;
+
+/**
  * Type of a qualified name
  */
 typedef struct css_qname {
diff --git a/src/bytecode/bytecode.h b/src/bytecode/bytecode.h
index 422f141..22703f7 100644
--- a/src/bytecode/bytecode.h
+++ b/src/bytecode/bytecode.h
@@ -52,6 +52,7 @@ typedef enum unit {
        UNIT_DEG  = (1 << 9) + 0,
        UNIT_GRAD = (1 << 9) + 1,
        UNIT_RAD  = (1 << 9) + 2,
+       UNIT_TURN = (1 << 9) + 3,
 
        UNIT_TIME = (1 << 10),
        UNIT_MS   = (1 << 10) + 0,
@@ -59,7 +60,12 @@ typedef enum unit {
 
        UNIT_FREQ = (1 << 11),
        UNIT_HZ   = (1 << 11) + 0,
-       UNIT_KHZ  = (1 << 11) + 1
+       UNIT_KHZ  = (1 << 11) + 1,
+
+       UNIT_RESOLUTION = (1 << 12),
+       UNIT_DPI  = (1 << 12) + 0,
+       UNIT_DPCM = (1 << 12) + 1,
+       UNIT_DPPX = (1 << 12) + 2,
 } unit;
 
 typedef uint32_t colour;
diff --git a/src/parse/Makefile b/src/parse/Makefile
index 99f55d0..6d11096 100644
--- a/src/parse/Makefile
+++ b/src/parse/Makefile
@@ -1,4 +1,4 @@
 # Sources
-DIR_SOURCES := parse.c language.c important.c propstrings.c font_face.c
+DIR_SOURCES := parse.c language.c important.c propstrings.c font_face.c mq.c
 
 include $(NSBUILD)/Makefile.subdir
diff --git a/src/parse/language.c b/src/parse/language.c
index 9af2547..54fac9a 100644
--- a/src/parse/language.c
+++ b/src/parse/language.c
@@ -15,6 +15,7 @@
 #include "parse/font_face.h"
 #include "parse/important.h"
 #include "parse/language.h"
+#include "parse/mq.h"
 #include "parse/parse.h"
 #include "parse/propstrings.h"
 #include "parse/properties/properties.h"
@@ -53,9 +54,6 @@ static css_error handleDeclaration(css_language *c,
                const parserutils_vector *vector);
 
 /* At-rule parsing */
-static css_error parseMediaList(css_language *c,
-               const parserutils_vector *vector, int *ctx,
-               uint64_t *media);
 static css_error addNamespace(css_language *c,
                lwc_string *prefix, lwc_string *uri);
 static css_error lookupNamespace(css_language *c,
@@ -416,10 +414,9 @@ css_error handleStartAtRule(css_language *c, const 
parserutils_vector *vector)
                        match) {
                if (c->state <= IMPORT_PERMITTED) {
                        lwc_string *url;
-                       uint64_t media = 0;
+                       css_mq_query *media = NULL;
 
-                       /* any0 = (STRING | URI) ws
-                        *        (IDENT ws (',' ws IDENT ws)* )? */
+                       /* any0 = (STRING | URI) ws (media query)? */
                        const css_token *uri =
                                parserutils_vector_iterate(vector, &ctx);
                        if (uri == NULL || (uri->type != CSS_TOKEN_STRING &&
@@ -429,15 +426,18 @@ css_error handleStartAtRule(css_language *c, const 
parserutils_vector *vector)
                        consumeWhitespace(vector, &ctx);
 
                        /* Parse media list */
-                       error = parseMediaList(c, vector, &ctx, &media);
+                       error = css__mq_parse_media_list(
+                                       c->strings, vector, &ctx, &media);
                        if (error != CSS_OK)
                                return error;
 
                        /* Create rule */
                        error = css__stylesheet_rule_create(c->sheet,
                                        CSS_RULE_IMPORT, &rule);
-                       if (error != CSS_OK)
+                       if (error != CSS_OK) {
+                               css__mq_query_destroy(media);
                                return error;
+                       }
 
                        /* Resolve import URI */
                        error = c->sheet->resolve(c->sheet->resolve_pw,
@@ -445,6 +445,7 @@ css_error handleStartAtRule(css_language *c, const 
parserutils_vector *vector)
                                        uri->idata, &url);
                        if (error != CSS_OK) {
                                css__stylesheet_rule_destroy(c->sheet, rule);
+                               css__mq_query_destroy(media);
                                return error;
                        }
 
@@ -454,13 +455,14 @@ css_error handleStartAtRule(css_language *c, const 
parserutils_vector *vector)
                        if (error != CSS_OK) {
                                lwc_string_unref(url);
                                css__stylesheet_rule_destroy(c->sheet, rule);
+                               css__mq_query_destroy(media);
                                return error;
                        }
 
                        /* Inform client of need for import */
                        if (c->sheet->import != NULL) {
                                error = c->sheet->import(c->sheet->import_pw,
-                                               c->sheet, url, media);
+                                               c->sheet, url);
                                if (error != CSS_OK) {
                                        lwc_string_unref(url);
                                        css__stylesheet_rule_destroy(c->sheet,
@@ -527,22 +529,25 @@ css_error handleStartAtRule(css_language *c, const 
parserutils_vector *vector)
                }
        } else if (lwc_string_caseless_isequal(atkeyword->idata, 
c->strings[MEDIA],
                        &match) == lwc_error_ok && match) {
-               uint64_t media = 0;
+               css_mq_query *media = NULL;
 
-               /* any0 = IDENT ws (',' ws IDENT ws)* */
-
-               error = parseMediaList(c, vector, &ctx, &media);
+               /* any0 = media query */
+               error = css__mq_parse_media_list(
+                               c->strings, vector, &ctx, &media);
                if (error != CSS_OK)
                        return error;
 
                error = css__stylesheet_rule_create(c->sheet,
                                CSS_RULE_MEDIA, &rule);
-               if (error != CSS_OK)
+               if (error != CSS_OK) {
+                       css__mq_query_destroy(media);
                        return error;
+               }
 
                error = css__stylesheet_rule_set_media(c->sheet, rule, media);
                if (error != CSS_OK) {
                        css__stylesheet_rule_destroy(c->sheet, rule);
+                       css__mq_query_destroy(media);
                        return error;
                }
 
@@ -795,85 +800,6 @@ css_error handleDeclaration(css_language *c, const 
parserutils_vector *vector)
  * At-rule parsing functions                                                 *
  
******************************************************************************/
 
-css_error parseMediaList(css_language *c,
-               const parserutils_vector *vector, int *ctx,
-               uint64_t *media)
-{
-       uint64_t ret = 0;
-       bool match = false;
-       const css_token *token;
-
-       token = parserutils_vector_iterate(vector, ctx);
-
-       while (token != NULL) {
-               if (token->type != CSS_TOKEN_IDENT)
-                       return CSS_INVALID;
-
-               if (lwc_string_caseless_isequal(token->idata, c->strings[AURAL],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_AURAL;
-               } else if (lwc_string_caseless_isequal(
-                               token->idata, c->strings[BRAILLE],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_BRAILLE;
-               } else if (lwc_string_caseless_isequal(
-                               token->idata, c->strings[EMBOSSED],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_EMBOSSED;
-               } else if (lwc_string_caseless_isequal(
-                               token->idata, c->strings[HANDHELD],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_HANDHELD;
-               } else if (lwc_string_caseless_isequal(
-                               token->idata, c->strings[PRINT],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_PRINT;
-               } else if (lwc_string_caseless_isequal(
-                               token->idata, c->strings[PROJECTION],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_PROJECTION;
-               } else if (lwc_string_caseless_isequal(
-                               token->idata, c->strings[SCREEN],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_SCREEN;
-               } else if (lwc_string_caseless_isequal(
-                               token->idata, c->strings[SPEECH],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_SPEECH;
-               } else if (lwc_string_caseless_isequal(
-                               token->idata, c->strings[TTY],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_TTY;
-               } else if (lwc_string_caseless_isequal(
-                               token->idata, c->strings[TV],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_TV;
-               } else if (lwc_string_caseless_isequal(
-                               token->idata, c->strings[ALL],
-                               &match) == lwc_error_ok && match) {
-                       ret |= CSS_MEDIA_ALL;
-               } else
-                       return CSS_INVALID;
-
-               consumeWhitespace(vector, ctx);
-
-               token = parserutils_vector_iterate(vector, ctx);
-               if (token != NULL && tokenIsChar(token, ',') == false)
-                       return CSS_INVALID;
-
-               consumeWhitespace(vector, ctx);
-       }
-
-       /* If, after parsing the media list, we still have no media,
-        * then it must be ALL. */
-       if (ret == 0)
-               ret = CSS_MEDIA_ALL;
-
-       *media = ret;
-
-       return CSS_OK;
-}
-
 /**
  * Add a namespace mapping
  *
diff --git a/src/parse/mq.c b/src/parse/mq.c
new file mode 100644
index 0000000..cb9345d
--- /dev/null
+++ b/src/parse/mq.c
@@ -0,0 +1,1150 @@
+/*
+ * This file is part of LibCSS.
+ * Licensed under the MIT License,
+ *               http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2016 John-Mark Bell <[email protected]>
+ */
+
+/* https://drafts.csswg.org/mediaqueries/ */
+
+#include <string.h>
+
+#include <libcss/fpmath.h>
+
+#include "stylesheet.h"
+#include "bytecode/bytecode.h"
+#include "parse/mq.h"
+#include "parse/properties/utils.h"
+#include "utils/utils.h"
+
+static void css__mq_value_destroy(css_mq_value *value)
+{
+       assert(value != NULL);
+
+       if (value->type == CSS_MQ_VALUE_TYPE_IDENT) {
+               lwc_string_unref(value->data.ident);
+       }
+}
+
+static void css__mq_feature_destroy(css_mq_feature *feature)
+{
+       if (feature != NULL) {
+               lwc_string_unref(feature->name);
+               css__mq_value_destroy(&feature->value);
+               css__mq_value_destroy(&feature->value2);
+               free(feature);
+       }
+}
+
+static void css__mq_cond_or_feature_destroy(
+               css_mq_cond_or_feature *cond_or_feature);
+
+static void css__mq_cond_parts_destroy(css_mq_cond_parts *cond_parts)
+{
+       if (cond_parts != NULL) {
+               for (uint32_t i = 0; i < cond_parts->nparts; i++) {
+                       css__mq_cond_or_feature_destroy(cond_parts->parts[i]);
+               }
+               free(cond_parts->parts);
+               free(cond_parts);
+       }
+}
+
+static void css__mq_cond_destroy(css_mq_cond *cond)
+{
+       if (cond != NULL) {
+               css__mq_cond_parts_destroy(cond->parts);
+               free(cond);
+       }
+}
+
+static void css__mq_cond_or_feature_destroy(
+               css_mq_cond_or_feature *cond_or_feature)
+{
+       if (cond_or_feature != NULL) {
+               switch (cond_or_feature->type) {
+               case CSS_MQ_FEATURE:
+                       css__mq_feature_destroy(cond_or_feature->data.feat);
+                       break;
+               case CSS_MQ_COND:
+                       css__mq_cond_destroy(cond_or_feature->data.cond);
+                       break;
+               }
+               free(cond_or_feature);
+       }
+}
+
+void css__mq_query_destroy(css_mq_query *media)
+{
+       while (media != NULL) {
+               css_mq_query *next = media->next;
+
+               css__mq_cond_destroy(media->cond);
+               free(media);
+
+               media = next;
+       }
+}
+
+static css_error mq_parse_condition(lwc_string **strings,
+               const parserutils_vector *vector, int *ctx,
+               bool permit_or, css_mq_cond **cond);
+
+static css_error mq_parse_ratio(
+               const parserutils_vector *vector, int *ctx,
+               const css_token *numerator, css_fixed *ratio)
+{
+       const css_token *token;
+       css_fixed num, den;
+       size_t num_len, den_len;
+
+       /* NUMBER ws* '/' ws* NUMBER */
+
+       /* numerator, ws* already consumed */
+
+       token = parserutils_vector_iterate(vector, ctx);
+       if (token == NULL || tokenIsChar(token, '/') == false) {
+               return CSS_INVALID;
+       }
+
+       consumeWhitespace(vector, ctx);
+
+       token = parserutils_vector_iterate(vector, ctx);
+       if (token == NULL || token->type != CSS_TOKEN_NUMBER) {
+               return CSS_INVALID;
+       }
+
+       num = css__number_from_lwc_string(numerator->idata, true, &num_len);
+       den = css__number_from_lwc_string(token->idata, true, &den_len);
+
+       *ratio = css_divide_fixed(num, den);
+
+       return CSS_OK;
+}
+
+static css_error mq_create_feature(
+               lwc_string *name,
+               css_mq_feature **feature)
+{
+       css_mq_feature *f;
+
+       f = malloc(sizeof(*f));
+       if (f == NULL) {
+               return CSS_NOMEM;
+       }
+
+       memset(f, 0, sizeof(*f));
+
+       f->name = lwc_string_ref(name);
+
+       *feature = f;
+
+       return CSS_OK;
+}
+
+static css_error mq_populate_value(css_mq_value *value,
+               const css_token *token)
+{
+       if (token->type == CSS_TOKEN_NUMBER) {
+               size_t num_len;
+               value->type = CSS_MQ_VALUE_TYPE_NUM;
+               value->data.num_or_ratio = css__number_from_lwc_string(
+                               token->idata, false, &num_len);
+       } else if (token->type == CSS_TOKEN_DIMENSION) {
+               size_t len = lwc_string_length(token->idata);
+               const char *data = lwc_string_data(token->idata);
+               uint32_t unit = UNIT_PX;
+               size_t consumed;
+               css_error error;
+
+               value->type = CSS_MQ_VALUE_TYPE_DIM;
+               value->data.dim.len = css__number_from_lwc_string(
+                               token->idata, false, &consumed);
+               error = css__parse_unit_keyword(data + consumed, len - consumed,
+                               &unit);
+               if (error != CSS_OK) {
+                       return error;
+               }
+               value->data.dim.unit = unit;
+       } else if (token->type == CSS_TOKEN_IDENT) {
+               value->type = CSS_MQ_VALUE_TYPE_IDENT;
+               value->data.ident = lwc_string_ref(token->idata);
+       }
+
+       return CSS_OK;
+}
+
+static css_error mq_parse_op(const css_token *token,
+               css_mq_feature_op *op)
+{
+       size_t len;
+       const char *data;
+
+       if (token == NULL || token->type != CSS_TOKEN_CHAR)
+               return CSS_INVALID;
+
+       len = lwc_string_length(token->idata);
+       data = lwc_string_data(token->idata);
+
+       if (len == 2) {
+               if (strncasecmp(data, "<=", 2) == 0)
+                       *op = CSS_MQ_FEATURE_OP_LTE;
+               else if (strncasecmp(data, ">=", 2) == 0)
+                       *op = CSS_MQ_FEATURE_OP_GTE;
+               else
+                       return CSS_INVALID;
+       } else if (len == 1) {
+               if (*data == '<')
+                       *op = CSS_MQ_FEATURE_OP_LT;
+               else if (*data == '=')
+                       *op = CSS_MQ_FEATURE_OP_EQ;
+               else if (*data == '>')
+                       *op = CSS_MQ_FEATURE_OP_GT;
+               else
+                       return CSS_INVALID;
+       } else {
+               return CSS_INVALID;
+       }
+
+       return CSS_OK;
+}
+
+static css_error mq_parse_range(lwc_string **strings,
+               const parserutils_vector *vector, int *ctx,
+               const css_token *name_or_value,
+               css_mq_feature **feature)
+{
+       const css_token *token, *value_or_name, *name = NULL, *value2 = NULL;
+       css_mq_feature *result;
+       css_mq_feature_op op, op2;
+       css_fixed ratio, ratio2;
+       bool name_first = false, value_is_ratio = false, value2_is_ratio = 
false, match;
+       css_error error;
+
+       /* <mf-range> = <mf-name> [ '<' | '>' ]? '='? <mf-value>
+        *            | <mf-value> [ '<' | '>' ]? '='? <mf-name>
+        *            | <mf-value> '<' '='? <mf-name> '<' '='? <mf-value>
+        *            | <mf-value> '>' '='? <mf-name> '>' '='? <mf-value>
+        */
+
+       if (name_or_value == NULL || (name_or_value->type != CSS_TOKEN_NUMBER &&
+                       name_or_value->type != CSS_TOKEN_DIMENSION &&
+                       name_or_value->type != CSS_TOKEN_IDENT)) {
+               return CSS_INVALID;
+       }
+
+       consumeWhitespace(vector, ctx);
+
+       /* Name-or-value */
+       if (name_or_value->type == CSS_TOKEN_NUMBER &&
+                       tokenIsChar(parserutils_vector_peek(vector, *ctx), 
'/')) {
+               /* ratio */
+               error = mq_parse_ratio(vector, ctx, name_or_value, &ratio);
+               if (error != CSS_OK) {
+                       return error;
+               }
+
+               consumeWhitespace(vector, ctx);
+
+               value_is_ratio = true;
+       } else if (name_or_value->type == CSS_TOKEN_IDENT &&
+                       lwc_string_caseless_isequal(name_or_value->idata,
+                               strings[INFINITE], &match) == lwc_error_ok && 
+                       match == false) {
+               /* The only ident permitted for mf-value is 'infinite', thus 
must have name */
+               name = name_or_value;
+               name_first = true;
+       }
+
+       /* Op */
+       token = parserutils_vector_iterate(vector, ctx);
+       error = mq_parse_op(token, &op);
+       if (error != CSS_OK) {
+               return error;
+       }
+
+       consumeWhitespace(vector, ctx);
+
+       /* Value-or-name */
+       value_or_name = parserutils_vector_iterate(vector, ctx);
+       if (value_or_name == NULL || (value_or_name->type != CSS_TOKEN_NUMBER &&
+                       value_or_name->type != CSS_TOKEN_DIMENSION &&
+                       value_or_name->type != CSS_TOKEN_IDENT)) {
+               return CSS_INVALID;
+       }
+
+       if (name == NULL) {
+               if (value_or_name->type != CSS_TOKEN_IDENT) {
+                       return CSS_INVALID;
+               } else {
+                       name = value_or_name;
+               }
+       }
+
+       consumeWhitespace(vector, ctx);
+
+       if (value_or_name->type == CSS_TOKEN_NUMBER &&
+                       tokenIsChar(parserutils_vector_peek(vector, *ctx), 
'/')) {
+               /* ratio */
+               error = mq_parse_ratio(vector, ctx, token, &ratio);
+               if (error != CSS_OK) {
+                       return error;
+               }
+
+               consumeWhitespace(vector, ctx);
+
+               value_is_ratio = true;
+       }
+
+       token = parserutils_vector_peek(vector, *ctx);
+       if (name_first == false && token != NULL && tokenIsChar(token, ')') == 
false) {
+               /* Op2 */
+               token = parserutils_vector_iterate(vector, ctx);
+               error = mq_parse_op(token, &op2);
+               if (error != CSS_OK) {
+                       return error;
+               }
+
+               consumeWhitespace(vector, ctx);
+
+               /* Validate operators: must both be LT(E) or GT(E) */
+               if (op == CSS_MQ_FEATURE_OP_LT || op == CSS_MQ_FEATURE_OP_LTE) {
+                       if (op2 != CSS_MQ_FEATURE_OP_LT && op2 != 
CSS_MQ_FEATURE_OP_LTE) {
+                               return CSS_INVALID;
+                       }
+               } else if (op == CSS_MQ_FEATURE_OP_GT || op == 
CSS_MQ_FEATURE_OP_GTE) {
+                      if (op2 != CSS_MQ_FEATURE_OP_GT && op2 != 
CSS_MQ_FEATURE_OP_GTE) {
+                               return CSS_INVALID;
+                       }
+               } else {
+                       return CSS_INVALID;
+               }
+
+               /* Value2 */
+               value2 = parserutils_vector_iterate(vector, ctx);
+               if (value2 == NULL || (value2->type != CSS_TOKEN_NUMBER &&
+                               value2->type != CSS_TOKEN_DIMENSION &&
+                               value2->type != CSS_TOKEN_IDENT)) {
+                       return CSS_INVALID;
+               }
+
+               consumeWhitespace(vector, ctx);
+
+               if (value_or_name->type == CSS_TOKEN_NUMBER &&
+                               tokenIsChar(parserutils_vector_peek(vector, 
*ctx), '/')) {
+                       /* ratio */
+                       error = mq_parse_ratio(vector, ctx, token, &ratio2);
+                       if (error != CSS_OK) {
+                               return error;
+                       }
+
+                       consumeWhitespace(vector, ctx);
+
+                       value2_is_ratio = true;
+               }
+       }
+
+       error = mq_create_feature(name->idata, &result);
+       if (error != CSS_OK) {
+               return error;
+       }
+       if (name_first) {
+               /* Invert operator */
+               if (op == CSS_MQ_FEATURE_OP_LT) {
+                       op = CSS_MQ_FEATURE_OP_GTE;
+               } else if (op == CSS_MQ_FEATURE_OP_LTE) {
+                       op = CSS_MQ_FEATURE_OP_GT;
+               } else if (op == CSS_MQ_FEATURE_OP_GT) {
+                       op = CSS_MQ_FEATURE_OP_LTE;
+               } else if (op == CSS_MQ_FEATURE_OP_GTE) {
+                       op = CSS_MQ_FEATURE_OP_LT;
+               }
+       }
+       result->op = op;
+       if (value_is_ratio) {
+               result->value.type = CSS_MQ_VALUE_TYPE_RATIO;
+               result->value.data.num_or_ratio = ratio;
+       } else {
+               /* num/dim/ident */
+               error = mq_populate_value(&result->value, token);
+               if (error != CSS_OK) {
+                       free(result);
+                       return error;
+               }
+       }
+       if (value2 != NULL) {
+               result->op2 = op2;
+               if (value2_is_ratio) {
+                       result->value2.type = CSS_MQ_VALUE_TYPE_RATIO;
+                       result->value2.data.num_or_ratio = ratio;
+               } else {
+                       /* num/dim/ident */
+                       error = mq_populate_value(&result->value2, token);
+                       if (error != CSS_OK) {
+                               css__mq_feature_destroy(result);
+                               return error;
+                       }
+               }
+       }
+
+       *feature = result;
+
+       return CSS_OK;
+}
+
+static css_error mq_parse_media_feature(lwc_string **strings,
+               const parserutils_vector *vector, int *ctx,
+               css_mq_feature **feature)
+{
+       const css_token *name_or_value, *token;
+       css_mq_feature *result;
+       css_error error;
+
+       /* <media-feature> = ( [ <mf-plain> | <mf-boolean> | <mf-range> ] )
+        * <mf-plain> = <mf-name> : <mf-value>
+        * <mf-boolean> = <mf-name>
+        * <mf-name> = <ident>
+        * <mf-value> = <number> | <dimension> | <ident> | <ratio>
+        */
+
+       /* ( already consumed */
+
+       consumeWhitespace(vector, ctx);
+
+       name_or_value = parserutils_vector_iterate(vector, ctx);
+       if (name_or_value == NULL)
+               return CSS_INVALID;
+
+       if (name_or_value->type == CSS_TOKEN_IDENT) {
+               consumeWhitespace(vector, ctx);
+
+               token = parserutils_vector_peek(vector, *ctx);
+               if (tokenIsChar(token, ')')) {
+                       /* mf-boolean */
+                       error = mq_create_feature(name_or_value->idata, 
&result);
+                       if (error != CSS_OK) {
+                               return error;
+                       }
+
+                       result->op = CSS_MQ_FEATURE_OP_BOOL;
+               } else if (tokenIsChar(token, ':')) {
+                       /* mf-plain */
+                       parserutils_vector_iterate(vector, ctx);
+
+                       consumeWhitespace(vector, ctx);
+
+                       token = parserutils_vector_iterate(vector, ctx);
+                       if (token == NULL || (token->type != CSS_TOKEN_NUMBER &&
+                                       token->type != CSS_TOKEN_DIMENSION &&
+                                       token->type != CSS_TOKEN_IDENT)) {
+                               return CSS_INVALID;
+                       }
+
+                       consumeWhitespace(vector, ctx);
+
+                       error = mq_create_feature(name_or_value->idata, 
&result);
+                       if (error != CSS_OK) {
+                               return error;
+                       }
+                       result->op = CSS_MQ_FEATURE_OP_EQ;
+
+                       if (token->type == CSS_TOKEN_NUMBER &&
+                                       
tokenIsChar(parserutils_vector_peek(vector, *ctx), '/')) {
+                               /* ratio */
+                               css_fixed ratio;
+
+                               error = mq_parse_ratio(vector, ctx, token, 
&ratio);
+                               if (error != CSS_OK) {
+                                       free(result);
+                                       return error;
+                               }
+
+                               result->value.type = CSS_MQ_VALUE_TYPE_RATIO;
+                               result->value.data.num_or_ratio = ratio;
+                       } else {
+                               /* num/dim/ident */
+                               error = mq_populate_value(&result->value, 
token);
+                               if (error != CSS_OK) {
+                                       free(result);
+                                       return error;
+                               }
+                       }
+
+                       consumeWhitespace(vector, ctx);
+               } else {
+                       /* mf-range */
+                       error = mq_parse_range(strings, vector, ctx,
+                                       name_or_value, &result);
+                       if (error != CSS_OK) {
+                               return error;
+                       }
+
+                       consumeWhitespace(vector, ctx);
+               }
+       } else {
+               /* mf-range */
+               error = mq_parse_range(strings, vector, ctx,
+                               name_or_value, &result);
+               if (error != CSS_OK) {
+                       return error;
+               }
+
+               consumeWhitespace(vector, ctx);
+       }
+
+       token = parserutils_vector_iterate(vector, ctx);
+       if (tokenIsChar(token, ')') == false) {
+               css__mq_feature_destroy(result);
+               return CSS_INVALID;
+       }
+
+       *feature = result;
+
+       return CSS_OK;
+}
+
+/*
+ * Consume any value
+ *
+ * CSS Syntax Module Level 3: 8.2
+ */
+static css_error mq_parse_consume_any_value(lwc_string **strings,
+               const parserutils_vector *vector, int *ctx,
+               bool until, const char until_char)
+{
+       const css_token *token;
+       css_error error;
+
+       while (true) {
+               consumeWhitespace(vector, ctx);
+
+               token = parserutils_vector_iterate(vector, ctx);
+               if (token == NULL) {
+                       return CSS_INVALID;
+               }
+
+               switch (token->type) {
+               case CSS_TOKEN_INVALID_STRING:
+                       return CSS_INVALID;
+
+               case CSS_TOKEN_CHAR:
+                       if (until && tokenIsChar(token, until_char)) {
+                               /* Found matching close bracket */
+                               return CSS_OK;
+
+                       } else if (tokenIsChar(token, ')') ||
+                                  tokenIsChar(token, ']') ||
+                                  tokenIsChar(token, '}')) {
+                               /* Non-matching close bracket */
+                               return CSS_INVALID;
+                       }
+                       if (tokenIsChar(token, '(')) {
+                               /* Need to consume until matching bracket. */
+                               error = mq_parse_consume_any_value(strings,
+                                               vector, ctx, true, ')');
+                               if (error != CSS_OK) {
+                                       return error;
+                               }
+                       } else if (tokenIsChar(token, '[')) {
+                               /* Need to consume until matching bracket. */
+                               error = mq_parse_consume_any_value(strings,
+                                               vector, ctx, true, ']');
+                               if (error != CSS_OK) {
+                                       return error;
+                               }
+                       } else if (tokenIsChar(token, '{')) {
+                               /* Need to consume until matching bracket. */
+                               error = mq_parse_consume_any_value(strings,
+                                               vector, ctx, true, '}');
+                               if (error != CSS_OK) {
+                                       return error;
+                               }
+                       }
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       return CSS_OK;
+}
+
+static css_error mq_parse_general_enclosed(lwc_string **strings,
+               const parserutils_vector *vector, int *ctx)
+{
+       const css_token *token;
+       css_error error;
+
+       /* <general-enclosed> = [ <function-token> <any-value> ) ]
+        *                    | ( <ident> <any-value> )
+        */
+
+       token = parserutils_vector_iterate(vector, ctx);
+       if (token == NULL) {
+               return CSS_INVALID;
+       }
+
+       switch (token->type) {
+       case CSS_TOKEN_FUNCTION:
+               error = mq_parse_consume_any_value(strings, vector, ctx,
+                               true, ')');
+               if (error != CSS_OK) {
+                       return error;
+               }
+
+               token = parserutils_vector_peek(vector, *ctx);
+               if (!tokenIsChar(token, ')')) {
+                       return CSS_INVALID;
+               }
+               break;
+
+       case CSS_TOKEN_IDENT:
+               error = mq_parse_consume_any_value(strings, vector, ctx,
+                               false, '\0');
+               if (error != CSS_OK) {
+                       return error;
+               }
+               break;
+
+       default:
+               return CSS_INVALID;
+       }
+
+       return CSS_OK;
+}
+
+static css_error mq_parse_media_in_parens(lwc_string **strings,
+               const parserutils_vector *vector, int *ctx,
+               css_mq_cond_or_feature **cond_or_feature)
+{
+       const css_token *token;
+       bool match;
+       int old_ctx;
+       css_mq_cond_or_feature *result = NULL;
+       css_error error = CSS_OK;
+
+       /* <media-in-parens> = ( <media-condition> ) | <media-feature> | 
<general-enclosed>
+        */
+
+       //LPAREN -> condition-or-feature
+       //        "not" or LPAREN -> condition
+       //        IDENT | NUMBER | DIMENSION | RATIO -> feature
+
+       token = parserutils_vector_iterate(vector, ctx);
+       if (token == NULL || tokenIsChar(token, '(') == false) {
+               return CSS_INVALID;
+       }
+
+       consumeWhitespace(vector, ctx);
+
+       token = parserutils_vector_peek(vector, *ctx);
+       if (token == NULL) {
+               return CSS_INVALID;
+       }
+
+       old_ctx = *ctx;
+
+       if (tokenIsChar(token, '(') || (token->type == CSS_TOKEN_IDENT &&
+                       lwc_string_caseless_isequal(token->idata,
+                               strings[NOT], &match) == lwc_error_ok &&
+                       match)) {
+               css_mq_cond *cond;
+               error = mq_parse_condition(strings, vector, ctx, true, &cond);
+               if (error == CSS_OK) {
+                       token = parserutils_vector_iterate(vector, ctx);
+                       if (tokenIsChar(token, ')') == false) {
+                               return CSS_INVALID;
+                       }
+
+                       result = malloc(sizeof(*result));
+                       if (result == NULL) {
+                               css__mq_cond_destroy(cond);
+                               return CSS_NOMEM;
+                       }
+                       memset(result, 0, sizeof(*result));
+                       result->type = CSS_MQ_COND;
+                       result->data.cond = cond;
+                       *cond_or_feature = result;
+                       return CSS_OK;
+               }
+       } else if (token->type == CSS_TOKEN_IDENT ||
+                       token->type == CSS_TOKEN_NUMBER ||
+                       token->type == CSS_TOKEN_DIMENSION) {
+               css_mq_feature *feature;
+               error = mq_parse_media_feature(strings, vector, ctx, &feature);
+               if (error == CSS_OK) {
+                       result = malloc(sizeof(*result));
+                       if (result == NULL) {
+                               css__mq_feature_destroy(feature);
+                               return CSS_NOMEM;
+                       }
+                       memset(result, 0, sizeof(*result));
+                       result->type = CSS_MQ_FEATURE;
+                       result->data.feat = feature;
+                       *cond_or_feature = result;
+                       return CSS_OK;
+               }
+       }
+
+       *ctx = old_ctx;
+       error = mq_parse_general_enclosed(strings, vector, ctx);
+
+       return error;
+}
+
+static css_error mq_parse_condition(lwc_string **strings,
+               const parserutils_vector *vector, int *ctx,
+               bool permit_or, css_mq_cond **cond)
+{
+       const css_token *token;
+       bool match = false;
+       int op = 0; /* Will be AND | OR once we've had one */
+       css_mq_cond_or_feature *cond_or_feature, **parts;
+       css_mq_cond *result;
+       css_error error;
+
+       /* <media-condition> = <media-not> | <media-in-parens> [ <media-and>* | 
<media-or>* ]
+        * <media-condition-without-or> = <media-not> | <media-in-parens> 
<media-and>*
+        * <media-not> = not <media-in-parens>
+        * <media-and> = and <media-in-parens>
+        * <media-or> = or <media-in-parens>
+        */
+
+       token = parserutils_vector_peek(vector, *ctx);
+       if (token == NULL ||
+                       (tokenIsChar(token, '(') == false &&
+                       token->type != CSS_TOKEN_IDENT &&
+                       lwc_string_caseless_isequal(token->idata,
+                               strings[NOT], &match) != lwc_error_ok &&
+                       match == false)) {
+               return CSS_INVALID;
+       }
+
+       result = malloc(sizeof(*result));
+       if (result == NULL) {
+               return CSS_NOMEM;
+       }
+       memset(result, 0, sizeof(*result));
+       result->parts = malloc(sizeof(*result->parts));
+       if (result->parts == NULL) {
+               free(result);
+               return CSS_NOMEM;
+       }
+       memset(result->parts, 0, sizeof(*result->parts));
+
+       if (tokenIsChar(token, '(') == false) {
+               /* Must be "not" */
+               parserutils_vector_iterate(vector, ctx);
+               consumeWhitespace(vector, ctx);
+
+               error = mq_parse_media_in_parens(strings,
+                               vector, ctx, &cond_or_feature);
+               if (error != CSS_OK) {
+                       css__mq_cond_destroy(result);
+                       return CSS_INVALID;
+               }
+
+               result->negate = 1;
+               result->parts->nparts = 1;
+               result->parts->parts = malloc(sizeof(*result->parts->parts));
+               if (result->parts->parts == NULL) {
+                       css__mq_cond_or_feature_destroy(cond_or_feature);
+                       css__mq_cond_destroy(result);
+                       return CSS_NOMEM;
+               }
+               result->parts->parts[0] = cond_or_feature;
+
+               *cond = result;
+
+               return CSS_OK;
+       }
+
+       /* FOLLOW(media-condition) := RPAREN | COMMA | EOF */
+       while (token != NULL && tokenIsChar(token, ')') == false &&
+                       tokenIsChar(token, ',') == false) {
+               error = mq_parse_media_in_parens(strings, vector, ctx,
+                               &cond_or_feature);
+               if (error != CSS_OK) {
+                       css__mq_cond_destroy(result);
+                       return CSS_INVALID;
+               }
+
+               parts = realloc(result->parts->parts,
+                               
(result->parts->nparts+1)*sizeof(*result->parts->parts));
+               if (parts == NULL) {
+                       css__mq_cond_or_feature_destroy(cond_or_feature);
+                       css__mq_cond_destroy(result);
+                       return CSS_NOMEM;
+               }
+               parts[result->parts->nparts] = cond_or_feature;
+               result->parts->parts = parts;
+               result->parts->nparts++;
+
+               consumeWhitespace(vector, ctx);
+
+               token = parserutils_vector_peek(vector, *ctx);
+               if (token != NULL && tokenIsChar(token, ')') == false &&
+                               tokenIsChar(token, ',') == false) {
+                       if (token->type != CSS_TOKEN_IDENT) {
+                               css__mq_cond_destroy(result);
+                               return CSS_INVALID;
+                       } else if (lwc_string_caseless_isequal(token->idata,
+                                       strings[AND], &match) == lwc_error_ok &&
+                                       match) {
+                               if (op != 0 && op != AND) {
+                                       css__mq_cond_destroy(result);
+                                       return CSS_INVALID;
+                               }
+                               op = AND;
+                       } else if (lwc_string_caseless_isequal(token->idata,
+                                               strings[OR], &match) == 
lwc_error_ok &&
+                                       match) {
+                               if (permit_or == false || (op != 0 && op != 
OR)) {
+                                       css__mq_cond_destroy(result);
+                                       return CSS_INVALID;
+                               }
+                               op = OR;
+                       } else {
+                               /* Neither AND nor OR */
+                               css__mq_cond_destroy(result);
+                               return CSS_INVALID;
+                       }
+
+                       parserutils_vector_iterate(vector, ctx);
+                       consumeWhitespace(vector, ctx);
+               }
+       }
+
+       if (op == OR) {
+               result->op = 1;
+       }
+
+       *cond = result;
+
+       return CSS_OK;
+}
+
+/**
+ * Parse a media query type.
+ */
+static uint64_t mq_parse_type(lwc_string **strings, lwc_string *type)
+{
+       bool match;
+
+       if (type == NULL) {
+               return CSS_MEDIA_ALL;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[AURAL],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_AURAL;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[BRAILLE],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_BRAILLE;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[EMBOSSED],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_EMBOSSED;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[HANDHELD],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_HANDHELD;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[PRINT],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_PRINT;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[PROJECTION],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_PROJECTION;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[SCREEN],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_SCREEN;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[SPEECH],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_SPEECH;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[TTY],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_TTY;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[TV],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_TV;
+       } else if (lwc_string_caseless_isequal(
+                       type, strings[ALL],
+                       &match) == lwc_error_ok && match) {
+               return CSS_MEDIA_ALL;
+       }
+
+       return 0;
+}
+
+static css_error mq_parse_media_query(lwc_string **strings,
+               const parserutils_vector *vector, int *ctx,
+               css_mq_query **query)
+{
+       const css_token *token;
+       bool match, is_condition = false;
+       css_mq_query *result;
+       css_error error;
+
+       /* <media-query> = <media-condition>
+        *               | [ not | only ]? <media-type> [ and 
<media-condition-without-or> ]?
+        * <media-type> = <ident> (except "not", "and", "or", "only")
+        */
+
+       // LPAREN -> media-condition
+       //    not LPAREN -> media-condition
+
+       consumeWhitespace(vector, ctx);
+
+       token = parserutils_vector_peek(vector, *ctx);
+       if (tokenIsChar(token, '(')) {
+               is_condition = true;
+       } else if (token->type == CSS_TOKEN_IDENT &&
+                       lwc_string_caseless_isequal(token->idata,
+                               strings[NOT], &match) == lwc_error_ok &&
+                               match) {
+               int old_ctx = *ctx;
+
+               parserutils_vector_iterate(vector, ctx);
+               consumeWhitespace(vector, ctx);
+
+               token = parserutils_vector_peek(vector, *ctx);
+               if (tokenIsChar(token, '(')) {
+                       is_condition = true;
+               }
+
+               *ctx = old_ctx;
+       }
+
+       result = malloc(sizeof(*result));
+       if (result == NULL) {
+               return CSS_NOMEM;
+       }
+       memset(result, 0, sizeof(*result));
+
+       if (is_condition) {
+               /* media-condition */
+               error = mq_parse_condition(strings, vector, ctx, true,
+                               &result->cond);
+               if (error != CSS_OK) {
+                       free(result);
+                       return error;
+               }
+
+               *query = result;
+               return CSS_OK;
+       }
+
+       token = parserutils_vector_iterate(vector, ctx);
+       if (token == NULL || token->type != CSS_TOKEN_IDENT) {
+               free(result);
+               return CSS_INVALID;
+       }
+
+       if (lwc_string_caseless_isequal(token->idata,
+                       strings[NOT], &match) == lwc_error_ok && match) {
+               result->negate_type = 1;
+               consumeWhitespace(vector, ctx);
+               token = parserutils_vector_iterate(vector, ctx);
+       } else if (lwc_string_caseless_isequal(token->idata,
+                       strings[ONLY], &match) == lwc_error_ok && match) {
+               consumeWhitespace(vector, ctx);
+               token = parserutils_vector_iterate(vector, ctx);
+       }
+
+       if (token == NULL || token->type != CSS_TOKEN_IDENT) {
+               free(result);
+               return CSS_INVALID;
+       }
+
+       result->type = mq_parse_type(strings, token->idata);
+
+       consumeWhitespace(vector, ctx);
+
+       token = parserutils_vector_iterate(vector, ctx);
+       if (token != NULL) {
+               if (token->type != CSS_TOKEN_IDENT ||
+                               lwc_string_caseless_isequal(token->idata,
+                                       strings[AND], &match) != lwc_error_ok ||
+                               match == false) {
+                       free(result);
+                       return CSS_INVALID;
+               }
+
+               consumeWhitespace(vector, ctx);
+
+               error = mq_parse_condition(strings, vector, ctx, false,
+                               &result->cond);
+               if (error != CSS_OK) {
+                       free(result);
+                       return error;
+               }
+       }
+
+       *query = result;
+       return CSS_OK;
+}
+
+css_error css__mq_parse_media_list(lwc_string **strings,
+               const parserutils_vector *vector, int *ctx,
+               css_mq_query **media)
+{
+       css_mq_query *result = NULL, *last = NULL;
+       const css_token *token;
+       css_error error;
+
+       /* <media-query-list> = <media-query> [ COMMA <media-query> ]* */
+
+       /* if {[(, push }]) to stack
+        * if func, push ) to stack
+        * on error, scan forward until stack is empty (or EOF), popping 
matching tokens off stack
+        * if stack is empty, the next input token must be comma or EOF
+        * if comma, consume, and start again from the next input token
+        */
+
+       token = parserutils_vector_peek(vector, *ctx);
+       while (token != NULL) {
+               css_mq_query *query;
+
+               error = mq_parse_media_query(strings, vector, ctx, &query);
+               if (error != CSS_OK) {
+                       /* TODO: error recovery (see above) */
+                       css__mq_query_destroy(result);
+                       return error;
+               } else {
+                       if (result == NULL) {
+                               result = last = query;
+                       } else {
+                               assert(last != NULL);
+                               last->next = query;
+                               last = query;
+                       }
+               }
+
+               consumeWhitespace(vector, ctx);
+
+               token = parserutils_vector_iterate(vector, ctx);
+               if (token != NULL && tokenIsChar(token, ',') == false) {
+                       /* Give up */
+                       break;
+               }
+       }
+
+       *media = result;
+
+       return CSS_OK;
+}
+
+typedef struct css_mq_parse_ctx {
+       lwc_string **strings;
+       css_mq_query *media;
+} css_mq_parse_ctx;
+
+static css_error css_parse_media_query_handle_event(
+               css_parser_event type,
+               const parserutils_vector *tokens,
+               void *pw)
+{
+       int idx = 0;
+       css_error err;
+       css_mq_query *media;
+       const css_token *tok;
+       css_mq_parse_ctx *ctx = pw;
+       lwc_string **strings = ctx->strings;
+
+       UNUSED(type);
+
+       /* Skip @media */
+       tok = parserutils_vector_iterate(tokens, &idx);
+       assert(tok->type == CSS_TOKEN_ATKEYWORD);
+       UNUSED(tok);
+
+       /* Skip whitespace */
+       tok = parserutils_vector_iterate(tokens, &idx);
+       assert(tok->type == CSS_TOKEN_S);
+       UNUSED(tok);
+
+       err = css__mq_parse_media_list(strings, tokens, &idx, &media);
+       if (err != CSS_OK) {
+               return CSS_OK;
+       }
+
+       ctx->media = media;
+       return CSS_OK;
+}
+
+css_error css_parse_media_query(lwc_string **strings,
+               const uint8_t *mq, size_t len,
+               css_mq_query **media_out)
+{
+       css_error err;
+       css_parser *parser;
+       css_mq_parse_ctx ctx = {
+               .strings = strings,
+       };
+       css_parser_optparams params_quirks = {
+               .quirks = false,
+       };
+       css_parser_optparams params_handler = {
+               .event_handler = {
+                       .handler = css_parse_media_query_handle_event,
+                       .pw = &ctx,
+               },
+       };
+
+       if (mq == NULL || len == 0) {
+               return CSS_BADPARM;
+       }
+
+       err = css__parser_create_for_media_query(NULL,
+                       CSS_CHARSET_DEFAULT, &parser);
+       if (err != CSS_OK) {
+               return err;
+       }
+
+       err = css__parser_setopt(parser, CSS_PARSER_QUIRKS,
+                       &params_quirks);
+       if (err != CSS_OK) {
+               css__parser_destroy(parser);
+               return err;
+       }
+
+       err = css__parser_setopt(parser, CSS_PARSER_EVENT_HANDLER,
+                       &params_handler);
+       if (err != CSS_OK) {
+               css__parser_destroy(parser);
+               return err;
+       }
+
+       err = css__parser_parse_chunk(parser,
+                       (const uint8_t *)"@media ",
+                                 strlen("@media "));
+       if (err != CSS_OK && err != CSS_NEEDDATA) {
+               css__parser_destroy(parser);
+               return err;
+       }
+
+       err = css__parser_parse_chunk(parser, mq, len);
+       if (err != CSS_OK && err != CSS_NEEDDATA) {
+               css__parser_destroy(parser);
+               return err;
+       }
+
+       err = css__parser_completed(parser);
+       if (err != CSS_OK) {
+               css__parser_destroy(parser);
+               return err;
+       }
+
+       css__parser_destroy(parser);
+
+       *media_out = ctx.media;
+       return CSS_OK;
+}
+
diff --git a/src/parse/mq.h b/src/parse/mq.h
new file mode 100644
index 0000000..0e2f845
--- /dev/null
+++ b/src/parse/mq.h
@@ -0,0 +1,100 @@
+/*
+ * This file is part of LibCSS.
+ * Licensed under the MIT License,
+ *               http://www.opensource.org/licenses/mit-license.php
+ * Copyright 2016 John-Mark Bell <[email protected]>
+ */
+
+#ifndef css_parse_mq_h_
+#define css_parse_mq_h_
+
+#include <parserutils/utils/vector.h>
+#include "parse/language.h"
+
+typedef struct {
+       enum {
+               CSS_MQ_VALUE_TYPE_NUM,
+               CSS_MQ_VALUE_TYPE_DIM,
+               CSS_MQ_VALUE_TYPE_IDENT,
+               CSS_MQ_VALUE_TYPE_RATIO
+       } type;
+       union {
+               css_fixed num_or_ratio; /* Where ratio is the result of a/b */
+               struct {
+                       css_fixed len;
+                       uint32_t unit;
+               } dim;
+               lwc_string *ident;
+       } data;
+} css_mq_value;
+
+/*
+ * "name : value" is encoded as "name = value"
+ * "name" is encoded by setting the operator to "bool"
+ * "value op name" is encoded verbatim (with op2 set to "unused")
+ * "name op value" inverts the operator to encode (i.e < becomes >=) (and sets 
op2 to "unused")
+ * "value op name op value" is encoded using op2 and value2
+ */
+typedef enum {
+       CSS_MQ_FEATURE_OP_BOOL, /* op only */
+       CSS_MQ_FEATURE_OP_UNUSED = CSS_MQ_FEATURE_OP_BOOL, /* op2 only */
+
+       CSS_MQ_FEATURE_OP_LT,
+       CSS_MQ_FEATURE_OP_LTE,
+       CSS_MQ_FEATURE_OP_EQ, /* op only */
+       CSS_MQ_FEATURE_OP_GTE,
+       CSS_MQ_FEATURE_OP_GT
+} css_mq_feature_op;
+
+typedef struct {
+       lwc_string *name;
+       css_mq_feature_op op;
+       css_mq_feature_op op2;
+       css_mq_value value;
+       css_mq_value value2;
+} css_mq_feature;
+
+typedef struct css_mq_cond_or_feature css_mq_cond_or_feature;
+
+typedef struct {
+       uint32_t nparts;
+       css_mq_cond_or_feature **parts;
+} css_mq_cond_parts;
+
+typedef struct {
+       uint32_t negate : 1, /* set if "not" */
+                op     : 1; /* clear if "and", set if "or" */
+       css_mq_cond_parts *parts;
+} css_mq_cond;
+
+struct css_mq_cond_or_feature {
+       enum {
+               CSS_MQ_FEATURE,
+               CSS_MQ_COND
+       } type;
+       union {
+               css_mq_cond *cond;
+               css_mq_feature *feat;
+       } data;
+};
+
+typedef struct css_mq_query {
+       struct css_mq_query *next;
+
+       uint32_t negate_type : 1; /* set if "not type" */
+       uint64_t type; /* or NULL */
+
+       css_mq_cond *cond;
+} css_mq_query;
+
+css_error css_parse_media_query(lwc_string **strings,
+               const uint8_t *mq, size_t len,
+               css_mq_query **media_out);
+
+css_error css__mq_parse_media_list(lwc_string **strings,
+               const parserutils_vector *vector, int *ctx,
+               css_mq_query **media);
+
+void css__mq_query_destroy(css_mq_query *media);
+
+#endif
diff --git a/src/parse/parse.c b/src/parse/parse.c
index 4cc1c98..cbd8b56 100644
--- a/src/parse/parse.c
+++ b/src/parse/parse.c
@@ -67,7 +67,8 @@ enum {
        sMalformedAtRule = 22,
        sInlineStyle = 23,
        sISBody0 = 24,
-       sISBody = 25
+       sISBody = 25,
+       sMediaQuery = 26,
 };
 
 /**
@@ -99,8 +100,6 @@ struct css_parser
        bool parseError;                /**< A parse error has occurred */
        parserutils_stack *open_items;  /**< Stack of open brackets */
 
-       uint8_t match_char;             /**< Close bracket type for parseAny */
-
        bool last_was_ws;               /**< Last token was whitespace */
 
        css_parser_event_handler event; /**< Client's event handler */
@@ -146,8 +145,9 @@ static css_error parseMalformedAtRule(css_parser *parser);
 static css_error parseInlineStyle(css_parser *parser);
 static css_error parseISBody0(css_parser *parser);
 static css_error parseISBody(css_parser *parser);
+static css_error parseMediaQuery(css_parser *parser);
 
-static void unref_interned_strings_in_tokens(css_parser *parser);
+static void discard_tokens(css_parser *parser);
 
 /**
  * Dispatch table for parsing, indexed by major state number
@@ -178,7 +178,8 @@ static css_error (*parseFuncs[])(css_parser *parser) = {
        parseMalformedAtRule,
        parseInlineStyle,
        parseISBody0,
-       parseISBody
+       parseISBody,
+       parseMediaQuery,
 };
 
 /**
@@ -220,6 +221,25 @@ css_error css__parser_create_for_inline_style(const char 
*charset,
 }
 
 /**
+ * Create a CSS parser for a media query
+ *
+ * \param charset     Charset of data, if known, or NULL
+ * \param cs_source   Source of charset information, or CSS_CHARSET_DEFAULT
+ * \param parser      Pointer to location to receive parser instance
+ * \return CSS_OK on success,
+ *         CSS_BADPARM on bad parameters,
+ *         CSS_NOMEM on memory exhaustion
+ */
+css_error css__parser_create_for_media_query(const char *charset,
+               css_charset_source cs_source, css_parser **parser)
+{
+       parser_state initial = { sMediaQuery, 0 };
+
+       return css__parser_create_internal(charset, cs_source,
+                       initial, parser);
+}
+
+/**
  * Destroy a CSS parser
  *
  * \param parser  The parser instance to destroy
@@ -459,7 +479,6 @@ css_error css__parser_create_internal(const char *charset,
        p->quirks = false;
        p->pushback = NULL;
        p->parseError = false;
-       p->match_char = 0;
        p->event = NULL;
        p->last_was_ws = false;
        p->event_pw = NULL;
@@ -746,8 +765,7 @@ css_error parseStart(css_parser *parser)
                                parser->event_pw);
        }
 
-        unref_interned_strings_in_tokens(parser);
-       parserutils_vector_clear(parser->tokens);
+       discard_tokens(parser);
 
        return done(parser);
 }
@@ -778,8 +796,7 @@ css_error parseStylesheet(css_parser *parser)
                                if (error != CSS_OK)
                                        return error;
 
-                                unref_interned_strings_in_tokens(parser);
-                               parserutils_vector_clear(parser->tokens);
+                               discard_tokens(parser);
 
                                return done(parser);
                        case CSS_TOKEN_CDO:
@@ -849,8 +866,7 @@ css_error parseRuleset(css_parser *parser)
 
        switch (state->substate) {
        case Initial:
-                unref_interned_strings_in_tokens(parser);
-               parserutils_vector_clear(parser->tokens);
+               discard_tokens(parser);
 
                error = getToken(parser, &token);
                if (error != CSS_OK)
@@ -890,17 +906,6 @@ css_error parseRuleset(css_parser *parser)
                }
                break;
        case Brace:
-#if !defined(NDEBUG) && defined(DEBUG_EVENTS)
-               printf("Begin ruleset\n");
-               parserutils_vector_dump(parser->tokens, __func__, tprinter);
-#endif
-               if (parser->parseError == false && parser->event != NULL) {
-                       if (parser->event(CSS_PARSER_START_RULESET,
-                                       parser->tokens, parser->event_pw) ==
-                                       CSS_INVALID)
-                               parser->parseError = true;
-               }
-
                if (parser->parseError == true) {
                        parser_state to = { sMalformedSelector, Initial };
 
@@ -911,22 +916,40 @@ css_error parseRuleset(css_parser *parser)
                if (error != CSS_OK)
                        return error;
 
-               if (token->type == CSS_TOKEN_EOF) {
+               if (token->type != CSS_TOKEN_CHAR ||
+                               lwc_string_length(token->idata) != 1 ||
+                               lwc_string_data(token->idata)[0] != '{') {
+                       /* FOLLOW(selector) contains only '{', but we may
+                        * also have seen EOF, which is a parse error. */
                        error = pushBack(parser, token);
                        if (error != CSS_OK)
                                return error;
 
+                       parser->parseError = true;
                        return done(parser);
                }
 
-               if (token->type != CSS_TOKEN_CHAR ||
-                               lwc_string_length(token->idata) != 1 ||
-                               lwc_string_data(token->idata)[0] != '{') {
-                       /* This should never happen, as FOLLOW(selector)
-                        * contains only '{' */
-                       assert(0 && "Expected {");
+               /* We don't want to emit the brace, so push it back */
+               error = pushBack(parser, token);
+               if (error != CSS_OK)
+                       return error;
+
+#if !defined(NDEBUG) && defined(DEBUG_EVENTS)
+               printf("Begin ruleset\n");
+               parserutils_vector_dump(parser->tokens, __func__, tprinter);
+#endif
+               if (parser->parseError == false && parser->event != NULL) {
+                       if (parser->event(CSS_PARSER_START_RULESET,
+                                       parser->tokens, parser->event_pw) ==
+                                       CSS_INVALID)
+                               parser->parseError = true;
                }
 
+               /* Re-read the brace */
+               error = getToken(parser, &token);
+               if (error != CSS_OK)
+                       return error;
+
                state->substate = WS;
                /* Fall through */
        case WS:
@@ -1041,8 +1064,7 @@ css_error parseAtRule(css_parser *parser)
 
        switch (state->substate) {
        case Initial:
-                unref_interned_strings_in_tokens(parser);
-               parserutils_vector_clear(parser->tokens);
+               discard_tokens(parser);
 
                error = getToken(parser, &token);
                if (error != CSS_OK)
@@ -1212,8 +1234,7 @@ css_error parseBlock(css_parser *parser)
                        assert(0 && "Expected {");
                }
 
-                unref_interned_strings_in_tokens(parser);
-               parserutils_vector_clear(parser->tokens);
+               discard_tokens(parser);
 
                state->substate = WS;
                /* Fall through */
@@ -1269,8 +1290,7 @@ css_error parseBlock(css_parser *parser)
                parser->event(CSS_PARSER_END_BLOCK, NULL, parser->event_pw);
        }
 
-        unref_interned_strings_in_tokens(parser);
-       parserutils_vector_clear(parser->tokens);
+       discard_tokens(parser);
 
        return done(parser);
 }
@@ -1322,10 +1342,7 @@ css_error parseBlockContent(css_parser *parser)
                                                        parser->event_pw);
                                        }
 
-                                       unref_interned_strings_in_tokens(
-                                                       parser);
-                                       parserutils_vector_clear(
-                                                       parser->tokens);
+                                       discard_tokens(parser);
 
                                        return transition(parser, to,
                                                        subsequent);
@@ -1353,10 +1370,7 @@ css_error parseBlockContent(css_parser *parser)
                                        if (error != CSS_OK)
                                                return error;
 
-                                       unref_interned_strings_in_tokens(
-                                                       parser);
-                                       parserutils_vector_clear(
-                                                       parser->tokens);
+                                       discard_tokens(parser);
 
                                        state->substate = WS;
                                } else if (lwc_string_length(
@@ -1379,10 +1393,7 @@ css_error parseBlockContent(css_parser *parser)
                                                        parser->event_pw);
                                        }
 
-                                       unref_interned_strings_in_tokens(
-                                                       parser);
-                                       parserutils_vector_clear(
-                                                       parser->tokens);
+                                       discard_tokens(parser);
 
                                        return done(parser);
                                }
@@ -1401,8 +1412,7 @@ css_error parseBlockContent(css_parser *parser)
                                                        parser->event_pw);
                                }
 
-                               unref_interned_strings_in_tokens(parser);
-                               parserutils_vector_clear(parser->tokens);
+                               discard_tokens(parser);
 
                                return done(parser);
                        }
@@ -1445,8 +1455,7 @@ css_error parseSelector(css_parser *parser)
                parser_state to = { sAny1, Initial };
                parser_state subsequent = { sSelector, AfterAny1 };
 
-                unref_interned_strings_in_tokens(parser);
-               parserutils_vector_clear(parser->tokens);
+               discard_tokens(parser);
 
                return transition(parser, to, subsequent);
        }
@@ -1472,8 +1481,7 @@ css_error parseDeclaration(css_parser *parser)
                parser_state to = { sProperty, Initial };
                parser_state subsequent = { sDeclaration, Colon };
 
-                unref_interned_strings_in_tokens(parser);
-               parserutils_vector_clear(parser->tokens);
+               discard_tokens(parser);
 
                return transition(parser, to, subsequent);
        }
@@ -1945,6 +1953,9 @@ css_error parseAny1(css_parser *parser)
                if (error != CSS_OK)
                        return error;
 
+               if (token->type == CSS_TOKEN_EOF)
+                       return done(parser);
+
                /* Grammar ambiguity: any0 can be followed by
                 * '{', ';', ')', ']'. any1 can only be followed by '{'. */
                if (token->type == CSS_TOKEN_CHAR &&
@@ -2025,18 +2036,19 @@ css_error parseAny(css_parser *parser)
                }
 
                if (token->type == CSS_TOKEN_FUNCTION) {
-                       parser->match_char = ')';
+                       parserutils_stack_push(parser->open_items, &")"[0]);
                        state->substate = WS;
                } else if (token->type == CSS_TOKEN_CHAR &&
                                lwc_string_length(token->idata) == 1 &&
                                (lwc_string_data(token->idata)[0] == '(' ||
                                lwc_string_data(token->idata)[0] == '[')) {
-                       parser->match_char = lwc_string_data(
-                                       token->idata)[0] == '(' ? ')' : ']';
+                       parserutils_stack_push(parser->open_items,
+                                       &(lwc_string_data(
+                                       token->idata)[0] == '(' ? ")" : 
"]")[0]);
                        state->substate = WS;
+               } else {
+                       state->substate = WS2;
                }
-
-               state->substate = WS2;
                /* Fall through */
        case WS:
        case WS2:
@@ -2063,11 +2075,24 @@ css_error parseAny(css_parser *parser)
                if (error != CSS_OK)
                        return error;
 
+               if (token->type == CSS_TOKEN_EOF) {
+                       error = pushBack(parser, token);
+                       if (error != CSS_OK)
+                               return error;
+
+                       /* parse error */
+                       parser->parseError = true;
+
+                       return done(parser);
+               }
+
                /* Match correct close bracket (grammar ambiguity) */
                if (token->type == CSS_TOKEN_CHAR &&
                                lwc_string_length(token->idata) == 1 &&
                                lwc_string_data(token->idata)[0] ==
-                               parser->match_char) {
+                               ((uint8_t *) parserutils_stack_get_current(
+                                               parser->open_items))[0]) {
+                       parserutils_stack_pop(parser->open_items, NULL);
                        state->substate = WS2;
                        goto ws2;
                }
@@ -2174,8 +2199,7 @@ css_error parseMalformedDeclaration(css_parser *parser)
                return error;
 
        /* Discard the tokens we've read */
-        unref_interned_strings_in_tokens(parser);
-       parserutils_vector_clear(parser->tokens);
+       discard_tokens(parser);
 
        return done(parser);
 }
@@ -2270,8 +2294,7 @@ css_error parseMalformedSelector(css_parser *parser)
                return error;
 
        /* Discard the tokens we've read */
-        unref_interned_strings_in_tokens(parser);
-       parserutils_vector_clear(parser->tokens);
+       discard_tokens(parser);
 
        return done(parser);
 }
@@ -2379,8 +2402,7 @@ css_error parseMalformedAtRule(css_parser *parser)
                return error;
 
        /* Discard the tokens we've read */
-        unref_interned_strings_in_tokens(parser);
-       parserutils_vector_clear(parser->tokens);
+       discard_tokens(parser);
 
        return done(parser);
 }
@@ -2425,7 +2447,7 @@ css_error parseInlineStyle(css_parser *parser)
        }
        case AfterISBody0:
                /* Clean up any remaining tokens */
-               unref_interned_strings_in_tokens(parser);
+               discard_tokens(parser);
 
                /* Emit remaining fake events to end the parse */
                if (parser->event != NULL) {
@@ -2571,22 +2593,48 @@ css_error parseISBody(css_parser *parser)
        return done(parser);
 }
 
+css_error parseMediaQuery(css_parser *parser)
+{
+       enum { Initial = 0, AfterAtRule = 1 };
+       parser_state *state = parserutils_stack_get_current(parser->states);
+
+       /* media-query = at-rule */
+
+       switch (state->substate) {
+       case Initial:
+       {
+               parser_state to = { sAtRule, Initial };
+               parser_state subsequent = { sMediaQuery, AfterAtRule };
+
+               return transition(parser, to, subsequent);
+       }
+       case AfterAtRule:
+               /* Clean up any remaining tokens */
+               discard_tokens(parser);
+               break;
+       }
+
+       return done(parser);
+}
+
 /**
- * Iterate the token vector and unref any interned strings in the tokens.
+ * Discard the contents of the token vector
  *
  * \param parser The parser whose tokens we are cleaning up.
  */
-void unref_interned_strings_in_tokens(css_parser *parser)
+void discard_tokens(css_parser *parser)
 {
-        int32_t ctx = 0;
-        const css_token *tok;
+       int32_t ctx = 0;
+       const css_token *tok;
 
-        while ((tok = parserutils_vector_iterate(
+       while ((tok = parserutils_vector_iterate(
                        parser->tokens, &ctx)) != NULL) {
-                if (tok->idata != NULL) {
-                        lwc_string_unref(tok->idata);
+               if (tok->idata != NULL) {
+                       lwc_string_unref(tok->idata);
                }
-        }
+       }
+
+       parserutils_vector_clear(parser->tokens);
 }
 
 #ifndef NDEBUG
@@ -2604,7 +2652,11 @@ static void tprinter(void *token)
 {
        css_token *t = token;
 
-       if (t->data.data)
+       if (t->idata) {
+               printf("%d: %.*s", t->type,
+                               (int) lwc_string_length(t->idata),
+                               lwc_string_data(t->idata));
+       } else if (t->data.data)
                printf("%d: %.*s", t->type, (int) t->data.len, t->data.data);
        else
                printf("%d", t->type);
diff --git a/src/parse/parse.h b/src/parse/parse.h
index 833aa51..e65f055 100644
--- a/src/parse/parse.h
+++ b/src/parse/parse.h
@@ -61,6 +61,8 @@ css_error css__parser_create(const char *charset, 
css_charset_source cs_source,
                css_parser **parser);
 css_error css__parser_create_for_inline_style(const char *charset,
                css_charset_source cs_source, css_parser **parser);
+css_error css__parser_create_for_media_query(const char *charset,
+               css_charset_source cs_source, css_parser **parser);
 css_error css__parser_destroy(css_parser *parser);
 
 css_error css__parser_setopt(css_parser *parser, css_parser_opttype type,
diff --git a/src/parse/properties/utils.c b/src/parse/properties/utils.c
index 7abef24..0e49853 100644
--- a/src/parse/properties/utils.c
+++ b/src/parse/properties/utils.c
@@ -1007,6 +1007,12 @@ css_error css__parse_unit_keyword(const char *ptr, 
size_t len, uint32_t *unit)
        if (len == 4) {
                if (strncasecmp(ptr, "grad", 4) == 0)
                        *unit = UNIT_GRAD;
+               else if (strncasecmp(ptr, "turn", 4) == 0)
+                       *unit = UNIT_TURN;
+               else if (strncasecmp(ptr, "dppx", 4) == 0)
+                       *unit = UNIT_DPPX;
+               else if (strncasecmp(ptr, "dpcm", 4) == 0)
+                       *unit = UNIT_DPCM;
                else if (strncasecmp(ptr, "vmin", 4) == 0)
                        *unit = UNIT_VMIN;
                else if (strncasecmp(ptr, "vmax", 4) == 0)
@@ -1026,6 +1032,8 @@ css_error css__parse_unit_keyword(const char *ptr, size_t 
len, uint32_t *unit)
                        *unit = UNIT_REM;
                else if (strncasecmp(ptr, "rlh", 3) == 0)
                        *unit = UNIT_RLH;
+               else if (strncasecmp(ptr, "dpi", 3) == 0)
+                       *unit = UNIT_DPI;
                else
                        return CSS_INVALID;
        } else if (len == 2) {
diff --git a/src/parse/propstrings.c b/src/parse/propstrings.c
index bfd2965..3c9401b 100644
--- a/src/parse/propstrings.c
+++ b/src/parse/propstrings.c
@@ -439,6 +439,10 @@ const stringmap_entry stringmap[LAST_KNOWN] = {
        { "column-reverse", SLEN("column-reverse") },
        { "wrap", SLEN("wrap") },
        { "wrap-reverse", SLEN("wrap-reverse") },
+       { "and", SLEN("and") },
+       { "or", SLEN("or") },
+       { "only", SLEN("only") },
+       { "infinite", SLEN("infinite") },
 
        { "aliceblue", SLEN("aliceblue") },
        { "antiquewhite", SLEN("antiquewhite") },
diff --git a/src/parse/propstrings.h b/src/parse/propstrings.h
index 67eaa5f..24b681b 100644
--- a/src/parse/propstrings.h
+++ b/src/parse/propstrings.h
@@ -101,7 +101,7 @@ enum {
        AVOID_PAGE, AVOID_COLUMN, BALANCE, HORIZONTAL_TB, VERTICAL_RL,
        VERTICAL_LR, CONTENT_BOX, BORDER_BOX, STRETCH, INLINE_FLEX, FLEX_START,
        FLEX_END, SPACE_BETWEEN, SPACE_AROUND, SPACE_EVENLY, ROW, ROW_REVERSE,
-       COLUMN_REVERSE, WRAP_STRING, WRAP_REVERSE,
+       COLUMN_REVERSE, WRAP_STRING, WRAP_REVERSE, AND, OR, ONLY, INFINITE,
 
        /* Named colours */
        FIRST_COLOUR,
diff --git a/src/select/computed.c b/src/select/computed.c
index ebb2b29..506b079 100644
--- a/src/select/computed.c
+++ b/src/select/computed.c
@@ -250,7 +250,7 @@ css_error css__computed_style_initialise(css_computed_style 
*style,
                return CSS_BADPARM;
 
        state.node = NULL;
-       state.media = CSS_MEDIA_ALL;
+       state.media = NULL;
        state.results = NULL;
        state.computed = style;
        state.handler = handler;
diff --git a/src/select/hash.h b/src/select/hash.h
index 71f610f..aecf15a 100644
--- a/src/select/hash.h
+++ b/src/select/hash.h
@@ -25,7 +25,7 @@ struct css_hash_selection_requirments {
        lwc_string *class;              /* Name of class, or NULL */
        lwc_string *id;                 /* Name of id, or NULL */
        lwc_string *uni;                /* Universal element string "*" */
-       uint64_t media;                 /* Media type(s) we're selecting for */
+       const css_media *media;         /* Media spec we're selecting for */
        const css_bloom *node_bloom;    /* Node's bloom filter */
 };
 
diff --git a/src/select/mq.h b/src/select/mq.h
index a0a9f6d..290505c 100644
--- a/src/select/mq.h
+++ b/src/select/mq.h
@@ -10,13 +10,51 @@
 #define css_select_mq_h_
 
 /**
+ * Match media query conditions.
+ *
+ * \param[in] cond  Condition to match.
+ * \return true if condition matches, otherwise false.
+ */
+static inline bool mq_match_condition(css_mq_cond *cond)
+{
+       /* TODO: Implement this. */
+       (void) cond;
+       return true;
+}
+
+/**
+ * Test whether media query list matches current media.
+ *
+ * If anything in the list matches, the list matches.  If none match
+ * it doesn't match.
+ *
+ * \param m      Media query list.
+ * \meaid media  Current media spec, to check against m.
+ * \return true if media query list matches media
+ */
+static inline bool mq__list_match(const css_mq_query *m, const css_media 
*media)
+{
+       for (; m != NULL; m = m->next) {
+               /* Check type */
+               if (!!(m->type & media->type) != m->negate_type) {
+                       if (mq_match_condition(m->cond)) {
+                               /* We have a match, no need to look further. */
+                               return true;
+                       }
+               }
+       }
+
+       return false;
+}
+
+/**
  * Test whether the rule applies for current media.
  *
- * \param rule         Rule to test
- * \meaid media                Current media type(s)
+ * \param rule   Rule to test
+ * \param media  Current media type(s)
  * \return true iff chain's rule applies for media
  */
-static inline bool mq_rule_good_for_media(const css_rule *rule, uint64_t media)
+static inline bool mq_rule_good_for_media(const css_rule *rule, const 
css_media *media)
 {
        bool applies = true;
        const css_rule *ancestor = rule;
@@ -24,10 +62,11 @@ static inline bool mq_rule_good_for_media(const css_rule 
*rule, uint64_t media)
        while (ancestor != NULL) {
                const css_rule_media *m = (const css_rule_media *) ancestor;
 
-               if (ancestor->type == CSS_RULE_MEDIA &&
-                               (m->media & media) == 0) {
-                       applies = false;
-                       break;
+               if (ancestor->type == CSS_RULE_MEDIA) {
+                       applies = mq__list_match(m->media, media);
+                       if (applies == false) {
+                               break;
+                       }
                }
 
                if (ancestor->ptype != CSS_RULE_PARENT_STYLESHEET) {
diff --git a/src/select/select.c b/src/select/select.c
index 644369a..1b0cadd 100644
--- a/src/select/select.c
+++ b/src/select/select.c
@@ -38,7 +38,7 @@
 typedef struct css_select_sheet {
        const css_stylesheet *sheet;    /**< Stylesheet */
        css_origin origin;              /**< Stylesheet origin */
-       uint64_t media;                 /**< Applicable media */
+       css_mq_query *media;            /**< Applicable media */
 } css_select_sheet;
 
 /**
@@ -97,7 +97,7 @@ typedef struct css_select_font_faces_list {
  */
 typedef struct css_select_font_faces_state {
        lwc_string *font_family;
-       uint64_t media;
+       const css_media *media;
 
        css_select_font_faces_list ua_font_faces;
        css_select_font_faces_list user_font_faces;
@@ -289,8 +289,12 @@ css_error css_select_ctx_destroy(css_select_ctx *ctx)
        if (ctx->default_style != NULL)
                css_computed_style_destroy(ctx->default_style);
 
-       if (ctx->sheets != NULL)
+       if (ctx->sheets != NULL) {
+               for (uint32_t index = 0; index < ctx->n_sheets; index++) {
+                       css__mq_query_destroy(ctx->sheets[index].media);
+               }
                free(ctx->sheets);
+       }
 
        free(ctx);
 
@@ -303,12 +307,12 @@ css_error css_select_ctx_destroy(css_select_ctx *ctx)
  * \param ctx     The context to append to
  * \param sheet   The sheet to append
  * \param origin  Origin of the sheet
- * \param media   Media types to which the sheet applies
+ * \param media   Media string for the stylesheet
  * \return CSS_OK on success, appropriate error otherwise
  */
 css_error css_select_ctx_append_sheet(css_select_ctx *ctx,
                const css_stylesheet *sheet, css_origin origin,
-               uint64_t media)
+               const char *media)
 {
        if (ctx == NULL || sheet == NULL)
                return CSS_BADPARM;
@@ -324,14 +328,16 @@ css_error css_select_ctx_append_sheet(css_select_ctx *ctx,
  * \param sheet  Sheet to insert
  * \param index  Index in context to insert sheet
  * \param origin  Origin of the sheet
- * \param media   Media types to which the sheet applies
+ * \param media   Media string for the stylesheet
  * \return CSS_OK on success, appropriate error otherwise
  */
 css_error css_select_ctx_insert_sheet(css_select_ctx *ctx,
                const css_stylesheet *sheet, uint32_t index,
-               css_origin origin, uint64_t media)
+               css_origin origin, const char *media)
 {
        css_select_sheet *temp;
+       css_mq_query *mq;
+       css_error error;
 
        if (ctx == NULL || sheet == NULL)
                return CSS_BADPARM;
@@ -357,9 +363,23 @@ css_error css_select_ctx_insert_sheet(css_select_ctx *ctx,
                        (ctx->n_sheets - index) * sizeof(css_select_sheet));
        }
 
+       error = css_parse_media_query(sheet->propstrings,
+                       (const uint8_t *)media,
+                       (media == NULL) ? 0 : strlen(media), &mq);
+       if (error == CSS_NOMEM) {
+               return error;
+       } else if (error != CSS_OK) {
+               /* Fall back to default media: "all". */
+               mq = calloc(1, sizeof(*mq));
+               if (mq == NULL) {
+                       return CSS_NOMEM;
+               }
+               mq->type = CSS_MEDIA_ALL;
+       }
+
        ctx->sheets[index].sheet = sheet;
        ctx->sheets[index].origin = origin;
-       ctx->sheets[index].media = media;
+       ctx->sheets[index].media = mq;
 
        ctx->n_sheets++;
 
@@ -389,6 +409,8 @@ css_error css_select_ctx_remove_sheet(css_select_ctx *ctx,
        if (index == ctx->n_sheets)
                return CSS_INVALID;
 
+       css__mq_query_destroy(ctx->sheets[index].media);
+
        ctx->n_sheets--;
 
        memmove(&ctx->sheets[index], &ctx->sheets[index + 1],
@@ -1032,7 +1054,7 @@ static void css_select__finalise_selection_state(
  * \param[in]  state    The selection state to initialise
  * \param[in]  node     The node we are selecting for.
  * \param[in]  parent   The node's parent node, or NULL.
- * \param[in]  media    The media type we're selecting for.
+ * \param[in]  media    The media specification we're selecting for.
  * \param[in]  handler  The client selection callback table.
  * \param[in]  pw       The client private data, passsed out to callbacks.
  * \return CSS_OK or appropriate error otherwise.
@@ -1041,7 +1063,7 @@ static css_error css_select__initialise_selection_state(
                css_select_state *state,
                void *node,
                void *parent,
-               uint64_t media,
+               const css_media *media,
                css_select_handler *handler,
                void *pw)
 {
@@ -1142,7 +1164,7 @@ failed:
  *
  * \param ctx             Selection context to use
  * \param node            Node to select style for
- * \param media           Currently active media types
+ * \param media           Currently active media specification
  * \param inline_style    Corresponding inline style for node, or NULL
  * \param handler         Dispatch table of handler functions
  * \param pw              Client-specific private data for handler functions
@@ -1159,7 +1181,7 @@ failed:
  * update the fully computed style for a node when layout changes.
  */
 css_error css_select_style(css_select_ctx *ctx, void *node,
-               uint64_t media, const css_stylesheet *inline_style,
+               const css_media *media, const css_stylesheet *inline_style,
                css_select_handler *handler, void *pw,
                css_select_results **result)
 {
@@ -1244,7 +1266,7 @@ css_error css_select_style(css_select_ctx *ctx, void 
*node,
        for (i = 0; i < ctx->n_sheets; i++) {
                const css_select_sheet s = ctx->sheets[i];
 
-               if ((s.media & media) != 0 &&
+               if (mq__list_match(s.media, media) &&
                                s.sheet->disabled == false) {
                        error = select_from_sheet(ctx, s.sheet,
                                        s.origin, &state);
@@ -1394,13 +1416,13 @@ css_error css_select_results_destroy(css_select_results 
*results)
  * Search a selection context for defined font faces
  *
  * \param ctx          Selection context
- * \param media        Currently active media types
+ * \param media        Currently active media spec
  * \param font_family  Font family to search for
  * \param result       Pointer to location to receive result
  * \return CSS_OK on success, appropriate error otherwise.
  */
 css_error css_select_font_faces(css_select_ctx *ctx,
-               uint64_t media, lwc_string *font_family,
+               const css_media *media, lwc_string *font_family,
                css_select_font_faces_results **result)
 {
        uint32_t i;
@@ -1421,7 +1443,7 @@ css_error css_select_font_faces(css_select_ctx *ctx,
        for (i = 0; i < ctx->n_sheets; i++) {
                const css_select_sheet s = ctx->sheets[i];
 
-               if ((s.media & media) != 0 &&
+               if (mq__list_match(s.media, media) &&
                                s.sheet->disabled == false) {
                        error = select_font_faces_from_sheet(s.sheet,
                                        s.origin, &state);
@@ -1847,7 +1869,8 @@ css_error select_from_sheet(css_select_ctx *ctx, const 
css_stylesheet *sheet,
                                        (const css_rule_import *) rule;
 
                        if (import->sheet != NULL &&
-                                       (import->media & state->media) != 0) {
+                                       mq__list_match(import->media,
+                                                       state->media)) {
                                /* It's applicable, so process it */
                                if (sp >= IMPORT_STACK_SIZE)
                                        return CSS_NOMEM;
@@ -1954,7 +1977,8 @@ static css_error select_font_faces_from_sheet(
                                        (const css_rule_import *) rule;
 
                        if (import->sheet != NULL &&
-                                       (import->media & state->media) != 0) {
+                                       mq__list_match(import->media,
+                                                       state->media)) {
                                /* It's applicable, so process it */
                                if (sp >= IMPORT_STACK_SIZE)
                                        return CSS_NOMEM;
diff --git a/src/select/select.h b/src/select/select.h
index 70f1ced..dc9aa4a 100644
--- a/src/select/select.h
+++ b/src/select/select.h
@@ -63,7 +63,7 @@ struct css_node_data {
  */
 typedef struct css_select_state {
        void *node;                     /* Node we're selecting for */
-       uint64_t media;                 /* Currently active media types */
+       const css_media *media;         /* Currently active media spec */
        css_select_results *results;    /* Result set to populate */
 
        css_pseudo_element current_pseudo;      /* Current pseudo element */
diff --git a/src/stylesheet.c b/src/stylesheet.c
index 7c6728b..22c7681 100644
--- a/src/stylesheet.c
+++ b/src/stylesheet.c
@@ -12,6 +12,7 @@
 #include "stylesheet.h"
 #include "bytecode/bytecode.h"
 #include "parse/language.h"
+#include "parse/mq.h"
 #include "utils/parserutilserror.h"
 #include "utils/utils.h"
 #include "select/dispatch.h"
@@ -377,8 +378,6 @@ css_error css_stylesheet_data_done(css_stylesheet *sheet)
  * \param parent  Parent stylesheet
  * \param url    Pointer to object to be populated with details of URL of
  *               imported stylesheet (potentially relative)
- * \param media          Pointer to location to receive applicable media types 
for
- *               imported sheet,
  * \return CSS_OK on success,
  *        CSS_INVALID if there are no pending imports remaining
  *
@@ -396,11 +395,11 @@ css_error css_stylesheet_data_done(css_stylesheet *sheet)
  * register an empty stylesheet with the parent in its place.
  */
 css_error css_stylesheet_next_pending_import(css_stylesheet *parent,
-               lwc_string **url, uint64_t *media)
+               lwc_string **url)
 {
        const css_rule *r;
 
-       if (parent == NULL || url == NULL || media == NULL)
+       if (parent == NULL || url == NULL)
                return CSS_BADPARM;
 
        for (r = parent->rule_list; r != NULL; r = r->next) {
@@ -413,7 +412,6 @@ css_error css_stylesheet_next_pending_import(css_stylesheet 
*parent,
 
                if (r->type == CSS_RULE_IMPORT && i->sheet == NULL) {
                        *url = lwc_string_ref(i->url);
-                       *media = i->media;
 
                        return CSS_OK;
                }
@@ -1154,6 +1152,9 @@ css_error css__stylesheet_rule_destroy(css_stylesheet 
*sheet, css_rule *rule)
                css_rule_import *import = (css_rule_import *) rule;
 
                lwc_string_unref(import->url);
+               if (import->media != NULL) {
+                       css__mq_query_destroy(import->media);
+               }
 
                /* Do not destroy imported sheet: it is owned by the client */
        }
@@ -1163,6 +1164,10 @@ css_error css__stylesheet_rule_destroy(css_stylesheet 
*sheet, css_rule *rule)
                css_rule_media *media = (css_rule_media *) rule;
                css_rule *c, *d;
 
+               if (media->media != NULL) {
+                       css__mq_query_destroy(media->media);
+               }
+
                for (c = media->first_child; c != NULL; c = d) {
                        d = c->next;
 
@@ -1326,7 +1331,7 @@ css_error css__stylesheet_rule_set_charset(css_stylesheet 
*sheet,
  */
 css_error css__stylesheet_rule_set_nascent_import(css_stylesheet *sheet,
                css_rule *rule, lwc_string *url,
-               uint64_t media)
+               css_mq_query *media)
 {
        css_rule_import *r = (css_rule_import *) rule;
 
@@ -1352,7 +1357,7 @@ css_error 
css__stylesheet_rule_set_nascent_import(css_stylesheet *sheet,
  * \return CSS_OK on success, appropriate error otherwise
  */
 css_error css__stylesheet_rule_set_media(css_stylesheet *sheet,
-               css_rule *rule, uint64_t media)
+               css_rule *rule, css_mq_query *media)
 {
        css_rule_media *r = (css_rule_media *) rule;
 
diff --git a/src/stylesheet.h b/src/stylesheet.h
index 18e077e..a44ad1f 100644
--- a/src/stylesheet.h
+++ b/src/stylesheet.h
@@ -20,6 +20,7 @@
 
 #include "bytecode/bytecode.h"
 #include "parse/parse.h"
+#include "parse/mq.h"
 #include "select/hash.h"
 
 typedef struct css_rule css_rule;
@@ -132,7 +133,7 @@ typedef struct css_rule_selector {
 typedef struct css_rule_media {
        css_rule base;
 
-       uint64_t media;
+       css_mq_query *media;
 
        css_rule *first_child;
        css_rule *last_child;
@@ -155,7 +156,7 @@ typedef struct css_rule_import {
        css_rule base;
 
        lwc_string *url;
-       uint64_t media;
+       css_mq_query *media;
 
        css_stylesheet *sheet;
 } css_rule_import;
@@ -268,10 +269,10 @@ css_error css__stylesheet_rule_set_charset(css_stylesheet 
*sheet,
                css_rule *rule, lwc_string *charset);
 
 css_error css__stylesheet_rule_set_nascent_import(css_stylesheet *sheet,
-               css_rule *rule, lwc_string *url, uint64_t media);
+               css_rule *rule, lwc_string *url, css_mq_query *media);
 
 css_error css__stylesheet_rule_set_media(css_stylesheet *sheet,
-               css_rule *rule, uint64_t media);
+               css_rule *rule, css_mq_query *media);
 
 css_error css__stylesheet_rule_set_page_selector(css_stylesheet *sheet,
                css_rule *rule, css_selector *sel);
diff --git a/test/css21.c b/test/css21.c
index a29fae1..cdd66f7 100644
--- a/test/css21.c
+++ b/test/css21.c
@@ -99,10 +99,8 @@ int main(int argc, char **argv)
 
                while (error == CSS_IMPORTS_PENDING) {
                        lwc_string *url;
-                       uint64_t media;
 
-                       error = css_stylesheet_next_pending_import(sheet,
-                                       &url, &media);
+                       error = css_stylesheet_next_pending_import(sheet, &url);
                        assert(error == CSS_OK || error == CSS_INVALID);
 
                        if (error == CSS_OK) {
diff --git a/test/data/parse2/illegal-values.dat 
b/test/data/parse2/illegal-values.dat
index 3187e18..2d58b54 100644
--- a/test/data/parse2/illegal-values.dat
+++ b/test/data/parse2/illegal-values.dat
@@ -864,7 +864,7 @@
 #reset
 
 #data
-* { display: �

Reply via email to