Adds support for @viewport

Fixes #42.
diff --git a/lib/parser.dart b/lib/parser.dart
index 4c65a29..35f9133 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -781,6 +781,9 @@
         return processDocumentDirective();
       case TokenKind.DIRECTIVE_SUPPORTS:
         return processSupportsDirective();
+      case TokenKind.DIRECTIVE_VIEWPORT:
+      case TokenKind.DIRECTIVE_MS_VIEWPORT:
+        return processViewportDirective();
     }
     return null;
   }
@@ -1122,6 +1125,13 @@
     return new SupportsConditionInParens(declaration, _makeSpan(start));
   }
 
+  ViewportDirective processViewportDirective() {
+    var start = _peekToken.span;
+    var name = _next().text;
+    var declarations = processDeclarations();
+    return new ViewportDirective(name, declarations, _makeSpan(start));
+  }
+
   RuleSet processRuleSet([SelectorGroup selectorGroup]) {
     if (selectorGroup == null) {
       selectorGroup = processSelectorGroup();
diff --git a/lib/src/css_printer.dart b/lib/src/css_printer.dart
index 0304fd7..79bcaed 100644
--- a/lib/src/css_printer.dart
+++ b/lib/src/css_printer.dart
@@ -128,6 +128,12 @@
     }
   }
 
+  void visitViewportDirective(ViewportDirective node) {
+    emit('@${node.name}$_sp{$_newLine');
+    node.declarations.visit(this);
+    emit('}');
+  }
+
   void visitMediaDirective(MediaDirective node) {
     emit('$_newLine@media');
     emitMediaQueries(node.mediaQueries);
diff --git a/lib/src/tokenkind.dart b/lib/src/tokenkind.dart
index 48696ab..14cf3c9 100644
--- a/lib/src/tokenkind.dart
+++ b/lib/src/tokenkind.dart
@@ -163,6 +163,8 @@
   static const int DIRECTIVE_EXTEND = 657;
   static const int DIRECTIVE_MOZ_DOCUMENT = 658;
   static const int DIRECTIVE_SUPPORTS = 659;
+  static const int DIRECTIVE_VIEWPORT = 660;
+  static const int DIRECTIVE_MS_VIEWPORT = 661;
 
   // Media query operators
   static const int MEDIA_OP_ONLY = 665; // Unary.
@@ -222,6 +224,8 @@
     const {'type': TokenKind.DIRECTIVE_EXTEND, 'value': 'extend'},
     const {'type': TokenKind.DIRECTIVE_MOZ_DOCUMENT, 'value': '-moz-document'},
     const {'type': TokenKind.DIRECTIVE_SUPPORTS, 'value': 'supports'},
+    const {'type': TokenKind.DIRECTIVE_VIEWPORT, 'value': 'viewport'},
+    const {'type': TokenKind.DIRECTIVE_MS_VIEWPORT, 'value': '-ms-viewport'},
   ];
 
   static const List<Map<String, dynamic>> MEDIA_OPERATORS = const [
diff --git a/lib/src/tree.dart b/lib/src/tree.dart
index d6b9dbf..509e708 100644
--- a/lib/src/tree.dart
+++ b/lib/src/tree.dart
@@ -540,6 +540,19 @@
   visit(VisitorBase visitor) => visitor.visitSupportsDisjunction(this);
 }
 
+class ViewportDirective extends Directive {
+  final String name;
+  final DeclarationGroup declarations;
+
+  ViewportDirective(this.name, this.declarations, SourceSpan span)
+      : super(span);
+
+  ViewportDirective clone() =>
+      new ViewportDirective(name, declarations.clone(), span);
+
+  visit(VisitorBase visitor) => visitor.visitViewportDirective(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 7ae67e4..ba35dec 100644
--- a/lib/src/tree_printer.dart
+++ b/lib/src/tree_printer.dart
@@ -134,6 +134,13 @@
     output.depth--;
   }
 
+  void visitViewportDirective(ViewportDirective node) {
+    heading('ViewportDirective', node);
+    output.depth++;
+    super.visitViewportDirective(node);
+    output.depth--;
+  }
+
   void visitPageDirective(PageDirective node) {
     heading('PageDirective', node);
     output.depth++;
diff --git a/lib/visitor.dart b/lib/visitor.dart
index ad94033..eff9e7b 100644
--- a/lib/visitor.dart
+++ b/lib/visitor.dart
@@ -26,6 +26,7 @@
   visitSupportsNegation(SupportsNegation node);
   visitSupportsConjunction(SupportsConjunction node);
   visitSupportsDisjunction(SupportsDisjunction node);
+  visitViewportDirective(ViewportDirective node);
   visitMediaExpression(MediaExpression node);
   visitMediaQuery(MediaQuery node);
   visitMediaDirective(MediaDirective node);
@@ -184,6 +185,10 @@
     _visitNodeList(node.conditions);
   }
 
+  visitViewportDirective(ViewportDirective node) {
+    node.declarations.visit(this);
+  }
+
   visitMediaDirective(MediaDirective node) {
     for (var mediaQuery in node.mediaQueries) {
       visitMediaQuery(mediaQuery);
diff --git a/test/declaration_test.dart b/test/declaration_test.dart
index a9a51cb..9236b12 100644
--- a/test/declaration_test.dart
+++ b/test/declaration_test.dart
@@ -603,6 +603,36 @@
   expect(errors.first.span.text, 'or');
 }
 
+void testViewport() {
+  // No declarations.
+  var css = '@viewport {\n}';
+  expectCss(css, css);
+
+  // All declarations.
+  css = '''
+@viewport {
+  min-width: auto;
+  max-width: 800px;
+  width: 400px;
+  min-height: 50%;
+  max-height: 200px;
+  height: 100px 200px;
+  zoom: auto;
+  min-zoom: 0.75;
+  max-zoom: 40%;
+  user-zoom: fixed;
+  orientation: landscape;
+}''';
+  expectCss(css, css);
+
+  // Vendor specific.
+  css = '''
+@-ms-viewport {
+  width: device-width;
+}''';
+  expectCss(css, css);
+}
+
 void testFontFace() {
   var errors = <Message>[];
 
@@ -1271,6 +1301,7 @@
   test('Media Queries', testMediaQueries);
   test('Document', testMozDocument);
   test('Supports', testSupports);
+  test('Viewport', testViewport);
   test('Font-Face', testFontFace);
   test('CSS file', testCssFile);
   test('Compact Emitter', testCompactEmitter);