Adds support for `@-moz-document`.

Also fixes emitted CSS formatting for `@media` and `@host`.
diff --git a/lib/parser.dart b/lib/parser.dart
index 168c9da..2bdaad1 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -485,7 +485,11 @@
    *  mixin:              '@mixin name [(args,...)] '{' declarations/ruleset '}'
    *  include:            '@include name [(@arg,@arg1)]
    *                      '@include name [(@arg...)]
-   *  content             '@content'
+   *  content:            '@content'
+   *  -moz-document:      '@-moz-document' [ <url> | url-prefix(<string>) |
+   *                          domain(<string>) | regexp(<string) ]# '{'
+   *                        declarations
+   *                      '}'
    */
   processDirective() {
     var start = _peekToken.span;
@@ -762,11 +766,12 @@
 
       case TokenKind.DIRECTIVE_INCLUDE:
         return processInclude(_makeSpan(start));
-
       case TokenKind.DIRECTIVE_CONTENT:
         // TODO(terry): TBD
         _warning("@content not implemented.", _makeSpan(start));
         return null;
+      case TokenKind.DIRECTIVE_MOZ_DOCUMENT:
+        return processDocumentDirective(start);
     }
     return null;
   }
@@ -987,6 +992,51 @@
     return new IncludeDirective(name.name, params, span);
   }
 
+  /// Assumes '@' has already been consumed.
+  DocumentDirective processDocumentDirective(SourceSpan start) {
+    _next(); // '-moz-document'
+    var functions = <LiteralTerm>[];
+    do {
+      var function;
+
+      // Consume function token: IDENT '('
+      var ident = identifier();
+      _eat(TokenKind.LPAREN);
+
+      // Consume function arguments.
+      if (ident.name == 'url-prefix' || ident.name == 'domain') {
+        // @-moz-document allows the 'url-prefix' and 'domain' functions to
+        // omit quotations around their argument, contrary to the standard
+        // in which they must be strings. To support this we consume a
+        // string with optional quotation marks, then reapply quotation
+        // marks so they're present in the emitted CSS.
+        var argumentStart = _peekToken.span;
+        var value = processQuotedString(true);
+        // Don't quote the argument if it's empty. '@-moz-document url-prefix()'
+        // is a common pattern used for browser detection.
+        var argument = value.isNotEmpty ? '"$value"' : '';
+        var argumentSpan = _makeSpan(argumentStart);
+
+        _eat(TokenKind.RPAREN);
+
+        var arguments = new Expressions(_makeSpan(argumentSpan))
+          ..add(new LiteralTerm(argument, argument, argumentSpan));
+        function = new FunctionTerm(
+            ident.name, ident.name, arguments, _makeSpan(ident.span));
+      } else {
+        function = processFunction(ident);
+      }
+
+      functions.add(function);
+    } while (_maybeEat(TokenKind.COMMA));
+
+    _eat(TokenKind.LBRACE);
+    var groupRuleBody = processGroupRuleBody();
+    _eat(TokenKind.RBRACE);
+    return new DocumentDirective(
+        functions, groupRuleBody, _makeSpan(start));
+  }
+
   RuleSet processRuleSet([SelectorGroup selectorGroup]) {
     if (selectorGroup == null) {
       selectorGroup = processSelectorGroup();
@@ -998,6 +1048,24 @@
     return null;
   }
 
+  List<TreeNode> processGroupRuleBody() {
+    var nodes = <TreeNode>[];
+    while (!(_peekKind(TokenKind.RBRACE) || _peekKind(TokenKind.END_OF_FILE))) {
+      var directive = processDirective();
+      if (directive != null) {
+        nodes.add(directive);
+        continue;
+      }
+      var ruleSet = processRuleSet();
+      if (ruleSet != null) {
+        nodes.add(ruleSet);
+        continue;
+      }
+      break;
+    }
+    return nodes;
+  }
+
   /**
    * Look ahead to see if what should be a declaration is really a selector.
    * If it's a selector than it's a nested selector.  This support's Less'
diff --git a/lib/src/css_printer.dart b/lib/src/css_printer.dart
index d3b62d0..2ae6b66 100644
--- a/lib/src/css_printer.dart
+++ b/lib/src/css_printer.dart
@@ -77,22 +77,36 @@
     }
   }
 
+  void visitDocumentDirective(DocumentDirective node) {
+    emit('$_newLine@-moz-document ');
+    node.functions.first.visit(this);
+    for (var function in node.functions.skip(1)) {
+      emit(',$_sp');
+      function.visit(this);
+    }
+    emit('$_sp{');
+    for (var ruleSet in node.groupRuleBody) {
+      ruleSet.visit(this);
+    }
+    emit('$_newLine}');
+  }
+
   void visitMediaDirective(MediaDirective node) {
-    emit(' @media');
+    emit('$_newLine@media');
     emitMediaQueries(node.mediaQueries);
-    emit(' {');
+    emit('$_sp{');
     for (var ruleset in node.rulesets) {
       ruleset.visit(this);
     }
-    emit('$_newLine\}');
+    emit('$_newLine}');
   }
 
   void visitHostDirective(HostDirective node) {
-    emit('\n@host {');
+    emit('$_newLine@host$_sp{');
     for (var ruleset in node.rulesets) {
       ruleset.visit(this);
     }
-    emit('$_newLine\}');
+    emit('$_newLine}');
   }
 
   /**
diff --git a/lib/src/tokenkind.dart b/lib/src/tokenkind.dart
index 85e6d40..f6a6c8c 100644
--- a/lib/src/tokenkind.dart
+++ b/lib/src/tokenkind.dart
@@ -161,6 +161,7 @@
   static const int DIRECTIVE_INCLUDE = 655;
   static const int DIRECTIVE_CONTENT = 656;
   static const int DIRECTIVE_EXTEND = 657;
+  static const int DIRECTIVE_MOZ_DOCUMENT = 658;
 
   // Media query operators
   static const int MEDIA_OP_ONLY = 665; // Unary.
@@ -218,6 +219,7 @@
     const {'type': TokenKind.DIRECTIVE_INCLUDE, 'value': 'include'},
     const {'type': TokenKind.DIRECTIVE_CONTENT, 'value': 'content'},
     const {'type': TokenKind.DIRECTIVE_EXTEND, 'value': 'extend'},
+    const {'type': TokenKind.DIRECTIVE_MOZ_DOCUMENT, 'value': '-moz-document'},
   ];
 
   static const List<Map<String, dynamic>> MEDIA_OPERATORS = const [
diff --git a/lib/src/tree.dart b/lib/src/tree.dart
index c3709d2..6321807 100644
--- a/lib/src/tree.dart
+++ b/lib/src/tree.dart
@@ -435,6 +435,20 @@
   visit(VisitorBase visitor) => visitor.visitDirective(this);
 }
 
+class DocumentDirective extends Directive {
+  final List<LiteralTerm> functions;
+  final List<TreeNode> groupRuleBody;
+
+  DocumentDirective(this.functions, this.groupRuleBody, SourceSpan span)
+      : super(span);
+
+  DocumentDirective clone() {
+    return new DocumentDirective(this.functions, this.groupRuleBody, span);
+  }
+
+  visit(VisitorBase visitor) => visitor.visitDocumentDirective(this);
+}
+
 class ImportDirective extends Directive {
   /** import name specified. */
   final String import;
diff --git a/lib/src/tree_printer.dart b/lib/src/tree_printer.dart
index ac4512c..97403dc 100644
--- a/lib/src/tree_printer.dart
+++ b/lib/src/tree_printer.dart
@@ -90,6 +90,15 @@
     output.depth--;
   }
 
+  void visitDocumentDirective(DocumentDirective node) {
+    heading('DocumentDirective', node);
+    output.depth++;
+    output.writeNodeList('functions', node.functions);
+    output.writeNodeList('group rule body', node.groupRuleBody);
+    super.visitDocumentDirective(node);
+    output.depth--;
+  }
+
   void visitPageDirective(PageDirective node) {
     heading('PageDirective', node);
     output.depth++;
diff --git a/lib/visitor.dart b/lib/visitor.dart
index 1123493..016f20e 100644
--- a/lib/visitor.dart
+++ b/lib/visitor.dart
@@ -20,6 +20,7 @@
   visitNoOp(NoOp node);
   visitTopLevelProduction(TopLevelProduction node);
   visitDirective(Directive node);
+  visitDocumentDirective(DocumentDirective node);
   visitMediaExpression(MediaExpression node);
   visitMediaQuery(MediaQuery node);
   visitMediaDirective(MediaDirective node);
@@ -152,6 +153,11 @@
     }
   }
 
+  visitDocumentDirective(DocumentDirective node) {
+    _visitNodeList(node.functions);
+    _visitNodeList(node.groupRuleBody);
+  }
+
   visitMediaDirective(MediaDirective node) {
     for (var mediaQuery in node.mediaQueries) {
       visitMediaQuery(mediaQuery);
diff --git a/test/declaration_test.dart b/test/declaration_test.dart
index 96600d8..0e7cf07 100644
--- a/test/declaration_test.dart
+++ b/test/declaration_test.dart
@@ -380,11 +380,13 @@
 .myclass {
   height: 20px;
 }
-} @media print AND (min-resolution:300dpi) {
+}
+@media print AND (min-resolution:300dpi) {
 #anotherId {
   color: #fff;
 }
-} @media print AND (min-resolution:280dpcm) {
+}
+@media print AND (min-resolution:280dpcm) {
 #finalId {
   color: #aaa;
 }
@@ -445,6 +447,49 @@
   expect(prettyPrint(stylesheet), generated);
 }
 
+void testMozDocument() {
+  var errors = <Message>[];
+  // Test empty url-prefix, commonly used for browser detection.
+  var css = '@-moz-document url-prefix() {}';
+  var expected = '@-moz-document url-prefix() {\n}';
+  var styleSheet = parseCss(css, errors: errors);
+  expect(styleSheet, isNotNull);
+  expect(errors, isEmpty);
+  expect(prettyPrint(styleSheet), expected);
+
+  // Test url-prefix with unquoted parameter
+  css = '@-moz-document url-prefix(http://www.w3.org/Style/) {}';
+  expected = '@-moz-document url-prefix("http://www.w3.org/Style/") {\n}';
+  styleSheet = parseCss(css, errors: errors);
+  expect(styleSheet, isNotNull);
+  expect(errors, isEmpty);
+  expect(prettyPrint(styleSheet), expected);
+
+  // Test domain with unquoted parameter
+  css = '@-moz-document domain(google.com) {}';
+  expected = '@-moz-document domain("google.com") {\n}';
+  styleSheet = parseCss(css, errors: errors);
+  expect(styleSheet, isNotNull);
+  expect(errors, isEmpty);
+  expect(prettyPrint(styleSheet), expected);
+
+  // Test all document functions combined.
+  css = '@-moz-document ' +
+      'url(http://www.w3.org/), ' +
+      "url-prefix('http://www.w3.org/Style/'), " +
+      'domain("google.com"), ' +
+      'regexp("https:.*") {} ';
+  expected = '@-moz-document ' +
+      'url("http://www.w3.org/"), ' +
+      'url-prefix("http://www.w3.org/Style/"), ' +
+      'domain("google.com"), ' +
+      'regexp("https:.*") {\n}';
+  styleSheet = parseCss(css, errors: errors);
+  expect(styleSheet, isNotNull);
+  expect(errors, isEmpty);
+  expect(prettyPrint(styleSheet), expected);
+}
+
 void testFontFace() {
   var errors = <Message>[];
 
@@ -1111,6 +1156,7 @@
   test('Unicode', testUnicode);
   test('Newer CSS', testNewerCss);
   test('Media Queries', testMediaQueries);
+  test('Document', testMozDocument);
   test('Font-Face', testFontFace);
   test('CSS file', testCssFile);
   test('Compact Emitter', testCompactEmitter);