svn merge -r 26292:26492 https://dart.googlecode.com/svn/branches/bleeding_edge trunk

git-svn-id: http://dart.googlecode.com/svn/trunk@26504 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 9206a32..f850b03 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -14,6 +14,5 @@
       input_api,
       output_api,
       json_url='http://dart-status.appspot.com/current?format=json')
-  # TODO(ricow): reenable when status page is back in shape
-  # results.extend(status_check)
+  results.extend(status_check)
   return results
diff --git a/README.dart-sdk b/README.dart-sdk
index c0c4b0d..04e9d4b 100644
--- a/README.dart-sdk
+++ b/README.dart-sdk
@@ -1,4 +1,4 @@
-The Dark SDK is a set of tools and libraries for the Dart programming language.
+The Dart SDK is a set of tools and libraries for the Dart programming language.
 
 You can find information about Dart online at dartlang.org.
 
diff --git a/pkg/analyzer_experimental/bin/analyzer.dart b/pkg/analyzer_experimental/bin/analyzer.dart
index 5fed0a8..119376f 100644
--- a/pkg/analyzer_experimental/bin/analyzer.dart
+++ b/pkg/analyzer_experimental/bin/analyzer.dart
@@ -8,6 +8,7 @@
 library analyzer;
 
 import 'dart:async';
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:analyzer_experimental/src/generated/engine.dart';
@@ -80,7 +81,7 @@
     // read line from stdin
     Stream cmdLine = stdin
         .transform(new StringDecoder())
-        .transform(new LineTransformer());
+        .transform(new LineSplitter());
     var subscription = cmdLine.listen((String line) {
       // may be finish
       if (line.isEmpty) {
diff --git a/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart b/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart
index 22e0d2a..a3745ec 100644
--- a/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart
+++ b/pkg/analyzer_experimental/lib/src/services/formatter_impl.dart
@@ -150,6 +150,13 @@
   /// Cached previous token for calculating preceding whitespace.
   Token previousToken;
 
+  /// A flag to indicate that a newline should be emitted before the next token.
+  bool needsNewline = false;
+  
+  /// A flag to indicate that user introduced newlines should be emitted before
+  /// the next token.
+  bool preservePrecedingNewlines = false;
+  
   /// Initialize a newly created visitor to write source code representing
   /// the visited nodes to the given [writer].
   SourceVisitor(FormatterOptions options, this.lineInfo) :
@@ -161,49 +168,59 @@
   }
 
   visitAnnotation(Annotation node) {
-    emitToken(node.atSign);
+    token(node.atSign);
     visit(node.name);
     visitPrefixed('.', node.constructorName);
     visit(node.arguments);
   }
 
   visitArgumentDefinitionTest(ArgumentDefinitionTest node) {
-    emitToken(node.question);
+    token(node.question);
     visit(node.identifier);
   }
 
   visitArgumentList(ArgumentList node) {
-    emitToken(node.leftParenthesis);
+    token(node.leftParenthesis);
     visitList(node.arguments, ', ');
-    emitToken(node.rightParenthesis);
+    token(node.rightParenthesis);
   }
 
   visitAsExpression(AsExpression node) {
     visit(node.expression);
-    emitToken(node.asOperator, prefix: ' ', suffix: ' ');
+    space();
+    token(node.asOperator);
+    space();
     visit(node.type);
   }
 
   visitAssertStatement(AssertStatement node) {
-    emitToken(node.keyword, suffix: ' (');
+    token(node.keyword);
+    space();
+    token(node.leftParenthesis);
     visit(node.condition);
-    emitToken(node.semicolon, prefix: ')');
+    token(node.rightParenthesis);
+    token(node.semicolon);
   }
 
   visitAssignmentExpression(AssignmentExpression node) {
     visit(node.leftHandSide);
-    emitToken(node.operator, prefix: ' ', suffix: ' ');
+    space();
+    token(node.operator);
+    space();
     visit(node.rightHandSide);
   }
 
   visitBinaryExpression(BinaryExpression node) {
     visit(node.leftOperand);
-    emitToken(node.operator, prefix: ' ', suffix: ' ');
+    space();
+    token(node.operator);
+    space();
     visit(node.rightOperand);
   }
 
   visitBlock(Block node) {
-    emitToken(node.leftBracket);
+    token(node.leftBracket);
+    needsNewline = true;
     indent();
 
     for (var stmt in node.statements) {
@@ -211,11 +228,9 @@
     }
 
     unindent();
-    newline();
-    print('}');
-//TODO(pquitslund): make this work    
-//    emitToken(node.rightBracket);
-    previousToken = node.rightBracket;
+    preservePrecedingNewlines = true;
+    needsNewline = true;
+    token(node.rightBracket);
   }
 
   visitBlockFunctionBody(BlockFunctionBody node) {
@@ -223,13 +238,15 @@
   }
 
   visitBooleanLiteral(BooleanLiteral node) {
-    emitToken(node.literal);
+    token(node.literal);
   }
 
   visitBreakStatement(BreakStatement node) {
-    emitToken(node.keyword);
+    preservePrecedingNewlines = true;
+    token(node.keyword);
     visitPrefixed(' ', node.label);
-    emitToken(node.semicolon);
+    token(node.semicolon);
+    needsNewline = true;
   }
 
   visitCascadeExpression(CascadeExpression node) {
@@ -241,28 +258,34 @@
     visitPrefixed('on ', node.exceptionType);
     if (node.catchKeyword != null) {
       if (node.exceptionType != null) {
-        print(' ');
+        space();
       }
-      print('catch (');
+      token(node.catchKeyword);
+      space();
+      token(node.leftParenthesis);
       visit(node.exceptionParameter);
       visitPrefixed(', ', node.stackTraceParameter);
-      print(') ');
+      token(node.rightParenthesis);
+      space();
     } else {
-      print(' ');
+      space();
     }
     visit(node.body);
-    newline();
+    needsNewline = true;
   }
 
   visitClassDeclaration(ClassDeclaration node) {
-    emitToken(node.abstractKeyword, suffix: ' ');
-    emitToken(node.classKeyword, suffix: ' ');
+    preservePrecedingNewlines = true;
+    modifier(node.abstractKeyword);
+    token(node.classKeyword);
+    space();
     visit(node.name);
     visit(node.typeParameters);
     visitPrefixed(' ', node.extendsClause);
     visitPrefixed(' ', node.withClause);
     visitPrefixed(' ', node.implementsClause);
-    emitToken(node.leftBracket, prefix: ' ');
+    space();
+    token(node.leftBracket);
     indent();
 
     for (var i = 0; i < node.members.length; i++) {
@@ -271,21 +294,26 @@
 
     unindent();
 
-    emitToken(node.rightBracket, minNewlines: 1);
+    emitPrecedingNewlines(node.rightBracket, min: 1);
+    token(node.rightBracket);
   }
 
   visitClassTypeAlias(ClassTypeAlias node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visit(node.name);
     visit(node.typeParameters);
-    print(' = ');
+    space();
+    token(node.equals);
+    space();
     if (node.abstractKeyword != null) {
-      print('abstract ');
+      token(node.abstractKeyword);
+      space();
     }
     visit(node.superclass);
     visitPrefixed(' ', node.withClause);
     visitPrefixed(' ', node.implementsClause);
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitComment(Comment node) => null;
@@ -293,31 +321,40 @@
   visitCommentReference(CommentReference node) => null;
 
   visitCompilationUnit(CompilationUnit node) {
+    
+    // Cache EOF for leading whitespace calculation
+    var start = node.beginToken.previous;
+    if (start != null && start.type is TokenType_EOF) {
+      previousToken = start;
+    }
+    
     var scriptTag = node.scriptTag;
     var directives = node.directives;
     visit(scriptTag);
-    var prefix = scriptTag == null ? '' : ' ';
-    visitPrefixedList(prefix, directives, ' ');
-    //prefix = scriptTag == null && directives.isEmpty ? '' : ' ';
-    prefix = '';
-    visitPrefixedList(prefix, node.declarations);
+    visitList(directives);
+    visitList(node.declarations);
 
-    //TODO(pquitslund): move this?
-    newline();
+    // Handle trailing whitespace
+    preservePrecedingNewlines = true;
+    token(node.endToken /* EOF */);
   }
 
   visitConditionalExpression(ConditionalExpression node) {
     visit(node.condition);
-    print(' ? ');
+    space();
+    token(node.question);
+    space();
     visit(node.thenExpression);
-    print(' : ');
+    space();
+    token(node.colon);
+    space();
     visit(node.elseExpression);
   }
 
   visitConstructorDeclaration(ConstructorDeclaration node) {
-    emitToken(node.externalKeyword, suffix: ' ');
-    emitToken(node.constKeyword, suffix: ' ');
-    emitToken(node.factoryKeyword, suffix: ' ');
+    modifier(node.externalKeyword);
+    modifier(node.constKeyword);
+    modifier(node.factoryKeyword);
     visit(node.returnType);
     visitPrefixed('.', node.name);
     visit(node.parameters);
@@ -327,9 +364,12 @@
   }
 
   visitConstructorFieldInitializer(ConstructorFieldInitializer node) {
-    emitToken(node.keyword, suffix: '.');
+    token(node.keyword);
+    token(node.period);
     visit(node.fieldName);
-    print(' = ');
+    space();
+    token(node.equals);
+    space();
     visit(node.expression);
   }
 
@@ -339,150 +379,179 @@
   }
 
   visitContinueStatement(ContinueStatement node) {
-    emitToken(node.keyword);
+    token(node.keyword);
     visitPrefixed(' ', node.label);
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitDeclaredIdentifier(DeclaredIdentifier node) {
-    emitToken(node.keyword, suffix: ' ');
-    visitSuffixed(node.type, ' ');
+    token(node.keyword);
+    space();
+    visit(node.type);
+    //TODO(pquitslund): avoiding visitSuffixed(..) but we can do better
+    if (node.type != null) {
+      space();
+    }
     visit(node.identifier);
   }
 
   visitDefaultFormalParameter(DefaultFormalParameter node) {
     visit(node.parameter);
     if (node.separator != null) {
-      print(' ');
-      print(node.separator.lexeme);
+      space();
+      token(node.separator);
       visitPrefixed(' ', node.defaultValue);
     }
   }
 
   visitDoStatement(DoStatement node) {
-    emitToken(node.doKeyword, suffix: ' ');
+    token(node.doKeyword);
+    space();
     visit(node.body);
-    emitToken(node.whileKeyword, prefix: ' ', suffix: ' (');
+    space();
+    token(node.whileKeyword);
+    space();
+    token(node.leftParenthesis);
     visit(node.condition);
-    emitToken(node.semicolon, prefix: ')');
+    token(node.rightParenthesis);
+    token(node.semicolon);
   }
 
   visitDoubleLiteral(DoubleLiteral node) {
-    print(node.literal.lexeme);
+    token(node.literal);
   }
 
   visitEmptyFunctionBody(EmptyFunctionBody node) {
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitEmptyStatement(EmptyStatement node) {
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitExportDirective(ExportDirective node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visit(node.uri);
     visitPrefixedList(' ', node.combinators, ' ');
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitExpressionFunctionBody(ExpressionFunctionBody node) {
-    emitToken(node.functionDefinition, suffix: ' ');
+    token(node.functionDefinition);
+    space();
     visit(node.expression);
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitExpressionStatement(ExpressionStatement node) {
     visit(node.expression);
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitExtendsClause(ExtendsClause node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visit(node.superclass);
   }
 
   visitFieldDeclaration(FieldDeclaration node) {
-    emitToken(node.keyword, suffix: ' ');
+    needsNewline = true;
+    preservePrecedingNewlines = true;
+    modifier(node.keyword);
     visit(node.fields);
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitFieldFormalParameter(FieldFormalParameter node) {
-    emitToken(node.keyword, suffix: ' ');
-    visitSuffixed(node.type, ' ');
-    print('this.');
+    token(node.keyword);
+    space();
+    visit(node.type);
+    space();
+    token(node.thisToken);
+    token(node.period);
     visit(node.identifier);
     visit(node.parameters);
   }
 
   visitForEachStatement(ForEachStatement node) {
-    print('for (');
+    token(node.forKeyword);
+    space();
+    token(node.leftParenthesis);
     visit(node.loopVariable);
-    print(' in ');
+    space();
+    token(node.inKeyword);
+    space();
     visit(node.iterator);
-    print(') ');
+    token(node.rightParenthesis);
+    space();
     visit(node.body);
   }
 
   visitFormalParameterList(FormalParameterList node) {
     var groupEnd = null;
-    print('(');
+    token(node.leftParenthesis);
     var parameters = node.parameters;
     var size = parameters.length;
     for (var i = 0; i < size; i++) {
       var parameter = parameters[i];
       if (i > 0) {
-        print(', ');
+        append(', ');
       }
       if (groupEnd == null && parameter is DefaultFormalParameter) {
         if (identical(parameter.kind, ParameterKind.NAMED)) {
           groupEnd = '}';
-          print('{');
+          append('{');
         } else {
           groupEnd = ']';
-          print('[');
+          append('[');
         }
       }
       parameter.accept(this);
     }
     if (groupEnd != null) {
-      print(groupEnd);
+      append(groupEnd);
     }
-    print(')');
+    token(node.rightParenthesis);
   }
 
   visitForStatement(ForStatement node) {
+    token(node.forKeyword);
+    space();
+    token(node.leftParenthesis);
     var initialization = node.initialization;
-    print('for (');
     if (initialization != null) {
       visit(initialization);
     } else {
       visit(node.variables);
     }
-    print(';');
+    token(node.leftSeparator);
     visitPrefixed(' ', node.condition);
-    print(';');
+    token(node.rightSeparator);
     visitPrefixedList(' ', node.updaters, ', ');
-    print(') ');
+    token(node.leftParenthesis);
+    space();
     visit(node.body);
   }
 
   visitFunctionDeclaration(FunctionDeclaration node) {
+    needsNewline = true;
+    preservePrecedingNewlines = true;
     visitSuffixed(node.returnType, ' ');
-    emitToken(node.propertyKeyword, suffix: ' ');
+    token(node.propertyKeyword, followedBy: space);
     visit(node.name);
     visit(node.functionExpression);
   }
 
   visitFunctionDeclarationStatement(FunctionDeclarationStatement node) {
     visit(node.functionDeclaration);
-    print(';');
+    // TODO(pquitslund): fix and handle in function body
+    append(';');
   }
 
   visitFunctionExpression(FunctionExpression node) {
     visit(node.parameters);
-    print(' ');
+    space();
     visit(node.body);
   }
 
@@ -492,12 +561,13 @@
   }
 
   visitFunctionTypeAlias(FunctionTypeAlias node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visitSuffixed(node.returnType, ' ');
     visit(node.name);
     visit(node.typeParameters);
     visit(node.parameters);
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitFunctionTypedFormalParameter(FunctionTypedFormalParameter node) {
@@ -507,81 +577,96 @@
   }
 
   visitHideCombinator(HideCombinator node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visitList(node.hiddenNames, ', ');
   }
 
   visitIfStatement(IfStatement node) {
-    emitToken(node.ifKeyword);
-    print(' (');
+    preservePrecedingNewlines = true;
+    token(node.ifKeyword);
+    space();
+    token(node.leftParenthesis);
     visit(node.condition);
-    print(') ');
+    token(node.rightParenthesis);
+    space();
     visit(node.thenStatement);
-    visitPrefixed(' else ', node.elseStatement);
+    //visitPrefixed(' else ', node.elseStatement);
+    if (node.elseStatement != null) {
+      space();
+      token(node.elseKeyword);
+      space();
+      visit(node.elseStatement);
+    }
+    needsNewline = true;
   }
-
+  
   visitImplementsClause(ImplementsClause node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visitList(node.interfaces, ', ');
   }
 
   visitImportDirective(ImportDirective node) {
-    emitToken(node.keyword, suffix: ' ');
+    preservePrecedingNewlines = true;
+    token(node.keyword);
+    space();
     visit(node.uri);
     visitPrefixed(' as ', node.prefix);
     visitPrefixedList(' ', node.combinators, ' ');
-    emitToken(node.semicolon);
+    token(node.semicolon);
+    needsNewline = true;
   }
 
   visitIndexExpression(IndexExpression node) {
     if (node.isCascaded) {
-      print('..');
+      token(node.period);
     } else {
       visit(node.target);
     }
-    print('[');
+    token(node.leftBracket);
     visit(node.index);
-    print(']');
+    token(node.rightBracket);
   }
 
   visitInstanceCreationExpression(InstanceCreationExpression node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visit(node.constructorName);
     visit(node.argumentList);
   }
 
   visitIntegerLiteral(IntegerLiteral node) {
-    print(node.literal.lexeme);
+    token(node.literal);
   }
 
   visitInterpolationExpression(InterpolationExpression node) {
     if (node.rightBracket != null) {
-      print('\${');
+      token(node.leftBracket);
       visit(node.expression);
-      print('}');
+      token(node.rightBracket);
     } else {
-      print('\$');
+      token(node.leftBracket);
       visit(node.expression);
     }
   }
 
   visitInterpolationString(InterpolationString node) {
-    print(node.contents.lexeme);
+    token(node.contents);
   }
 
   visitIsExpression(IsExpression node) {
     visit(node.expression);
-    if (node.notOperator == null) {
-      print(' is ');
-    } else {
-      print(' is! ');
-    }
+    space();
+    token(node.isOperator);
+    token(node.notOperator);
+    space();
     visit(node.type);
   }
 
   visitLabel(Label node) {
     visit(node.label);
-    print(':');
+    token(node.colon);
   }
 
   visitLabeledStatement(LabeledStatement node) {
@@ -590,49 +675,51 @@
   }
 
   visitLibraryDirective(LibraryDirective node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visit(node.name);
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitLibraryIdentifier(LibraryIdentifier node) {
-    print(node.name);
+    append(node.name);
   }
 
   visitListLiteral(ListLiteral node) {
     if (node.modifier != null) {
-      print(node.modifier.lexeme);
-      print(' ');
+      token(node.modifier);
+      space();
     }
     visit(node.typeArguments);
-    print('[');
+    token(node.leftBracket);
     visitList(node.elements, ', ');
-    print(']');
+    token(node.rightBracket);
   }
 
   visitMapLiteral(MapLiteral node) {
-    if (node.modifier != null) {
-      print(node.modifier.lexeme);
-      print(' ');
-    }
+    modifier(node.modifier);
     visitSuffixed(node.typeArguments, ' ');
-    print('{');
+    token(node.leftBracket);
     visitList(node.entries, ', ');
-    print('}');
+    token(node.rightBracket);
   }
 
   visitMapLiteralEntry(MapLiteralEntry node) {
     visit(node.key);
-    print(' : ');
+    space();
+    token(node.separator);
+    space();
     visit(node.value);
   }
 
   visitMethodDeclaration(MethodDeclaration node) {
-    emitToken(node.externalKeyword, suffix: ' ');
-    emitToken(node.modifierKeyword, suffix: ' ');
+    needsNewline = true;
+    preservePrecedingNewlines = true;
+    modifier(node.externalKeyword);
+    modifier(node.modifierKeyword);
     visitSuffixed(node.returnType, ' ');
-    emitToken(node.propertyKeyword, suffix: ' ');
-    emitToken(node.operatorKeyword, suffix: ' ');
+    modifier(node.propertyKeyword);
+    modifier(node.operatorKeyword);
     visit(node.name);
     if (!node.isGetter) {
       visit(node.parameters);
@@ -642,7 +729,7 @@
 
   visitMethodInvocation(MethodInvocation node) {
     if (node.isCascaded) {
-      print('..');
+      token(node.period);
     } else {
       visitSuffixed(node.target, '.');
     }
@@ -656,107 +743,114 @@
   }
 
   visitNativeClause(NativeClause node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visit(node.name);
   }
 
   visitNativeFunctionBody(NativeFunctionBody node) {
-    emitToken(node.nativeToken, suffix: ' ');
+    token(node.nativeToken);
+    space();
     visit(node.stringLiteral);
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitNullLiteral(NullLiteral node) {
-    emitToken(node.literal);
+    token(node.literal);
   }
 
   visitParenthesizedExpression(ParenthesizedExpression node) {
-    emitToken(node.leftParenthesis);
+    token(node.leftParenthesis);
     visit(node.expression);
-    emitToken(node.rightParenthesis);
+    token(node.rightParenthesis);
   }
 
   visitPartDirective(PartDirective node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visit(node.uri);
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitPartOfDirective(PartOfDirective node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visit(node.libraryName);
-    emitToken(node.semicolon);
+    token(node.semicolon);
   }
 
   visitPostfixExpression(PostfixExpression node) {
     visit(node.operand);
-    print(node.operator.lexeme);
+    token(node.operator);
   }
 
   visitPrefixedIdentifier(PrefixedIdentifier node) {
     visit(node.prefix);
-    print('.');
+    token(node.period);
     visit(node.identifier);
   }
 
   visitPrefixExpression(PrefixExpression node) {
-    emitToken(node.operator);
+    token(node.operator);
     visit(node.operand);
   }
 
   visitPropertyAccess(PropertyAccess node) {
     if (node.isCascaded) {
-      print('..');
+      token(node.operator);
     } else {
       visit(node.target);
-      print('.');
+      token(node.operator);
     }
     visit(node.propertyName);
   }
 
   visitRedirectingConstructorInvocation(RedirectingConstructorInvocation node) {
-    emitToken(node.keyword);
+    token(node.keyword);
     visitPrefixed('.', node.constructorName);
     visit(node.argumentList);
   }
 
   visitRethrowExpression(RethrowExpression node) {
-    emitToken(node.keyword);
+    token(node.keyword);
   }
 
   visitReturnStatement(ReturnStatement node) {
+    preservePrecedingNewlines = true;
     var expression = node.expression;
     if (expression == null) {
-      emitToken(node.keyword, minNewlines: 1);
-      emitToken(node.semicolon);
+      token(node.keyword);
+      token(node.semicolon);
     } else {
-      emitToken(node.keyword, suffix: ' ', minNewlines: 1);
+      token(node.keyword);
+      space();
       expression.accept(this);
-      emitToken(node.semicolon);
+      token(node.semicolon);
     }
   }
 
   visitScriptTag(ScriptTag node) {
-    print(node.scriptTag.lexeme);
+    token(node.scriptTag);
   }
 
   visitShowCombinator(ShowCombinator node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visitList(node.shownNames, ', ');
   }
 
   visitSimpleFormalParameter(SimpleFormalParameter node) {
-    emitToken(node.keyword, suffix: ' ');
+    modifier(node.keyword);
     visitSuffixed(node.type, ' ');
     visit(node.identifier);
   }
 
   visitSimpleIdentifier(SimpleIdentifier node) {
-    emitToken(node.token);
+    token(node.token);
   }
 
   visitSimpleStringLiteral(SimpleStringLiteral node) {
-    emitToken(node.literal);
+    token(node.literal);
   }
 
   visitStringInterpolation(StringInterpolation node) {
@@ -764,42 +858,50 @@
   }
 
   visitSuperConstructorInvocation(SuperConstructorInvocation node) {
-    emitToken(node.keyword);
+    token(node.keyword);
     visitPrefixed('.', node.constructorName);
     visit(node.argumentList);
   }
 
   visitSuperExpression(SuperExpression node) {
-    emitToken(node.keyword);
+    token(node.keyword);
   }
 
   visitSwitchCase(SwitchCase node) {
+    preservePrecedingNewlines = true;
     visitSuffixedList(node.labels, ' ', ' ');
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visit(node.expression);
-    print(':');
+    token(node.colon);
     indent();
+    needsNewline = true;
     visitList(node.statements);
     unindent();
   }
 
   visitSwitchDefault(SwitchDefault node) {
+    preservePrecedingNewlines = true;
     visitSuffixedList(node.labels, ' ', ' ');
-    emitToken(node.keyword, suffix: ': ');
+    token(node.keyword);
+    token(node.colon);
+    space();
     visitList(node.statements, ' ');
   }
 
   visitSwitchStatement(SwitchStatement node) {
-    emitToken(node.keyword);
-    print(' (');
+    token(node.keyword);
+    space();
+    token(node.leftParenthesis);
     visit(node.expression);
-    print(') ');
-    emitToken(node.leftBracket);
+    token(node.rightParenthesis);
+    space();
+    token(node.leftBracket);
     indent();
     visitList(node.members);
     unindent();
-    emitToken(node.rightBracket);
-    newline();
+    token(node.rightBracket);
+    needsNewline = true;
   }
 
   visitSymbolLiteral(SymbolLiteral node) {
@@ -807,29 +909,34 @@
   }
 
   visitThisExpression(ThisExpression node) {
-    emitToken(node.keyword);
+    token(node.keyword);
   }
 
   visitThrowExpression(ThrowExpression node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visit(node.expression);
   }
 
   visitTopLevelVariableDeclaration(TopLevelVariableDeclaration node) {
-    visitSuffixed(node.variables, ';');
+    preservePrecedingNewlines = true;
+    visit(node.variables);
+    token(node.semicolon);
   }
 
   visitTryStatement(TryStatement node) {
-    emitToken(node.tryKeyword, suffix: ' ');
+    preservePrecedingNewlines = true;
+    token(node.tryKeyword);
+    space();
     visit(node.body);
     visitPrefixedList(' ', node.catchClauses, ' ');
     visitPrefixed(' finally ', node.finallyClause);
   }
 
   visitTypeArgumentList(TypeArgumentList node) {
-    emitToken(node.leftBracket);
+    token(node.leftBracket);
     visitList(node.arguments, ', ');
-    emitToken(node.rightBracket);
+    token(node.rightBracket);
   }
 
   visitTypeName(TypeName node) {
@@ -843,36 +950,47 @@
   }
 
   visitTypeParameterList(TypeParameterList node) {
-    emitToken(node.leftBracket);
+    token(node.leftBracket);
     visitList(node.typeParameters, ', ');
-    emitToken(node.rightBracket);
+    token(node.rightBracket);
   }
 
   visitVariableDeclaration(VariableDeclaration node) {
     visit(node.name);
-    visitPrefixed(' = ', node.initializer);
+    if (node.initializer != null) {
+      space();
+      token(node.equals);
+      space();
+      visit(node.initializer);
+    }
   }
 
   visitVariableDeclarationList(VariableDeclarationList node) {
-    emitToken(node.keyword, suffix: ' ');
+    token(node.keyword);
+    space();
     visitSuffixed(node.type, ' ');
     visitList(node.variables, ', ');
   }
 
   visitVariableDeclarationStatement(VariableDeclarationStatement node) {
     visit(node.variables);
-    emitToken(node.semicolon);
+    token(node.semicolon);
+    needsNewline = true;
   }
 
   visitWhileStatement(WhileStatement node) {
-    emitToken(node.keyword, suffix: ' (');
+    token(node.keyword);
+    space();
+    token(node.leftParenthesis);
     visit(node.condition);
-    print(') ');
+    token(node.rightParenthesis);
+    space();
     visit(node.body);
   }
 
   visitWithClause(WithClause node) {
-    emitToken(node.withKeyword, suffix: ' ');
+    token(node.withKeyword);
+    space();
     visitList(node.mixinTypes, ', ');
   }
 
@@ -888,7 +1006,7 @@
   visitSuffixed(ASTNode node, String suffix) {
     if (node != null) {
       node.accept(this);
-      print(suffix);
+      append(suffix);
     }
   }
 
@@ -896,7 +1014,7 @@
   /// it is non-null.
   visitPrefixed(String prefix, ASTNode node) {
     if (node != null) {
-      print(prefix);
+      append(prefix);
       node.accept(this);
     }
   }
@@ -905,7 +1023,7 @@
   /// body is not empty.
   visitPrefixedBody(String prefix, FunctionBody body) {
     if (body is! EmptyFunctionBody) {
-      print(prefix);
+      append(prefix);
     }
     visit(body);
   }
@@ -916,7 +1034,7 @@
       var size = nodes.length;
       for (var i = 0; i < size; i++) {
         if (i > 0) {
-          print(separator);
+          append(separator);
         }
         nodes[i].accept(this);
       }
@@ -930,11 +1048,11 @@
       if (size > 0) {
         for (var i = 0; i < size; i++) {
           if (i > 0) {
-            print(separator);
+            append(separator);
           }
           nodes[i].accept(this);
         }
-        print(suffix);
+        append(suffix);
       }
     }
   }
@@ -945,10 +1063,10 @@
     if (nodes != null) {
       var size = nodes.length;
       if (size > 0) {
-        print(prefix);
+        append(prefix);
         for (var i = 0; i < size; i++) {
           if (i > 0 && separator != null) {
-            print(separator);
+            append(separator);
           }
           nodes[i].accept(this);
         }
@@ -956,37 +1074,50 @@
     }
   }
 
-
-  /// Emit the given [token], if it's non-null, preceded by any detected 
-  /// newlines or a minimum as specified by [minNewlines], printing a [prefix] 
-  /// before and a [suffix] after.
-  emitToken(Token token, {String prefix, String suffix, 
-      int minNewlines: 0}) {
+  /// Emit the given [modifier] if it's non null, followed by non-breaking 
+  /// whitespace.
+  modifier(Token modifier) {
+    token(modifier, followedBy: space);
+  }
+    
+  token(Token token, {followedBy(), int minNewlines: 0}) {
     if (token != null) {
-      print(prefix);
-      emitPrecedingNewlines(token, min: minNewlines);
-      print(token.lexeme);
-      print(suffix);
-    }
+      if (needsNewline) {
+        minNewlines = max(1, minNewlines);
+      }
+      if (preservePrecedingNewlines || minNewlines > 0) {
+        var emitted = emitPrecedingNewlines(token, min: minNewlines);
+        preservePrecedingNewlines = false;
+        if (emitted > 0) {
+          needsNewline = false;
+        }
+      }
+      append(token.lexeme);
+      if (followedBy != null) { 
+        followedBy();
+      }
+      previousToken = token;
+    }    
+  }
+    
+  /// Emit a non-breakable space.
+  space() {
+    //TODO(pquitslund): replace with a proper space token
+    append(' ');
   }
   
-  /// Print the given [string] to the source writer if it's non-null.
-  print(String string) {
+  /// Emit a breakable space
+  breakableSpace() {
+    //Implement
+  }
+  
+  /// Append the given [string] to the source writer if it's non-null.
+  append(String string) {
     if (string != null) {
       writer.print(string);
     }
   }
-  
-  /// Emit a newline.
-  newline() {
-   writer.newline();
-  }
-  
-  /// Emit [n] newlines.
-  newlines(n) {
-   writer.newlines(n);
-  }
-  
+    
   /// Indent.
   indent() {
     writer.indent();
@@ -998,18 +1129,19 @@
   }
   
   /// Emit any detected newlines or a minimum as specified by [minNewlines].
-  emitPrecedingNewlines(Token token, {min: 0}) {
+  int emitPrecedingNewlines(Token token, {min: 0}) {
     var comment = token.precedingComments;
     var currentToken = comment != null ? comment : token;
     var lines = max(min, countNewlinesBetween(previousToken, currentToken));
-    newlines(lines);
+    writer.newlines(lines);
     while (comment != null) {
-      print(comment.toString().trim());
-      newline();
+      append(comment.toString().trim());
+      writer.newline();
       comment = comment.next;
     }
 
     previousToken = token;
+    return lines;
   }
 
   /// Count the blanks between these two nodes.
diff --git a/pkg/analyzer_experimental/test/services/formatter_test.dart b/pkg/analyzer_experimental/test/services/formatter_test.dart
index 3f21446..2ad1d13 100644
--- a/pkg/analyzer_experimental/test/services/formatter_test.dart
+++ b/pkg/analyzer_experimental/test/services/formatter_test.dart
@@ -21,9 +21,13 @@
 
     test('CU (1)', () {
       expectCUFormatsTo(
-          'class A  {\n'
-          '}',
           'class A {\n'
+          '  var z;\n'
+          '  inc(int x) => ++x;\n'
+          '}\n',
+          'class A {\n'
+          '  var z;\n'
+          '  inc(int x) => ++x;\n'
           '}\n'
         );
     });
@@ -31,7 +35,7 @@
     test('CU (2)', () {
       expectCUFormatsTo(
           'class      A  {  \n'
-          '}',
+          '}\n',
           'class A {\n'
           '}\n'
         );
@@ -42,19 +46,78 @@
           'class A {\n'
           '  }',
           'class A {\n'
-          '}\n'
+          '}'
         );
     });
 
     test('CU (4)', () {
       expectCUFormatsTo(
           ' class A {\n'
-          '}',
+          '}\n',
           'class A {\n'
           '}\n'
         );
     });
 
+    test('CU (5)', () {
+      expectCUFormatsTo(
+          'class A  { int meaningOfLife() => 42; }',
+          'class A {\n'
+          '  int meaningOfLife() => 42;\n'
+          '}'
+      );
+    });
+    
+    
+//    test('CU - comments', () {
+//      expectCUFormatsTo(
+//          'library foo;\n'
+//          '\n'
+//          '//comment one\n\n'
+//          '//comment two\n\n'
+//          'class C {\n}\n',
+//          'library foo;\n'
+//          '\n'
+//          '//comment one\n\n'
+//          '//comment two\n\n'
+//          'class C {\n}\n'
+//      );
+//    });
+    
+    test('CU - top level', () {
+      expectCUFormatsTo(
+          '\n\n'
+          'foo() {\n'
+          '}\n'
+          'bar() {\n'
+          '}\n',
+          '\n\n'
+          'foo() {\n'
+          '}\n'
+          'bar() {\n'
+          '}\n'
+      );
+      expectCUFormatsTo(
+          'const A = 42;\n'
+          'final foo = 32;\n',
+          'const A = 42;\n'
+          'final foo = 32;\n'
+      );
+    });
+    
+    test('CU - imports', () {
+      expectCUFormatsTo(
+          'import "dart:io";\n\n'
+          'import "package:unittest/unittest.dart";\n'
+          'foo() {\n'
+          '}\n',
+          'import "dart:io";\n\n'
+          'import "package:unittest/unittest.dart";\n'
+          'foo() {\n'
+          '}\n'
+      );
+    });
+
     test('CU w/class decl comment', () {
       expectCUFormatsTo(
           'import "foo";\n\n'
@@ -64,29 +127,64 @@
           'import "foo";\n\n'
           '//Killer class\n'
           'class A {\n'
-          '}\n'
+          '}'
         );
     });
 
-
+    test('CU (method body)', () {
+      expectCUFormatsTo(
+          'class A {\n'
+          '  foo(path) {\n'
+          '    var buffer = new StringBuffer();\n'
+          '    var file = new File(path);\n'
+          '    return file;\n'
+          '  }\n'
+          '}\n',
+          'class A {\n'
+          '  foo(path) {\n'
+          '    var buffer = new StringBuffer();\n'
+          '    var file = new File(path);\n'
+          '    return file;\n'
+          '  }\n'
+          '}\n'
+      );
+      expectCUFormatsTo(
+          'class A {\n'
+          '  foo(files) {\n'
+          '    for (var  file in files) {\n'
+          '      print(file);\n'
+          '    }\n'
+          '  }\n'
+          '}\n',
+          'class A {\n'
+          '  foo(files) {\n'
+          '    for (var file in files) {\n'
+          '      print(file);\n'
+          '    }\n'
+          '  }\n'
+          '}\n'
+      );
+    });
+    
     test('CU (method indent)', () {
       expectCUFormatsTo(
           'class A {\n'
           'void x(){\n'
           '}\n'
-          '}',
+          '}\n',
           'class A {\n'
           '  void x() {\n'
           '  }\n'
           '}\n'
-        );
+      );
     });
 
     test('CU (method indent - 2)', () {
       expectCUFormatsTo(
           'class A {\n'
-          ' static  bool x(){ return true; }\n'
-          ' }',
+          ' static  bool x(){\n'
+          'return true; }\n'
+          ' }\n',
           'class A {\n'
           '  static bool x() {\n'
           '    return true;\n'
@@ -99,7 +197,7 @@
       expectCUFormatsTo(
           'class A {\n'
           ' int x() =>   42   + 3 ;  \n'
-          '   }',
+          '   }\n',
           'class A {\n'
           '  int x() => 42 + 3;\n'
           '}\n'
@@ -110,10 +208,12 @@
       expectCUFormatsTo(
           'class A {\n'
           ' int x() { \n'
-          'if (true) {return 42;\n'
-          '} else { return 13; }\n'
+          'if (true) {\n'
+          'return 42;\n'
+          '} else {\n'
+          'return 13;\n }\n'
           '   }'
-          '}',
+          '}\n',
           'class A {\n'
           '  int x() {\n'
           '    if (true) {\n'
@@ -192,7 +292,7 @@
         'case "fig":\n'
         'print("bleh");\n'
         'break;\n'
-        '}\n',
+        '}',
         'switch (fruit) {\n'
         '  case "apple":\n'
         '    print("delish");\n'
@@ -200,7 +300,7 @@
         '  case "fig":\n'
         '    print("bleh");\n'
         '    break;\n'
-        '}\n'
+        '}'
       );
     });
   
@@ -217,12 +317,12 @@
         'doSomething();\n'
         '} catch (e) {\n'
         'print(e);\n'
-        '}\n',
+        '}',
         'try {\n'
         '  doSomething();\n'
         '} catch (e) {\n'
         '  print(e);\n'
-        '}\n'
+        '}'
       );
     });
     
diff --git a/pkg/barback/lib/barback.dart b/pkg/barback/lib/barback.dart
index a211873..e37bd1b 100644
--- a/pkg/barback/lib/barback.dart
+++ b/pkg/barback/lib/barback.dart
@@ -6,9 +6,10 @@
 
 export 'src/asset.dart';
 export 'src/asset_id.dart';
+export 'src/asset_set.dart';
 export 'src/barback.dart';
 export 'src/build_result.dart';
-export 'src/errors.dart';
+export 'src/errors.dart' hide flattenAggregateExceptions;
 export 'src/package_provider.dart';
 export 'src/transform.dart' show Transform;
 export 'src/transform_logger.dart';
diff --git a/pkg/barback/lib/src/asset_cascade.dart b/pkg/barback/lib/src/asset_cascade.dart
index 341480d..e8e9355 100644
--- a/pkg/barback/lib/src/asset_cascade.dart
+++ b/pkg/barback/lib/src/asset_cascade.dart
@@ -10,10 +10,10 @@
 import 'asset.dart';
 import 'asset_id.dart';
 import 'asset_node.dart';
+import 'asset_set.dart';
 import 'build_result.dart';
 import 'cancelable_future.dart';
 import 'errors.dart';
-import 'change_batch.dart';
 import 'package_graph.dart';
 import 'phase.dart';
 import 'transformer.dart';
@@ -83,31 +83,14 @@
   /// last began.
   var _newChanges = false;
 
+  /// Returns all currently-available output assets from this cascade.
+  AssetSet get availableOutputs => _phases.last.availableOutputs;
+
   /// Creates a new [AssetCascade].
   ///
-  /// It loads source assets within [package] using [provider] and then uses
-  /// [transformerPhases] to generate output files from them.
-  //TODO(rnystrom): Better way of specifying transformers and their ordering.
-  AssetCascade(this.graph, this.package,
-      Iterable<Iterable<Transformer>> transformerPhases) {
-    // Flatten the phases to a list so we can traverse backwards to wire up
-    // each phase to its next.
-    var phases = transformerPhases.toList();
-
-    // Each phase writes its outputs as inputs to the next phase after it.
-    // Add a phase at the end for the final outputs of the last phase.
-    phases.add([]);
-
-    Phase nextPhase = null;
-    for (var transformers in phases.reversed) {
-      nextPhase = new Phase(this, _phases.length, transformers.toList(),
-          nextPhase);
-      nextPhase.onDirty.listen((_) {
-        _newChanges = true;
-        _waitForProcess();
-      });
-      _phases.insert(0, nextPhase);
-    }
+  /// It loads source assets within [package] using [provider].
+  AssetCascade(this.graph, this.package) {
+    _addPhase(new Phase(this, []));
   }
 
   /// Gets the asset identified by [id].
@@ -128,7 +111,7 @@
     // * If [id] has never been generated and all active transformers provide
     //   metadata about the file names of assets it can emit, we can prove that
     //   none of them can emit [id] and fail early.
-    return _phases.last.getInput(id).then((node) {
+    return _phases.last.getOutput(id).then((node) {
       // If the requested asset is available, we can just return it.
       if (node != null && node.state.isAvailable) return node;
 
@@ -190,11 +173,42 @@
     });
   }
 
+  /// Sets this cascade's transformer phases to [transformers].
+  void updateTransformers(Iterable<Iterable<Transformer>> transformers) {
+    transformers = transformers.toList();
+
+    for (var i = 0; i < transformers.length; i++) {
+      if (_phases.length > i) {
+        _phases[i].updateTransformers(transformers[i]);
+        continue;
+      }
+
+      _addPhase(_phases.last.addPhase(transformers[i]));
+    }
+
+    if (transformers.length < _phases.length) {
+      for (var i = transformers.length; i < _phases.length; i++) {
+        // TODO(nweiz): actually remove phases rather than emptying them of
+        // transformers.
+        _phases[i].updateTransformers([]);
+      }
+    }
+  }
+
   void reportError(BarbackException error) {
     _accumulatedErrors.add(error);
     _errorsController.add(error);
   }
 
+  /// Add [phase] to the end of [_phases] and watch its [onDirty] stream.
+  void _addPhase(Phase phase) {
+    phase.onDirty.listen((_) {
+      _newChanges = true;
+      _waitForProcess();
+    });
+    _phases.add(phase);
+  }
+
   /// Starts the build process asynchronously if there is work to be done.
   ///
   /// Returns a future that completes with the background processing is done.
diff --git a/pkg/barback/lib/src/asset_forwarder.dart b/pkg/barback/lib/src/asset_forwarder.dart
new file mode 100644
index 0000000..7dc74dc
--- /dev/null
+++ b/pkg/barback/lib/src/asset_forwarder.dart
@@ -0,0 +1,53 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library barback.asset_forwarder;
+
+import 'dart:async';
+
+import 'asset_node.dart';
+
+/// A wrapper for an [AssetNode] that forwards events to a new node.
+///
+/// A forwarder is used when a class wants to forward an [AssetNode] that it
+/// gets as an input, but also wants to have control over when that node is
+/// marked as removed. The forwarder can be closed, thus removing its output
+/// node, without the original node having been removed.
+class AssetForwarder {
+  /// The subscription on the input node.
+  StreamSubscription _subscription;
+
+  /// The controller for the output node.
+  final AssetNodeController _controller;
+
+  /// The node to which events are forwarded.
+  AssetNode get node => _controller.node;
+
+  AssetForwarder(AssetNode node)
+      : _controller = new AssetNodeController(node.id, node.transform) {
+    _subscription = node.onStateChange.listen((state) {
+      if (state.isAvailable) {
+        _controller.setAvailable(node.asset);
+      } else if (state.isDirty) {
+        _controller.setDirty();
+      } else {
+        assert(state.isRemoved);
+        close();
+      }
+    });
+
+    if (node.state.isAvailable) {
+      _controller.setAvailable(node.asset);
+    } else if (node.state.isRemoved) {
+      close();
+    }
+  }
+
+  /// Closes the forwarder and marks [node] as removed.
+  void close() {
+    if (_controller.node.state.isRemoved) return;
+    _subscription.cancel();
+    _controller.setRemoved();
+  }
+}
diff --git a/pkg/barback/lib/src/asset_id.dart b/pkg/barback/lib/src/asset_id.dart
index d9cef32..c4ed1d8 100644
--- a/pkg/barback/lib/src/asset_id.dart
+++ b/pkg/barback/lib/src/asset_id.dart
@@ -4,9 +4,6 @@
 
 library barback.asset_id;
 
-import 'dart:async';
-import 'dart:io';
-
 import 'package:path/path.dart' as pathos;
 
 /// AssetIDs always use POSIX style paths regardless of the host platform.
diff --git a/pkg/barback/lib/src/asset_node.dart b/pkg/barback/lib/src/asset_node.dart
index 587f2bd..edc7408 100644
--- a/pkg/barback/lib/src/asset_node.dart
+++ b/pkg/barback/lib/src/asset_node.dart
@@ -9,7 +9,6 @@
 import 'asset.dart';
 import 'asset_id.dart';
 import 'errors.dart';
-import 'phase.dart';
 import 'transform_node.dart';
 
 /// Describes the current state of an asset as part of a transformation graph.
diff --git a/pkg/barback/lib/src/asset_set.dart b/pkg/barback/lib/src/asset_set.dart
index b432425..08c2db9 100644
--- a/pkg/barback/lib/src/asset_set.dart
+++ b/pkg/barback/lib/src/asset_set.dart
@@ -4,9 +4,7 @@
 
 library barback.asset_set;
 
-import 'dart:async';
 import 'dart:collection';
-import 'dart:io';
 
 import 'asset.dart';
 import 'asset_id.dart';
diff --git a/pkg/barback/lib/src/barback.dart b/pkg/barback/lib/src/barback.dart
index 3dcb23e..313d42c 100644
--- a/pkg/barback/lib/src/barback.dart
+++ b/pkg/barback/lib/src/barback.dart
@@ -8,10 +8,12 @@
 
 import 'asset.dart';
 import 'asset_id.dart';
+import 'asset_set.dart';
 import 'build_result.dart';
 import 'errors.dart';
 import 'package_graph.dart';
 import 'package_provider.dart';
+import 'transformer.dart';
 
 /// A general-purpose asynchronous build dependency graph manager.
 ///
@@ -88,4 +90,19 @@
   /// Removes [removed] from the graph's known set of source assets.
   void removeSources(Iterable<AssetId> removed) =>
       _graph.removeSources(removed);
-}
\ No newline at end of file
+
+  /// Gets all output assets.
+  ///
+  /// If a build is currently in progress, waits until it completes. The
+  /// returned future will complete with a [BarbackException] if the build is
+  /// not successful.
+  Future<AssetSet> getAllAssets() => _graph.getAllAssets();
+
+  /// Sets the transformer phases for [package]'s assets to [transformers].
+  ///
+  /// To the extent that [transformers] is similar to the previous transformer
+  /// phases for [package], the existing asset graph will be preserved.
+  void updateTransformers(String package,
+          Iterable<Iterable<Transformer>> transformers) =>
+      _graph.updateTransformers(package, transformers);
+}
diff --git a/pkg/barback/lib/src/build_result.dart b/pkg/barback/lib/src/build_result.dart
index 89ee681..2cb305c 100644
--- a/pkg/barback/lib/src/build_result.dart
+++ b/pkg/barback/lib/src/build_result.dart
@@ -8,6 +8,7 @@
 
 import 'package:stack_trace/stack_trace.dart';
 
+import 'errors.dart';
 import 'utils.dart';
 
 /// An event indicating that the cascade has finished building all assets.
@@ -23,7 +24,7 @@
   bool get succeeded => errors.isEmpty;
 
   BuildResult(Iterable<BarbackException> errors)
-      : errors = errors.toSet();
+      : errors = flattenAggregateExceptions(errors).toSet();
 
   /// Creates a build result indicating a successful build.
   ///
diff --git a/pkg/barback/lib/src/change_batch.dart b/pkg/barback/lib/src/change_batch.dart
index aa2a1c4..8f98995 100644
--- a/pkg/barback/lib/src/change_batch.dart
+++ b/pkg/barback/lib/src/change_batch.dart
@@ -4,7 +4,6 @@
 
 library barback.change_batch;
 
-import 'asset.dart';
 import 'asset_id.dart';
 
 /// Represents a batch of source asset changes: additions, removals and
diff --git a/pkg/barback/lib/src/errors.dart b/pkg/barback/lib/src/errors.dart
index f356066..a240bb4 100644
--- a/pkg/barback/lib/src/errors.dart
+++ b/pkg/barback/lib/src/errors.dart
@@ -5,12 +5,12 @@
 library barback.errors;
 
 import 'dart:async';
-import 'dart:io';
 
 import 'package:stack_trace/stack_trace.dart';
 
 import 'asset_id.dart';
 import 'transformer.dart';
+import 'utils.dart';
 
 /// Error thrown when an asset with [id] cannot be found.
 class AssetNotFoundException implements Exception {
@@ -21,10 +21,54 @@
   String toString() => "Could not find asset $id.";
 }
 
+/// Replaces any occurrences of [AggregateException] in [errors] with the list
+/// of errors it contains.
+Iterable<BarbackException> flattenAggregateExceptions(
+    Iterable<BarbackException> errors) {
+  return errors.expand((error) {
+    if (error is! AggregateException) return [error];
+    return error.errors;
+  });
+}
+
 /// The interface for exceptions from the barback graph or its transformers.
 ///
 /// These exceptions are never produced by programming errors in barback.
-abstract class BarbackException implements Exception {}
+abstract class BarbackException implements Exception {
+  /// Takes a collection of [BarbackExceptions] and returns a single exception
+  /// that contains them all.
+  ///
+  /// If [errors] is empty, returns `null`. If it only has one error, that
+  /// error is returned. Otherwise, an [AggregateException] is returned.
+  static BarbackException aggregate(Iterable<BarbackException> errors) {
+    if (errors.isEmpty) return null;
+    if (errors.length == 1) return errors.single;
+    return new AggregateException(errors);
+  }
+}
+
+/// An error that wraps a collection of other [BarbackException]s.
+///
+/// It implicitly flattens any [AggregateException]s that occur in the list of
+/// exceptions it wraps.
+class AggregateException implements BarbackException {
+  final Set<BarbackException> errors;
+
+  AggregateException(Iterable<BarbackException> errors)
+      : errors = flattenAggregateExceptions(errors).toSet();
+
+  String toString() {
+    var buffer = new StringBuffer();
+    buffer.writeln("Multiple errors occurred:\n");
+
+    for (var error in errors) {
+      buffer.writeln(prefixLines(error.toString(),
+                                 prefix: "  ", firstPrefix: "- "));
+    }
+
+    return buffer.toString();
+  }
+}
 
 /// Error thrown when two or more transformers both output an asset with [id].
 class AssetCollisionException implements BarbackException {
@@ -67,34 +111,49 @@
       "same package (${transform.primaryId.package}).";
 }
 
-/// Error wrapping an exception thrown by a transform.
-class TransformerException implements BarbackException {
-  /// The transform that threw the exception.
-  final TransformInfo transform;
-
+/// Base class for an error that wraps another.
+abstract class _WrappedException implements BarbackException {
   /// The wrapped exception.
   final error;
 
-  TransformerException(this.transform, this.error);
+  _WrappedException(this.error);
 
-  String toString() => "Transform $transform threw error: $error\n" +
-    new Trace.from(getAttachedStackTrace(error)).terse.toString();
+  String get _message;
+
+  String toString() {
+    var result = "$_message: $error";
+
+    var stack = getAttachedStackTrace(error);
+    if (stack != null) {
+      result = "$result\n${new Trace.from(stack).terse}";
+    }
+
+    return result;
+  }
+}
+
+/// Error wrapping an exception thrown by a transform.
+class TransformerException extends _WrappedException {
+  /// The transform that threw the exception.
+  final TransformInfo transform;
+
+  TransformerException(this.transform, error)
+      : super(error);
+
+  String get _message => "Transform $transform threw error";
 }
 
 /// Error thrown when a source asset [id] fails to load.
 ///
 /// This can be thrown either because the source asset was expected to exist and
 /// did not or because reading it failed somehow.
-class AssetLoadException implements BarbackException {
+class AssetLoadException extends _WrappedException {
   final AssetId id;
 
-  /// The wrapped exception.
-  final error;
+  AssetLoadException(this.id, error)
+      : super(error);
 
-  AssetLoadException(this.id, this.error);
-
-  String toString() => "Failed to load source asset $id: $error\n"
-      "${new Trace.from(getAttachedStackTrace(error)).terse}";
+  String get _message => "Failed to load source asset $id";
 }
 
 /// Information about a single transform in the barback graph.
diff --git a/pkg/barback/lib/src/package_graph.dart b/pkg/barback/lib/src/package_graph.dart
index c46ed55..a402a27 100644
--- a/pkg/barback/lib/src/package_graph.dart
+++ b/pkg/barback/lib/src/package_graph.dart
@@ -6,15 +6,14 @@
 
 import 'dart:async';
 
-import 'package:stack_trace/stack_trace.dart';
-
-import 'asset.dart';
 import 'asset_cascade.dart';
 import 'asset_id.dart';
 import 'asset_node.dart';
+import 'asset_set.dart';
 import 'build_result.dart';
 import 'errors.dart';
 import 'package_provider.dart';
+import 'transformer.dart';
 import 'utils.dart';
 
 /// The collection of [AssetCascade]s for an entire application.
@@ -55,12 +54,17 @@
   Stream<BarbackException> get errors => _errors;
   Stream<BarbackException> _errors;
 
+  /// The most recent error emitted from a cascade's result stream.
+  ///
+  /// This is used to pipe an unexpected error from a build to the resulting
+  /// [Future] returned by [getAllAssets].
+  var _lastUnexpectedError;
+
   /// Creates a new [PackageGraph] that will transform assets in all packages
   /// made available by [provider].
   PackageGraph(this.provider) {
     for (var package in provider.packages) {
-      var cascade = new AssetCascade(this, package,
-          provider.getTransformers(package));
+      var cascade = new AssetCascade(this, package);
       // The initial result for each cascade is "success" since the cascade
       // doesn't start building until some source in that graph is updated.
       _cascadeResults[package] = new BuildResult.success();
@@ -76,7 +80,10 @@
         // errors, the result will automatically be considered a success.
         _resultsController.add(new BuildResult(unionAll(
             _cascadeResults.values.map((result) => result.errors))));
-      }, onError: _resultsController.addError);
+      }, onError: (error) {
+        _lastUnexpectedError = error;
+        _resultsController.addError(error);
+      });
     }
 
     _errors = mergeStreams(_cascades.values.map((cascade) => cascade.errors));
@@ -95,6 +102,38 @@
     return new Future.value(null);
   }
 
+  /// Gets all output assets.
+  ///
+  /// If a build is currently in progress, waits until it completes. The
+  /// returned future will complete with an error if the build is not
+  /// successful.
+  Future<AssetSet> getAllAssets() {
+    if (_cascadeResults.values.contains(null)) {
+      // A build is still ongoing, so wait for it to complete and try again.
+      return results.first.then((_) => getAllAssets());
+    }
+
+    // If an unexpected error occurred, complete with that.
+    if (_lastUnexpectedError != null) {
+      var error = _lastUnexpectedError;
+      _lastUnexpectedError = null;
+      return new Future.error(error);
+    }
+
+    // If the build completed with an error, complete the future with it.
+    var errors = unionAll(
+        _cascadeResults.values.map((result) => result.errors));
+    if (errors.isNotEmpty) {
+      return new Future.error(BarbackException.aggregate(errors));
+    }
+
+    // Otherwise, return all of the final output assets.
+    var assets = unionAll(_cascades.values.map(
+        (cascade) => cascade.availableOutputs.toSet()));
+
+    return new Future.value(new AssetSet.from(assets));
+  }
+
   /// Adds [sources] to the graph's known set of source assets.
   ///
   /// Begins applying any transforms that can consume any of the sources. If a
@@ -118,4 +157,10 @@
       cascade.removeSources(ids);
     });
   }
+
+  void updateTransformers(String package,
+      Iterable<Iterable<Transformer>> transformers) {
+    _cascadeResults[package] = null;
+    _cascades[package].updateTransformers(transformers);
+  }
 }
diff --git a/pkg/barback/lib/src/package_provider.dart b/pkg/barback/lib/src/package_provider.dart
index 031caf8..349785c 100644
--- a/pkg/barback/lib/src/package_provider.dart
+++ b/pkg/barback/lib/src/package_provider.dart
@@ -8,7 +8,6 @@
 
 import 'asset.dart';
 import 'asset_id.dart';
-import 'transformer.dart';
 
 /// API for locating and accessing packages on disk.
 ///
@@ -21,12 +20,6 @@
   /// dependencies.
   Iterable<String> get packages;
 
-  /// Returns the list of transformer phases that are applicable to [package].
-  ///
-  /// The phases will be run in sequence, with the outputs of one pipelined into
-  /// the next. All [Transformer]s in a single phase will be run in parallel.
-  Iterable<Iterable<Transformer>> getTransformers(String package);
-
   /// Loads an asset from disk.
   ///
   /// This should be re-entrant; it may be called multiple times with the same
diff --git a/pkg/barback/lib/src/phase.dart b/pkg/barback/lib/src/phase.dart
index 86444ad..690126a 100644
--- a/pkg/barback/lib/src/phase.dart
+++ b/pkg/barback/lib/src/phase.dart
@@ -7,14 +7,13 @@
 import 'dart:async';
 import 'dart:collection';
 
-import 'asset.dart';
 import 'asset_cascade.dart';
 import 'asset_id.dart';
 import 'asset_node.dart';
 import 'asset_set.dart';
 import 'errors.dart';
+import 'phase_input.dart';
 import 'stream_pool.dart';
-import 'transform_node.dart';
 import 'transformer.dart';
 import 'utils.dart';
 
@@ -34,47 +33,16 @@
   /// The cascade that owns this phase.
   final AssetCascade cascade;
 
-  /// This phase's position relative to the other phases. Zero-based.
-  final int _index;
-
   /// The transformers that can access [inputs].
   ///
   /// Their outputs will be available to the next phase.
-  final List<Transformer> _transformers;
+  final Set<Transformer> _transformers;
 
-  /// The inputs that are available for transforms in this phase to consume.
+  /// The inputs for this phase.
   ///
   /// For the first phase, these will be the source assets. For all other
   /// phases, they will be the outputs from the previous phase.
-  final _inputs = new Map<AssetId, AssetNode>();
-
-  /// The transforms currently applicable to assets in [inputs], indexed by
-  /// the ids of their primary inputs.
-  ///
-  /// These are the transforms that have been "wired up": they represent a
-  /// repeatable transformation of a single concrete set of inputs. "dart2js"
-  /// is a transformer. "dart2js on web/main.dart" is a transform.
-  final _transforms = new Map<AssetId, Set<TransformNode>>();
-
-  /// Controllers for assets that aren't consumed by transforms in this phase.
-  ///
-  /// These assets are passed to the next phase unmodified. They need
-  /// intervening controllers to ensure that the outputs can be marked dirty
-  /// when determining whether transforms apply, and removed if they do.
-  final _passThroughControllers = new Map<AssetId, AssetNodeController>();
-
-  /// Futures that will complete once the transformers that can consume a given
-  /// asset are determined.
-  ///
-  /// Whenever an asset is added or modified, we need to asynchronously
-  /// determine which transformers can use it as their primary input. We can't
-  /// start processing until we know which transformers to run, and this allows
-  /// us to wait until we do.
-  var _adjustTransformersFutures = new Map<AssetId, Future>();
-
-  /// New asset nodes that were added while [_adjustTransformers] was still
-  /// being run on an old version of that asset.
-  var _pendingNewInputs = new Map<AssetId, AssetNode>();
+  final _inputs = new Map<AssetId, PhaseInput>();
 
   /// A map of output ids to the asset node outputs for those ids and the
   /// transforms that produced those asset nodes.
@@ -97,18 +65,25 @@
 
   /// A controller whose stream feeds into [_onDirtyPool].
   ///
-  /// This is used whenever an input is added, changed, or removed. It's
-  /// sometimes redundant with the events collected from [_transforms], but this
-  /// stream is necessary for new and removed inputs, and the transform stream
-  /// is necessary for modified secondary inputs.
+  /// This is used whenever an input is added or transforms are changed.
   final _onDirtyController = new StreamController.broadcast(sync: true);
 
   /// The phase after this one.
   ///
   /// Outputs from this phase will be passed to it.
-  final Phase _next;
+  Phase get next => _next;
+  Phase _next;
 
-  Phase(this.cascade, this._index, this._transformers, this._next) {
+  /// Returns all currently-available output assets for this phase.
+  AssetSet get availableOutputs {
+    return new AssetSet.from(_outputs.values
+        .map((queue) => queue.first)
+        .where((node) => node.state.isAvailable)
+        .map((node) => node.asset));
+  }
+
+  Phase(this.cascade, Iterable<Transformer> transformers)
+      : _transformers = transformers.toSet() {
     _onDirtyPool.add(_onDirtyController.stream);
   }
 
@@ -123,51 +98,13 @@
   /// removed and re-created. The phase will automatically handle updated assets
   /// using the [AssetNode.onStateChange] stream.
   void addInput(AssetNode node) {
-    // We remove [node.id] from [inputs] as soon as the node is removed rather
-    // than at the same time [node.id] is removed from [_transforms] so we don't
-    // have to wait on [_adjustTransformers]. It's important that [inputs] is
-    // always up-to-date so that the [AssetCascade] can look there for available
-    // assets.
-    _inputs[node.id] = node;
-    node.whenRemoved.then((_) => _inputs.remove(node.id));
+    if (_inputs.containsKey(node.id)) _inputs[node.id].remove();
 
-    if (!_adjustTransformersFutures.containsKey(node.id)) {
-      _transforms[node.id] = new Set<TransformNode>();
-      _adjustTransformers(node);
-      return;
-    }
-
-    // If an input is added while the same input is still being processed,
-    // that means that the asset was removed and recreated while
-    // [_adjustTransformers] was being run on the old value. We have to wait
-    // until that finishes, then run it again on whatever the newest version
-    // of that asset is.
-
-    // We may already be waiting for the existing [_adjustTransformers] call to
-    // finish. If so, all we need to do is change the node that will be loaded
-    // after it completes.
-    var containedKey = _pendingNewInputs.containsKey(node.id);
-    _pendingNewInputs[node.id] = node;
-    if (containedKey) return;
-
-    // If we aren't already waiting, start doing so.
-    _adjustTransformersFutures[node.id].then((_) {
-      assert(!_adjustTransformersFutures.containsKey(node.id));
-      assert(_pendingNewInputs.containsKey(node.id));
-      _transforms[node.id] = new Set<TransformNode>();
-      _adjustTransformers(_pendingNewInputs.remove(node.id));
-    }, onError: (_) {
-      // If there was a programmatic error while processing the old input,
-      // we don't want to just ignore it; it may have left the system in an
-      // inconsistent state. We also don't want to top-level it, so we
-      // ignore it here but don't start processing the new input. That way
-      // when [process] is called, the error will be piped through its
-      // return value.
-    }).catchError((e) {
-      // If our code above has a programmatic error, ensure it will be piped
-      // through [process] by putting it into [_adjustTransformersFutures].
-      _adjustTransformersFutures[node.id] = new Future.error(e);
-    });
+    var input = new PhaseInput(this, node, _transformers);
+    _inputs[node.id] = input;
+    input.input.whenRemoved.then((_) => _inputs.remove(node.id));
+    _onDirtyPool.add(input.onDirty);
+    _onDirtyController.add(null);
   }
 
   /// Gets the asset node for an input [id].
@@ -175,189 +112,60 @@
   /// If an input with that ID cannot be found, returns null.
   Future<AssetNode> getInput(AssetId id) {
     return newFuture(() {
-      if (id.package == cascade.package) return _inputs[id];
-      return cascade.graph.getAssetNode(id);
+      if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
+      if (_inputs.containsKey(id)) return _inputs[id].input;
+      return null;
     });
   }
 
-  /// Asynchronously determines which transformers can consume [node] as a
-  /// primary input and creates transforms for them.
+  /// Gets the asset node for an output [id].
   ///
-  /// This ensures that if [node] is modified or removed during or after the
-  /// time it takes to adjust its transformers, they're appropriately
-  /// re-adjusted. Its progress can be tracked in [_adjustTransformersFutures].
-  void _adjustTransformers(AssetNode node) {
-    // Mark the phase as dirty. This may not actually end up creating any new
-    // transforms, but we want adding or removing a source asset to consistently
-    // kick off a build, even if that build does nothing.
+  /// If an output with that ID cannot be found, returns null.
+  Future<AssetNode> getOutput(AssetId id) {
+    return newFuture(() {
+      if (id.package != cascade.package) return cascade.graph.getAssetNode(id);
+      if (!_outputs.containsKey(id)) return null;
+      return _outputs[id].first;
+    });
+  }
+
+  /// Set this phase's transformers to [transformers].
+  void updateTransformers(Iterable<Transformer> transformers) {
     _onDirtyController.add(null);
-
-    // If there's a pass-through for this node, mark it dirty while we figure
-    // out whether we need to add any transforms for it.
-    var controller = _passThroughControllers[node.id];
-    if (controller != null) controller.setDirty();
-
-    // Once the input is available, hook up transformers for it. If it changes
-    // while that's happening, try again.
-    _adjustTransformersFutures[node.id] = node.tryUntilStable((asset) {
-      var oldTransformers = _transforms[node.id]
-          .map((transform) => transform.transformer).toSet();
-
-      return _removeStaleTransforms(asset)
-          .then((_) => _addFreshTransforms(node, oldTransformers));
-    }).then((_) {
-      _adjustPassThrough(node);
-
-      // Now all the transforms are set up correctly and the asset is available
-      // for the time being. Set up handlers for when the asset changes in the
-      // future.
-      node.onStateChange.first.then((state) {
-        if (state.isRemoved) {
-          _onDirtyController.add(null);
-          _transforms.remove(node.id);
-          var passThrough = _passThroughControllers.remove(node.id);
-          if (passThrough != null) passThrough.setRemoved();
-        } else {
-          _adjustTransformers(node);
-        }
-      }).catchError((e) {
-        _adjustTransformersFutures[node.id] = new Future.error(e);
-      });
-    }).catchError((error) {
-      if (error is! AssetNotFoundException || error.id != node.id) throw error;
-
-      // If the asset is removed, [tryUntilStable] will throw an
-      // [AssetNotFoundException]. In that case, just remove all transforms for
-      // the node, and its pass-through.
-      _transforms.remove(node.id);
-      var passThrough = _passThroughControllers.remove(node.id);
-      if (passThrough != null) passThrough.setRemoved();
-    }).whenComplete(() {
-      _adjustTransformersFutures.remove(node.id);
-    });
-
-    // Don't top-level errors coming from the input processing. Any errors will
-    // eventually be piped through [process]'s returned Future.
-    _adjustTransformersFutures[node.id].catchError((_) {});
-  }
-
-  // Remove any old transforms that used to have [asset] as a primary asset but
-  // no longer apply to its new contents.
-  Future _removeStaleTransforms(Asset asset) {
-    return Future.wait(_transforms[asset.id].map((transform) {
-      // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
-      // results.
-      return transform.transformer.isPrimary(asset).then((isPrimary) {
-        if (isPrimary) return;
-        _transforms[asset.id].remove(transform);
-        _onDirtyPool.remove(transform.onDirty);
-        transform.remove();
-      });
-    }));
-  }
-
-  // Add new transforms for transformers that consider [node]'s asset to be a
-  // primary input.
-  //
-  // [oldTransformers] is the set of transformers that had [node] as a primary
-  // input prior to this. They don't need to be checked, since they were removed
-  // or preserved in [_removeStaleTransforms].
-  Future _addFreshTransforms(AssetNode node, Set<Transformer> oldTransformers) {
-    return Future.wait(_transformers.map((transformer) {
-      if (oldTransformers.contains(transformer)) return new Future.value();
-
-      // If the asset is unavailable, the results of this [_adjustTransformers]
-      // run will be discarded, so we can just short-circuit.
-      if (node.asset == null) return new Future.value();
-
-      // We can safely access [node.asset] here even though it might have
-      // changed since (as above) if it has, [_adjustTransformers] will just be
-      // re-run.
-      // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
-      // results.
-      return transformer.isPrimary(node.asset).then((isPrimary) {
-        if (!isPrimary) return;
-        var transform = new TransformNode(this, transformer, node);
-        _transforms[node.id].add(transform);
-        _onDirtyPool.add(transform.onDirty);
-      });
-    }));
-  }
-
-  /// Adjust whether [node] is passed through the phase unmodified, based on
-  /// whether it's consumed by other transforms in this phase.
-  ///
-  /// If [node] was already passed-through, this will update the passed-through
-  /// value.
-  void _adjustPassThrough(AssetNode node) {
-    assert(node.state.isAvailable);
-
-    if (_transforms[node.id].isEmpty) {
-      var controller = _passThroughControllers[node.id];
-      if (controller != null) {
-        controller.setAvailable(node.asset);
-      } else {
-        _passThroughControllers[node.id] =
-            new AssetNodeController.available(node.asset, node.transform);
-      }
-    } else {
-      var controller = _passThroughControllers.remove(node.id);
-      if (controller != null) controller.setRemoved();
+    _transformers.clear();
+    _transformers.addAll(transformers);
+    for (var input in _inputs.values) {
+      input.updateTransformers(_transformers);
     }
   }
 
+  /// Add a new phase after this one with [transformers].
+  ///
+  /// This may only be called on a phase with no phase following it.
+  Phase addPhase(Iterable<Transformer> transformers) {
+    assert(_next == null);
+    _next = new Phase(cascade, transformers);
+    for (var outputs in _outputs.values) {
+      _next.addInput(outputs.first);
+    }
+    return _next;
+  }
+
   /// Processes this phase.
   ///
   /// Returns a future that completes when processing is done. If there is
   /// nothing to process, returns `null`.
   Future process() {
-    if (_adjustTransformersFutures.isEmpty) return _processTransforms();
-    return _waitForInputs().then((_) => _processTransforms());
-  }
+    if (!_inputs.values.any((input) => input.isDirty)) return null;
 
-  Future _waitForInputs() {
-    if (_adjustTransformersFutures.isEmpty) return new Future.value();
-    return Future.wait(_adjustTransformersFutures.values)
-        .then((_) => _waitForInputs());
-  }
-
-  /// Applies all currently wired up and dirty transforms.
-  Future _processTransforms() {
-    if (_next == null) return;
-
-    var newPassThroughs = _passThroughControllers.values
-        .map((controller) => controller.node)
-        .where((output) {
-      return !_outputs.containsKey(output.id) ||
-        !_outputs[output.id].contains(output);
-    }).toSet();
-
-    // Convert this to a list so we can safely modify _transforms while
-    // iterating over it.
-    var dirtyTransforms =
-        flatten(_transforms.values.map((transforms) => transforms.toList()))
-        .where((transform) => transform.isDirty).toList();
-
-    if (dirtyTransforms.isEmpty && newPassThroughs.isEmpty) return null;
-
-    var collisions = _passAssetsThrough(newPassThroughs);
-    return Future.wait(dirtyTransforms.map((transform) {
-      return transform.apply().then((outputs) {
-        for (var output in outputs) {
-          if (_outputs.containsKey(output.id)) {
-            _outputs[output.id].add(output);
-            collisions.add(output.id);
-          } else {
-            _outputs[output.id] = new Queue<AssetNode>.from([output]);
-            _next.addInput(output);
-          }
-
-          _handleOutputRemoval(output);
-        }
+    return Future.wait(_inputs.values.map((input) {
+      if (!input.isDirty) return new Future.value(new Set());
+      return input.process().then((outputs) {
+        return outputs.where(_addOutput).map((output) => output.id).toSet();
       });
-    })).then((_) {
+    })).then((collisionsList) {
       // Report collisions in a deterministic order.
-      collisions = collisions.toList();
+      var collisions = unionAll(collisionsList).toList();
       collisions.sort((a, b) => a.compareTo(b));
       for (var collision in collisions) {
         // Ensure that there's still a collision. It's possible it was resolved
@@ -371,28 +179,21 @@
     });
   }
 
-  /// Pass all new assets that aren't consumed by transforms through to the next
-  /// phase.
+  /// Add [output] as an output of this phase, forwarding it to the next phase
+  /// if necessary.
   ///
-  /// Returns a set of asset ids that have collisions between new passed-through
-  /// assets and pre-existing transform outputs.
-  Set<AssetId> _passAssetsThrough(Set<AssetId> newPassThroughs) {
-    var collisions = new Set<AssetId>();
-    for (var output in newPassThroughs) {
-      if (_outputs.containsKey(output.id)) {
-        // There shouldn't be another pass-through asset with the same id.
-        assert(!_outputs[output.id].any((asset) => asset.transform == null));
+  /// Returns whether or not [output] collides with another pre-existing output.
+  bool _addOutput(AssetNode output) {
+    _handleOutputRemoval(output);
 
-        _outputs[output.id].add(output);
-        collisions.add(output.id);
-      } else {
-        _outputs[output.id] = new Queue<AssetNode>.from([output]);
-        _next.addInput(output);
-      }
-
-      _handleOutputRemoval(output);
+    if (_outputs.containsKey(output.id)) {
+      _outputs[output.id].add(output);
+      return true;
     }
-    return collisions;
+
+    _outputs[output.id] = new Queue<AssetNode>.from([output]);
+    if (_next != null) _next.addInput(output);
+    return false;
   }
 
   /// Properly resolve collisions when [output] is removed.
@@ -414,7 +215,7 @@
       // (chronologically) to the next phase. Pump the event queue first to give
       // [_next] a chance to handle the removal of its input before getting a
       // new input.
-      if (wasFirst) {
+      if (wasFirst && _next != null) {
         newFuture(() => _next.addInput(assets.first));
       }
 
diff --git a/pkg/barback/lib/src/phase_input.dart b/pkg/barback/lib/src/phase_input.dart
new file mode 100644
index 0000000..7ba6c20
--- /dev/null
+++ b/pkg/barback/lib/src/phase_input.dart
@@ -0,0 +1,296 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library barback.phase_input;
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'asset.dart';
+import 'asset_forwarder.dart';
+import 'asset_node.dart';
+import 'errors.dart';
+import 'stream_pool.dart';
+import 'transform_node.dart';
+import 'transformer.dart';
+import 'utils.dart';
+
+/// A class for watching a single [AssetNode] and running any transforms that
+/// take that node as a primary input.
+class PhaseInput {
+  /// The phase for which this is an input.
+  final Phase _phase;
+
+  /// The transformers to (potentially) run against [input].
+  final Set<Transformer> _transformers;
+
+  /// The transforms currently applicable to [input].
+  ///
+  /// These are the transforms that have been "wired up": they represent a
+  /// repeatable transformation of a single concrete set of inputs. "dart2js" is
+  /// a transformer. "dart2js on web/main.dart" is a transform.
+  final _transforms = new Set<TransformNode>();
+
+  /// A forwarder for the input [AssetNode] for this phase.
+  ///
+  /// This is used to mark the node as removed should the input ever be removed.
+  final AssetForwarder _inputForwarder;
+
+  /// The asset node for this input.
+  AssetNode get input => _inputForwarder.node;
+
+  /// The controller that's used for the output node if [input] isn't consumed
+  /// by any transformers.
+  ///
+  /// This needs an intervening controller to ensure that the output can be
+  /// marked dirty when determining whether transforms apply, and removed if
+  /// they do. It's null if the asset is not being passed through.
+  AssetNodeController _passThroughController;
+
+  /// Whether [_passThroughController] has been newly created since [process]
+  /// last completed.
+  bool _newPassThrough = false;
+
+  /// A Future that will complete once the transformers that consume [input] are
+  /// determined.
+  Future _adjustTransformersFuture;
+
+  /// A stream that emits an event whenever this input becomes dirty and needs
+  /// [process] to be called.
+  ///
+  /// This may emit events when the input was already dirty or while processing
+  /// transforms. Events are emitted synchronously to ensure that the dirty
+  /// state is thoroughly propagated as soon as any assets are changed.
+  Stream get onDirty => _onDirtyPool.stream;
+  final _onDirtyPool = new StreamPool.broadcast();
+
+  /// A controller whose stream feeds into [_onDirtyPool].
+  ///
+  /// This is used whenever the input is changed or removed. It's sometimes
+  /// redundant with the events collected from [_transforms], but this stream is
+  /// necessary for removed inputs, and the transform stream is necessary for
+  /// modified secondary inputs.
+  final _onDirtyController = new StreamController.broadcast(sync: true);
+
+  /// Whether this input is dirty and needs [process] to be called.
+  bool get isDirty => _adjustTransformersFuture != null ||
+      _newPassThrough || _transforms.any((transform) => transform.isDirty);
+
+  PhaseInput(this._phase, AssetNode input, Iterable<Transformer> transformers)
+      : _transformers = transformers.toSet(),
+        _inputForwarder = new AssetForwarder(input) {
+    _onDirtyPool.add(_onDirtyController.stream);
+
+    input.onStateChange.listen((state) {
+      if (state.isRemoved) {
+        remove();
+      } else if (_adjustTransformersFuture == null) {
+        _adjustTransformers();
+      }
+    });
+
+    _adjustTransformers();
+  }
+
+  /// Removes this input.
+  ///
+  /// This marks all outputs of the input as removed.
+  void remove() {
+    _onDirtyController.add(null);
+    _onDirtyPool.close();
+    _inputForwarder.close();
+    if (_passThroughController != null) {
+      _passThroughController.setRemoved();
+      _passThroughController = null;
+    }
+  }
+
+  /// Set this input's transformers to [transformers].
+  void updateTransformers(Set<Transformer> newTransformers) {
+    var oldTransformers = _transformers.toSet();
+    for (var removedTransformer in
+         oldTransformers.difference(newTransformers)) {
+      _transformers.remove(removedTransformer);
+
+      // If the transformers are being adjusted for [id], it will
+      // automatically pick up on [removedTransformer] being gone.
+      if (_adjustTransformersFuture != null) continue;
+
+      _transforms.removeWhere((transform) {
+        if (transform.transformer != removedTransformer) return false;
+        transform.remove();
+        return true;
+      });
+    }
+
+    if (_transforms.isEmpty && _adjustTransformersFuture == null &&
+        _passThroughController == null) {
+      _passThroughController =
+          new AssetNodeController.available(input.asset, input.transform);
+      _newPassThrough = true;
+    }
+
+    var brandNewTransformers = newTransformers.difference(oldTransformers);
+    if (brandNewTransformers.isEmpty) return;
+
+    brandNewTransformers.forEach(_transformers.add);
+    _adjustTransformers();
+  }
+
+  /// Asynchronously determines which transformers can consume [input] as a
+  /// primary input and creates transforms for them.
+  ///
+  /// This ensures that if [input] is modified or removed during or after the
+  /// time it takes to adjust its transformers, they're appropriately
+  /// re-adjusted. Its progress can be tracked in [_adjustTransformersFuture].
+  void _adjustTransformers() {
+    // Mark the input as dirty. This may not actually end up creating any new
+    // transforms, but we want adding or removing a source asset to consistently
+    // kick off a build, even if that build does nothing.
+    _onDirtyController.add(null);
+
+    // If there's a pass-through for this input, mark it dirty while we figure
+    // out whether we need to add any transforms for it.
+    if (_passThroughController != null) _passThroughController.setDirty();
+
+    // Once the input is available, hook up transformers for it. If it changes
+    // while that's happening, try again.
+    _adjustTransformersFuture = _tryUntilStable((asset, transformers) {
+      var oldTransformers =
+          _transforms.map((transform) => transform.transformer).toSet();
+
+      return _removeStaleTransforms(asset, transformers).then((_) =>
+          _addFreshTransforms(transformers, oldTransformers));
+    }).then((_) => _adjustPassThrough()).catchError((error) {
+      if (error is! AssetNotFoundException || error.id != input.id) {
+        throw error;
+      }
+
+      // If the asset is removed, [_tryUntilStable] will throw an
+      // [AssetNotFoundException]. In that case, just remove it.
+      remove();
+    }).whenComplete(() {
+      _adjustTransformersFuture = null;
+    });
+
+    // Don't top-level errors coming from the input processing. Any errors will
+    // eventually be piped through [process]'s returned Future.
+    _adjustTransformersFuture.catchError((_) {});
+  }
+
+  // Remove any old transforms that used to have [asset] as a primary asset but
+  // no longer apply to its new contents.
+  Future _removeStaleTransforms(Asset asset, Set<Transformer> transformers) {
+    return Future.wait(_transforms.map((transform) {
+      return newFuture(() {
+        if (!transformers.contains(transform.transformer)) return false;
+
+        // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
+        // results.
+        return transform.transformer.isPrimary(asset);
+      }).then((isPrimary) {
+        if (isPrimary) return;
+        _transforms.remove(transform);
+        transform.remove();
+      });
+    }));
+  }
+
+  // Add new transforms for transformers that consider [input]'s asset to be a
+  // primary input.
+  //
+  // [oldTransformers] is the set of transformers for which there were
+  // transforms that had [input] as a primary input prior to this. They don't
+  // need to be checked, since their transforms were removed or preserved in
+  // [_removeStaleTransforms].
+  Future _addFreshTransforms(Set<Transformer> transformers,
+      Set<Transformer> oldTransformers) {
+    return Future.wait(transformers.map((transformer) {
+      if (oldTransformers.contains(transformer)) return new Future.value();
+
+      // If the asset is unavailable, the results of this [_adjustTransformers]
+      // run will be discarded, so we can just short-circuit.
+      if (input.asset == null) return new Future.value();
+
+      // We can safely access [input.asset] here even though it might have
+      // changed since (as above) if it has, [_adjustTransformers] will just be
+      // re-run.
+      // TODO(rnystrom): Catch all errors from isPrimary() and redirect to
+      // results.
+      return transformer.isPrimary(input.asset).then((isPrimary) {
+        if (!isPrimary) return;
+        var transform = new TransformNode(_phase, transformer, input);
+        _transforms.add(transform);
+        _onDirtyPool.add(transform.onDirty);
+      });
+    }));
+  }
+
+  /// Adjust whether [input] is passed through the phase unmodified, based on
+  /// whether it's consumed by other transforms in this phase.
+  ///
+  /// If [input] was already passed-through, this will update the passed-through
+  /// value.
+  void _adjustPassThrough() {
+    assert(input.state.isAvailable);
+
+    if (_transforms.isEmpty) {
+      if (_passThroughController != null) {
+        _passThroughController.setAvailable(input.asset);
+      } else {
+        _passThroughController =
+            new AssetNodeController.available(input.asset, input.transform);
+        _newPassThrough = true;
+      }
+    } else if (_passThroughController != null) {
+      _passThroughController.setRemoved();
+      _passThroughController = null;
+      _newPassThrough = false;
+    }
+  }
+
+  /// Like [AssetNode.tryUntilStable], but also re-runs [callback] if this
+  /// phase's transformers are modified.
+  Future _tryUntilStable(
+      Future callback(Asset asset, Set<Transformer> transformers)) {
+    var oldTransformers;
+    return input.tryUntilStable((asset) {
+      oldTransformers = _transformers.toSet();
+      return callback(asset, _transformers);
+    }).then((result) {
+      if (setEquals(oldTransformers, _transformers)) return result;
+      return _tryUntilStable(callback);
+    });
+  }
+
+  /// Processes the transforms for this input.
+  Future<Set<AssetNode>> process() {
+    if (_adjustTransformersFuture == null) return _processTransforms();
+    return _waitForInputs().then((_) => _processTransforms());
+  }
+
+  Future _waitForInputs() {
+    // Return a synchronous future so we can be sure [_adjustTransformers] isn't
+    // called between now and when the Future completes.
+    if (_adjustTransformersFuture == null) return new Future.sync(() {});
+    return _adjustTransformersFuture.then((_) => _waitForInputs());
+  }
+
+  /// Applies all currently wired up and dirty transforms.
+  Future<Set<AssetNode>> _processTransforms() {
+    if (input.state.isRemoved) return new Future.value(new Set());
+
+    if (_passThroughController != null) {
+      if (!_newPassThrough) return new Future.value(new Set());
+      _newPassThrough = false;
+      return new Future.value(
+          new Set<AssetNode>.from([_passThroughController.node]));
+    }
+
+    return Future.wait(_transforms.map((transform) {
+      if (!transform.isDirty) return new Future.value(new Set());
+      return transform.apply();
+    })).then((outputs) => unionAll(outputs));
+  }
+}
diff --git a/pkg/barback/lib/src/transform.dart b/pkg/barback/lib/src/transform.dart
index c254778..d5d7176 100644
--- a/pkg/barback/lib/src/transform.dart
+++ b/pkg/barback/lib/src/transform.dart
@@ -13,7 +13,6 @@
 import 'errors.dart';
 import 'transform_logger.dart';
 import 'transform_node.dart';
-import 'utils.dart';
 
 /// Creates a [Transform] by forwarding to the private constructor.
 ///
diff --git a/pkg/barback/lib/src/transform_node.dart b/pkg/barback/lib/src/transform_node.dart
index 1d3d1dd..578b2fa 100644
--- a/pkg/barback/lib/src/transform_node.dart
+++ b/pkg/barback/lib/src/transform_node.dart
@@ -14,7 +14,6 @@
 import 'phase.dart';
 import 'transform.dart';
 import 'transformer.dart';
-import 'utils.dart';
 
 /// Describes a transform on a set of assets and its relationship to the build
 /// dependency graph.
@@ -133,7 +132,7 @@
       // Don't allow partial results from a failed transform.
       newOutputs.clear();
     }).then((_) {
-      if (_isDirty) return [];
+      if (_isDirty) return new Set();
 
       return _adjustOutputs(newOutputs);
     });
diff --git a/pkg/barback/lib/src/transformer.dart b/pkg/barback/lib/src/transformer.dart
index 422066f..e8479f1 100644
--- a/pkg/barback/lib/src/transformer.dart
+++ b/pkg/barback/lib/src/transformer.dart
@@ -5,7 +5,6 @@
 library barback.transformer;
 
 import 'dart:async';
-import 'dart:io';
 
 import 'asset.dart';
 import 'transform.dart';
@@ -17,6 +16,15 @@
 /// files are all examples of transformers. To define your own transformation
 /// step, extend (or implement) this class.
 abstract class Transformer {
+  /// Override this to return a space-separated list of file extensions
+  /// (with leading `.`) that are allowed for the primary inputs to this
+  /// transformer.
+  ///
+  /// If you don't override [isPrimary] yourself, it defaults to allowing any
+  /// asset whose extension matches one of the ones returned by this. If you
+  /// don't override [isPrimary] *or* this, it allows all files.
+  String get allowedExtensions => null;
+
   /// Returns `true` if [input] can be a primary input for this transformer.
   ///
   /// While a transformer can read from multiple input files, one must be the
@@ -29,7 +37,20 @@
   /// of those to generate the final JS. However you still run dart2js "on" a
   /// single file: the entrypoint Dart file that has your `main()` method.
   /// This entrypoint file would be the primary input.
-  Future<bool> isPrimary(Asset input);
+  ///
+  /// If this is not overridden, defaults to allow any asset whose extension
+  /// matches one of the ones returned by [allowedExtensions]. If *that* is
+  /// not overridden, allows all assets.
+  Future<bool> isPrimary(Asset input) {
+    // Allow all files if [primaryExtensions] is not overridden.
+    if (allowedExtensions == null) return new Future.value(true);
+
+    for (var extension in allowedExtensions.split(" ")) {
+      if (input.id.extension == extension) return new Future.value(true);
+    }
+
+    return new Future.value(false);
+  }
 
   /// Run this transformer on on the primary input specified by [transform].
   ///
diff --git a/pkg/barback/lib/src/utils.dart b/pkg/barback/lib/src/utils.dart
index bee6084..3272e9e 100644
--- a/pkg/barback/lib/src/utils.dart
+++ b/pkg/barback/lib/src/utils.dart
@@ -72,6 +72,10 @@
 Map mapMapValues(Map map, fn(key, value)) =>
   new Map.fromIterable(map.keys, value: (key) => fn(key, map[key]));
 
+/// Returns whether [set1] has exactly the same elements as [set2].
+bool setEquals(Set set1, Set set2) =>
+  set1.length == set2.length && set1.containsAll(set2);
+
 /// Merges [streams] into a single stream that emits events from all sources.
 Stream mergeStreams(Iterable<Stream> streams) {
   streams = streams.toList();
diff --git a/pkg/barback/test/asset_id_test.dart b/pkg/barback/test/asset_id_test.dart
index 6e4a7d8..a55bb18 100644
--- a/pkg/barback/test/asset_id_test.dart
+++ b/pkg/barback/test/asset_id_test.dart
@@ -4,8 +4,6 @@
 
 library barback.test.asset_id_test;
 
-import 'dart:async';
-
 import 'package:barback/barback.dart';
 import 'package:unittest/unittest.dart';
 
diff --git a/pkg/barback/test/asset_set_test.dart b/pkg/barback/test/asset_set_test.dart
index 5b6b652..3208329 100644
--- a/pkg/barback/test/asset_set_test.dart
+++ b/pkg/barback/test/asset_set_test.dart
@@ -4,11 +4,7 @@
 
 library barback.test.asset_set_test;
 
-import 'dart:async';
-import 'dart:io';
-
 import 'package:barback/barback.dart';
-import 'package:barback/src/asset_set.dart';
 import 'package:unittest/unittest.dart';
 
 import 'utils.dart';
diff --git a/pkg/barback/test/package_graph/add_remove_transform_test.dart b/pkg/barback/test/package_graph/add_remove_transform_test.dart
new file mode 100644
index 0000000..b79c69a
--- /dev/null
+++ b/pkg/barback/test/package_graph/add_remove_transform_test.dart
@@ -0,0 +1,240 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library barback.test.package_graph.transform_test;
+
+import 'package:barback/src/utils.dart';
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../utils.dart';
+
+main() {
+  initConfig();
+  test("a new transformer is applied to a matching asset", () {
+    initGraph(["app|foo.blub"]);
+
+    updateSources(["app|foo.blub"]);
+    expectAsset("app|foo.blub", "foo");
+    buildShouldSucceed();
+
+    updateTransformers("app", [[new RewriteTransformer("blub", "blab")]]);
+    expectAsset("app|foo.blab", "foo.blab");
+    expectNoAsset("app|foo.blub");
+    buildShouldSucceed();
+  });
+
+  test("a new transformer is not applied to a non-matching asset", () {
+    initGraph(["app|foo.blub"]);
+
+    updateSources(["app|foo.blub"]);
+    expectAsset("app|foo.blub", "foo");
+    buildShouldSucceed();
+
+    updateTransformers("app", [[new RewriteTransformer("zip", "zap")]]);
+    expectAsset("app|foo.blub", "foo");
+    expectNoAsset("app|foo.zap");
+    buildShouldSucceed();
+  });
+
+  test("updateTransformers doesn't re-run an old transformer", () {
+    var rewrite = new RewriteTransformer("blub", "blab");
+    initGraph(["app|foo.blub"], {"app": [[rewrite]]});
+
+    updateSources(["app|foo.blub"]);
+    expectAsset("app|foo.blab", "foo.blab");
+    expectNoAsset("app|foo.blub");
+    buildShouldSucceed();
+
+    updateTransformers("app", [[rewrite]]);
+    expectAsset("app|foo.blab", "foo.blab");
+    expectNoAsset("app|foo.blub");
+    buildShouldSucceed();
+
+    expect(rewrite.numRuns, completion(equals(1)));
+  });
+
+  test("updateTransformers re-runs old transformers in a new phase", () {
+    var rewrite1 = new RewriteTransformer("txt", "blub");
+    var rewrite2 = new RewriteTransformer("blub", "blab");
+    initGraph(["app|foo.txt"], {"app": [[rewrite1], [rewrite2]]});
+
+    updateSources(["app|foo.txt"]);
+    expectAsset("app|foo.blab", "foo.blub.blab");
+    expectNoAsset("app|foo.blub");
+    buildShouldSucceed();
+
+    updateTransformers("app", [[rewrite2], [rewrite1]]);
+    expectAsset("app|foo.blub", "foo.blub");
+    expectNoAsset("app|foo.blab");
+    buildShouldSucceed();
+  });
+
+  test("updateTransformers re-runs an old transformer when a previous phase "
+      "changes", () {
+    var rewrite = new RewriteTransformer("txt", "out");
+    initGraph(["app|foo.txt"], {"app": [[], [rewrite]]});
+
+    updateSources(["app|foo.txt"]);
+    expectAsset("app|foo.out", "foo.out");
+    buildShouldSucceed();
+
+    updateTransformers("app", [
+      [new RewriteTransformer("txt", "txt")],
+      [rewrite]
+    ]);
+    expectAsset("app|foo.out", "foo.txt.out");
+    buildShouldSucceed();
+  });
+
+  test("a removed transformer is no longer applied", () {
+    initGraph(["app|foo.blub"], {"app": [
+      [new RewriteTransformer("blub", "blab")]
+    ]});
+
+    updateSources(["app|foo.blub"]);
+    expectAsset("app|foo.blab", "foo.blab");
+    expectNoAsset("app|foo.blub");
+    buildShouldSucceed();
+
+    updateTransformers("app", []);
+    expectAsset("app|foo.blub", "foo");
+    expectNoAsset("app|foo.blab");
+    buildShouldSucceed();
+  });
+
+  test("a new transformer is pipelined", () {
+    var rewrite1 = new RewriteTransformer("source", "phase1");
+    var rewrite3 = new RewriteTransformer("phase2", "phase3");
+    initGraph(["app|foo.source"], {"app": [
+      [rewrite1],
+      [rewrite3]
+    ]});
+
+    updateSources(["app|foo.source"]);
+    expectNoAsset("app|foo.phase3");
+    buildShouldSucceed();
+
+    updateTransformers("app", [
+      [rewrite1],
+      [new RewriteTransformer("phase1", "phase2")],
+      [rewrite3]
+    ]);
+    expectAsset("app|foo.phase3", "foo.phase1.phase2.phase3");
+    buildShouldSucceed();
+  });
+
+  test("a removed transformer is un-pipelined", () {
+    var rewrite1 = new RewriteTransformer("source", "phase1");
+    var rewrite3 = new RewriteTransformer("phase2", "phase3");
+    initGraph(["app|foo.source"], {"app": [
+      [rewrite1],
+      [new RewriteTransformer("phase1", "phase2")],
+      [rewrite3]
+    ]});
+
+    updateSources(["app|foo.source"]);
+    expectAsset("app|foo.phase3", "foo.phase1.phase2.phase3");
+    buildShouldSucceed();
+
+    updateTransformers("app", [[rewrite1], [rewrite3]]);
+    expectNoAsset("app|foo.phase3");
+    buildShouldSucceed();
+  });
+
+  test("a transformer is removed during isPrimary", () {
+    var rewrite = new RewriteTransformer("blub", "blab");
+    initGraph(["app|foo.blub"], {"app": [[rewrite]]});
+
+    rewrite.pauseIsPrimary("app|foo.blub");
+    updateSources(["app|foo.blub"]);
+    // Ensure we're waiting on [rewrite.isPrimary].
+    schedule(pumpEventQueue);
+
+    updateTransformers("app", []);
+    rewrite.resumeIsPrimary("app|foo.blub");
+    expectAsset("app|foo.blub", "foo");
+    expectNoAsset("app|foo.blab");
+    buildShouldSucceed();
+  });
+
+  test("a transformer is removed during apply", () {
+    var rewrite = new RewriteTransformer("blub", "blab");
+    initGraph(["app|foo.blub"], {"app": [[rewrite]]});
+
+    rewrite.pauseApply();
+    updateSources(["app|foo.blub"]);
+    // Ensure we're waiting on [rewrite.apply].
+    schedule(pumpEventQueue);
+
+    updateTransformers("app", []);
+    rewrite.resumeApply();
+    expectAsset("app|foo.blub", "foo");
+    expectNoAsset("app|foo.blab");
+    buildShouldSucceed();
+  });
+
+  test("a new transformer can see pass-through assets", () {
+    var rewrite = new RewriteTransformer("zip", "zap");
+    initGraph(["app|foo.blub"], {"app": [[rewrite]]});
+
+    updateSources(["app|foo.blub"]);
+    buildShouldSucceed();
+
+    updateTransformers("app", [
+      [rewrite],
+      [new RewriteTransformer("blub", "blab")]
+    ]);
+    expectAsset("app|foo.blab", "foo.blab");
+    expectNoAsset("app|foo.blub");
+    buildShouldSucceed();
+  });
+
+  test("a cross-package transform sees a new transformer in a new phase", () {
+    // TODO(nweiz): make this work.
+    return;
+
+    var rewrite = new RewriteTransformer("inc", "inc");
+    initGraph({
+      "pkg1|foo.txt": "pkg2|foo.inc",
+      "pkg2|foo.inc": "foo"
+    }, {
+      "pkg1": [[new ManyToOneTransformer("txt")]],
+      "pkg2": [[rewrite]]
+    });
+
+    updateSources(["pkg1|foo.txt", "pkg2|foo.inc"]);
+    expectAsset("pkg1|foo.out", "foo");
+    buildShouldSucceed();
+
+    updateTransformers("pkg2", [
+      [rewrite],
+      [new RewriteTransformer("inc", "inc")]
+    ]);
+    expectAsset("pkg1|foo.out", "foo.inc.inc");
+    buildShouldSucceed();
+  });
+
+  test("a cross-package transform doesn't see a removed transformer in a "
+      "removed phase", () {
+    var rewrite = new RewriteTransformer("inc", "inc");
+    initGraph({
+      "pkg1|foo.txt": "pkg2|foo.inc",
+      "pkg2|foo.inc": "foo"
+    }, {
+      "pkg1": [[new ManyToOneTransformer("txt")]],
+      "pkg2": [
+        [rewrite],
+        [new RewriteTransformer("inc", "inc")]
+      ]
+    });
+
+    updateSources(["pkg1|foo.txt", "pkg2|foo.inc"]);
+    expectAsset("pkg1|foo.out", "foo.inc.inc");
+    buildShouldSucceed();
+
+    updateTransformers("pkg2", [[rewrite]]);
+    expectAsset("pkg1|foo.out", "foo.inc");
+    buildShouldSucceed();
+  });
+}
\ No newline at end of file
diff --git a/pkg/barback/test/package_graph/errors_test.dart b/pkg/barback/test/package_graph/errors_test.dart
index 8e4fd89..8c74991 100644
--- a/pkg/barback/test/package_graph/errors_test.dart
+++ b/pkg/barback/test/package_graph/errors_test.dart
@@ -4,9 +4,6 @@
 
 library barback.test.package_graph.source_test;
 
-import 'dart:async';
-
-import 'package:barback/barback.dart';
 import 'package:barback/src/utils.dart';
 import 'package:scheduled_test/scheduled_test.dart';
 
diff --git a/pkg/barback/test/package_graph/get_all_assets_test.dart b/pkg/barback/test/package_graph/get_all_assets_test.dart
new file mode 100644
index 0000000..0ece84b
--- /dev/null
+++ b/pkg/barback/test/package_graph/get_all_assets_test.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library barback.test.barback_test;
+
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../utils.dart';
+
+main() {
+  initConfig();
+
+  test("gets all source assets", () {
+    initGraph(["app|a.txt", "app|b.txt", "app|c.txt"]);
+    updateSources(["app|a.txt", "app|b.txt", "app|c.txt"]);
+    expectAllAssets(["app|a.txt", "app|b.txt", "app|c.txt"]);
+    buildShouldSucceed();
+  });
+
+  test("includes transformed outputs, but not consumed ones", () {
+    initGraph(["app|a.txt", "app|foo.blub"], {"app": [
+      [new RewriteTransformer("blub", "blab")]
+    ]});
+    updateSources(["app|a.txt", "app|foo.blub"]);
+    expectAllAssets(["app|a.txt", "app|foo.blab"]);
+    buildShouldSucceed();
+  });
+
+  test("includes non-primary inputs to transformers", () {
+    var transformer = new ManyToOneTransformer("txt");
+    initGraph({
+      "app|a.txt": "a.inc",
+      "app|a.inc": "a"
+    }, {"app": [[transformer]]});
+
+    updateSources(["app|a.txt", "app|a.inc"]);
+    expectAllAssets(["app|a.inc", "app|a.out"]);
+    buildShouldSucceed();
+  });
+
+  test("completes to an error if two transformers output the same file", () {
+    initGraph(["app|foo.a"], {"app": [
+      [
+        new RewriteTransformer("a", "b"),
+        new RewriteTransformer("a", "b")
+      ]
+    ]});
+    updateSources(["app|foo.a"]);
+    expectAllAssetsShouldFail(isAssetCollisionException("app|foo.b"));
+  });
+
+  test("completes to an error if a transformer fails", () {
+    initGraph(["app|foo.txt"], {"app": [
+      [new BadTransformer(["app|foo.out"])]
+    ]});
+
+    updateSources(["app|foo.txt"]);
+    expectAllAssetsShouldFail(isTransformerException(
+        equals(BadTransformer.ERROR)));
+  });
+
+  test("completes to an aggregate error if there are multiple errors", () {
+    initGraph(["app|foo.txt"], {"app": [
+      [
+        new BadTransformer(["app|foo.out"]),
+        new BadTransformer(["app|foo.out2"])
+      ]
+    ]});
+
+    updateSources(["app|foo.txt"]);
+    expectAllAssetsShouldFail(isAggregateException([
+      isTransformerException(equals(BadTransformer.ERROR)),
+      isTransformerException(equals(BadTransformer.ERROR))
+    ]));
+  });
+}
diff --git a/pkg/barback/test/package_graph/source_test.dart b/pkg/barback/test/package_graph/source_test.dart
index 4c47df3..b5763a8 100644
--- a/pkg/barback/test/package_graph/source_test.dart
+++ b/pkg/barback/test/package_graph/source_test.dart
@@ -4,10 +4,6 @@
 
 library barback.test.package_graph.source_test;
 
-import 'dart:async';
-
-import 'package:barback/barback.dart';
-import 'package:barback/src/utils.dart';
 import 'package:scheduled_test/scheduled_test.dart';
 
 import '../utils.dart';
diff --git a/pkg/barback/test/package_graph/transform_test.dart b/pkg/barback/test/package_graph/transform_test.dart
index 4d7b97e..8c108b3 100644
--- a/pkg/barback/test/package_graph/transform_test.dart
+++ b/pkg/barback/test/package_graph/transform_test.dart
@@ -4,9 +4,6 @@
 
 library barback.test.package_graph.transform_test;
 
-import 'dart:async';
-
-import 'package:barback/barback.dart';
 import 'package:barback/src/utils.dart';
 import 'package:scheduled_test/scheduled_test.dart';
 
@@ -1058,5 +1055,45 @@
       expectNoAsset("pkg1|c.done");
       buildShouldSucceed();
     });
+
+    test("sees a transformer that's newly applied to a cross-package "
+        "dependency", () {
+      initGraph({
+        "pkg1|a.txt": "pkg2|a.inc",
+        "pkg2|a.inc": "a"
+      }, {
+        "pkg1": [[new ManyToOneTransformer("txt")]],
+        "pkg2": [[new CheckContentTransformer("b", " transformed")]]
+      });
+
+      updateSources(["pkg1|a.txt", "pkg2|a.inc"]);
+      expectAsset("pkg1|a.out", "a");
+      buildShouldSucceed();
+
+      modifyAsset("pkg2|a.inc", "b");
+      updateSources(["pkg2|a.inc"]);
+      expectAsset("pkg1|a.out", "b transformed");
+      buildShouldSucceed();
+    });
+
+    test("doesn't see a transformer that's newly not applied to a "
+        "cross-package dependency", () {
+      initGraph({
+        "pkg1|a.txt": "pkg2|a.inc",
+        "pkg2|a.inc": "a"
+      }, {
+        "pkg1": [[new ManyToOneTransformer("txt")]],
+        "pkg2": [[new CheckContentTransformer("a", " transformed")]]
+      });
+
+      updateSources(["pkg1|a.txt", "pkg2|a.inc"]);
+      expectAsset("pkg1|a.out", "a transformed");
+      buildShouldSucceed();
+
+      modifyAsset("pkg2|a.inc", "b");
+      updateSources(["pkg2|a.inc"]);
+      expectAsset("pkg1|a.out", "b");
+      buildShouldSucceed();
+    });
   });
 }
diff --git a/pkg/barback/test/transformer/many_to_one.dart b/pkg/barback/test/transformer/many_to_one.dart
index 45f8f25..59a190c 100644
--- a/pkg/barback/test/transformer/many_to_one.dart
+++ b/pkg/barback/test/transformer/many_to_one.dart
@@ -7,7 +7,6 @@
 import 'dart:async';
 
 import 'package:barback/barback.dart';
-import 'package:barback/src/utils.dart';
 
 import 'mock.dart';
 
diff --git a/pkg/barback/test/transformer_test.dart b/pkg/barback/test/transformer_test.dart
new file mode 100644
index 0000000..9df3f4a
--- /dev/null
+++ b/pkg/barback/test/transformer_test.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library barback.test.transformer_test;
+
+import 'dart:async';
+
+import 'package:barback/barback.dart';
+import 'package:unittest/unittest.dart';
+
+import 'utils.dart';
+
+main() {
+  initConfig();
+
+  group("isPrimary", () {
+    test("defaults to allowedExtensions", () {
+      var transformer = new ExtensionTransformer(".txt .bin");
+      expect(transformer.isPrimary(makeAsset("foo.txt")),
+          completion(isTrue));
+
+      expect(transformer.isPrimary(makeAsset("foo.bin")),
+          completion(isTrue));
+
+      expect(transformer.isPrimary(makeAsset("foo.nottxt")),
+          completion(isFalse));
+    });
+
+    test("allows all files if allowedExtensions is not overridden", () {
+      var transformer = new MockTransformer();
+      expect(transformer.isPrimary(makeAsset("foo.txt")),
+          completion(isTrue));
+
+      expect(transformer.isPrimary(makeAsset("foo.bin")),
+          completion(isTrue));
+
+      expect(transformer.isPrimary(makeAsset("anything")),
+          completion(isTrue));
+    });
+  });
+}
+
+Asset makeAsset(String path) =>
+    new Asset.fromString(new AssetId.parse("app|$path"), "");
+
+class MockTransformer extends Transformer {
+  MockTransformer();
+
+  Future apply(Transform transform) => new Future.value();
+}
+
+class ExtensionTransformer extends Transformer {
+  final String allowedExtensions;
+
+  ExtensionTransformer(this.allowedExtensions);
+
+  Future apply(Transform transform) => new Future.value();
+}
\ No newline at end of file
diff --git a/pkg/barback/test/utils.dart b/pkg/barback/test/utils.dart
index 99a7ec1..73f4f8b 100644
--- a/pkg/barback/test/utils.dart
+++ b/pkg/barback/test/utils.dart
@@ -9,7 +9,6 @@
 import 'dart:io';
 
 import 'package:barback/barback.dart';
-import 'package:barback/src/asset_set.dart';
 import 'package:barback/src/cancelable_future.dart';
 import 'package:barback/src/utils.dart';
 import 'package:path/path.dart' as pathos;
@@ -61,9 +60,38 @@
   if (assets == null) assets = [];
   if (transformers == null) transformers = {};
 
-  _provider = new MockProvider(assets, transformers);
+  var assetList;
+  if (assets is Map) {
+    assetList = assets.keys.map((asset) {
+      var id = new AssetId.parse(asset);
+      return new _MockAsset(id, assets[asset]);
+    });
+  } else if (assets is Iterable) {
+    assetList = assets.map((asset) {
+      var id = new AssetId.parse(asset);
+      var contents = pathos.basenameWithoutExtension(id.path);
+      return new _MockAsset(id, contents);
+    });
+  }
+
+  var assetMap = mapMapValues(groupBy(assetList, (asset) => asset.id.package),
+      (package, assets) => new AssetSet.from(assets));
+
+  // Make sure that packages that have transformers but no assets are considered
+  // by MockProvider to exist.
+  for (var package in transformers.keys) {
+    assetMap.putIfAbsent(package, () => new AssetSet());
+  }
+
+  _provider = new MockProvider(assetMap);
   _barback = new Barback(_provider);
   _nextBuildResult = 0;
+
+  transformers.forEach(_barback.updateTransformers);
+
+  // There should be one successful build after adding all the transformers but
+  // before adding any sources.
+  if (!transformers.isEmpty) buildShouldSucceed();
 }
 
 /// Updates [assets] in the current [PackageProvider].
@@ -102,6 +130,13 @@
 void removeSourcesSync(Iterable assets) =>
     _barback.removeSources(_parseAssets(assets));
 
+/// Sets the transformers for [package] to [transformers].
+void updateTransformers(String package,
+    Iterable<Iterable<Transformer>> transformers) {
+  schedule(() => _barback.updateTransformers(package, transformers),
+      "updating transformers for $package");
+}
+
 /// Parse a list of strings or [AssetId]s into a list of [AssetId]s.
 List<AssetId> _parseAssets(Iterable assets) {
   return assets.map((asset) {
@@ -223,6 +258,39 @@
   }, "get asset $name");
 }
 
+/// Schedules an expectation that the graph will output all of the given
+/// assets, and no others.
+///
+/// [assets] is a list of strings that can be parsed to [AssetID]s.
+void expectAllAssets(Iterable<String> assets) {
+  var expected = assets.map((asset) => new AssetId.parse(asset));
+
+  schedule(() {
+    return _barback.getAllAssets().then((actualAssets) {
+      var actualIds = actualAssets.map((asset) => asset.id).toSet();
+
+      for (var id in expected) {
+        expect(actualIds, contains(id));
+        actualIds.remove(id);
+      }
+
+      expect(actualIds, isEmpty);
+    });
+  }, "get all assets, expecting ${expected.join(', ')}");
+}
+
+/// Schedules an expectation that [Barback.getAllAssets] will return a [Future]
+/// that completes to a error that matches [matcher].
+///
+/// If [match] is a [List], then it expects the completed error to be an
+/// [AggregateException] whose errors match each matcher in the list. Otherwise,
+/// [match] should be a single matcher that the error should match.
+void expectAllAssetsShouldFail(Matcher matcher) {
+  schedule(() {
+    expect(_barback.getAllAssets(), throwsA(matcher));
+  }, "get all assets should fail");
+}
+
 /// Schedules an expectation that a [getAssetById] call for the given asset
 /// won't terminate at this point in the schedule.
 void expectAssetDoesNotComplete(String name) {
@@ -236,6 +304,25 @@
   }, "asset $id should not complete");
 }
 
+/// Returns a matcher for an [AggregateException] containing errors that match
+/// [matchers].
+Matcher isAggregateException(Iterable<Matcher> errors) {
+  // Match the aggregate error itself.
+  var matchers = [
+    new isInstanceOf<AggregateException>(),
+    transform((error) => error.errors, hasLength(errors.length),
+        'errors.length == ${errors.length}')
+  ];
+
+  // Make sure its contained errors match the matchers.
+  for (var error in errors) {
+    matchers.add(transform((error) => error.errors, contains(error),
+        error.toString()));
+  }
+
+  return allOf(matchers);
+}
+
 /// Returns a matcher for an [AssetNotFoundException] with the given [id].
 Matcher isAssetNotFoundException(String name) {
   var id = new AssetId.parse(name);
@@ -340,9 +427,9 @@
 
 /// An [AssetProvider] that provides the given set of assets.
 class MockProvider implements PackageProvider {
-  Iterable<String> get packages => _packages.keys;
+  Iterable<String> get packages => _assets.keys;
 
-  Map<String, _MockPackage> _packages;
+  Map<String, AssetSet> _assets;
 
   /// The set of assets for which [MockLoadException]s should be emitted if
   /// they're loaded.
@@ -366,42 +453,19 @@
     _pauseCompleter = null;
   }
 
-  MockProvider(assets,
-      Map<String, Iterable<Iterable<Transformer>>> transformers) {
-    var assetList;
-    if (assets is Map) {
-      assetList = assets.keys.map((asset) {
-        var id = new AssetId.parse(asset);
-        return new _MockAsset(id, assets[asset]);
-      });
-    } else if (assets is Iterable) {
-      assetList = assets.map((asset) {
-        var id = new AssetId.parse(asset);
-        var contents = pathos.basenameWithoutExtension(id.path);
-        return new _MockAsset(id, contents);
-      });
-    }
-
-    _packages = mapMapValues(groupBy(assetList, (asset) => asset.id.package),
-        (package, assets) {
-      var packageTransformers = transformers[package];
-      if (packageTransformers == null) packageTransformers = [];
-      return new _MockPackage(
-          new AssetSet.from(assets), packageTransformers.toList());
-    });
-
+  MockProvider(this._assets) {
     // If there are no assets or transformers, add a dummy package. This better
     // simulates the real world, where there'll always be at least the
     // entrypoint package.
-    if (_packages.isEmpty) {
-      _packages = {"app": new _MockPackage(new AssetSet(), [])};
+    if (_assets.isEmpty) {
+      _assets = {"app": new AssetSet()};
     }
   }
 
   void _modifyAsset(String name, String contents) {
     var id = new AssetId.parse(name);
     _errors.remove(id);
-    _packages[id.package].assets[id].contents = contents;
+    _assets[id.package][id].contents = contents;
   }
 
   void _setAssetError(String name) => _errors.add(new AssetId.parse(name));
@@ -411,23 +475,15 @@
       throw new UnimplementedError("Doesn't handle 'within' yet.");
     }
 
-    return _packages[package].assets.map((asset) => asset.id);
-  }
-
-  Iterable<Iterable<Transformer>> getTransformers(String package) {
-    var mockPackage = _packages[package];
-    if (mockPackage == null) {
-      throw new ArgumentError("No package named $package.");
-    }
-    return mockPackage.transformers;
+    return _assets[package].map((asset) => asset.id);
   }
 
   Future<Asset> getAsset(AssetId id) {
     // Eagerly load the asset so we can test an asset's value changing between
     // when a load starts and when it finishes.
-    var package = _packages[id.package];
+    var assets = _assets[id.package];
     var asset;
-    if (package != null) asset = package.assets[id];
+    if (assets != null) asset = assets[id];
 
     var hasError = _errors.contains(id);
 
@@ -455,16 +511,6 @@
   String toString() => "Error loading $id.";
 }
 
-/// Used by [MockProvider] to keep track of which assets and transformers exist
-/// for each package.
-class _MockPackage {
-  final AssetSet assets;
-  final List<List<Transformer>> transformers;
-
-  _MockPackage(this.assets, Iterable<Iterable<Transformer>> transformers)
-      : transformers = transformers.map((phase) => phase.toList()).toList();
-}
-
 /// An implementation of [Asset] that never hits the file system.
 class _MockAsset implements Asset {
   final AssetId id;
diff --git a/pkg/crypto/lib/crypto.dart b/pkg/crypto/lib/crypto.dart
index f5f737b..ee11c25 100644
--- a/pkg/crypto/lib/crypto.dart
+++ b/pkg/crypto/lib/crypto.dart
@@ -2,6 +2,10 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+/**
+ * Cryptographic algorithms, with support for hash functions such as
+ * SHA-1, SHA-256, HMAC, and MD5.
+ */
 library crypto;
 
 import 'dart:math';
diff --git a/pkg/custom_element/lib/custom-elements.debug.js b/pkg/custom_element/lib/custom-elements.debug.js
new file mode 100644
index 0000000..400730b
--- /dev/null
+++ b/pkg/custom_element/lib/custom-elements.debug.js
@@ -0,0 +1,1458 @@
+// Copyright (c) 2012 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+(function() {
+
+var scope = window.PolymerLoader = {};
+var flags = {};
+
+// convert url arguments to flags
+
+if (!flags.noOpts) {
+  location.search.slice(1).split('&').forEach(function(o) {
+    o = o.split('=');
+    o[0] && (flags[o[0]] = o[1] || true);
+  });
+}
+
+// process global logFlags
+
+parseLogFlags(flags);
+
+function load(scopeName) {
+  // imports
+
+  var scope = window[scopeName];
+  var entryPointName = scope.entryPointName;
+  var processFlags = scope.processFlags;
+
+  // acquire attributes and base path from entry point
+
+  var entryPoint = findScript(entryPointName);
+  var base = entryPoint.basePath;
+
+  // acquire common flags
+  var flags = scope.flags;
+
+  // convert attributes to flags
+  var flags = PolymerLoader.flags;
+  for (var i=0, a; (a=entryPoint.attributes[i]); i++) {
+    if (a.name !== 'src') {
+      flags[a.name] = a.value || true;
+    }
+  }
+
+  // parse log flags into global
+  parseLogFlags(flags);
+
+  // exports
+
+  scope.basePath = base;
+  scope.flags = flags;
+
+  // process flags for dynamic dependencies
+
+  if (processFlags) {
+    processFlags.call(scope, flags);
+  }
+
+  // post-process imports
+
+  var modules = scope.modules || [];
+  var sheets = scope.sheets || [];
+
+  // write script tags for dependencies
+
+  modules.forEach(function(src) {
+    document.write('<script src="' + base + src + '"></script>');
+  });
+
+  // write link tags for styles
+
+  sheets.forEach(function(src) {
+    document.write('<link rel="stylesheet" href="' + base + src + '">');
+  });
+}
+
+// utility method
+
+function findScript(fileName) {
+  var script = document.querySelector('script[src*="' + fileName + '"]');
+  var src = script.attributes.src.value;
+  script.basePath = src.slice(0, src.indexOf(fileName));
+  return script;
+}
+
+function parseLogFlags(flags) {
+  var logFlags = window.logFlags = window.logFlags || {};
+  if (flags.log) {
+    flags.log.split(',').forEach(function(f) {
+      logFlags[f] = true;
+    });
+  }
+}
+
+scope.flags = flags;
+scope.load = load;
+
+})();
+
+window.CustomElements = {flags:{}};
+// SideTable is a weak map where possible. If WeakMap is not available the
+// association is stored as an expando property.
+var SideTable;
+// TODO(arv): WeakMap does not allow for Node etc to be keys in Firefox
+if (typeof WeakMap !== 'undefined' && navigator.userAgent.indexOf('Firefox/') < 0) {
+  SideTable = WeakMap;
+} else {
+  (function() {
+    var defineProperty = Object.defineProperty;
+    var hasOwnProperty = Object.hasOwnProperty;
+    var counter = new Date().getTime() % 1e9;
+
+    SideTable = function() {
+      this.name = '__st' + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
+    };
+
+    SideTable.prototype = {
+      set: function(key, value) {
+        defineProperty(key, this.name, {value: value, writable: true});
+      },
+      get: function(key) {
+        return hasOwnProperty.call(key, this.name) ? key[this.name] : undefined;
+      },
+      delete: function(key) {
+        this.set(key, undefined);
+      }
+    }
+  })();
+}
+
+(function(global) {
+
+  var registrationsTable = new SideTable();
+
+  // We use setImmediate or postMessage for our future callback.
+  var setImmediate = window.msSetImmediate;
+
+  // Use post message to emulate setImmediate.
+  if (!setImmediate) {
+    var setImmediateQueue = [];
+    var sentinel = String(Math.random());
+    window.addEventListener('message', function(e) {
+      if (e.data === sentinel) {
+        var queue = setImmediateQueue;
+        setImmediateQueue = [];
+        queue.forEach(function(func) {
+          func();
+        });
+      }
+    });
+    setImmediate = function(func) {
+      setImmediateQueue.push(func);
+      window.postMessage(sentinel, '*');
+    };
+  }
+
+  // This is used to ensure that we never schedule 2 callas to setImmediate
+  var isScheduled = false;
+
+  // Keep track of observers that needs to be notified next time.
+  var scheduledObservers = [];
+
+  /**
+   * Schedules |dispatchCallback| to be called in the future.
+   * @param {MutationObserver} observer
+   */
+  function scheduleCallback(observer) {
+    scheduledObservers.push(observer);
+    if (!isScheduled) {
+      isScheduled = true;
+      setImmediate(dispatchCallbacks);
+    }
+  }
+
+  function wrapIfNeeded(node) {
+    return window.ShadowDOMPolyfill &&
+        window.ShadowDOMPolyfill.wrapIfNeeded(node) ||
+        node;
+  }
+
+  function dispatchCallbacks() {
+    // http://dom.spec.whatwg.org/#mutation-observers
+
+    isScheduled = false; // Used to allow a new setImmediate call above.
+
+    var observers = scheduledObservers;
+    scheduledObservers = [];
+    // Sort observers based on their creation UID (incremental).
+    observers.sort(function(o1, o2) {
+      return o1.uid_ - o2.uid_;
+    });
+
+    var anyNonEmpty = false;
+    observers.forEach(function(observer) {
+
+      // 2.1, 2.2
+      var queue = observer.takeRecords();
+      // 2.3. Remove all transient registered observers whose observer is mo.
+      removeTransientObserversFor(observer);
+
+      // 2.4
+      if (queue.length) {
+        observer.callback_(queue, observer);
+        anyNonEmpty = true;
+      }
+    });
+
+    // 3.
+    if (anyNonEmpty)
+      dispatchCallbacks();
+  }
+
+  function removeTransientObserversFor(observer) {
+    observer.nodes_.forEach(function(node) {
+      var registrations = registrationsTable.get(node);
+      if (!registrations)
+        return;
+      registrations.forEach(function(registration) {
+        if (registration.observer === observer)
+          registration.removeTransientObservers();
+      });
+    });
+  }
+
+  /**
+   * This function is used for the "For each registered observer observer (with
+   * observer's options as options) in target's list of registered observers,
+   * run these substeps:" and the "For each ancestor ancestor of target, and for
+   * each registered observer observer (with options options) in ancestor's list
+   * of registered observers, run these substeps:" part of the algorithms. The
+   * |options.subtree| is checked to ensure that the callback is called
+   * correctly.
+   *
+   * @param {Node} target
+   * @param {function(MutationObserverInit):MutationRecord} callback
+   */
+  function forEachAncestorAndObserverEnqueueRecord(target, callback) {
+    for (var node = target; node; node = node.parentNode) {
+      var registrations = registrationsTable.get(node);
+
+      if (registrations) {
+        for (var j = 0; j < registrations.length; j++) {
+          var registration = registrations[j];
+          var options = registration.options;
+
+          // Only target ignores subtree.
+          if (node !== target && !options.subtree)
+            continue;
+
+          var record = callback(options);
+          if (record)
+            registration.enqueue(record);
+        }
+      }
+    }
+  }
+
+  var uidCounter = 0;
+
+  /**
+   * The class that maps to the DOM MutationObserver interface.
+   * @param {Function} callback.
+   * @constructor
+   */
+  function JsMutationObserver(callback) {
+    this.callback_ = callback;
+    this.nodes_ = [];
+    this.records_ = [];
+    this.uid_ = ++uidCounter;
+  }
+
+  JsMutationObserver.prototype = {
+    observe: function(target, options) {
+      target = wrapIfNeeded(target);
+
+      // 1.1
+      if (!options.childList && !options.attributes && !options.characterData ||
+
+          // 1.2
+          options.attributeOldValue && !options.attributes ||
+
+          // 1.3
+          options.attributeFilter && options.attributeFilter.length &&
+              !options.attributes ||
+
+          // 1.4
+          options.characterDataOldValue && !options.characterData) {
+
+        throw new SyntaxError();
+      }
+
+      var registrations = registrationsTable.get(target);
+      if (!registrations)
+        registrationsTable.set(target, registrations = []);
+
+      // 2
+      // If target's list of registered observers already includes a registered
+      // observer associated with the context object, replace that registered
+      // observer's options with options.
+      var registration;
+      for (var i = 0; i < registrations.length; i++) {
+        if (registrations[i].observer === this) {
+          registration = registrations[i];
+          registration.removeListeners();
+          registration.options = options;
+          break;
+        }
+      }
+
+      // 3.
+      // Otherwise, add a new registered observer to target's list of registered
+      // observers with the context object as the observer and options as the
+      // options, and add target to context object's list of nodes on which it
+      // is registered.
+      if (!registration) {
+        registration = new Registration(this, target, options);
+        registrations.push(registration);
+        this.nodes_.push(target);
+      }
+
+      registration.addListeners();
+    },
+
+    disconnect: function() {
+      this.nodes_.forEach(function(node) {
+        var registrations = registrationsTable.get(node);
+        for (var i = 0; i < registrations.length; i++) {
+          var registration = registrations[i];
+          if (registration.observer === this) {
+            registration.removeListeners();
+            registrations.splice(i, 1);
+            // Each node can only have one registered observer associated with
+            // this observer.
+            break;
+          }
+        }
+      }, this);
+      this.records_ = [];
+    },
+
+    takeRecords: function() {
+      var copyOfRecords = this.records_;
+      this.records_ = [];
+      return copyOfRecords;
+    }
+  };
+
+  /**
+   * @param {string} type
+   * @param {Node} target
+   * @constructor
+   */
+  function MutationRecord(type, target) {
+    this.type = type;
+    this.target = target;
+    this.addedNodes = [];
+    this.removedNodes = [];
+    this.previousSibling = null;
+    this.nextSibling = null;
+    this.attributeName = null;
+    this.attributeNamespace = null;
+    this.oldValue = null;
+  }
+
+  function copyMutationRecord(original) {
+    var record = new MutationRecord(original.type, original.target);
+    record.addedNodes = original.addedNodes.slice();
+    record.removedNodes = original.removedNodes.slice();
+    record.previousSibling = original.previousSibling;
+    record.nextSibling = original.nextSibling;
+    record.attributeName = original.attributeName;
+    record.attributeNamespace = original.attributeNamespace;
+    record.oldValue = original.oldValue;
+    return record;
+  };
+
+  // We keep track of the two (possibly one) records used in a single mutation.
+  var currentRecord, recordWithOldValue;
+
+  /**
+   * Creates a record without |oldValue| and caches it as |currentRecord| for
+   * later use.
+   * @param {string} oldValue
+   * @return {MutationRecord}
+   */
+  function getRecord(type, target) {
+    return currentRecord = new MutationRecord(type, target);
+  }
+
+  /**
+   * Gets or creates a record with |oldValue| based in the |currentRecord|
+   * @param {string} oldValue
+   * @return {MutationRecord}
+   */
+  function getRecordWithOldValue(oldValue) {
+    if (recordWithOldValue)
+      return recordWithOldValue;
+    recordWithOldValue = copyMutationRecord(currentRecord);
+    recordWithOldValue.oldValue = oldValue;
+    return recordWithOldValue;
+  }
+
+  function clearRecords() {
+    currentRecord = recordWithOldValue = undefined;
+  }
+
+  /**
+   * @param {MutationRecord} record
+   * @return {boolean} Whether the record represents a record from the current
+   * mutation event.
+   */
+  function recordRepresentsCurrentMutation(record) {
+    return record === recordWithOldValue || record === currentRecord;
+  }
+
+  /**
+   * Selects which record, if any, to replace the last record in the queue.
+   * This returns |null| if no record should be replaced.
+   *
+   * @param {MutationRecord} lastRecord
+   * @param {MutationRecord} newRecord
+   * @param {MutationRecord}
+   */
+  function selectRecord(lastRecord, newRecord) {
+    if (lastRecord === newRecord)
+      return lastRecord;
+
+    // Check if the the record we are adding represents the same record. If
+    // so, we keep the one with the oldValue in it.
+    if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord))
+      return recordWithOldValue;
+
+    return null;
+  }
+
+  /**
+   * Class used to represent a registered observer.
+   * @param {MutationObserver} observer
+   * @param {Node} target
+   * @param {MutationObserverInit} options
+   * @constructor
+   */
+  function Registration(observer, target, options) {
+    this.observer = observer;
+    this.target = target;
+    this.options = options;
+    this.transientObservedNodes = [];
+  }
+
+  Registration.prototype = {
+    enqueue: function(record) {
+      var records = this.observer.records_;
+      var length = records.length;
+
+      // There are cases where we replace the last record with the new record.
+      // For example if the record represents the same mutation we need to use
+      // the one with the oldValue. If we get same record (this can happen as we
+      // walk up the tree) we ignore the new record.
+      if (records.length > 0) {
+        var lastRecord = records[length - 1];
+        var recordToReplaceLast = selectRecord(lastRecord, record);
+        if (recordToReplaceLast) {
+          records[length - 1] = recordToReplaceLast;
+          return;
+        }
+      } else {
+        scheduleCallback(this.observer);
+      }
+
+      records[length] = record;
+    },
+
+    addListeners: function() {
+      this.addListeners_(this.target);
+    },
+
+    addListeners_: function(node) {
+      var options = this.options;
+      if (options.attributes)
+        node.addEventListener('DOMAttrModified', this, true);
+
+      if (options.characterData)
+        node.addEventListener('DOMCharacterDataModified', this, true);
+
+      if (options.childList)
+        node.addEventListener('DOMNodeInserted', this, true);
+
+      if (options.childList || options.subtree)
+        node.addEventListener('DOMNodeRemoved', this, true);
+    },
+
+    removeListeners: function() {
+      this.removeListeners_(this.target);
+    },
+
+    removeListeners_: function(node) {
+      var options = this.options;
+      if (options.attributes)
+        node.removeEventListener('DOMAttrModified', this, true);
+
+      if (options.characterData)
+        node.removeEventListener('DOMCharacterDataModified', this, true);
+
+      if (options.childList)
+        node.removeEventListener('DOMNodeInserted', this, true);
+
+      if (options.childList || options.subtree)
+        node.removeEventListener('DOMNodeRemoved', this, true);
+    },
+
+    /**
+     * Adds a transient observer on node. The transient observer gets removed
+     * next time we deliver the change records.
+     * @param {Node} node
+     */
+    addTransientObserver: function(node) {
+      // Don't add transient observers on the target itself. We already have all
+      // the required listeners set up on the target.
+      if (node === this.target)
+        return;
+
+      this.addListeners_(node);
+      this.transientObservedNodes.push(node);
+      var registrations = registrationsTable.get(node);
+      if (!registrations)
+        registrationsTable.set(node, registrations = []);
+
+      // We know that registrations does not contain this because we already
+      // checked if node === this.target.
+      registrations.push(this);
+    },
+
+    removeTransientObservers: function() {
+      var transientObservedNodes = this.transientObservedNodes;
+      this.transientObservedNodes = [];
+
+      transientObservedNodes.forEach(function(node) {
+        // Transient observers are never added to the target.
+        this.removeListeners_(node);
+
+        var registrations = registrationsTable.get(node);
+        for (var i = 0; i < registrations.length; i++) {
+          if (registrations[i] === this) {
+            registrations.splice(i, 1);
+            // Each node can only have one registered observer associated with
+            // this observer.
+            break;
+          }
+        }
+      }, this);
+    },
+
+    handleEvent: function(e) {
+      // Stop propagation since we are managing the propagation manually.
+      // This means that other mutation events on the page will not work
+      // correctly but that is by design.
+      e.stopImmediatePropagation();
+
+      switch (e.type) {
+        case 'DOMAttrModified':
+          // http://dom.spec.whatwg.org/#concept-mo-queue-attributes
+
+          var name = e.attrName;
+          var namespace = e.relatedNode.namespaceURI;
+          var target = e.target;
+
+          // 1.
+          var record = new getRecord('attributes', target);
+          record.attributeName = name;
+          record.attributeNamespace = namespace;
+
+          // 2.
+          var oldValue =
+              e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
+
+          forEachAncestorAndObserverEnqueueRecord(target, function(options) {
+            // 3.1, 4.2
+            if (!options.attributes)
+              return;
+
+            // 3.2, 4.3
+            if (options.attributeFilter && options.attributeFilter.length &&
+                options.attributeFilter.indexOf(name) === -1 &&
+                options.attributeFilter.indexOf(namespace) === -1) {
+              return;
+            }
+            // 3.3, 4.4
+            if (options.attributeOldValue)
+              return getRecordWithOldValue(oldValue);
+
+            // 3.4, 4.5
+            return record;
+          });
+
+          break;
+
+        case 'DOMCharacterDataModified':
+          // http://dom.spec.whatwg.org/#concept-mo-queue-characterdata
+          var target = e.target;
+
+          // 1.
+          var record = getRecord('characterData', target);
+
+          // 2.
+          var oldValue = e.prevValue;
+
+
+          forEachAncestorAndObserverEnqueueRecord(target, function(options) {
+            // 3.1, 4.2
+            if (!options.characterData)
+              return;
+
+            // 3.2, 4.3
+            if (options.characterDataOldValue)
+              return getRecordWithOldValue(oldValue);
+
+            // 3.3, 4.4
+            return record;
+          });
+
+          break;
+
+        case 'DOMNodeRemoved':
+          this.addTransientObserver(e.target);
+          // Fall through.
+        case 'DOMNodeInserted':
+          // http://dom.spec.whatwg.org/#concept-mo-queue-childlist
+          var target = e.relatedNode;
+          var changedNode = e.target;
+          var addedNodes, removedNodes;
+          if (e.type === 'DOMNodeInserted') {
+            addedNodes = [changedNode];
+            removedNodes = [];
+          } else {
+
+            addedNodes = [];
+            removedNodes = [changedNode];
+          }
+          var previousSibling = changedNode.previousSibling;
+          var nextSibling = changedNode.nextSibling;
+
+          // 1.
+          var record = getRecord('childList', target);
+          record.addedNodes = addedNodes;
+          record.removedNodes = removedNodes;
+          record.previousSibling = previousSibling;
+          record.nextSibling = nextSibling;
+
+          forEachAncestorAndObserverEnqueueRecord(target, function(options) {
+            // 2.1, 3.2
+            if (!options.childList)
+              return;
+
+            // 2.2, 3.3
+            return record;
+          });
+
+      }
+
+      clearRecords();
+    }
+  };
+
+  global.JsMutationObserver = JsMutationObserver;
+
+})(this);
+
+if (!window.MutationObserver) {
+  window.MutationObserver =
+      window.WebKitMutationObserver ||
+      window.JsMutationObserver;
+  if (!MutationObserver) {
+    throw new Error("no mutation observer support");
+  }
+}
+
+(function(scope){
+
+/*
+if (HTMLElement.prototype.webkitShadowRoot) {
+  Object.defineProperty(HTMLElement.prototype, 'shadowRoot', {
+    get: function() {
+      return this.webkitShadowRoot;
+    }
+  };
+}
+*/
+
+// walk the subtree rooted at node, applying 'find(element, data)' function
+// to each element
+// if 'find' returns true for 'element', do not search element's subtree
+function findAll(node, find, data) {
+  var e = node.firstElementChild;
+  if (!e) {
+    e = node.firstChild;
+    while (e && e.nodeType !== Node.ELEMENT_NODE) {
+      e = e.nextSibling;
+    }
+  }
+  while (e) {
+    if (find(e, data) !== true) {
+      findAll(e, find, data);
+    }
+    e = e.nextElementSibling;
+  }
+  return null;
+}
+
+// walk all shadowRoots on a given node.
+function forRoots(node, cb) {
+  var root = node.webkitShadowRoot;
+  while(root) {
+    forSubtree(root, cb);
+    root = root.olderShadowRoot;
+  }
+}
+
+// walk the subtree rooted at node, including descent into shadow-roots,
+// applying 'cb' to each element
+function forSubtree(node, cb) {
+  //logFlags.dom && node.childNodes && node.childNodes.length && console.group('subTree: ', node);
+  findAll(node, function(e) {
+    if (cb(e)) {
+      return true;
+    }
+    forRoots(e, cb);
+  });
+  forRoots(node, cb);
+  //logFlags.dom && node.childNodes && node.childNodes.length && console.groupEnd();
+}
+
+// manage lifecycle on added node
+function added(node) {
+  if (upgrade(node)) {
+    insertedNode(node);
+    return true;
+  }
+  inserted(node);
+}
+
+// manage lifecycle on added node's subtree only
+function addedSubtree(node) {
+  forSubtree(node, function(e) {
+    if (added(e)) {
+      return true;
+    }
+  });
+}
+
+// manage lifecycle on added node and it's subtree
+function addedNode(node) {
+  return added(node) || addedSubtree(node);
+}
+
+// upgrade custom elements at node, if applicable
+function upgrade(node) {
+  if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) {
+    var type = node.getAttribute('is') || node.localName;
+    var definition = scope.registry[type];
+    if (definition) {
+      logFlags.dom && console.group('upgrade:', node.localName);
+      scope.upgrade(node);
+      logFlags.dom && console.groupEnd();
+      return true;
+    }
+  }
+}
+
+function insertedNode(node) {
+  inserted(node);
+  if (inDocument(node)) {
+    forSubtree(node, function(e) {
+      inserted(e);
+    });
+  }
+}
+
+// TODO(sjmiles): if there are descents into trees that can never have inDocument(*) true, fix this
+
+function inserted(element) {
+  // TODO(sjmiles): it's possible we were inserted and removed in the space
+  // of one microtask, in which case we won't be 'inDocument' here
+  // But there are other cases where we are testing for inserted without
+  // specific knowledge of mutations, and must test 'inDocument' to determine
+  // whether to call inserted
+  // If we can factor these cases into separate code paths we can have
+  // better diagnostics.
+  // TODO(sjmiles): when logging, do work on all custom elements so we can
+  // track behavior even when callbacks not defined
+  //console.log('inserted: ', element.localName);
+  if (element.enteredDocumentCallback || (element.__upgraded__ && logFlags.dom)) {
+    logFlags.dom && console.group('inserted:', element.localName);
+    if (inDocument(element)) {
+      element.__inserted = (element.__inserted || 0) + 1;
+      // if we are in a 'removed' state, bluntly adjust to an 'inserted' state
+      if (element.__inserted < 1) {
+        element.__inserted = 1;
+      }
+      // if we are 'over inserted', squelch the callback
+      if (element.__inserted > 1) {
+        logFlags.dom && console.warn('inserted:', element.localName,
+          'insert/remove count:', element.__inserted)
+      } else if (element.enteredDocumentCallback) {
+        logFlags.dom && console.log('inserted:', element.localName);
+        element.enteredDocumentCallback();
+      }
+    }
+    logFlags.dom && console.groupEnd();
+  }
+}
+
+function removedNode(node) {
+  removed(node);
+  forSubtree(node, function(e) {
+    removed(e);
+  });
+}
+
+function removed(element) {
+  // TODO(sjmiles): temporary: do work on all custom elements so we can track
+  // behavior even when callbacks not defined
+  if (element.leftDocumentCallback || (element.__upgraded__ && logFlags.dom)) {
+    logFlags.dom && console.log('removed:', element.localName);
+    if (!inDocument(element)) {
+      element.__inserted = (element.__inserted || 0) - 1;
+      // if we are in a 'inserted' state, bluntly adjust to an 'removed' state
+      if (element.__inserted > 0) {
+        element.__inserted = 0;
+      }
+      // if we are 'over removed', squelch the callback
+      if (element.__inserted < 0) {
+        logFlags.dom && console.warn('removed:', element.localName,
+            'insert/remove count:', element.__inserted)
+      } else if (element.leftDocumentCallback) {
+        element.leftDocumentCallback();
+      }
+    }
+  }
+}
+
+function inDocument(element) {
+  var p = element;
+  while (p) {
+    if (p == element.ownerDocument) {
+      return true;
+    }
+    p = p.parentNode || p.host;
+  }
+}
+
+function watchShadow(node) {
+  if (node.webkitShadowRoot && !node.webkitShadowRoot.__watched) {
+    logFlags.dom && console.log('watching shadow-root for: ', node.localName);
+    // watch all unwatched roots...
+    var root = node.webkitShadowRoot;
+    while (root) {
+      watchRoot(root);
+      root = root.olderShadowRoot;
+    }
+  }
+}
+
+function watchRoot(root) {
+  if (!root.__watched) {
+    observe(root);
+    root.__watched = true;
+  }
+}
+
+function watchAllShadows(node) {
+  watchShadow(node);
+  forSubtree(node, function(e) {
+    watchShadow(node);
+  });
+}
+
+function filter(inNode) {
+  switch (inNode.localName) {
+    case 'style':
+    case 'script':
+    case 'template':
+    case undefined:
+      return true;
+  }
+}
+
+function handler(mutations) {
+  //
+  if (logFlags.dom) {
+    var mx = mutations[0];
+    if (mx && mx.type === 'childList' && mx.addedNodes) {
+        if (mx.addedNodes) {
+          var d = mx.addedNodes[0];
+          while (d && d !== document && !d.host) {
+            d = d.parentNode;
+          }
+          var u = d && (d.URL || d._URL || (d.host && d.host.localName)) || '';
+          u = u.split('/?').shift().split('/').pop();
+        }
+    }
+    console.group('mutations (%d) [%s]', mutations.length, u || '');
+  }
+  //
+  mutations.forEach(function(mx) {
+    //logFlags.dom && console.group('mutation');
+    if (mx.type === 'childList') {
+      forEach(mx.addedNodes, function(n) {
+        //logFlags.dom && console.log(n.localName);
+        if (filter(n)) {
+          return;
+        }
+        // watch shadow-roots on nodes that have had them attached manually
+        // TODO(sjmiles): remove if createShadowRoot is overridden
+        // TODO(sjmiles): removed as an optimization, manual shadow roots
+        // must be watched explicitly
+        //watchAllShadows(n);
+        // nodes added may need lifecycle management
+        addedNode(n);
+      });
+      // removed nodes may need lifecycle management
+      forEach(mx.removedNodes, function(n) {
+        //logFlags.dom && console.log(n.localName);
+        if (filter(n)) {
+          return;
+        }
+        removedNode(n);
+      });
+    }
+    //logFlags.dom && console.groupEnd();
+  });
+  logFlags.dom && console.groupEnd();
+};
+
+var observer = new MutationObserver(handler);
+
+function takeRecords() {
+  // TODO(sjmiles): ask Raf why we have to call handler ourselves
+  handler(observer.takeRecords());
+}
+
+var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
+
+function observe(inRoot) {
+  observer.observe(inRoot, {childList: true, subtree: true});
+}
+
+function observeDocument(document) {
+  observe(document);
+}
+
+function upgradeDocument(document) {
+  logFlags.dom && console.group('upgradeDocument: ', (document.URL || document._URL || '').split('/').pop());
+  addedNode(document);
+  logFlags.dom && console.groupEnd();
+}
+
+// exports
+
+scope.watchShadow = watchShadow;
+scope.watchAllShadows = watchAllShadows;
+
+scope.upgradeAll = addedNode;
+scope.upgradeSubtree = addedSubtree;
+
+scope.observeDocument = observeDocument;
+scope.upgradeDocument = upgradeDocument;
+
+scope.takeRecords = takeRecords;
+
+})(window.CustomElements);
+
+/**
+ * Implements `document.register`
+ * @module CustomElements
+*/
+
+/**
+ * Polyfilled extensions to the `document` object.
+ * @class Document
+*/
+
+(function(scope) {
+
+// imports
+
+if (!scope) {
+  scope = window.CustomElements = {flags:{}};
+}
+var flags = scope.flags;
+
+// native document.register?
+
+var hasNative = Boolean(document.webkitRegister || document.register);
+var useNative = !flags.register && hasNative;
+
+if (useNative) {
+
+  // normalize
+  document.register = document.register || document.webkitRegister;
+
+  // stub
+  var nop = function() {};
+
+  // exports
+  scope.registry = {};
+  scope.upgradeElement = nop;
+
+  scope.watchShadow = nop;
+  scope.watchAllShadows = nop;
+  scope.upgrade = nop;
+  scope.upgradeAll = nop;
+  scope.upgradeSubtree = nop;
+  scope.observeDocument = nop;
+  scope.upgradeDocument = nop;
+  scope.takeRecords = nop;
+
+} else {
+
+  /**
+   * Registers a custom tag name with the document.
+   *
+   * When a registered element is created, a `readyCallback` method is called
+   * in the scope of the element. The `readyCallback` method can be specified on
+   * either `inOptions.prototype` or `inOptions.lifecycle` with the latter taking
+   * precedence.
+   *
+   * @method register
+   * @param {String} inName The tag name to register. Must include a dash ('-'),
+   *    for example 'x-component'.
+   * @param {Object} inOptions
+   *    @param {String} [inOptions.extends]
+   *      (_off spec_) Tag name of an element to extend (or blank for a new
+   *      element). This parameter is not part of the specification, but instead
+   *      is a hint for the polyfill because the extendee is difficult to infer.
+   *      Remember that the input prototype must chain to the extended element's
+   *      prototype (or HTMLElement.prototype) regardless of the value of
+   *      `extends`.
+   *    @param {Object} inOptions.prototype The prototype to use for the new
+   *      element. The prototype must inherit from HTMLElement.
+   *    @param {Object} [inOptions.lifecycle]
+   *      Callbacks that fire at important phases in the life of the custom
+   *      element.
+   *
+   * @example
+   *      FancyButton = document.register("fancy-button", {
+   *        extends: 'button',
+   *        prototype: Object.create(HTMLButtonElement.prototype, {
+   *          readyCallback: {
+   *            value: function() {
+   *              console.log("a fancy-button was created",
+   *            }
+   *          }
+   *        })
+   *      });
+   * @return {Function} Constructor for the newly registered type.
+   */
+  function register(inName, inOptions) {
+    //console.warn('document.register("' + inName + '", ', inOptions, ')');
+    // construct a defintion out of options
+    // TODO(sjmiles): probably should clone inOptions instead of mutating it
+    var definition = inOptions || {};
+    if (!inName) {
+      // TODO(sjmiles): replace with more appropriate error (EricB can probably
+      // offer guidance)
+      throw new Error('Name argument must not be empty');
+    }
+    // record name
+    definition.name = inName;
+    // must have a prototype, default to an extension of HTMLElement
+    // TODO(sjmiles): probably should throw if no prototype, check spec
+    if (!definition.prototype) {
+      // TODO(sjmiles): replace with more appropriate error (EricB can probably
+      // offer guidance)
+      throw new Error('Options missing required prototype property');
+    }
+    // ensure a lifecycle object so we don't have to null test it
+    definition.lifecycle = definition.lifecycle || {};
+    // build a list of ancestral custom elements (for native base detection)
+    // TODO(sjmiles): we used to need to store this, but current code only
+    // uses it in 'resolveTagName': it should probably be inlined
+    definition.ancestry = ancestry(definition.extends);
+    // extensions of native specializations of HTMLElement require localName
+    // to remain native, and use secondary 'is' specifier for extension type
+    resolveTagName(definition);
+    // some platforms require modifications to the user-supplied prototype
+    // chain
+    resolvePrototypeChain(definition);
+    // overrides to implement attributeChanged callback
+    overrideAttributeApi(definition.prototype);
+    // 7.1.5: Register the DEFINITION with DOCUMENT
+    registerDefinition(inName, definition);
+    // 7.1.7. Run custom element constructor generation algorithm with PROTOTYPE
+    // 7.1.8. Return the output of the previous step.
+    definition.ctor = generateConstructor(definition);
+    definition.ctor.prototype = definition.prototype;
+    // force our .constructor to be our actual constructor
+    definition.prototype.constructor = definition.ctor;
+    // if initial parsing is complete
+    if (scope.ready) {
+      // upgrade any pre-existing nodes of this type
+      scope.upgradeAll(document);
+    }
+    return definition.ctor;
+  }
+
+  function ancestry(inExtends) {
+    var extendee = registry[inExtends];
+    if (extendee) {
+      return ancestry(extendee.extends).concat([extendee]);
+    }
+    return [];
+  }
+
+  function resolveTagName(inDefinition) {
+    // if we are explicitly extending something, that thing is our
+    // baseTag, unless it represents a custom component
+    var baseTag = inDefinition.extends;
+    // if our ancestry includes custom components, we only have a
+    // baseTag if one of them does
+    for (var i=0, a; (a=inDefinition.ancestry[i]); i++) {
+      baseTag = a.is && a.tag;
+    }
+    // our tag is our baseTag, if it exists, and otherwise just our name
+    inDefinition.tag = baseTag || inDefinition.name;
+    if (baseTag) {
+      // if there is a base tag, use secondary 'is' specifier
+      inDefinition.is = inDefinition.name;
+    }
+  }
+
+  function resolvePrototypeChain(inDefinition) {
+    // if we don't support __proto__ we need to locate the native level
+    // prototype for precise mixing in
+    if (!Object.__proto__) {
+      // default prototype
+      var native = HTMLElement.prototype;
+      // work out prototype when using type-extension
+      if (inDefinition.is) {
+        var inst = document.createElement(inDefinition.tag);
+        native = Object.getPrototypeOf(inst);
+      }
+      // ensure __proto__ reference is installed at each point on the prototype
+      // chain.
+      // NOTE: On platforms without __proto__, a mixin strategy is used instead
+      // of prototype swizzling. In this case, this generated __proto__ provides
+      // limited support for prototype traversal.
+      var proto = inDefinition.prototype, ancestor;
+      while (proto && (proto !== native)) {
+        var ancestor = Object.getPrototypeOf(proto);
+        proto.__proto__ = ancestor;
+        proto = ancestor;
+      }
+    }
+    // cache this in case of mixin
+    inDefinition.native = native;
+  }
+
+  // SECTION 4
+
+  function instantiate(inDefinition) {
+    // 4.a.1. Create a new object that implements PROTOTYPE
+    // 4.a.2. Let ELEMENT by this new object
+    //
+    // the custom element instantiation algorithm must also ensure that the
+    // output is a valid DOM element with the proper wrapper in place.
+    //
+    return upgrade(domCreateElement(inDefinition.tag), inDefinition);
+  }
+
+  function upgrade(inElement, inDefinition) {
+    // some definitions specify an 'is' attribute
+    if (inDefinition.is) {
+      inElement.setAttribute('is', inDefinition.is);
+    }
+    // make 'element' implement inDefinition.prototype
+    implement(inElement, inDefinition);
+    // flag as upgraded
+    inElement.__upgraded__ = true;
+    // there should never be a shadow root on inElement at this point
+    // we require child nodes be upgraded before `created`
+    scope.upgradeSubtree(inElement);
+    // lifecycle management
+    created(inElement);
+    // OUTPUT
+    return inElement;
+  }
+
+  function implement(inElement, inDefinition) {
+    // prototype swizzling is best
+    if (Object.__proto__) {
+      inElement.__proto__ = inDefinition.prototype;
+    } else {
+      // where above we can re-acquire inPrototype via
+      // getPrototypeOf(Element), we cannot do so when
+      // we use mixin, so we install a magic reference
+      customMixin(inElement, inDefinition.prototype, inDefinition.native);
+      inElement.__proto__ = inDefinition.prototype;
+    }
+  }
+
+  function customMixin(inTarget, inSrc, inNative) {
+    // TODO(sjmiles): 'used' allows us to only copy the 'youngest' version of
+    // any property. This set should be precalculated. We also need to
+    // consider this for supporting 'super'.
+    var used = {};
+    // start with inSrc
+    var p = inSrc;
+    // sometimes the default is HTMLUnknownElement.prototype instead of
+    // HTMLElement.prototype, so we add a test
+    // the idea is to avoid mixing in native prototypes, so adding
+    // the second test is WLOG
+    while (p !== inNative && p !== HTMLUnknownElement.prototype) {
+      var keys = Object.getOwnPropertyNames(p);
+      for (var i=0, k; k=keys[i]; i++) {
+        if (!used[k]) {
+          Object.defineProperty(inTarget, k,
+              Object.getOwnPropertyDescriptor(p, k));
+          used[k] = 1;
+        }
+      }
+      p = Object.getPrototypeOf(p);
+    }
+  }
+
+  function created(inElement) {
+    // invoke createdCallback
+    if (inElement.createdCallback) {
+      inElement.createdCallback();
+    }
+  }
+
+  // attribute watching
+
+  function overrideAttributeApi(prototype) {
+    // overrides to implement callbacks
+    // TODO(sjmiles): should support access via .attributes NamedNodeMap
+    // TODO(sjmiles): preserves user defined overrides, if any
+    var setAttribute = prototype.setAttribute;
+    prototype.setAttribute = function(name, value) {
+      changeAttribute.call(this, name, value, setAttribute);
+    }
+    var removeAttribute = prototype.removeAttribute;
+    prototype.removeAttribute = function(name, value) {
+      changeAttribute.call(this, name, value, removeAttribute);
+    }
+  }
+
+  function changeAttribute(name, value, operation) {
+    var oldValue = this.getAttribute(name);
+    operation.apply(this, arguments);
+    if (this.attributeChangedCallback
+        && (this.getAttribute(name) !== oldValue)) {
+      this.attributeChangedCallback(name, oldValue);
+    }
+  }
+
+  // element registry (maps tag names to definitions)
+
+  var registry = {};
+
+  function registerDefinition(inName, inDefinition) {
+    if (registry[inName]) {
+      throw new Error('Cannot register a tag more than once');
+    }
+    registry[inName] = inDefinition;
+  }
+
+  function generateConstructor(inDefinition) {
+    return function() {
+      return instantiate(inDefinition);
+    };
+  }
+
+  function createElement(tag, typeExtension) {
+    // TODO(sjmiles): ignore 'tag' when using 'typeExtension', we could
+    // error check it, or perhaps there should only ever be one argument
+    var definition = registry[typeExtension || tag];
+    if (definition) {
+      return new definition.ctor();
+    }
+    return domCreateElement(tag);
+  }
+
+  function upgradeElement(inElement) {
+    if (!inElement.__upgraded__ && (inElement.nodeType === Node.ELEMENT_NODE)) {
+      var type = inElement.getAttribute('is') || inElement.localName;
+      var definition = registry[type];
+      return definition && upgrade(inElement, definition);
+    }
+  }
+
+  function cloneNode(deep) {
+    // call original clone
+    var n = domCloneNode.call(this, deep);
+    // upgrade the element and subtree
+    scope.upgradeAll(n);
+    // return the clone
+    return n;
+  }
+  // capture native createElement before we override it
+
+  var domCreateElement = document.createElement.bind(document);
+
+  // capture native cloneNode before we override it
+
+  var domCloneNode = Node.prototype.cloneNode;
+
+  // exports
+
+  document.register = register;
+  document.createElement = createElement; // override
+  Node.prototype.cloneNode = cloneNode; // override
+
+  scope.registry = registry;
+
+  /**
+   * Upgrade an element to a custom element. Upgrading an element
+   * causes the custom prototype to be applied, an `is` attribute
+   * to be attached (as needed), and invocation of the `readyCallback`.
+   * `upgrade` does nothing if the element is already upgraded, or
+   * if it matches no registered custom tag name.
+   *
+   * @method ugprade
+   * @param {Element} inElement The element to upgrade.
+   * @return {Element} The upgraded element.
+   */
+  scope.upgrade = upgradeElement;
+}
+
+scope.hasNative = hasNative;
+scope.useNative = useNative;
+
+})(window.CustomElements);
+
+(function() {
+
+// import
+
+var IMPORT_LINK_TYPE = window.HTMLImports ? HTMLImports.IMPORT_LINK_TYPE : 'none';
+
+// highlander object for parsing a document tree
+
+var parser = {
+  selectors: [
+    'link[rel=' + IMPORT_LINK_TYPE + ']'
+  ],
+  map: {
+    link: 'parseLink'
+  },
+  parse: function(inDocument) {
+    if (!inDocument.__parsed) {
+      // only parse once
+      inDocument.__parsed = true;
+      // all parsable elements in inDocument (depth-first pre-order traversal)
+      var elts = inDocument.querySelectorAll(parser.selectors);
+      // for each parsable node type, call the mapped parsing method
+      forEach(elts, function(e) {
+        parser[parser.map[e.localName]](e);
+      });
+      // upgrade all upgradeable static elements, anything dynamically
+      // created should be caught by observer
+      CustomElements.upgradeDocument(inDocument);
+      // observe document for dom changes
+      CustomElements.observeDocument(inDocument);
+    }
+  },
+  parseLink: function(linkElt) {
+    // imports
+    if (isDocumentLink(linkElt)) {
+      this.parseImport(linkElt);
+    }
+  },
+  parseImport: function(linkElt) {
+    if (linkElt.content) {
+      parser.parse(linkElt.content);
+    }
+  }
+};
+
+function isDocumentLink(inElt) {
+  return (inElt.localName === 'link'
+      && inElt.getAttribute('rel') === IMPORT_LINK_TYPE);
+}
+
+var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach);
+
+// exports
+
+CustomElements.parser = parser;
+
+})();
+(function(){
+
+// bootstrap parsing
+
+function bootstrap() {
+  // go async so call stack can unwind
+  setTimeout(function() {
+    // parse document
+    CustomElements.parser.parse(document);
+    // one more pass before register is 'live'
+    CustomElements.upgradeDocument(document);
+    // set internal 'ready' flag, now document.register will trigger
+    // synchronous upgrades
+    CustomElements.ready = true;
+    // capture blunt profiling data
+    CustomElements.readyTime = Date.now();
+    if (window.HTMLImports) {
+      CustomElements.elapsed = CustomElements.readyTime - HTMLImports.readyTime;
+    }
+    // notify the system that we are bootstrapped
+    document.body.dispatchEvent(
+      new CustomEvent('WebComponentsReady', {bubbles: true})
+    );
+  }, 0);
+}
+
+// CustomEvent shim for IE
+if (typeof window.CustomEvent !== 'function') {
+  window.CustomEvent = function(inType) {
+     var e = document.createEvent('HTMLEvents');
+     e.initEvent(inType, true, true);
+     return e;
+  };
+}
+
+if (document.readyState === 'complete') {
+  bootstrap();
+} else {
+  var loadEvent = window.HTMLImports ? 'HTMLImportsLoaded' : 'DOMContentLoaded';
+  window.addEventListener(loadEvent, bootstrap);
+}
+
+})();
diff --git a/pkg/custom_element/lib/custom-elements.min.js b/pkg/custom_element/lib/custom-elements.min.js
new file mode 100644
index 0000000..f0c3e81
--- /dev/null
+++ b/pkg/custom_element/lib/custom-elements.min.js
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The Polymer Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//    * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//    * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//    * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+window.CustomElements={flags:{}};var SideTable;if("undefined"!=typeof WeakMap&&navigator.userAgent.indexOf("Firefox/")<0?SideTable=WeakMap:function(){var a=Object.defineProperty,b=Object.hasOwnProperty,c=(new Date).getTime()%1e9;SideTable=function(){this.name="__st"+(1e9*Math.random()>>>0)+(c++ +"__")},SideTable.prototype={set:function(b,c){a(b,this.name,{value:c,writable:!0})},get:function(a){return b.call(a,this.name)?a[this.name]:void 0},"delete":function(a){this.set(a,void 0)}}}(),function(a){function b(a){u.push(a),t||(t=!0,q(d))}function c(a){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(a)||a}function d(){t=!1;var a=u;u=[],a.sort(function(a,b){return a.uid_-b.uid_});var b=!1;a.forEach(function(a){var c=a.takeRecords();e(a),c.length&&(a.callback_(c,a),b=!0)}),b&&d()}function e(a){a.nodes_.forEach(function(b){var c=p.get(b);c&&c.forEach(function(b){b.observer===a&&b.removeTransientObservers()})})}function f(a,b){for(var c=a;c;c=c.parentNode){var d=p.get(c);if(d)for(var e=0;e<d.length;e++){var f=d[e],g=f.options;if(c===a||g.subtree){var h=b(g);h&&f.enqueue(h)}}}}function g(a){this.callback_=a,this.nodes_=[],this.records_=[],this.uid_=++v}function h(a,b){this.type=a,this.target=b,this.addedNodes=[],this.removedNodes=[],this.previousSibling=null,this.nextSibling=null,this.attributeName=null,this.attributeNamespace=null,this.oldValue=null}function i(a){var b=new h(a.type,a.target);return b.addedNodes=a.addedNodes.slice(),b.removedNodes=a.removedNodes.slice(),b.previousSibling=a.previousSibling,b.nextSibling=a.nextSibling,b.attributeName=a.attributeName,b.attributeNamespace=a.attributeNamespace,b.oldValue=a.oldValue,b}function j(a,b){return w=new h(a,b)}function k(a){return x?x:(x=i(w),x.oldValue=a,x)}function l(){w=x=void 0}function m(a){return a===x||a===w}function n(a,b){return a===b?a:x&&m(a)?x:null}function o(a,b,c){this.observer=a,this.target=b,this.options=c,this.transientObservedNodes=[]}var p=new SideTable,q=window.msSetImmediate;if(!q){var r=[],s=String(Math.random());window.addEventListener("message",function(a){if(a.data===s){var b=r;r=[],b.forEach(function(a){a()})}}),q=function(a){r.push(a),window.postMessage(s,"*")}}var t=!1,u=[],v=0;g.prototype={observe:function(a,b){if(a=c(a),!b.childList&&!b.attributes&&!b.characterData||b.attributeOldValue&&!b.attributes||b.attributeFilter&&b.attributeFilter.length&&!b.attributes||b.characterDataOldValue&&!b.characterData)throw new SyntaxError;var d=p.get(a);d||p.set(a,d=[]);for(var e,f=0;f<d.length;f++)if(d[f].observer===this){e=d[f],e.removeListeners(),e.options=b;break}e||(e=new o(this,a,b),d.push(e),this.nodes_.push(a)),e.addListeners()},disconnect:function(){this.nodes_.forEach(function(a){for(var b=p.get(a),c=0;c<b.length;c++){var d=b[c];if(d.observer===this){d.removeListeners(),b.splice(c,1);break}}},this),this.records_=[]},takeRecords:function(){var a=this.records_;return this.records_=[],a}};var w,x;o.prototype={enqueue:function(a){var c=this.observer.records_,d=c.length;if(c.length>0){var e=c[d-1],f=n(e,a);if(f)return c[d-1]=f,void 0}else b(this.observer);c[d]=a},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(a){var b=this.options;b.attributes&&a.addEventListener("DOMAttrModified",this,!0),b.characterData&&a.addEventListener("DOMCharacterDataModified",this,!0),b.childList&&a.addEventListener("DOMNodeInserted",this,!0),(b.childList||b.subtree)&&a.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(a){var b=this.options;b.attributes&&a.removeEventListener("DOMAttrModified",this,!0),b.characterData&&a.removeEventListener("DOMCharacterDataModified",this,!0),b.childList&&a.removeEventListener("DOMNodeInserted",this,!0),(b.childList||b.subtree)&&a.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(a){if(a!==this.target){this.addListeners_(a),this.transientObservedNodes.push(a);var b=p.get(a);b||p.set(a,b=[]),b.push(this)}},removeTransientObservers:function(){var a=this.transientObservedNodes;this.transientObservedNodes=[],a.forEach(function(a){this.removeListeners_(a);for(var b=p.get(a),c=0;c<b.length;c++)if(b[c]===this){b.splice(c,1);break}},this)},handleEvent:function(a){switch(a.stopImmediatePropagation(),a.type){case"DOMAttrModified":var b=a.attrName,c=a.relatedNode.namespaceURI,d=a.target,e=new j("attributes",d);e.attributeName=b,e.attributeNamespace=c;var g=a.attrChange===MutationEvent.ADDITION?null:a.prevValue;f(d,function(a){return!a.attributes||a.attributeFilter&&a.attributeFilter.length&&-1===a.attributeFilter.indexOf(b)&&-1===a.attributeFilter.indexOf(c)?void 0:a.attributeOldValue?k(g):e});break;case"DOMCharacterDataModified":var d=a.target,e=j("characterData",d),g=a.prevValue;f(d,function(a){return a.characterData?a.characterDataOldValue?k(g):e:void 0});break;case"DOMNodeRemoved":this.addTransientObserver(a.target);case"DOMNodeInserted":var h,i,d=a.relatedNode,m=a.target;"DOMNodeInserted"===a.type?(h=[m],i=[]):(h=[],i=[m]);var n=m.previousSibling,o=m.nextSibling,e=j("childList",d);e.addedNodes=h,e.removedNodes=i,e.previousSibling=n,e.nextSibling=o,f(d,function(a){return a.childList?e:void 0})}l()}},a.JsMutationObserver=g}(this),!window.MutationObserver&&(window.MutationObserver=window.WebKitMutationObserver||window.JsMutationObserver,!MutationObserver))throw new Error("no mutation observer support");!function(a){function b(a,c,d){var e=a.firstElementChild;if(!e)for(e=a.firstChild;e&&e.nodeType!==Node.ELEMENT_NODE;)e=e.nextSibling;for(;e;)c(e,d)!==!0&&b(e,c,d),e=e.nextElementSibling;return null}function c(a,b){for(var c=a.webkitShadowRoot;c;)d(c,b),c=c.olderShadowRoot}function d(a,d){b(a,function(a){return d(a)?!0:(c(a,d),void 0)}),c(a,d)}function e(a){return h(a)?(i(a),!0):(j(a),void 0)}function f(a){d(a,function(a){return e(a)?!0:void 0})}function g(a){return e(a)||f(a)}function h(b){if(!b.__upgraded__&&b.nodeType===Node.ELEMENT_NODE){var c=b.getAttribute("is")||b.localName,d=a.registry[c];if(d)return logFlags.dom&&console.group("upgrade:",b.localName),a.upgrade(b),logFlags.dom&&console.groupEnd(),!0}}function i(a){j(a),m(a)&&d(a,function(a){j(a)})}function j(a){(a.enteredDocumentCallback||a.__upgraded__&&logFlags.dom)&&(logFlags.dom&&console.group("inserted:",a.localName),m(a)&&(a.__inserted=(a.__inserted||0)+1,a.__inserted<1&&(a.__inserted=1),a.__inserted>1?logFlags.dom&&console.warn("inserted:",a.localName,"insert/remove count:",a.__inserted):a.enteredDocumentCallback&&(logFlags.dom&&console.log("inserted:",a.localName),a.enteredDocumentCallback())),logFlags.dom&&console.groupEnd())}function k(a){l(a),d(a,function(a){l(a)})}function l(a){(a.leftDocumentCallback||a.__upgraded__&&logFlags.dom)&&(logFlags.dom&&console.log("removed:",a.localName),m(a)||(a.__inserted=(a.__inserted||0)-1,a.__inserted>0&&(a.__inserted=0),a.__inserted<0?logFlags.dom&&console.warn("removed:",a.localName,"insert/remove count:",a.__inserted):a.leftDocumentCallback&&a.leftDocumentCallback()))}function m(a){for(var b=a;b;){if(b==a.ownerDocument)return!0;b=b.parentNode||b.host}}function n(a){if(a.webkitShadowRoot&&!a.webkitShadowRoot.__watched){logFlags.dom&&console.log("watching shadow-root for: ",a.localName);for(var b=a.webkitShadowRoot;b;)o(b),b=b.olderShadowRoot}}function o(a){a.__watched||(t(a),a.__watched=!0)}function p(a){n(a),d(a,function(){n(a)})}function q(a){switch(a.localName){case"style":case"script":case"template":case void 0:return!0}}function r(a){if(logFlags.dom){var b=a[0];if(b&&"childList"===b.type&&b.addedNodes&&b.addedNodes){for(var c=b.addedNodes[0];c&&c!==document&&!c.host;)c=c.parentNode;var d=c&&(c.URL||c._URL||c.host&&c.host.localName)||"";d=d.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",a.length,d||"")}a.forEach(function(a){"childList"===a.type&&(x(a.addedNodes,function(a){q(a)||g(a)}),x(a.removedNodes,function(a){q(a)||k(a)}))}),logFlags.dom&&console.groupEnd()}function s(){r(w.takeRecords())}function t(a){w.observe(a,{childList:!0,subtree:!0})}function u(a){t(a)}function v(a){logFlags.dom&&console.group("upgradeDocument: ",(a.URL||a._URL||"").split("/").pop()),g(a),logFlags.dom&&console.groupEnd()}var w=new MutationObserver(r),x=Array.prototype.forEach.call.bind(Array.prototype.forEach);a.watchShadow=n,a.watchAllShadows=p,a.upgradeAll=g,a.upgradeSubtree=f,a.observeDocument=u,a.upgradeDocument=v,a.takeRecords=s}(window.CustomElements),function(a){function b(b,f){var g=f||{};if(!b)throw new Error("Name argument must not be empty");if(g.name=b,!g.prototype)throw new Error("Options missing required prototype property");return g.lifecycle=g.lifecycle||{},g.ancestry=c(g.extends),d(g),e(g),k(g.prototype),m(b,g),g.ctor=n(g),g.ctor.prototype=g.prototype,g.prototype.constructor=g.ctor,a.ready&&a.upgradeAll(document),g.ctor}function c(a){var b=v[a];return b?c(b.extends).concat([b]):[]}function d(a){for(var b,c=a.extends,d=0;b=a.ancestry[d];d++)c=b.is&&b.tag;a.tag=c||a.name,c&&(a.is=a.name)}function e(a){if(!Object.__proto__){var b=HTMLElement.prototype;if(a.is){var c=document.createElement(a.tag);b=Object.getPrototypeOf(c)}for(var d,e=a.prototype;e&&e!==b;){var d=Object.getPrototypeOf(e);e.__proto__=d,e=d}}a.native=b}function f(a){return g(w(a.tag),a)}function g(b,c){return c.is&&b.setAttribute("is",c.is),h(b,c),b.__upgraded__=!0,a.upgradeSubtree(b),j(b),b}function h(a,b){Object.__proto__?a.__proto__=b.prototype:(i(a,b.prototype,b.native),a.__proto__=b.prototype)}function i(a,b,c){for(var d={},e=b;e!==c&&e!==HTMLUnknownElement.prototype;){for(var f,g=Object.getOwnPropertyNames(e),h=0;f=g[h];h++)d[f]||(Object.defineProperty(a,f,Object.getOwnPropertyDescriptor(e,f)),d[f]=1);e=Object.getPrototypeOf(e)}}function j(a){a.createdCallback&&a.createdCallback()}function k(a){var b=a.setAttribute;a.setAttribute=function(a,c){l.call(this,a,c,b)};var c=a.removeAttribute;a.removeAttribute=function(a,b){l.call(this,a,b,c)}}function l(a,b,c){var d=this.getAttribute(a);c.apply(this,arguments),this.attributeChangedCallback&&this.getAttribute(a)!==d&&this.attributeChangedCallback(a,d)}function m(a,b){if(v[a])throw new Error("Cannot register a tag more than once");v[a]=b}function n(a){return function(){return f(a)}}function o(a,b){var c=v[b||a];return c?new c.ctor:w(a)}function p(a){if(!a.__upgraded__&&a.nodeType===Node.ELEMENT_NODE){var b=a.getAttribute("is")||a.localName,c=v[b];return c&&g(a,c)}}function q(b){var c=x.call(this,b);return a.upgradeAll(c),c}a||(a=window.CustomElements={flags:{}});var r=a.flags,s=Boolean(document.webkitRegister||document.register),t=!r.register&&s;if(t){document.register=document.register||document.webkitRegister;var u=function(){};a.registry={},a.upgradeElement=u,a.watchShadow=u,a.watchAllShadows=u,a.upgrade=u,a.upgradeAll=u,a.upgradeSubtree=u,a.observeDocument=u,a.upgradeDocument=u,a.takeRecords=u}else{var v={},w=document.createElement.bind(document),x=Node.prototype.cloneNode;document.register=b,document.createElement=o,Node.prototype.cloneNode=q,a.registry=v,a.upgrade=p}a.hasNative=s,a.useNative=t}(window.CustomElements),function(){function a(a){return"link"===a.localName&&a.getAttribute("rel")===b}var b=window.HTMLImports?HTMLImports.IMPORT_LINK_TYPE:"none",c={selectors:["link[rel="+b+"]"],map:{link:"parseLink"},parse:function(a){if(!a.__parsed){a.__parsed=!0;var b=a.querySelectorAll(c.selectors);d(b,function(a){c[c.map[a.localName]](a)}),CustomElements.upgradeDocument(a),CustomElements.observeDocument(a)}},parseLink:function(b){a(b)&&this.parseImport(b)},parseImport:function(a){a.content&&c.parse(a.content)}},d=Array.prototype.forEach.call.bind(Array.prototype.forEach);CustomElements.parser=c}(),function(){function a(){setTimeout(function(){CustomElements.parser.parse(document),CustomElements.upgradeDocument(document),CustomElements.ready=!0,CustomElements.readyTime=Date.now(),window.HTMLImports&&(CustomElements.elapsed=CustomElements.readyTime-HTMLImports.readyTime),document.body.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))},0)}if("function"!=typeof window.CustomEvent&&(window.CustomEvent=function(a){var b=document.createEvent("HTMLEvents");return b.initEvent(a,!0,!0),b}),"complete"===document.readyState)a();else{var b=window.HTMLImports?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(b,a)}}();
diff --git a/pkg/custom_element/lib/custom_element.dart b/pkg/custom_element/lib/custom_element.dart
index 16978ae..4121dbf 100644
--- a/pkg/custom_element/lib/custom_element.dart
+++ b/pkg/custom_element/lib/custom_element.dart
@@ -313,6 +313,8 @@
     host.innerHtml = v;
   }
 
+  InputMethodContext get inputMethodContext => host.inputMethodContext;
+
   bool get isContentEditable => host.isContentEditable;
 
   String get lang => host.lang;
@@ -338,7 +340,8 @@
 
   void click() { host.click(); }
 
-  InputMethodContext getInputContext() => host.getInputContext();
+  List<Node> getDestinationInsertionPoints() =>
+    host.getDestinationInsertionPoints();
 
   Element insertAdjacentElement(String where, Element element) =>
     host.insertAdjacentElement(where, element);
diff --git a/pkg/docgen/README.md b/pkg/docgen/README.md
index 24ba58a..66d0a46 100644
--- a/pkg/docgen/README.md
+++ b/pkg/docgen/README.md
@@ -32,19 +32,21 @@
 
 - `-h`, `--help` Prints help and usage information.
 - `-v`, `--verbose` Output more logging information.
-- `-j`, `--[no-]json` Outputs to JSON. Files are outputted to YAML by default.
+- `-j`, `--[no-]json` Outputs to JSON. Files are outputted to YAML by default. 
+If `--append` is used, it takes the file-format of the previous run stated in 
+library_list.json ignoring the flag.
 - `--include-private` Flag to include private declarations.
 - `--include-sdk` Flag to parse SDK Library files imported.
 - `--parse-sdk` Parses the SDK libraries only. (Ignores the path passed in.)
 - `--package-root` Sets the package root of the library being analyzed.
-- `--append` Appends to the docs folder, library_list.txt, and index.txt.
+- `--append` Appends to the docs folder, library_list.json, and index.txt.
 - `--introduction` Adds the provided markdown text file as the introduction
 for the outputted documentation.
 
 
 ###### Output Directory
 Documented libraries will be located at bin/docs in either YAML or JSON format 
-depending on options specified. There will also be a library_list.txt, 
+depending on options specified. There will also be a library_list.json, 
 containing a list of all the libraries inside the docs folder. 
 
 To get more information on how to use the outputted documentation with 
diff --git a/pkg/docgen/bin/docgen.dart b/pkg/docgen/bin/docgen.dart
index ca4f780..293f3cf 100644
--- a/pkg/docgen/bin/docgen.dart
+++ b/pkg/docgen/bin/docgen.dart
@@ -49,7 +49,9 @@
         if (verbose) Logger.root.level = Level.FINEST;
       });
   parser.addFlag('json', abbr: 'j', 
-      help: 'Outputs to JSON. Files are outputted to YAML by default.', 
+      help: 'Outputs to JSON. Files are outputted to YAML by default. ' 
+        'If --append is used, it takes the file-format of the previous '
+        'run stated in library_list.json ignoring the flag.', 
       negatable: true);
   parser.addFlag('include-private', 
       help: 'Flag to include private declarations.', negatable: false);
@@ -61,7 +63,7 @@
   parser.addOption('package-root', 
       help: 'Sets the package root of the library being analyzed.');
   parser.addFlag('append', 
-      help: 'Append to the docs folder, library_list.txt and index.txt', 
+      help: 'Append to the docs folder, library_list.json and index.txt', 
       defaultsTo: false, negatable: false);
   parser.addOption('introduction', 
       help: 'Adds the provided markdown text file as the introduction' 
diff --git a/pkg/docgen/lib/dart2yaml.dart b/pkg/docgen/lib/dart2yaml.dart
index 57ca7fe..343b61f 100644
--- a/pkg/docgen/lib/dart2yaml.dart
+++ b/pkg/docgen/lib/dart2yaml.dart
@@ -7,6 +7,8 @@
  */
 library dart2yaml;
 
+import 'dart:collection';
+
 /**
  * Gets a String representing the input Map in YAML format.
  */
@@ -26,10 +28,11 @@
  */
 void _addLevel(StringBuffer yaml, Map documentData, int level,
                {bool isList: false}) {
-  // Since the ordering of the keys could be non-deterministic, the keys
-  // are sorted to ensure consistency in the output.
+  // The order of the keys could be nondeterministic, but it is insufficient 
+  // to just sort the keys no matter what, as their order could be significant
+  // (i.e. parameters to a method). The order of the keys should be enforced
+  // by the caller of this function.
   var keys = documentData.keys.toList();
-  keys.sort();
   keys.forEach((key) {
     _calcSpaces(level, yaml);
     // Only the first entry of the map should be preceeded with a '-' since
diff --git a/pkg/docgen/lib/docgen.dart b/pkg/docgen/lib/docgen.dart
index a51ab86..6faca3f5 100644
--- a/pkg/docgen/lib/docgen.dart
+++ b/pkg/docgen/lib/docgen.dart
@@ -244,26 +244,44 @@
   if (parseSdk) entityMap['dart.core.Object'].subclasses.clear();
 
   var filteredEntities = entityMap.values.where(_isVisible);
+  
+  // Outputs a JSON file with all libraries and their preview comments. 
+  // This will help the viewer know what libraries are available to read in.
+  var libraryMap;
+  if (append) {
+    var docsDir = listDir('docs');
+    if (!docsDir.contains('docs/library_list.json')) {
+      throw new StateError('No library_list.json');
+    }
+    libraryMap = parse(new File('docs/library_list.json').readAsStringSync());
+    libraryMap['libraries'].addAll(filteredEntities
+        .where((e) => e is Library)
+        .map((e) => e.previewMap));
+    if (introduction.isNotEmpty) {
+      var intro = libraryMap['introduction'];
+      if (intro.isNotEmpty) intro += '<br/><br/>';
+      intro += markdown.markdownToHtml(
+          new File(introduction).readAsStringSync(), 
+              linkResolver: linkResolver, inlineSyntaxes: markdownSyntaxes);
+      libraryMap['introduction'] = intro;
+    }
+    outputToYaml = libraryMap['filetype'] == 'yaml';
+  } else {
+    libraryMap = {
+      'libraries' : filteredEntities.where((e) => 
+          e is Library).map((e) => e.previewMap).toList(),
+      'introduction' : introduction == '' ? 
+          '' : markdown.markdownToHtml(new File(introduction)
+              .readAsStringSync(), linkResolver: linkResolver, 
+                  inlineSyntaxes: markdownSyntaxes), 
+      'filetype' : outputToYaml ? 'yaml' : 'json'
+    };
+  }
+  _writeToFile(stringify(libraryMap), 'library_list.json');
   // Output libraries and classes to file after all information is generated.
   filteredEntities.where((e) => e is Class || e is Library).forEach((output) {
     _writeIndexableToFile(output, outputToYaml);
   });
-  // Outputs a YAML or JSON file with all libraries and their preview comments 
-  // after creating all libraries. This will help the viewer know what 
-  // libraries are available to read in.
-  var libraryMap = {
-    'libraries' : filteredEntities.where((e) => 
-        e is Library).map((e) => e.previewMap).toList(),
-    'introduction' : introduction == '' ? 
-        '' : markdown.markdownToHtml(new File(introduction).readAsStringSync(),
-            linkResolver: linkResolver, inlineSyntaxes: markdownSyntaxes)
-  };
-  if (outputToYaml) {
-    _writeToFile(getYamlString(libraryMap), 'library_list.yaml', 
-        append: append);
-  } else {
-    _writeToFile(stringify(libraryMap), 'library_list.json', append: append);
-  }
   // Outputs all the qualified names documented with their type.
   // This will help generate search results.
   _writeToFile(filteredEntities.map((e) => 
diff --git a/pkg/intl/lib/generate_localized.dart b/pkg/intl/lib/generate_localized.dart
index 9516372..6477023 100644
--- a/pkg/intl/lib/generate_localized.dart
+++ b/pkg/intl/lib/generate_localized.dart
@@ -215,7 +215,8 @@
 Future initializeMessages(String localeName) {
   initializeInternalMessageLookup(() => new CompositeMessageLookup());
   messageLookup.addLocale(localeName, _findGeneratedMessagesFor);
-  return deferredLibraries[localeName].load();
+  var lib = deferredLibraries[localeName];                                                           
+  return lib == null ? new Future.value(false) : lib.load();     
 }
 
 MessageLookupByLibrary _findGeneratedMessagesFor(locale) {
diff --git a/pkg/intl/test/message_extraction/sample_with_messages.dart b/pkg/intl/test/message_extraction/sample_with_messages.dart
index 292273c..753f39c 100644
--- a/pkg/intl/test/message_extraction/sample_with_messages.dart
+++ b/pkg/intl/test/message_extraction/sample_with_messages.dart
@@ -178,6 +178,8 @@
   var fr = new Intl("fr");
   var english = new Intl("en_US");
   var de = new Intl("de_DE");
+  // Throw in an initialize of a null locale to make sure it doesn't throw.
+  initializeMessages(null);
   initializeMessages(fr.locale).then((_) => printStuff(fr));
   initializeMessages(de.locale).then((_) => printStuff(de));
   printStuff(english);
diff --git a/pkg/meta/lib/meta.dart b/pkg/meta/lib/meta.dart
index c86e377..9960d59 100644
--- a/pkg/meta/lib/meta.dart
+++ b/pkg/meta/lib/meta.dart
@@ -46,3 +46,18 @@
 class _Override {
   const _Override();
 }
+
+/**
+ * An annotation used to mark a class that should be considered to implement
+ * every possible getter, setter and method. Tools can use this annotation to
+ * suppress warnings when there is no explicit implementation of a referenced
+ * member. Tools should provide a hint if this annotation is applied to a class
+ * that does not implement or inherit an implementation of the method
+ * [:noSuchMethod:] (other than the implementation in [Object]). Note that
+ * classes are not affected by the use of this annotation on a supertype.
+ */
+const proxy = const _Proxy();
+
+class _Proxy {
+  const _Proxy();
+}
\ No newline at end of file
diff --git a/pkg/pkg.status b/pkg/pkg.status
index 39486e0..f41776a 100644
--- a/pkg/pkg.status
+++ b/pkg/pkg.status
@@ -8,9 +8,12 @@
 */packages/*/*: Skip
 */*/packages/*/*: Skip
 */*/*/packages/*/*: Skip
+*/*/*/*/packages/*/*: Skip
 
 # Skip non-test files ending with "_test".
 scheduled_test/lib/*: Skip
+polymer/lib/*: Skip
+polymer/example/*: Skip
 
 scheduled_test/test/scheduled_server_test: Pass, Fail, Slow, Crash # Issue 9231, 9582
 scheduled_test/test/scheduled_process_test: Pass, Slow # Issue 9231
@@ -20,7 +23,6 @@
 
 [ $compiler == dart2js && $runtime == d8 ]
 unmodifiable_collection/test/unmodifiable_collection_test: Pass, Fail # Issue 12429
-csslib/test/declaration_test: Pass, Crash # V8 issue 2846
 
 [ $compiler == dart2js ]
 analyzer_experimental/test/generated/ast_test: Fail #Issue 12341
@@ -28,7 +30,7 @@
 [ $compiler == dart2js && $checked && $runtime == ie9 ]
 crypto/test/base64_test: Timeout # Issue 12486
 
-[ $compiler == dart2js && ($runtime == d8 || $runtime == drt) ]
+[ $compiler == dart2js && $runtime == drt ]
 crypto/test/hmac_sha256_test: Pass, Fail # v8 bug: Issue 12293
 crypto/test/sha1_test: Pass, Fail # v8 bug: Issue 12293
 crypto/test/sha256_test: Pass, Fail # v8 bug: Issue 12293
@@ -87,6 +89,8 @@
 observe/test/observe_test: Fail # Issue 11970
 observe/test/path_observer_test: Fail  # Issue 11970
 mdv/test/binding_syntax_test: Fail # Issue 11970
+polymer_expressions/test/eval_test: Fail # Issue 12578
+polymer_expressions/test/syntax_test: Fail # Issue 12578
 serialization/test/serialization_test: Fail # Issue 6490
 serialization/test/no_library_test: Fail # Issue 6490
 
@@ -109,7 +113,6 @@
 csslib/test/var_test: Fail # looking for VM-specific stack traces, issue 12469
 
 [ $compiler == dart2js && $runtime == drt ]
-third_party/html5lib/test/parser_feature_test: Fail # issue 12466
 csslib: Pass, Fail # issue 12466
 
 [ $browser ]
@@ -138,6 +141,7 @@
 oauth2/test/handle_access_token_response_test: Fail, OK # Uses dart:io.
 observe/test/transform_test: Fail, OK # Uses dart:io.
 path/test/io_test: Fail, OK # Uses dart:io.
+polymer/test/*: Fail, OK # Uses dart:io.
 watcher/test/*: Fail, OK # Uses dart:io.
 
 scheduled_test/test/descriptor/async_test: Fail # http://dartbug.com/8440
@@ -179,9 +183,6 @@
 # not minified.
 unittest/test/*_minified_test: Skip # DO NOT COPY THIS UNLESS YOU WORK ON DART2JS
 
-[ $compiler == none && $runtime == drt ]
-dartdoc/test/dartdoc_test: Skip # See dartbug.com/4541.
-
 [ $arch == arm ]
 *: Skip
 
diff --git a/pkg/polymer/README.md b/pkg/polymer/README.md
new file mode 100644
index 0000000..d4d177f0b
--- /dev/null
+++ b/pkg/polymer/README.md
@@ -0,0 +1,85 @@
+Polymer.dart
+============
+
+Polymer is a new type of library for the web, built on top of Web Components,
+and designed to leverage the evolving web platform on modern browsers.
+
+Polymer.dart is a Dart port of Polymer created and maintained by the Dart team.
+The Dart team is collaborating with the Polymer team to ensure that polymer.dart
+elements and polyfills are fully compatible with Polymer.
+
+For more information about Polymer, see <http://www.polymer-project.org/>.
+For more information about Dart, see <http://www.dartlang.org/>.
+
+Try It Now
+-----------
+Add the polymer.dart package to your pubspec.yaml file:
+
+```yaml
+dependencies:
+  polymer: any
+```
+
+Instead of using `any`, we recommend using version ranges to avoid getting your
+project broken on each release. Using a version range lets you upgrade your
+package at your own pace. You can find the latest version number at
+<https://pub.dartlang.org/packages/polymer>.
+
+
+Learn More
+----------
+
+**Note**: these documents are currently out of date.
+
+* [Read an overview][overview]
+* [Setup your tools][tools]
+* [Browse the features][features]
+* [Dive into the specification][spec]
+
+See our [TodoMVC][] example by opening up the Dart Editor's Welcome Page and
+selecting "TodoMVC".
+
+Running Tests
+-------------
+
+Dependencies are installed using the [Pub Package Manager][pub].
+```bash
+pub install
+
+# Run command line tests and automated end-to-end tests. It needs two
+# executables on your path: `dart` and `content_shell` (see below
+# for links to download `content_shell`)
+test/run.sh
+```
+Note: to run browser tests you will need to have [content_shell][cs],
+which can be downloaded prebuilt for [Ubuntu Lucid][cs_lucid],
+[Windows][cs_win], or [Mac][cs_mac]. You can also build it from the
+[Dartium and content_shell sources][dartium_src].
+
+For Linux users all the necessary fonts must be installed see
+<https://code.google.com/p/chromium/wiki/LayoutTestsLinux>.
+
+Contacting Us
+-------------
+
+Please file issues in our [Issue Tracker][issues] or contact us on the
+[Dart Web UI mailing list][mailinglist].
+
+We also have the [Web UI development list][devlist] for discussions about
+internals of the code, code reviews, etc.
+
+[wc]: http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html
+[pub]: http://www.dartlang.org/docs/pub-package-manager/
+[cs]: http://www.chromium.org/developers/testing/webkit-layout-tests
+[cs_lucid]: http://gsdview.appspot.com/dartium-archive/continuous/drt-lucid64.zip
+[cs_mac]: http://gsdview.appspot.com/dartium-archive/continuous/drt-mac.zip
+[cs_win]: http://gsdview.appspot.com/dartium-archive/continuous/drt-win.zip
+[dartium_src]: http://code.google.com/p/dart/wiki/BuildingDartium
+[TodoMVC]: http://addyosmani.github.com/todomvc/
+[issues]: http://dartbug.com/new
+[mailinglist]: https://groups.google.com/a/dartlang.org/forum/?fromgroups#!forum/web-ui
+[devlist]: https://groups.google.com/a/dartlang.org/forum/?fromgroups#!forum/web-ui-dev
+[overview]: http://www.dartlang.org/articles/dart-web-components/
+[tools]: https://www.dartlang.org/articles/dart-web-components/tools.html
+[spec]: https://www.dartlang.org/articles/dart-web-components/spec.html
+[features]: https://www.dartlang.org/articles/dart-web-components/summary.html
diff --git a/pkg/polymer/bin/dwc.dart b/pkg/polymer/bin/dwc.dart
new file mode 100755
index 0000000..3d3a0a2
--- /dev/null
+++ b/pkg/polymer/bin/dwc.dart
@@ -0,0 +1,8 @@
+#!/usr/bin/env dart
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:polymer/dwc.dart' as dwc;
+
+void main() => dwc.main();
diff --git a/pkg/polymer/build.dart b/pkg/polymer/build.dart
new file mode 100755
index 0000000..5e863b8
--- /dev/null
+++ b/pkg/polymer/build.dart
@@ -0,0 +1,17 @@
+#!/usr/bin/env dart
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/** Build logic that lets the Dart editor build examples in the background. */
+library build;
+import 'package:polymer/component_build.dart';
+import 'dart:io';
+
+void main() {
+  var args = new Options().arguments.toList()..addAll(['--', '--deploy']);
+  build(args, [
+    'example/component/news/web/index.html',
+    'example/scoped_style/index.html',
+    '../../samples/third_party/todomvc/web/index.html']);
+}
diff --git a/pkg/polymer/example/component/news/test/expected/news_index_test.html.txt b/pkg/polymer/example/component/news/test/expected/news_index_test.html.txt
new file mode 100644
index 0000000..f5907ed
--- /dev/null
+++ b/pkg/polymer/example/component/news/test/expected/news_index_test.html.txt
@@ -0,0 +1,85 @@
+Content-Type: text/plain
+<html><head><style>template { display: none; }</style>
+  <title>Simple Web Components Example</title>
+  
+  <script src="../../packages/polymer/testing/testing.js"></script>
+<style>template,
+thead[template],
+tbody[template],
+tfoot[template],
+th[template],
+tr[template],
+td[template],
+caption[template],
+colgroup[template],
+col[template],
+option[template] {
+  display: none;
+}</style></head>
+<body><polymer-element name="x-news" extends="ul">
+    <template>
+        <style scoped="">
+            div.breaking {
+                color: Red;
+                font-size: 20px;
+                border: 1px dashed Purple;
+            }
+            div.other {
+                padding: 2px 0 0 0;
+                border: 1px solid Cyan;
+            }
+        </style>
+        <div class="breaking">
+            <h2>Breaking Stories</h2>
+            <ul>
+                <content select=".breaking"></content>
+            </ul>
+        </div>
+        <div class="other">
+            <h2>Other News</h2>
+            <ul>
+                <content></content>
+            </ul>
+        </div>
+    </template>
+    
+</polymer-element>
+
+<h1>Simple Web Components Example</h1>
+<ul is="x-news"><shadow-root>
+        <style scoped="">
+            div.breaking {
+                color: Red;
+                font-size: 20px;
+                border: 1px dashed Purple;
+            }
+            div.other {
+                padding: 2px 0 0 0;
+                border: 1px solid Cyan;
+            }
+        </style>
+        <div class="breaking">
+            <h2>Breaking Stories</h2>
+            <ul>
+                <content select=".breaking"></content>
+            </ul>
+        </div>
+        <div class="other">
+            <h2>Other News</h2>
+            <ul>
+                <content></content>
+            </ul>
+        </div>
+    </shadow-root>
+    <li><a href="//example.com/stories/1">A story</a></li>
+    <li><a href="//example.com/stories/2">Another story</a></li>
+    <li class="breaking"><a href="//example.com/stories/3">Also a story</a></li>
+    <li><a href="//example.com/stories/4">Yet another story</a></li>
+    <li><a href="//example.com/stories/4">Awesome story</a></li>
+    <li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
+</ul>
+
+
+
+<script type="text/javascript" src="packages/shadow_dom/shadow_dom.debug.js"></script>
+<script type="application/dart" src="news_index_test.html_bootstrap.dart"></script></body></html>
diff --git a/pkg/polymer/example/component/news/test/news_index_test.html b/pkg/polymer/example/component/news/test/news_index_test.html
new file mode 100644
index 0000000..95bfadd
--- /dev/null
+++ b/pkg/polymer/example/component/news/test/news_index_test.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!--
+This example is from
+https://github.com/dglazkov/Web-Components-Polyfill/blob/master/samples/news
+-->
+<html>
+<head>
+  <title>Simple Web Components Example</title>
+  <link rel="import" href="../web/news-component.html">
+  <script src="packages/polymer/testing/testing.js"></script>
+</head>
+<body>
+<h1>Simple Web Components Example</h1>
+<ul is="x-news">
+    <li><a href="//example.com/stories/1">A story</a></li>
+    <li><a href="//example.com/stories/2">Another story</a></li>
+    <li class="breaking"><a href="//example.com/stories/3">Also a story</a></li>
+    <li><a href="//example.com/stories/4">Yet another story</a></li>
+    <li><a href="//example.com/stories/4">Awesome story</a></li>
+    <li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
+</ul>
+<script type="application/dart">
+import 'dart:html';
+main() {
+  window.postMessage('done', '*');
+}
+</script>
+</body>
+</html>
diff --git a/pkg/polymer/example/component/news/test/test.dart b/pkg/polymer/example/component/news/test/test.dart
new file mode 100755
index 0000000..a4c98a0
--- /dev/null
+++ b/pkg/polymer/example/component/news/test/test.dart
@@ -0,0 +1,14 @@
+#!/usr/bin/env dart
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io';
+import 'package:polymer/testing/content_shell_test.dart';
+import 'package:unittest/compact_vm_config.dart';
+
+void main() {
+  useCompactVMConfiguration();
+  // Base directory, input, expected, output:
+  renderTests('..', '.', 'expected', 'out');
+}
diff --git a/pkg/polymer/example/component/news/web/index.html b/pkg/polymer/example/component/news/web/index.html
new file mode 100644
index 0000000..3354d9b
--- /dev/null
+++ b/pkg/polymer/example/component/news/web/index.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<!--
+This example is from
+https://github.com/dglazkov/Web-Components-Polyfill/blob/master/samples/news
+-->
+<html>
+<head>
+    <title>Simple Web Components Example</title>
+    <link rel="import" href="news-component.html">
+    <script src='packages/polymer/boot.js'></script>
+</head>
+<body>
+<h1>Simple Web Components Example</h1>
+<ul is="x-news">
+    <li><a href="//example.com/stories/1">A story</a></li>
+    <li><a href="//example.com/stories/2">Another story</a></li>
+    <li class="breaking"><a href="//example.com/stories/3">Also a story</a></li>
+    <li><a href="//example.com/stories/4">Yet another story</a></li>
+    <li><a href="//example.com/stories/4">Awesome story</a></li>
+    <li class="breaking"><a href="//example.com/stories/5">Horrible story</a></li>
+</ul>
+  <script type="application/dart">main() {}</script>
+</body>
+</html>
diff --git a/pkg/polymer/example/component/news/web/news-component.html b/pkg/polymer/example/component/news/web/news-component.html
new file mode 100644
index 0000000..8e604d2
--- /dev/null
+++ b/pkg/polymer/example/component/news/web/news-component.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!--
+This example is from
+https://github.com/dglazkov/Web-Components-Polyfill/blob/master/samples/news
+-->
+<html>
+<head>
+    <title>News Component</title>
+</head>
+<body>
+<polymer-element name="x-news" extends="ul">
+    <template>
+        <style scoped>
+            div.breaking {
+                color: Red;
+                font-size: 20px;
+                border: 1px dashed Purple;
+            }
+            div.other {
+                padding: 2px 0 0 0;
+                border: 1px solid Cyan;
+            }
+        </style>
+        <div class="breaking">
+            <h2>Breaking Stories</h2>
+            <ul>
+                <content select=".breaking"></content>
+            </ul>
+        </div>
+        <div class="other">
+            <h2>Other News</h2>
+            <ul>
+                <content></content>
+            </ul>
+        </div>
+    </template>
+    <script type="application/dart">
+      import 'package:polymer/polymer.dart';
+
+      class XNews extends PolymerElement {}
+
+      @initMethod
+      _init() {
+        registerPolymerElement('x-news', () => new XNews());
+      }
+    </script>
+</polymer-element>
+</body>
+</html>
diff --git a/pkg/polymer/example/scoped_style/index.html b/pkg/polymer/example/scoped_style/index.html
new file mode 100644
index 0000000..bc81e7a
--- /dev/null
+++ b/pkg/polymer/example/scoped_style/index.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Simple CSS Test</title>
+    <link rel="import" href="my_test.html">
+    <script src='packages/polymer/boot.js'></script>
+</head>
+<body>
+<style>
+  p { color: black;}
+</style>
+<p>outside of element, should be black</p>
+
+<my-test></my-test>
+</body>
+</html>
diff --git a/pkg/polymer/example/scoped_style/my_test.html b/pkg/polymer/example/scoped_style/my_test.html
new file mode 100644
index 0000000..2713a3f
--- /dev/null
+++ b/pkg/polymer/example/scoped_style/my_test.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Test Compopnent</title>
+</head>
+<body>
+<polymer-element name="my-test">
+  <template>
+    <style>
+      p { color: red;}
+    </style>
+    <p>Inside element, should be red</p>
+  </template>
+  <script type="application/dart">
+    import 'package:polymer/polymer.dart';
+
+    @CustomTag('my-test')
+    class MyTest extends PolymerElement {}
+  </script>
+</polymer-element>
+</body>
+</html>
diff --git a/pkg/polymer/lib/boot.js b/pkg/polymer/lib/boot.js
new file mode 100644
index 0000000..84bb489
--- /dev/null
+++ b/pkg/polymer/lib/boot.js
@@ -0,0 +1,158 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// This script dynamically prepares a set of files to run polymer.dart. It uses
+// the html_import polyfill to search for all imported files, then
+// it inlines all <polymer-element> definitions on the top-level page (needed by
+// registerPolymerElement), and it removes script tags that appear inside
+// those tags. It finally rewrites the main entrypoint to call an initialization
+// function on each of the declared <polymer-elements>.
+//
+// This script is needed only when running polymer.dart in Dartium. It should be
+// removed by the polymer deployment commands.
+
+// As an example, given an input of this form:
+//   <polymer-element name="c1">
+//      <template></template>
+//      <script type="application/dart" src="url0.dart"></script>
+//   </polymer-element>
+//   <element name="c2">
+//      <template></template>
+//      <script type="application/dart">main() => { print('body2'); }</script>
+//   </element>
+//   <c1></c1>
+//   <c2></c2>
+//   <script type="application/dart" src="url2.dart"></script>
+//   <script src="packages/polymer/boot.js"></script>
+//
+// This script will simplifies the page as follows:
+//   <polymer-element name="c1">
+//      <template></template>
+//   </polymer-element>
+//   <polymer-element name="c2">
+//      <template></template>
+//   </polymer-element>
+//   <c1></c1>
+//   <c2></c2>
+//   <script type="application/dart">
+//     import 'url0.dart' as i0;
+//     import "data:application/dart;base64,CiAgICBtYWluKCkgewogICAgICBwcmludCgnYm9keTInKTsKICAgIH0KICAgIA==" as i1;
+//     import 'url2.dart' as i2;
+//     ...
+//     main() {
+//       // code that checks which libraries have a 'main' and invokes them.
+//       // practically equivalent to: i0._init(); i1._init(); i2.main();
+//     }
+//   </script>
+
+
+(function() {
+  // Only run in Dartium.
+  if (!navigator.webkitStartDart) {
+    // TODO(sigmund): rephrase when we split build.dart in two: analysis vs
+    // deploy pieces.
+    console.warn('boot.js only works in Dartium. Run the build.dart' +
+      ' tool to compile a depolyable JavaScript version')
+    return;
+  }
+  document.write(
+    '<script src="packages/html_import/html_import.min.js"></script>');
+
+  // Extract a Dart import URL from a script tag, which is the 'src' attribute
+  // of the script tag, or a data-url with the script contents for inlined code.
+  function getScriptUrl(script) {
+    var url = script.src;
+    if (url) {
+      // Normalize package: urls
+      var index = url.indexOf('packages/');
+      if (index == 0 || (index > 0 && url[index - 1] == '/')) {
+        url = "package:" + url.slice(index + 9);
+      }
+      return url;
+    } else {
+      // TODO(sigmund): investigate how to eliminate the warning in Dartium
+      // (changing to text/javascript hides the warning, but seems wrong).
+      return "data:application/dart;base64," + window.btoa(script.textContent);
+    }
+  }
+
+  // Moves <polymer-elements> from imported documents into the top-level page.
+  function inlinePolymerElements(content, ref, seen) {
+    if (!seen) seen = {};
+    var links = content.querySelectorAll('link[rel="import"]');
+    for (var i = 0; i < links.length; i++) {
+      var link = links[i].import;
+      if (seen[link.href]) continue;
+      seen[link.href] = link;
+      inlinePolymerElements(link.content, ref, seen);
+    }
+
+    if (content != document) { // no need to do anything for the top-level page
+      var elements = content.querySelectorAll('polymer-element');
+      for (var i = 0; i < elements.length; i++)  {
+        document.body.insertBefore(elements[i], ref);
+      }
+    }
+  }
+
+  // Creates a Dart program that imports [urls] and passes them to initPolymer
+  // (which in turn will invoke their main function, their methods marked with
+  // @initMethod, and register any custom tag labeled with @CustomTag).
+  function createMain(urls, mainUrl) {
+    var imports = Array(urls.length + 1);
+    for (var i = 0; i < urls.length; ++i) {
+      imports[i] = 'import "' + urls[i] + '" as i' + i + ';';
+    }
+    imports[urls.length] = 'import "package:polymer/polymer.dart" as polymer;';
+    var arg = urls.length == 0 ? '[]' :
+        ('[\n      "' + urls.join('",\n      "') + '"\n     ]');
+    return (imports.join('\n') +
+        '\n\nmain() {\n' +
+        '  polymer.initPolymer(' + arg + ');\n' +
+        '}\n');
+  }
+
+  // Finds all top-level <script> tags, and <script> tags in custom elements
+  // and merges them into a single entrypoint.
+  function mergeScripts() {
+    var scripts = document.getElementsByTagName("script");
+    var length = scripts.length;
+
+    var urls = [];
+    var toRemove = [];
+
+    // Collect the information we need to replace the script tags
+    for (var i = 0; i < length; ++i) {
+      var script = scripts[i];
+      if (script.type == "application/dart") {
+        urls.push(getScriptUrl(script));
+        toRemove.push(script);
+      }
+    }
+
+    toRemove.forEach(function (s) { s.parentNode.removeChild(s); });
+
+    // Append a new script tag that initializes everything.
+    var newScript = document.createElement('script');
+    newScript.type = "application/dart";
+    newScript.textContent = createMain(urls);
+    document.body.appendChild(newScript);
+  }
+
+  var alreadyRan = false;
+  window.addEventListener('HTMLImportsLoaded', function (e) {
+    if (alreadyRan) {
+      console.warn('HTMLImportsLoaded fired again.');
+      return;
+    }
+    alreadyRan = true;
+    var ref = document.body.children[0];
+    inlinePolymerElements(document, ref);
+    mergeScripts();
+    if (!navigator.webkitStartDart()) {
+      document.body.innerHTML = 'This build has expired. Please download a ' +
+          'new Dartium at http://www.dartlang.org/dartium/index.html';
+    }
+  });
+})();
diff --git a/pkg/polymer/lib/component_build.dart b/pkg/polymer/lib/component_build.dart
new file mode 100644
index 0000000..316124e
--- /dev/null
+++ b/pkg/polymer/lib/component_build.dart
@@ -0,0 +1,166 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Common logic to make it easy to create a `build.dart` for your project.
+ *
+ * The `build.dart` script is invoked automatically by the Editor whenever a
+ * file in the project changes. It must be placed in the root of a project
+ * (where pubspec.yaml lives) and should be named exactly 'build.dart'.
+ *
+ * A common `build.dart` would look as follows:
+ *
+ *     import 'dart:io';
+ *     import 'package:polymer/component_build.dart';
+ *
+ *     main() => build(new Options().arguments, ['web/index.html']);
+ */
+library build_utils;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:json' as json;
+import 'package:args/args.dart';
+
+import 'dwc.dart' as dwc;
+import 'src/utils.dart';
+import 'src/compiler_options.dart';
+
+/**
+ * Set up 'build.dart' to compile with the dart web components compiler every
+ * [entryPoints] listed. On clean commands, the directory where [entryPoints]
+ * live will be scanned for generated files to delete them.
+ */
+// TODO(jmesserly): we need a better way to automatically detect input files
+Future<List<dwc.CompilerResult>> build(List<String> arguments,
+    List<String> entryPoints,
+    {bool printTime: true, bool shouldPrint: true}) {
+  bool useColors = stdioType(stdout) == StdioType.TERMINAL;
+  return asyncTime('Total time', () {
+    var args = _processArgs(arguments);
+    var tasks = new FutureGroup();
+    var lastTask = new Future.value(null);
+    tasks.add(lastTask);
+
+    var changedFiles = args["changed"];
+    var removedFiles = args["removed"];
+    var cleanBuild = args["clean"];
+    var machineFormat = args["machine"];
+    // Also trigger a full build if the script was run from the command line
+    // with no arguments
+    var fullBuild = args["full"] || (!machineFormat && changedFiles.isEmpty &&
+        removedFiles.isEmpty && !cleanBuild);
+
+    var options = CompilerOptions.parse(args.rest, checkUsage: false);
+
+    // [outputOnlyDirs] contains directories known to only have output files.
+    // When outputDir is not specified, we create a new directory which only
+    // contains output files. If options.outputDir is specified, we don't know
+    // if the output directory may also have input files. In which case,
+    // [_handleCleanCommand] and [_isInputFile] are more conservative.
+    //
+    // TODO(sigmund): get rid of this. Instead, use the compiler to understand
+    // which files are input or output files.
+    var outputOnlyDirs = options.outputDir == null ? []
+        : entryPoints.map((e) => _outDir(e)).toList();
+
+    if (cleanBuild) {
+      _handleCleanCommand(outputOnlyDirs);
+    } else if (fullBuild
+        || changedFiles.any((f) => _isInputFile(f, outputOnlyDirs))
+        || removedFiles.any((f) => _isInputFile(f, outputOnlyDirs))) {
+      for (var file in entryPoints) {
+        var dwcArgs = new List.from(args.rest);
+        if (machineFormat) dwcArgs.add('--json_format');
+        if (!useColors) dwcArgs.add('--no-colors');
+        // We'll set 'out/' as the out folder, unless an output directory was
+        // already specified in the command line.
+        if (options.outputDir == null) dwcArgs.addAll(['-o', _outDir(file)]);
+        dwcArgs.add(file);
+        // Chain tasks to that we run one at a time.
+        lastTask = lastTask.then((_) => dwc.run(dwcArgs, printTime: printTime,
+            shouldPrint: shouldPrint));
+        if (machineFormat) {
+          lastTask = lastTask.then((res) {
+            appendMessage(Map jsonMessage) {
+              var message = json.stringify([jsonMessage]);
+              if (shouldPrint) print(message);
+              res.messages.add(message);
+            }
+            // Print for the Editor messages about mappings and generated files
+            res.outputs.forEach((out, input) {
+              if (out.endsWith(".html") && input != null) {
+                appendMessage({
+                  "method": "mapping",
+                  "params": {"from": input, "to": out},
+                });
+              }
+              appendMessage({"method": "generated", "params": {"file": out}});
+            });
+            return res;
+          });
+        }
+        tasks.add(lastTask);
+      }
+    }
+    return tasks.future.then((r) => r.where((v) => v != null));
+  }, printTime: printTime, useColors: useColors);
+}
+
+String _outDir(String file) => path.join(path.dirname(file), 'out');
+
+/** Tell whether [filePath] is a generated file. */
+bool _isGeneratedFile(String filePath, List<String> outputOnlyDirs) {
+  var dirPrefix = path.dirname(filePath);
+  for (var outDir in outputOnlyDirs) {
+    if (dirPrefix.startsWith(outDir)) return true;
+  }
+  return path.basename(filePath).startsWith('_');
+}
+
+/** Tell whether [filePath] is an input file. */
+bool _isInputFile(String filePath, List<String> outputOnlyDirs) {
+  var ext = path.extension(filePath);
+  return (ext == '.dart' || ext == '.html') &&
+      !_isGeneratedFile(filePath, outputOnlyDirs);
+}
+
+/**
+ * Delete all generated files. Currently we only delete files under directories
+ * that are known to contain only generated code.
+ */
+void _handleCleanCommand(List<String> outputOnlyDirs) {
+  for (var dirPath in outputOnlyDirs) {
+    var dir = new Directory(dirPath);
+    if (!dir.existsSync()) continue;
+    for (var f in dir.listSync(recursive: false)) {
+      if (f is File && _isGeneratedFile(f.path, outputOnlyDirs)) f.deleteSync();
+    }
+  }
+}
+
+/** Process the command-line arguments. */
+ArgResults _processArgs(List<String> arguments) {
+  var parser = new ArgParser()
+    ..addOption("changed", help: "the file has changed since the last build",
+        allowMultiple: true)
+    ..addOption("removed", help: "the file was removed since the last build",
+        allowMultiple: true)
+    ..addFlag("clean", negatable: false, help: "remove any build artifacts")
+    ..addFlag("full", negatable: false, help: "perform a full build")
+    ..addFlag("machine", negatable: false,
+        help: "produce warnings in a machine parseable format")
+    ..addFlag("help", abbr: 'h',
+        negatable: false, help: "displays this help and exit");
+  var args = parser.parse(arguments);
+  if (args["help"]) {
+    print('A build script that invokes the web-ui compiler (dwc).');
+    print('Usage: dart build.dart [options] [-- [dwc-options]]');
+    print('\nThese are valid options expected by build.dart:');
+    print(parser.getUsage());
+    print('\nThese are valid options expected by dwc:');
+    dwc.run(['-h']).then((_) => exit(0));
+  }
+  return args;
+}
diff --git a/pkg/polymer/lib/dwc.dart b/pkg/polymer/lib/dwc.dart
new file mode 100755
index 0000000..b70f29b
--- /dev/null
+++ b/pkg/polymer/lib/dwc.dart
@@ -0,0 +1,198 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/** The entry point to the compiler. Used to implement `bin/dwc.dart`. */
+library dwc;
+
+import 'dart:async';
+import 'dart:io';
+import 'package:logging/logging.dart' show Level;
+
+import 'src/compiler.dart';
+import 'src/file_system.dart';
+import 'src/file_system/console.dart';
+import 'src/files.dart';
+import 'src/messages.dart';
+import 'src/compiler_options.dart';
+import 'src/utils.dart';
+
+FileSystem _fileSystem;
+
+void main() {
+  run(new Options().arguments).then((result) {
+    exit(result.success ? 0 : 1);
+  });
+}
+
+/** Contains the result of a compiler run. */
+class CompilerResult {
+  final bool success;
+
+  /** Map of output path to source, if there is one */
+  final Map<String, String> outputs;
+
+  /** List of files read during compilation */
+  final List<String> inputs;
+
+  final List<String> messages;
+  String bootstrapFile;
+
+  CompilerResult([this.success = true,
+                  this.outputs,
+                  this.inputs,
+                  this.messages = const [],
+                  this.bootstrapFile]);
+
+  factory CompilerResult._(bool success,
+      List<String> messages, List<OutputFile> outputs, List<SourceFile> files) {
+    var file;
+    var outs = new Map<String, String>();
+    for (var out in outputs) {
+      if (path.basename(out.path).endsWith('_bootstrap.dart')) {
+        file = out.path;
+      }
+      outs[out.path] = out.source;
+    }
+    var inputs = files.map((f) => f.path).toList();
+    return new CompilerResult(success, outs, inputs, messages, file);
+  }
+}
+
+/**
+ * Runs the web components compiler with the command-line options in [args].
+ * See [CompilerOptions] for the definition of valid arguments.
+ */
+// TODO(jmesserly): fix this to return a proper exit code
+// TODO(justinfagnani): return messages in the result
+Future<CompilerResult> run(List<String> args, {bool printTime,
+    bool shouldPrint: true}) {
+  var options = CompilerOptions.parse(args);
+  if (options == null) return new Future.value(new CompilerResult());
+  if (printTime == null) printTime = options.verbose;
+
+  _fileSystem = new ConsoleFileSystem();
+  var messages = new Messages(options: options, shouldPrint: shouldPrint);
+
+  return asyncTime('Total time spent on ${options.inputFile}', () {
+    var compiler = new Compiler(_fileSystem, options, messages);
+    var res;
+    return compiler.run()
+      .then((_) {
+        var success = messages.messages.every((m) => m.level != Level.SEVERE);
+        var msgs = options.jsonFormat
+            ? messages.messages.map((m) => m.toJson())
+            : messages.messages.map((m) => m.toString());
+        res = new CompilerResult._(success, msgs.toList(),
+            compiler.output, compiler.files);
+      })
+      .then((_) => _symlinkPubPackages(res, options, messages))
+      .then((_) => _emitFiles(compiler.output, options.clean))
+      .then((_) => res);
+  }, printTime: printTime, useColors: options.useColors);
+}
+
+Future _emitFiles(List<OutputFile> outputs, bool clean) {
+  outputs.forEach((f) => _writeFile(f.path, f.contents, clean));
+  return _fileSystem.flush();
+}
+
+void _writeFile(String filePath, String contents, bool clean) {
+  if (clean) {
+    File fileOut = new File(filePath);
+    if (fileOut.existsSync()) {
+      fileOut.deleteSync();
+    }
+  } else {
+    _createIfNeeded(path.dirname(filePath));
+    _fileSystem.writeString(filePath, contents);
+  }
+}
+
+void _createIfNeeded(String outdir) {
+  if (outdir.isEmpty) return;
+  var outDirectory = new Directory(outdir);
+  if (!outDirectory.existsSync()) {
+    _createIfNeeded(path.dirname(outdir));
+    outDirectory.createSync();
+  }
+}
+
+/**
+ * Creates a symlink to the pub packages directory in the output location. The
+ * returned future completes when the symlink was created (or immediately if it
+ * already exists).
+ */
+Future _symlinkPubPackages(CompilerResult result, CompilerOptions options,
+    Messages messages) {
+  if (options.outputDir == null || result.bootstrapFile == null
+      || options.packageRoot != null) {
+    // We don't need to copy the packages directory if the output was generated
+    // in-place where the input lives, if the compiler was called without an
+    // entry-point file, or if the compiler was called with a package-root
+    // option.
+    return new Future.value(null);
+  }
+
+  var linkDir = path.dirname(result.bootstrapFile);
+  _createIfNeeded(linkDir);
+  var linkPath = path.join(linkDir, 'packages');
+  // A resolved symlink works like a directory
+  // TODO(sigmund): replace this with something smarter once we have good
+  // symlink support in dart:io
+  if (new Directory(linkPath).existsSync()) {
+    // Packages directory already exists.
+    return new Future.value(null);
+  }
+
+  // A broken symlink works like a file
+  var toFile = new File(linkPath);
+  if (toFile.existsSync()) {
+    toFile.deleteSync();
+  }
+
+  var targetPath = path.join(path.dirname(options.inputFile), 'packages');
+  // [fullPathSync] will canonicalize the path, resolving any symlinks.
+  // TODO(sigmund): once it's possible in dart:io, we just want to use a full
+  // path, but not necessarily resolve symlinks.
+  var target = new File(targetPath).fullPathSync().toString();
+  return createSymlink(target, linkPath, messages: messages);
+}
+
+
+// TODO(jmesserly): this code was taken from Pub's io library.
+// Added error handling and don't return the file result, to match the code
+// we had previously. Also "target" and "link" only accept strings. And inlined
+// the relevant parts of runProcess. Note that it uses "cmd" to get the path
+// on Windows.
+/**
+ * Creates a new symlink that creates an alias of [target] at [link], both of
+ * which can be a [String], [File], or [Directory]. Returns a [Future] which
+ * completes to the symlink file (i.e. [link]).
+ */
+Future createSymlink(String target, String link, {Messages messages: null}) {
+  messages = messages == null? new Messages.silent() : messages;
+  var command = 'ln';
+  var args = ['-s', target, link];
+
+  if (Platform.operatingSystem == 'windows') {
+    // Call mklink on Windows to create an NTFS junction point. Only works on
+    // Vista or later. (Junction points are available earlier, but the "mklink"
+    // command is not.) I'm using a junction point (/j) here instead of a soft
+    // link (/d) because the latter requires some privilege shenanigans that
+    // I'm not sure how to specify from the command line.
+    command = 'cmd';
+    args = ['/c', 'mklink', '/j', link, target];
+  }
+
+  return Process.run(command, args).then((result) {
+    if (result.exitCode != 0) {
+      var details = 'subprocess stdout:\n${result.stdout}\n'
+                    'subprocess stderr:\n${result.stderr}';
+      messages.error(
+        'unable to create symlink\n target: $target\n link:$link\n$details',
+        null);
+    }
+    return null;
+  });
+}
diff --git a/pkg/polymer/lib/observe.dart b/pkg/polymer/lib/observe.dart
new file mode 100644
index 0000000..0a02a90
--- /dev/null
+++ b/pkg/polymer/lib/observe.dart
@@ -0,0 +1,124 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Helpers for observable objects.
+ * Intended for use with `package:observe`.
+ */
+library polymer.observe;
+
+import 'dart:async';
+import 'package:observe/observe.dart';
+
+const _VALUE = const Symbol('value');
+
+/**
+ * Forwards an observable property from one object to another. For example:
+ *
+ *     class MyModel extends ObservableBase {
+ *       StreamSubscription _sub;
+ *       MyOtherModel _otherModel;
+ *
+ *       MyModel() {
+ *         ...
+ *         _sub = bindProperty(_otherModel, const Symbol('value'),
+ *             () => notifyProperty(this, const Symbol('prop'));
+ *       }
+ *
+ *       String get prop => _otherModel.value;
+ *       set prop(String value) { _otherModel.value = value; }
+ *     }
+ *
+ * See also [notifyProperty].
+ */
+StreamSubscription bindProperty(Observable source, Symbol sourceName,
+    void callback()) {
+  return source.changes.listen((records) {
+    for (var record in records) {
+      if (record.changes(sourceName)) {
+        callback();
+      }
+    }
+  });
+}
+
+/**
+ * Notify the property change. Shorthand for:
+ *
+ *     target.notifyChange(new PropertyChangeRecord(targetName));
+ */
+void notifyProperty(Observable target, Symbol targetName) {
+  target.notifyChange(new PropertyChangeRecord(targetName));
+}
+
+
+// Inspired by ArrayReduction at:
+// https://raw.github.com/rafaelw/ChangeSummary/master/util/array_reduction.js
+// The main difference is we support anything on the rich Dart Iterable API.
+
+/**
+ * Observes a path starting from each item in the list.
+ */
+class ListPathObserver<E, P> extends ChangeNotifierBase {
+  final ObservableList<E> list;
+  final String _itemPath;
+  final List<PathObserver> _observers = <PathObserver>[];
+  final List<StreamSubscription> _subs = <StreamSubscription>[];
+  StreamSubscription _sub;
+  bool _scheduled = false;
+  Iterable<P> _value;
+
+  ListPathObserver(this.list, String path)
+      : _itemPath = path {
+
+    _sub = list.changes.listen((records) {
+      for (var record in records) {
+        if (record is ListChangeRecord) {
+          _observeItems(record.addedCount - record.removedCount);
+        }
+      }
+      _scheduleReduce(null);
+    });
+
+    _observeItems(list.length);
+    _reduce();
+  }
+
+  Iterable<P> get value => _value;
+
+  void dispose() {
+    if (_sub != null) _sub.cancel();
+    _subs.forEach((s) => s.cancel());
+    _subs.clear();
+  }
+
+  void _reduce() {
+    _scheduled = false;
+    _value = _observers.map((o) => o.value);
+    notifyChange(new PropertyChangeRecord(_VALUE));
+  }
+
+  void _scheduleReduce(_) {
+    if (_scheduled) return;
+    _scheduled = true;
+    runAsync(_reduce);
+  }
+
+  void _observeItems(int lengthAdjust) {
+    if (lengthAdjust > 0) {
+      for (int i = 0; i < lengthAdjust; i++) {
+        int len = _observers.length;
+        var pathObs = new PathObserver(list, '$len.$_itemPath');
+        _subs.add(pathObs.changes.listen(_scheduleReduce));
+        _observers.add(pathObs);
+      }
+    } else if (lengthAdjust < 0) {
+      for (int i = 0; i < -lengthAdjust; i++) {
+        _subs.removeLast().cancel();
+      }
+      int len = _observers.length;
+      _observers.removeRange(len + lengthAdjust, len);
+    }
+  }
+}
diff --git a/pkg/polymer/lib/observe_html.dart b/pkg/polymer/lib/observe_html.dart
new file mode 100644
index 0000000..7280b0e
--- /dev/null
+++ b/pkg/polymer/lib/observe_html.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// TODO(jmesserly): can we handle this more elegantly?
+// In general, it seems like we want a convenient way to take a Stream plus a
+// getter and convert this into an Observable.
+
+/** Helpers for exposing dart:html as observable data. */
+library polymer.observe_html;
+
+import 'dart:html';
+import 'package:observe/observe.dart';
+
+/** An observable version of [window.location.hash]. */
+final ObservableLocationHash windowLocation = new ObservableLocationHash._();
+
+class ObservableLocationHash extends ChangeNotifierBase {
+  ObservableLocationHash._() {
+    // listen on changes to #hash in the URL
+    // Note: listen on both popState and hashChange, because IE9 doesn't support
+    // history API. See http://dartbug.com/5483
+    // TODO(jmesserly): only listen to these if someone is listening to our
+    // changes.
+    window.onHashChange.listen(_notifyHashChange);
+    window.onPopState.listen(_notifyHashChange);
+  }
+
+  String get hash => window.location.hash;
+
+  /**
+   * Pushes a new URL state, similar to the affect of clicking a link.
+   * Has no effect if the [value] already equals [window.location.hash].
+   */
+  void set hash(String value) {
+    if (value == hash) return;
+
+    window.history.pushState(null, '', value);
+    _notifyHashChange(null);
+  }
+
+  void _notifyHashChange(_) {
+    notifyChange(new PropertyChangeRecord(const Symbol('hash')));
+  }
+}
+
+/** Add or remove CSS class [className] based on the [value]. */
+void updateCssClass(Element element, String className, bool value) {
+  if (value == true) {
+    element.classes.add(className);
+  } else {
+    element.classes.remove(className);
+  }
+}
+
+/** Bind a CSS class to the observable [object] and property [path]. */
+PathObserver bindCssClass(Element element, String className,
+    Observable object, String path) {
+
+  return new PathObserver(object, path)..bindSync((value) {
+    updateCssClass(element, className, value);
+  });
+}
diff --git a/pkg/polymer/lib/polymer.dart b/pkg/polymer/lib/polymer.dart
new file mode 100644
index 0000000..575619b
--- /dev/null
+++ b/pkg/polymer/lib/polymer.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * This library exports all of the commonly used functions and types for
+ * building UI's.
+ *
+ * See this article for more information:
+ * <http://www.dartlang.org/articles/dart-web-components/>.
+ */
+library polymer;
+
+import 'dart:async';
+import 'dart:mirrors';
+
+import 'package:mdv/mdv.dart' as mdv;
+import 'package:observe/src/microtask.dart';
+import 'package:path/path.dart' as path;
+import 'polymer_element.dart' show registerPolymerElement;
+
+export 'package:custom_element/custom_element.dart';
+export 'package:observe/observe.dart';
+export 'package:observe/src/microtask.dart';
+
+export 'observe.dart';
+export 'observe_html.dart';
+export 'polymer_element.dart';
+export 'safe_html.dart';
+
+
+/** Annotation used to automatically register polymer elements. */
+class CustomTag {
+  final String tagName;
+  const CustomTag(this.tagName);
+}
+
+/**
+ * Metadata used to label static or top-level methods that are called
+ * automatically when loading the library of a custom element.
+ */
+const initMethod = const _InitMethodAnnotation();
+
+/**
+ * Initializes a polymer application as follows:
+ *   * set up up polling for observable changes
+ *   *  initialize MDV
+ *   *  for each library in [libraries], register custom elements labeled with
+ *      [CustomTag] and invoke the initialization method on it.
+ *
+ * The initialization on each library is either a method named `main` or
+ * a top-level function and annotated with [initMethod].
+ *
+ * The urls in [libraries] can be absolute or relative to [srcUrl].
+ */
+void initPolymer(List<String> libraries, [String srcUrl]) {
+  wrapMicrotask(() {
+    // DOM events don't yet go through microtasks, so we catch those here.
+    new Timer.periodic(new Duration(milliseconds: 125),
+        (_) => performMicrotaskCheckpoint());
+
+    // TODO(jmesserly): mdv should use initMdv instead of mdv.initialize.
+    mdv.initialize();
+    for (var lib in libraries) {
+      _loadLibrary(lib, srcUrl);
+    }
+  })();
+}
+
+/** All libraries in the current isolate. */
+final _libs = currentMirrorSystem().libraries;
+
+/**
+ * Reads the library at [uriString] (which can be an absolute URI or a relative
+ * URI from [srcUrl]), and:
+ *
+ *   * If present, invokes `main`.
+ *
+ *   * If present, invokes any top-level and static functions marked
+ *     with the [initMethod] annotation (in the order they appear).
+ *
+ *   * Registers any [PolymerElement] that is marked with the [CustomTag]
+ *     annotation.
+ */
+void _loadLibrary(String uriString, [String srcUrl]) {
+  var uri = Uri.parse(uriString);
+  if (uri.scheme == '' && srcUrl != null) {
+    uri = Uri.parse(path.normalize(path.join(path.dirname(srcUrl), uriString)));
+  }
+  var lib = _libs[uri];
+  if (lib == null) {
+    print('warning: $uri library not found');
+    return;
+  }
+
+  // Invoke `main`, if present.
+  if (lib.functions[const Symbol('main')] != null) {
+    lib.invoke(const Symbol('main'), const []);
+  }
+
+  // Search top-level functions marked with @initMethod
+  for (var f in lib.functions.values) {
+    _maybeInvoke(lib, f);
+  }
+
+  for (var c in lib.classes.values) {
+    // Search for @CustomTag on classes
+    for (var m in c.metadata) {
+      var meta = m.reflectee;
+      if (meta is CustomTag) {
+        registerPolymerElement(meta.tagName,
+            () => c.newInstance(const Symbol(''), const []).reflectee);
+      }
+    }
+
+    // TODO(sigmund): check also static methods marked with @initMethod.
+    // This is blocked on two bugs:
+    //  - dartbug.com/12133 (static methods are incorrectly listed as top-level
+    //    in dart2js, so they end up being called twice)
+    //  - dartbug.com/12134 (sometimes "method.metadata" throws an exception,
+    //    we could wrap and hide those exceptions, but it's not ideal).
+  }
+}
+
+void _maybeInvoke(ObjectMirror obj, MethodMirror method) {
+  var annotationFound = false;
+  for (var meta in method.metadata) {
+    if (identical(meta.reflectee, initMethod)) {
+      annotationFound = true;
+      break;
+    }
+  }
+  if (!annotationFound) return;
+  if (!method.isStatic) {
+    print("warning: methods marked with @initMethod should be static,"
+        " ${method.simpleName} is not.");
+    return;
+  }
+  if (!method.parameters.where((p) => !p.isOptional).isEmpty) {
+    print("warning: methods marked with @initMethod should take no "
+        "arguments, ${method.simpleName} expects some.");
+    return;
+  }
+  obj.invoke(method.simpleName, const []);
+}
+
+class _InitMethodAnnotation {
+  const _InitMethodAnnotation();
+}
diff --git a/pkg/polymer/lib/polymer_element.dart b/pkg/polymer/lib/polymer_element.dart
new file mode 100644
index 0000000..297625e
--- /dev/null
+++ b/pkg/polymer/lib/polymer_element.dart
@@ -0,0 +1,566 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library polymer.polymer_element;
+
+import 'dart:async';
+import 'dart:html';
+import 'dart:mirrors';
+import 'dart:js' as dartJs;
+
+import 'package:custom_element/custom_element.dart';
+import 'package:js/js.dart' as js;
+import 'package:mdv/mdv.dart' show NodeBinding;
+import 'package:observe/observe.dart';
+import 'package:observe/src/microtask.dart';
+import 'package:polymer_expressions/polymer_expressions.dart';
+
+import 'src/utils_observe.dart' show toCamelCase, toHyphenedName;
+
+/**
+ * Registers a [PolymerElement]. This is similar to [registerCustomElement]
+ * but it is designed to work with the `<element>` element and adds additional
+ * features.
+ */
+void registerPolymerElement(String localName, PolymerElement create()) {
+  registerCustomElement(localName, () => create().._initialize(localName));
+}
+
+/**
+ * *Warning*: many features of this class are not fully implemented.
+ *
+ * The base class for Polymer elements. It provides convience features on top
+ * of the custom elements web standard.
+ *
+ * Currently it supports publishing attributes via:
+ *
+ *     <element name="..." attributes="foo, bar, baz">
+ *
+ * Any attribute published this way can be used in a data binding expression,
+ * and it should contain a corresponding DOM field.
+ *
+ * *Warning*: due to dart2js mirror limititations, the mapping from HTML
+ * attribute to element property is a conversion from `dash-separated-words`
+ * to camelCase, rather than searching for a property with the same name.
+ */
+// TODO(jmesserly): fix the dash-separated-words issue. Polymer uses lowercase.
+class PolymerElement extends CustomElement with _EventsMixin {
+  // This is a partial port of:
+  // https://github.com/Polymer/polymer/blob/stable/src/attrs.js
+  // https://github.com/Polymer/polymer/blob/stable/src/bindProperties.js
+  // https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
+  // https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
+  // TODO(jmesserly): we still need to port more of the functionality
+
+  /// The one syntax to rule them all.
+  static BindingDelegate _polymerSyntax = new PolymerExpressions();
+  // TODO(sigmund): delete. The next line is only added to avoid warnings from
+  // the analyzer (see http://dartbug.com/11672)
+  Element get host => super.host;
+
+  bool get applyAuthorStyles => false;
+  bool get resetStyleInheritance => false;
+
+  /**
+   * The declaration of this polymer-element, used to extract template contents
+   * and other information.
+   */
+  static Map<String, Element> _declarations = {};
+  static Element getDeclaration(String localName) {
+    if (localName == null) return null;
+    var element = _declarations[localName];
+    if (element == null) {
+      element = document.query('polymer-element[name="$localName"]');
+      _declarations[localName] = element;
+    }
+    return element;
+  }
+
+  Map<String, PathObserver> _publishedAttrs;
+  Map<String, StreamSubscription> _bindings;
+  final List<String> _localNames = [];
+
+  void _initialize(String localName) {
+    if (localName == null) return;
+
+    var declaration = getDeclaration(localName);
+    if (declaration == null) return;
+
+    if (declaration.attributes['extends'] != null) {
+      var base = declaration.attributes['extends'];
+      // Skip normal tags, only initialize parent custom elements.
+      if (base.contains('-')) _initialize(base);
+    }
+
+    _parseHostEvents(declaration);
+    _parseLocalEvents(declaration);
+    _publishAttributes(declaration);
+    _localNames.add(localName);
+  }
+
+  void _publishAttributes(elementElement) {
+    _bindings = {};
+    _publishedAttrs = {};
+
+    var attrs = elementElement.attributes['attributes'];
+    if (attrs != null) {
+      // attributes='a b c' or attributes='a,b,c'
+      for (var name in attrs.split(attrs.contains(',') ? ',' : ' ')) {
+        name = name.trim();
+
+        // TODO(jmesserly): PathObserver is overkill here; it helps avoid
+        // "new Symbol" and other mirrors-related warnings.
+        _publishedAttrs[name] = new PathObserver(this, toCamelCase(name));
+      }
+    }
+  }
+
+  void created() {
+    // TODO(jmesserly): this breaks until we get some kind of type conversion.
+    // _publishedAttrs.forEach((name, propObserver) {
+    // var value = attributes[name];
+    //   if (value != null) propObserver.value = value;
+    // });
+    _initShadowRoot();
+    _addHostListeners();
+  }
+
+  /**
+   * Creates the document fragment to use for each instance of the custom
+   * element, given the `<template>` node. By default this is equivalent to:
+   *
+   *     template.createInstance(this, polymerSyntax);
+   *
+   * Where polymerSyntax is a singleton `PolymerExpressions` instance from the
+   * [polymer_expressions](https://pub.dartlang.org/packages/polymer_expressions)
+   * package.
+   *
+   * You can override this method to change the instantiation behavior of the
+   * template, for example to use a different data-binding syntax.
+   */
+  DocumentFragment instanceTemplate(Element template) =>
+      template.createInstance(this, _polymerSyntax);
+
+  void _initShadowRoot() {
+    for (var localName in _localNames) {
+      var declaration = getDeclaration(localName);
+      var root = createShadowRoot(localName);
+      _addInstanceListeners(root, localName);
+
+      root.applyAuthorStyles = applyAuthorStyles;
+      root.resetStyleInheritance = resetStyleInheritance;
+
+      var templateNode = declaration.children.firstWhere(
+          (n) => n.localName == 'template', orElse: () => null);
+      if (templateNode == null) return;
+
+      // Create the contents of the element's ShadowRoot, and add them.
+      root.nodes.add(instanceTemplate(templateNode));
+
+      var extendsName = declaration.attributes['extends'];
+      _shimCss(root, localName, extendsName);
+    }
+  }
+
+  NodeBinding createBinding(String name, model, String path) {
+    var propObserver = _publishedAttrs[name];
+    if (propObserver != null) {
+      return new _PolymerBinding(this, name, model, path, propObserver);
+    }
+    return super.createBinding(name, model, path);
+  }
+
+  /**
+   * Using Polymer's platform/src/ShadowCSS.js passing the style tag's content.
+   */
+  void _shimCss(ShadowRoot root, String localName, String extendsName) {
+    // TODO(terry): Need to detect if ShadowCSS.js has been loaded.  Under
+    //              Dartium this wouldn't exist.  However, dart:js isn't robust
+    //              to use to detect in both Dartium and dart2js if Platform is
+    //              defined. Instead in Dartium it throws an exception but in
+    //              dart2js it works enough to know if Platform is defined (just
+    //              can't be used for further derefs).  This bug is described
+    //              https://code.google.com/p/dart/issues/detail?id=12548
+    //              When fixed only use dart:js.  This is necessary under
+    //              Dartium (no compile) we want to run w/o the JS polyfill.
+    try {
+      if (dartJs.context["Platform"] == null) { return; }
+    } on NoSuchMethodError catch (e) { return; }
+
+    var platform = js.context["Platform"];
+    if (platform == null) return;
+    var shadowCss = platform.ShadowCSS;
+    if (shadowCss == null) return;
+
+    // TODO(terry): Remove calls to shimShadowDOMStyling2 and replace with
+    //              shimShadowDOMStyling when we support unwrapping dart:html
+    //              Element to a JS DOM node.
+    var shimShadowDOMStyling2 = shadowCss.shimShadowDOMStyling2;
+    if (shimShadowDOMStyling2 == null) return;
+    var style = root.query('style');
+    if (style == null) return;
+    var scopedCSS = shimShadowDOMStyling2(style.text, localName);
+
+    // TODO(terry): Remove when shimShadowDOMStyling is called we don't need to
+    //              replace original CSS with scoped CSS shimShadowDOMStyling
+    //              does that.
+    style.text = scopedCSS;
+  }
+}
+
+class _PolymerBinding extends NodeBinding {
+  final PathObserver _publishedAttr;
+
+  _PolymerBinding(node, property, model, path, PathObserver this._publishedAttr)
+      : super(node, property, model, path);
+
+  void boundValueChanged(newValue) {
+    _publishedAttr.value = newValue;
+  }
+}
+
+/**
+ * Polymer features to handle the syntactic sugar on-* to declare to
+ * automatically map event handlers to instance methods of the [PolymerElement].
+ * This mixin is a port of:
+ * https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
+ * https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
+ */
+abstract class _EventsMixin {
+  // TODO(sigmund): implement the Dart equivalent of 'inheritDelegates'
+  // Notes about differences in the implementation below:
+  //  - _templateDelegates: polymer stores the template delegates directly on
+  //    the template node (see in parseLocalEvents: 't.delegates = {}'). Here we
+  //    simply use a separate map, where keys are the name of the
+  //    custom-element.
+  //  - _listenLocal we return true/false and propagate that up, JS
+  //    implementation does't forward the return value.
+  //  - we don't keep the side-table (weak hash map) of unhandled events (see
+  //    handleIfNotHandled)
+  //  - we don't use event.type to dispatch events, instead we save the event
+  //    name with the event listeners. We do so to avoid translating back and
+  //    forth between Dom and Dart event names.
+
+  // ---------------------------------------------------------------------------
+  // The following section was ported from:
+  // https://github.com/Polymer/polymer/blob/7936ff8/src/declaration/events.js
+  // ---------------------------------------------------------------------------
+
+  /** Maps event names and their associated method in the element class. */
+  final Map<String, String> _delegates = {};
+
+  /** Expected events per element node. */
+  // TODO(sigmund): investigate whether we need more than 1 set of local events
+  // per element (why does the js implementation stores 1 per template node?)
+  final Map<String, Set<String>> _templateDelegates =
+      new Map<String, Set<String>>();
+
+  /** [host] is needed by this mixin, but not defined here. */
+  Element get host;
+
+  /** Attribute prefix used for declarative event handlers. */
+  static const _eventPrefix = 'on-';
+
+  /** Whether an attribute declares an event. */
+  static bool _isEvent(String attr) => attr.startsWith(_eventPrefix);
+
+  /** Extracts events from the element tag attributes. */
+  void _parseHostEvents(elementElement) {
+    for (var attr in elementElement.attributes.keys.where(_isEvent)) {
+      _delegates[toCamelCase(attr)] = elementElement.attributes[attr];
+    }
+  }
+
+  /** Extracts events under the element's <template>. */
+  void _parseLocalEvents(elementElement) {
+    var name = elementElement.attributes["name"];
+    if (name == null) return;
+    var events = null;
+    for (var template in elementElement.queryAll('template')) {
+      var content = template.content;
+      if (content != null) {
+        for (var child in content.children) {
+          events = _accumulateEvents(child, events);
+        }
+      }
+    }
+    if (events != null) {
+      _templateDelegates[name] = events;
+    }
+  }
+
+  /** Returns all events names listened by [element] and it's children. */
+  static Set<String> _accumulateEvents(Element element, [Set<String> events]) {
+    events = events == null ? new Set<String>() : events;
+
+    // from: accumulateAttributeEvents, accumulateEvent
+    events.addAll(element.attributes.keys.where(_isEvent).map(toCamelCase));
+
+    // from: accumulateChildEvents
+    for (var child in element.children) {
+      _accumulateEvents(child, events);
+    }
+
+    // from: accumulateTemplatedEvents
+    if (element.isTemplate) {
+      var content = element.content;
+      if (content != null) {
+        for (var child in content.children) {
+          _accumulateEvents(child, events);
+        }
+      }
+    }
+    return events;
+  }
+
+  // ---------------------------------------------------------------------------
+  // The following section was ported from:
+  // https://github.com/Polymer/polymer/blob/7936ff8/src/instance/events.js
+  // ---------------------------------------------------------------------------
+
+  /** Attaches event listeners on the [host] element. */
+  void _addHostListeners() {
+    for (var eventName in _delegates.keys) {
+      _addNodeListener(host, eventName,
+          (e) => _hostEventListener(eventName, e));
+    }
+  }
+
+  void _addNodeListener(node, String onEvent, Function listener) {
+    // If [node] is an element (typically when listening for host events) we
+    // use directly the '.onFoo' event stream of the element instance.
+    if (node is Element) {
+      reflect(node).getField(new Symbol(onEvent)).reflectee.listen(listener);
+      return;
+    }
+
+    // When [node] is not an element, most commonly when [node] is the
+    // shadow-root of the polymer-element, we find the appropriate static event
+    // stream providers and attach it to [node].
+    var eventProvider = _eventStreamProviders[onEvent];
+    if (eventProvider != null) {
+      eventProvider.forTarget(node).listen(listener);
+      return;
+    }
+
+    // When no provider is available, mainly because of custom-events, we use
+    // the underlying event listeners from the DOM.
+    var eventName = onEvent.substring(2).toLowerCase(); // onOneTwo => onetwo
+    // Most events names in Dart match those in JS in lowercase except for some
+    // few events listed in this map. We expect these cases to be handled above,
+    // but just in case we include them as a safety net here.
+    var jsNameFixes = const {
+      'animationend': 'webkitAnimationEnd',
+      'animationiteration': 'webkitAnimationIteration',
+      'animationstart': 'webkitAnimationStart',
+      'doubleclick': 'dblclick',
+      'fullscreenchange': 'webkitfullscreenchange',
+      'fullscreenerror': 'webkitfullscreenerror',
+      'keyadded': 'webkitkeyadded',
+      'keyerror': 'webkitkeyerror',
+      'keymessage': 'webkitkeymessage',
+      'needkey': 'webkitneedkey',
+      'speechchange': 'webkitSpeechChange',
+    };
+    var fixedName = jsNameFixes[eventName];
+    node.on[fixedName != null ? fixedName : eventName].listen(listener);
+  }
+
+  void _addInstanceListeners(ShadowRoot root, String elementName) {
+    var events = _templateDelegates[elementName];
+    if (events == null) return;
+    for (var eventName in events) {
+      _addNodeListener(root, eventName,
+          (e) => _instanceEventListener(eventName, e));
+    }
+  }
+
+  void _hostEventListener(String eventName, Event event) {
+    var method = _delegates[eventName];
+    if (event.bubbles && method != null) {
+      _dispatchMethod(this, method, event, host);
+    }
+  }
+
+  void _dispatchMethod(Object receiver, String methodName, Event event,
+      Node target) {
+    var detail = event is CustomEvent ? (event as CustomEvent).detail : null;
+    var args = [event, detail, target];
+
+    var method = new Symbol(methodName);
+    // TODO(sigmund): consider making event listeners list all arguments
+    // explicitly. Unless VM mirrors are optimized first, this reflectClass call
+    // will be expensive once custom elements extend directly from Element (see
+    // dartbug.com/11108).
+    var methodDecl = reflectClass(receiver.runtimeType).methods[method];
+    if (methodDecl != null) {
+      // This will either truncate the argument list or extend it with extra
+      // null arguments, so it will match the signature.
+      // TODO(sigmund): consider accepting optional arguments when we can tell
+      // them appart from named arguments (see http://dartbug.com/11334)
+      args.length = methodDecl.parameters.where((p) => !p.isOptional).length;
+    }
+    reflect(receiver).invoke(method, args);
+    performMicrotaskCheckpoint();
+  }
+
+  bool _instanceEventListener(String eventName, Event event) {
+    if (event.bubbles) {
+      if (event.path == null || !ShadowRoot.supported) {
+        return _listenLocalNoEventPath(eventName, event);
+      } else {
+        return _listenLocal(eventName, event);
+      }
+    }
+    return false;
+  }
+
+  bool _listenLocal(String eventName, Event event) {
+    var controller = null;
+    for (var target in event.path) {
+      // if we hit host, stop
+      if (target == host) return true;
+
+      // find a controller for the target, unless we already found `host`
+      // as a controller
+      controller = (controller == host) ? controller : _findController(target);
+
+      // if we have a controller, dispatch the event, and stop if the handler
+      // returns true
+      if (controller != null
+          && handleEvent(controller, eventName, event, target)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  // TODO(sorvell): remove when ShadowDOM polyfill supports event path.
+  // Note that _findController will not return the expected controller when the
+  // event target is a distributed node.  This is because we cannot traverse
+  // from a composed node to a node in shadowRoot.
+  // This will be addressed via an event path api
+  // https://www.w3.org/Bugs/Public/show_bug.cgi?id=21066
+  bool _listenLocalNoEventPath(String eventName, Event event) {
+    var target = event.target;
+    var controller = null;
+    while (target != null && target != host) {
+      controller = (controller == host) ? controller : _findController(target);
+      if (controller != null
+          && handleEvent(controller, eventName, event, target)) {
+        return true;
+      }
+      target = target.parent;
+    }
+    return false;
+  }
+
+  // TODO(sigmund): investigate if this implementation is correct. Polymer looks
+  // up the shadow-root that contains [node] and uses a weak-hashmap to find the
+  // host associated with that root. This implementation assumes that the
+  // [node] is under [host]'s shadow-root.
+  Element _findController(Node node) => host.xtag;
+
+  bool handleEvent(
+      Element controller, String eventName, Event event, Element element) {
+    // Note: local events are listened only in the shadow root. This dynamic
+    // lookup is used to distinguish determine whether the target actually has a
+    // listener, and if so, to determine lazily what's the target method.
+    var methodName = element.attributes[toHyphenedName(eventName)];
+    if (methodName != null) {
+      _dispatchMethod(controller, methodName, event, element);
+    }
+    return event.bubbles;
+  }
+}
+
+
+/** Event stream providers per event name. */
+// TODO(sigmund): after dartbug.com/11108 is fixed, consider eliminating this
+// table and using reflection instead.
+const Map<String, EventStreamProvider> _eventStreamProviders = const {
+  'onMouseWheel': Element.mouseWheelEvent,
+  'onTransitionEnd': Element.transitionEndEvent,
+  'onAbort': Element.abortEvent,
+  'onBeforeCopy': Element.beforeCopyEvent,
+  'onBeforeCut': Element.beforeCutEvent,
+  'onBeforePaste': Element.beforePasteEvent,
+  'onBlur': Element.blurEvent,
+  'onChange': Element.changeEvent,
+  'onClick': Element.clickEvent,
+  'onContextMenu': Element.contextMenuEvent,
+  'onCopy': Element.copyEvent,
+  'onCut': Element.cutEvent,
+  'onDoubleClick': Element.doubleClickEvent,
+  'onDrag': Element.dragEvent,
+  'onDragEnd': Element.dragEndEvent,
+  'onDragEnter': Element.dragEnterEvent,
+  'onDragLeave': Element.dragLeaveEvent,
+  'onDragOver': Element.dragOverEvent,
+  'onDragStart': Element.dragStartEvent,
+  'onDrop': Element.dropEvent,
+  'onError': Element.errorEvent,
+  'onFocus': Element.focusEvent,
+  'onInput': Element.inputEvent,
+  'onInvalid': Element.invalidEvent,
+  'onKeyDown': Element.keyDownEvent,
+  'onKeyPress': Element.keyPressEvent,
+  'onKeyUp': Element.keyUpEvent,
+  'onLoad': Element.loadEvent,
+  'onMouseDown': Element.mouseDownEvent,
+  'onMouseMove': Element.mouseMoveEvent,
+  'onMouseOut': Element.mouseOutEvent,
+  'onMouseOver': Element.mouseOverEvent,
+  'onMouseUp': Element.mouseUpEvent,
+  'onPaste': Element.pasteEvent,
+  'onReset': Element.resetEvent,
+  'onScroll': Element.scrollEvent,
+  'onSearch': Element.searchEvent,
+  'onSelect': Element.selectEvent,
+  'onSelectStart': Element.selectStartEvent,
+  'onSubmit': Element.submitEvent,
+  'onTouchCancel': Element.touchCancelEvent,
+  'onTouchEnd': Element.touchEndEvent,
+  'onTouchEnter': Element.touchEnterEvent,
+  'onTouchLeave': Element.touchLeaveEvent,
+  'onTouchMove': Element.touchMoveEvent,
+  'onTouchStart': Element.touchStartEvent,
+  'onFullscreenChange': Element.fullscreenChangeEvent,
+  'onFullscreenError': Element.fullscreenErrorEvent,
+  'onAutocomplete': FormElement.autocompleteEvent,
+  'onAutocompleteError': FormElement.autocompleteErrorEvent,
+  'onSpeechChange': InputElement.speechChangeEvent,
+  'onCanPlay': MediaElement.canPlayEvent,
+  'onCanPlayThrough': MediaElement.canPlayThroughEvent,
+  'onDurationChange': MediaElement.durationChangeEvent,
+  'onEmptied': MediaElement.emptiedEvent,
+  'onEnded': MediaElement.endedEvent,
+  'onLoadStart': MediaElement.loadStartEvent,
+  'onLoadedData': MediaElement.loadedDataEvent,
+  'onLoadedMetadata': MediaElement.loadedMetadataEvent,
+  'onPause': MediaElement.pauseEvent,
+  'onPlay': MediaElement.playEvent,
+  'onPlaying': MediaElement.playingEvent,
+  'onProgress': MediaElement.progressEvent,
+  'onRateChange': MediaElement.rateChangeEvent,
+  'onSeeked': MediaElement.seekedEvent,
+  'onSeeking': MediaElement.seekingEvent,
+  'onShow': MediaElement.showEvent,
+  'onStalled': MediaElement.stalledEvent,
+  'onSuspend': MediaElement.suspendEvent,
+  'onTimeUpdate': MediaElement.timeUpdateEvent,
+  'onVolumeChange': MediaElement.volumeChangeEvent,
+  'onWaiting': MediaElement.waitingEvent,
+  'onKeyAdded': MediaElement.keyAddedEvent,
+  'onKeyError': MediaElement.keyErrorEvent,
+  'onKeyMessage': MediaElement.keyMessageEvent,
+  'onNeedKey': MediaElement.needKeyEvent,
+  'onWebGlContextLost': CanvasElement.webGlContextLostEvent,
+  'onWebGlContextRestored': CanvasElement.webGlContextRestoredEvent,
+  'onPointerLockChange': Document.pointerLockChangeEvent,
+  'onPointerLockError': Document.pointerLockErrorEvent,
+  'onReadyStateChange': Document.readyStateChangeEvent,
+  'onSelectionChange': Document.selectionChangeEvent,
+  'onSecurityPolicyViolation': Document.securityPolicyViolationEvent,
+};
diff --git a/pkg/polymer/lib/safe_html.dart b/pkg/polymer/lib/safe_html.dart
new file mode 100644
index 0000000..c15dd5c
--- /dev/null
+++ b/pkg/polymer/lib/safe_html.dart
@@ -0,0 +1,39 @@
+// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// TODO(sigmund): move this library to a shared package? or make part of
+// dart:html?
+library polymer.safe_html;
+
+/** Declares a string that is a well-formed HTML fragment. */
+class SafeHtml {
+
+  /** Underlying html string. */
+  final String _html;
+
+  // TODO(sigmund): provide a constructor that does html validation
+  SafeHtml.unsafe(this._html);
+
+  String toString() => _html;
+
+  operator ==(other) => other is SafeHtml && _html == other._html;
+  int get hashCode => _html.hashCode;
+}
+
+/**
+ * Declares a string that is safe to use in a Uri attribute, such as `<a href=`,
+ * to avoid cross-site scripting (XSS) attacks.
+ */
+class SafeUri {
+  final String _uri;
+
+  // TODO(sigmund): provide a constructor that takes or creates a Uri and
+  // validates that it is safe (not a javascript: scheme, for example)
+  SafeUri.unsafe(this._uri);
+
+  String toString() => _uri;
+
+  operator ==(other) => other is SafeUri && _uri == other._uri;
+  int get hashCode => _uri.hashCode;
+}
diff --git a/pkg/polymer/lib/src/analyzer.dart b/pkg/polymer/lib/src/analyzer.dart
new file mode 100644
index 0000000..193e139
--- /dev/null
+++ b/pkg/polymer/lib/src/analyzer.dart
@@ -0,0 +1,566 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Part of the template compilation that concerns with extracting information
+ * from the HTML parse tree.
+ */
+library analyzer;
+
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/dom_parsing.dart';
+import 'package:source_maps/span.dart' hide SourceFile;
+
+import 'custom_tag_name.dart';
+import 'dart_parser.dart' show parseDartCode;
+import 'files.dart';
+import 'info.dart';
+import 'messages.dart';
+import 'summary.dart';
+
+/**
+ * Finds custom elements in this file and the list of referenced files with
+ * component declarations. This is the first pass of analysis on a file.
+ *
+ * Adds emitted error/warning messages to [messages], if [messages] is
+ * supplied.
+ */
+FileInfo analyzeDefinitions(GlobalInfo global, UrlInfo inputUrl,
+    Document document, String packageRoot,
+    Messages messages, {bool isEntryPoint: false}) {
+  var result = new FileInfo(inputUrl, isEntryPoint);
+  var loader = new _ElementLoader(global, result, packageRoot, messages);
+  loader.visit(document);
+  return result;
+}
+
+/**
+ *  Extract relevant information from all files found from the root document.
+ *
+ *  Adds emitted error/warning messages to [messages], if [messages] is
+ *  supplied.
+ */
+void analyzeFile(SourceFile file, Map<String, FileInfo> info,
+                 Iterator<int> uniqueIds, GlobalInfo global,
+                 Messages messages, emulateScopedCss) {
+  var fileInfo = info[file.path];
+  var analyzer = new _Analyzer(fileInfo, uniqueIds, global, messages,
+      emulateScopedCss);
+  analyzer._normalize(fileInfo, info);
+  analyzer.visit(file.document);
+}
+
+
+/** A visitor that walks the HTML to extract all the relevant information. */
+class _Analyzer extends TreeVisitor {
+  final FileInfo _fileInfo;
+  LibraryInfo _currentInfo;
+  Iterator<int> _uniqueIds;
+  GlobalInfo _global;
+  Messages _messages;
+
+  int _generatedClassNumber = 0;
+
+  /**
+   * Whether to keep indentation spaces. Break lines and indentation spaces
+   * within templates are preserved in HTML. When users specify the attribute
+   * 'indentation="remove"' on a template tag, we'll trim those indentation
+   * spaces that occur within that tag and its decendants. If any decendant
+   * specifies 'indentation="preserve"', then we'll switch back to the normal
+   * behavior.
+   */
+  bool _keepIndentationSpaces = true;
+
+  final bool _emulateScopedCss;
+
+  _Analyzer(this._fileInfo, this._uniqueIds, this._global, this._messages,
+      this._emulateScopedCss) {
+    _currentInfo = _fileInfo;
+  }
+
+  void visitElement(Element node) {
+    if (node.tagName == 'script') {
+      // We already extracted script tags in previous phase.
+      return;
+    }
+
+    if (node.tagName == 'style') {
+      // We've already parsed the CSS.
+      // If this is a component remove the style node.
+      if (_currentInfo is ComponentInfo && _emulateScopedCss) node.remove();
+      return;
+    }
+
+    _bindCustomElement(node);
+
+    var lastInfo = _currentInfo;
+    if (node.tagName == 'polymer-element') {
+      // If element is invalid _ElementLoader already reported an error, but
+      // we skip the body of the element here.
+      var name = node.attributes['name'];
+      if (name == null) return;
+
+      ComponentInfo component = _fileInfo.components[name];
+      if (component == null) return;
+
+      _analyzeComponent(component);
+
+      _currentInfo = component;
+
+      // Remove the <element> tag from the tree
+      node.remove();
+    }
+
+    node.attributes.forEach((name, value) {
+      if (name.startsWith('on')) {
+        _validateEventHandler(node, name, value);
+      } else  if (name == 'pseudo' && _currentInfo is ComponentInfo) {
+        // Any component's custom pseudo-element(s) defined?
+        _processPseudoAttribute(node, value.split(' '));
+      }
+    });
+
+    var keepSpaces = _keepIndentationSpaces;
+    if (node.tagName == 'template' &&
+        node.attributes.containsKey('indentation')) {
+      var value = node.attributes['indentation'];
+      if (value != 'remove' && value != 'preserve') {
+        _messages.warning(
+            "Invalid value for 'indentation' ($value). By default we preserve "
+            "the indentation. Valid values are either 'remove' or 'preserve'.",
+            node.sourceSpan);
+      }
+      _keepIndentationSpaces = value != 'remove';
+    }
+
+    // Invoke super to visit children.
+    super.visitElement(node);
+
+    _keepIndentationSpaces = keepSpaces;
+    _currentInfo = lastInfo;
+
+    if (node.tagName == 'body' || node.parent == null) {
+      _fileInfo.body = node;
+    }
+  }
+
+  void _analyzeComponent(ComponentInfo component) {
+    var baseTag = component.extendsTag;
+    component.extendsComponent = baseTag == null ? null
+        : _fileInfo.components[baseTag];
+    if (component.extendsComponent == null && isCustomTag(baseTag)) {
+      _messages.warning(
+          'custom element with tag name ${component.extendsTag} not found.',
+          component.element.sourceSpan);
+    }
+
+    // Now that the component's code has been loaded, we can validate that the
+    // class exists.
+    component.findClassDeclaration(_messages);
+  }
+
+  void _bindCustomElement(Element node) {
+    // <fancy-button>
+    var component = _fileInfo.components[node.tagName];
+    if (component == null) {
+      // TODO(jmesserly): warn for unknown element tags?
+
+      // <button is="fancy-button">
+      var componentName = node.attributes['is'];
+      if (componentName != null) {
+        component = _fileInfo.components[componentName];
+      } else if (isCustomTag(node.tagName)) {
+        componentName = node.tagName;
+      }
+      if (component == null && componentName != null &&
+          componentName != 'polymer-element') {
+        _messages.warning(
+            'custom element with tag name $componentName not found.',
+            node.sourceSpan);
+      }
+    }
+
+    if (component != null) {
+      if (!component.hasConflict) {
+        _currentInfo.usedComponents[component] = true;
+      }
+
+      var baseTag = component.baseExtendsTag;
+      var nodeTag = node.tagName;
+      var hasIsAttribute = node.attributes.containsKey('is');
+
+      if (baseTag != null && !hasIsAttribute) {
+        _messages.warning(
+            'custom element "${component.tagName}" extends from "$baseTag", but'
+            ' this tag will not include the default properties of "$baseTag". '
+            'To fix this, either write this tag as <$baseTag '
+            'is="${component.tagName}"> or remove the "extends" attribute from '
+            'the custom element declaration.', node.sourceSpan);
+      } else if (hasIsAttribute) {
+        if (baseTag == null) {
+          _messages.warning(
+              'custom element "${component.tagName}" doesn\'t declare any type '
+              'extensions. To fix this, either rewrite this tag as '
+              '<${component.tagName}> or add \'extends="$nodeTag"\' to '
+              'the custom element declaration.', node.sourceSpan);
+        } else if (baseTag != nodeTag) {
+          _messages.warning(
+              'custom element "${component.tagName}" extends from "$baseTag". '
+              'Did you mean to write <$baseTag is="${component.tagName}">?',
+              node.sourceSpan);
+        }
+      }
+    }
+  }
+
+  void _processPseudoAttribute(Node node, List<String> values) {
+    List mangledValues = [];
+    for (var pseudoElement in values) {
+      if (_global.pseudoElements.containsKey(pseudoElement)) continue;
+
+      _uniqueIds.moveNext();
+      var newValue = "${pseudoElement}_${_uniqueIds.current}";
+      _global.pseudoElements[pseudoElement] = newValue;
+      // Mangled name of pseudo-element.
+      mangledValues.add(newValue);
+
+      if (!pseudoElement.startsWith('x-')) {
+        // TODO(terry): The name must start with x- otherwise it's not a custom
+        //              pseudo-element.  May want to relax since components no
+        //              longer need to start with x-.  See isse #509 on
+        //              pseudo-element prefix.
+        _messages.warning("Custom pseudo-element must be prefixed with 'x-'.",
+            node.sourceSpan);
+      }
+    }
+
+    // Update the pseudo attribute with the new mangled names.
+    node.attributes['pseudo'] = mangledValues.join(' ');
+  }
+
+  /**
+   * Support for inline event handlers that take expressions.
+   * For example: `on-double-click=myHandler($event, todo)`.
+   */
+  void _validateEventHandler(Element node, String name, String value) {
+    if (!name.startsWith('on-')) {
+      // TODO(jmesserly): do we need an option to suppress this warning?
+      _messages.warning('Event handler $name will be interpreted as an inline '
+          'JavaScript event handler. Use the form '
+          'on-event-name="handlerName" if you want a Dart handler '
+          'that will automatically update the UI based on model changes.',
+          node.sourceSpan);
+    }
+
+    if (value.contains('.') || value.contains('(')) {
+      // TODO(sigmund): should we allow more if we use fancy-syntax?
+      _messages.warning('Invalid event handler body "$value". Declare a method '
+          'in your custom element "void handlerName(event, detail, target)" '
+          'and use the form on-event-name="handlerName".',
+          node.sourceSpan);
+    }
+  }
+
+  /**
+   * Normalizes references in [info]. On the [analyzeDefinitions] phase, the
+   * analyzer extracted names of files and components. Here we link those names
+   * to actual info classes. In particular:
+   *   * we initialize the [FileInfo.components] map in [info] by importing all
+   *     [declaredComponents],
+   *   * we scan all [info.componentLinks] and import their
+   *     [info.declaredComponents], using [files] to map the href to the file
+   *     info. Names in [info] will shadow names from imported files.
+   *   * we fill [LibraryInfo.externalCode] on each component declared in
+   *     [info].
+   */
+  void _normalize(FileInfo info, Map<String, FileInfo> files) {
+    _attachExtenalScript(info, files);
+
+    for (var component in info.declaredComponents) {
+      _addComponent(info, component);
+      _attachExtenalScript(component, files);
+    }
+
+    for (var link in info.componentLinks) {
+      var file = files[link.resolvedPath];
+      // We already issued an error for missing files.
+      if (file == null) continue;
+      file.declaredComponents.forEach((c) => _addComponent(info, c));
+    }
+  }
+
+  /**
+   * Stores a direct reference in [info] to a dart source file that was loaded
+   * in a script tag with the 'src' attribute.
+   */
+  void _attachExtenalScript(LibraryInfo info, Map<String, FileInfo> files) {
+    var externalFile = info.externalFile;
+    if (externalFile != null) {
+      info.externalCode = files[externalFile.resolvedPath];
+      if (info.externalCode != null) info.externalCode.htmlFile = info;
+    }
+  }
+
+  /** Adds a component's tag name to the names in scope for [fileInfo]. */
+  void _addComponent(FileInfo fileInfo, ComponentSummary component) {
+    var existing = fileInfo.components[component.tagName];
+    if (existing != null) {
+      if (existing == component) {
+        // This is the same exact component as the existing one.
+        return;
+      }
+
+      if (existing is ComponentInfo && component is! ComponentInfo) {
+        // Components declared in [fileInfo] shadow component names declared in
+        // imported files.
+        return;
+      }
+
+      if (existing.hasConflict) {
+        // No need to report a second error for the same name.
+        return;
+      }
+
+      existing.hasConflict = true;
+
+      if (component is ComponentInfo) {
+        _messages.error('duplicate custom element definition for '
+            '"${component.tagName}".', existing.sourceSpan);
+        _messages.error('duplicate custom element definition for '
+            '"${component.tagName}" (second location).', component.sourceSpan);
+      } else {
+        _messages.error('imported duplicate custom element definitions '
+            'for "${component.tagName}".', existing.sourceSpan);
+        _messages.error('imported duplicate custom element definitions '
+            'for "${component.tagName}" (second location).',
+            component.sourceSpan);
+      }
+    } else {
+      fileInfo.components[component.tagName] = component;
+    }
+  }
+}
+
+/** A visitor that finds `<link rel="import">` and `<element>` tags.  */
+class _ElementLoader extends TreeVisitor {
+  final GlobalInfo _global;
+  final FileInfo _fileInfo;
+  LibraryInfo _currentInfo;
+  String _packageRoot;
+  bool _inHead = false;
+  Messages _messages;
+
+  /**
+   * Adds emitted warning/error messages to [_messages]. [_messages]
+   * must not be null.
+   */
+  _ElementLoader(this._global, this._fileInfo, this._packageRoot,
+      this._messages) {
+    _currentInfo = _fileInfo;
+  }
+
+  void visitElement(Element node) {
+    switch (node.tagName) {
+      case 'link': visitLinkElement(node); break;
+      case 'element':
+        _messages.warning('<element> elements are not supported, use'
+            ' <polymer-element> instead', node.sourceSpan);
+        break;
+      case 'polymer-element':
+         visitElementElement(node);
+         break;
+      case 'script': visitScriptElement(node); break;
+      case 'head':
+        var savedInHead = _inHead;
+        _inHead = true;
+        super.visitElement(node);
+        _inHead = savedInHead;
+        break;
+      default: super.visitElement(node); break;
+    }
+  }
+
+  /**
+   * Process `link rel="import"` as specified in:
+   * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/components/index.html#link-type-component>
+   */
+  void visitLinkElement(Element node) {
+    var rel = node.attributes['rel'];
+    if (rel != 'component' && rel != 'components' &&
+        rel != 'import' && rel != 'stylesheet') return;
+
+    if (!_inHead) {
+      _messages.warning('link rel="$rel" only valid in '
+          'head.', node.sourceSpan);
+      return;
+    }
+
+    if (rel == 'component' || rel == 'components') {
+      _messages.warning('import syntax is changing, use '
+          'rel="import" instead of rel="$rel".', node.sourceSpan);
+    }
+
+    var href = node.attributes['href'];
+    if (href == null || href == '') {
+      _messages.warning('link rel="$rel" missing href.',
+          node.sourceSpan);
+      return;
+    }
+
+    bool isStyleSheet = rel == 'stylesheet';
+    var urlInfo = UrlInfo.resolve(href, _fileInfo.inputUrl, node.sourceSpan,
+        _packageRoot, _messages, ignoreAbsolute: isStyleSheet);
+    if (urlInfo == null) return;
+    if (isStyleSheet) {
+      _fileInfo.styleSheetHrefs.add(urlInfo);
+    } else {
+      _fileInfo.componentLinks.add(urlInfo);
+    }
+  }
+
+  void visitElementElement(Element node) {
+    // TODO(jmesserly): what do we do in this case? It seems like an <element>
+    // inside a Shadow DOM should be scoped to that <template> tag, and not
+    // visible from the outside.
+    if (_currentInfo is ComponentInfo) {
+      _messages.error('Nested component definitions are not yet supported.',
+          node.sourceSpan);
+      return;
+    }
+
+    var tagName = node.attributes['name'];
+    var extendsTag = node.attributes['extends'];
+
+    if (tagName == null) {
+      _messages.error('Missing tag name of the component. Please include an '
+          'attribute like \'name="your-tag-name"\'.',
+          node.sourceSpan);
+      return;
+    }
+
+    var component = new ComponentInfo(node, _fileInfo, tagName, extendsTag);
+    _fileInfo.declaredComponents.add(component);
+    _addComponent(component);
+
+    var lastInfo = _currentInfo;
+    _currentInfo = component;
+    super.visitElement(node);
+    _currentInfo = lastInfo;
+  }
+
+  /** Adds a component's tag name to the global list. */
+  void _addComponent(ComponentInfo component) {
+    var existing = _global.components[component.tagName];
+    if (existing != null) {
+      if (existing.hasConflict) {
+        // No need to report a second error for the same name.
+        return;
+      }
+
+      existing.hasConflict = true;
+
+      _messages.error('duplicate custom element definition for '
+          '"${component.tagName}".', existing.sourceSpan);
+      _messages.error('duplicate custom element definition for '
+          '"${component.tagName}" (second location).', component.sourceSpan);
+    } else {
+      _global.components[component.tagName] = component;
+    }
+  }
+
+  void visitScriptElement(Element node) {
+    var scriptType = node.attributes['type'];
+    var src = node.attributes["src"];
+
+    if (scriptType == null) {
+      // Note: in html5 leaving off type= is fine, but it defaults to
+      // text/javascript. Because this might be a common error, we warn about it
+      // in two cases:
+      //   * an inline script tag in a web component
+      //   * a script src= if the src file ends in .dart (component or not)
+      //
+      // The hope is that neither of these cases should break existing valid
+      // code, but that they'll help component authors avoid having their Dart
+      // code accidentally interpreted as JavaScript by the browser.
+      if (src == null && _currentInfo is ComponentInfo) {
+        _messages.warning('script tag in component with no type will '
+            'be treated as JavaScript. Did you forget type="application/dart"?',
+            node.sourceSpan);
+      }
+      if (src != null && src.endsWith('.dart')) {
+        _messages.warning('script tag with .dart source file but no type will '
+            'be treated as JavaScript. Did you forget type="application/dart"?',
+            node.sourceSpan);
+      }
+      return;
+    }
+
+    if (scriptType != 'application/dart') {
+      if (_currentInfo is ComponentInfo) {
+        // TODO(jmesserly): this warning should not be here, but our compiler
+        // does the wrong thing and it could cause surprising behavior, so let
+        // the user know! See issue #340 for more info.
+        // What we should be doing: leave JS component untouched by compiler.
+        _messages.warning('our custom element implementation does not support '
+            'JavaScript components yet. If this is affecting you please let us '
+            'know at https://github.com/dart-lang/web-ui/issues/340.',
+            node.sourceSpan);
+      }
+
+      return;
+    }
+
+    if (src != null) {
+      if (!src.endsWith('.dart')) {
+        _messages.warning('"application/dart" scripts should '
+            'use the .dart file extension.',
+            node.sourceSpan);
+      }
+
+      if (node.innerHtml.trim() != '') {
+        _messages.error('script tag has "src" attribute and also has script '
+            'text.', node.sourceSpan);
+      }
+
+      if (_currentInfo.codeAttached) {
+        _tooManyScriptsError(node);
+      } else {
+        _currentInfo.externalFile = UrlInfo.resolve(src, _fileInfo.inputUrl,
+            node.sourceSpan, _packageRoot, _messages);
+      }
+      return;
+    }
+
+    if (node.nodes.length == 0) return;
+
+    // I don't think the html5 parser will emit a tree with more than
+    // one child of <script>
+    assert(node.nodes.length == 1);
+    Text text = node.nodes[0];
+
+    if (_currentInfo.codeAttached) {
+      _tooManyScriptsError(node);
+    } else if (_currentInfo == _fileInfo && !_fileInfo.isEntryPoint) {
+      _messages.warning('top-level dart code is ignored on '
+          ' HTML pages that define components, but are not the entry HTML '
+          'file.', node.sourceSpan);
+    } else {
+      _currentInfo.inlinedCode = parseDartCode(
+          _currentInfo.dartCodeUrl.resolvedPath, text.value,
+          text.sourceSpan.start);
+      if (_currentInfo.userCode.partOf != null) {
+        _messages.error('expected a library, not a part.',
+            node.sourceSpan);
+      }
+    }
+  }
+
+  void _tooManyScriptsError(Node node) {
+    var location = _currentInfo is ComponentInfo ?
+        'a custom element declaration' : 'the top-level HTML page';
+
+    _messages.error('there should be only one dart script tag in $location.',
+        node.sourceSpan);
+  }
+}
diff --git a/pkg/polymer/lib/src/compiler.dart b/pkg/polymer/lib/src/compiler.dart
new file mode 100644
index 0000000..5e2c98f
--- /dev/null
+++ b/pkg/polymer/lib/src/compiler.dart
@@ -0,0 +1,770 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library compiler;
+
+import 'dart:async';
+import 'dart:collection' show SplayTreeMap;
+import 'dart:json' as json;
+
+import 'package:analyzer_experimental/src/generated/ast.dart' show Directive, UriBasedDirective;
+import 'package:csslib/visitor.dart' show StyleSheet, treeToDebugString;
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/parser.dart';
+import 'package:observe/transform.dart' show transformObservables;
+import 'package:source_maps/span.dart' show Span;
+import 'package:source_maps/refactor.dart' show TextEditTransaction;
+import 'package:source_maps/printer.dart';
+
+import 'analyzer.dart';
+import 'css_analyzer.dart' show analyzeCss, findUrlsImported,
+       findImportsInStyleSheet, parseCss;
+import 'css_emitters.dart' show rewriteCssUris,
+       emitComponentStyleSheet, emitOriginalCss, emitStyleSheet;
+import 'dart_parser.dart';
+import 'emitters.dart';
+import 'file_system.dart';
+import 'files.dart';
+import 'info.dart';
+import 'messages.dart';
+import 'compiler_options.dart';
+import 'paths.dart';
+import 'utils.dart';
+
+/**
+ * Parses an HTML file [contents] and returns a DOM-like tree.
+ * Note that [contents] will be a [String] if coming from a browser-based
+ * [FileSystem], or it will be a [List<int>] if running on the command line.
+ *
+ * Adds emitted error/warning to [messages], if [messages] is supplied.
+ */
+Document parseHtml(contents, String sourcePath, Messages messages) {
+  var parser = new HtmlParser(contents, generateSpans: true,
+      sourceUrl: sourcePath);
+  var document = parser.parse();
+
+  // Note: errors aren't fatal in HTML (unless strict mode is on).
+  // So just print them as warnings.
+  for (var e in parser.errors) {
+    messages.warning(e.message, e.span);
+  }
+  return document;
+}
+
+/** Compiles an application written with Dart web components. */
+class Compiler {
+  final FileSystem fileSystem;
+  final CompilerOptions options;
+  final List<SourceFile> files = <SourceFile>[];
+  final List<OutputFile> output = <OutputFile>[];
+
+  String _mainPath;
+  String _resetCssFile;
+  StyleSheet _cssResetStyleSheet;
+  PathMapper _pathMapper;
+  Messages _messages;
+
+  FutureGroup _tasks;
+  Set _processed;
+
+  /** Information about source [files] given their href. */
+  final Map<String, FileInfo> info = new SplayTreeMap<String, FileInfo>();
+  final _edits = new Map<DartCodeInfo, TextEditTransaction>();
+
+  final GlobalInfo global = new GlobalInfo();
+
+  /** Creates a compiler with [options] using [fileSystem]. */
+  Compiler(this.fileSystem, this.options, this._messages) {
+    _mainPath = options.inputFile;
+    var mainDir = path.dirname(_mainPath);
+    var baseDir = options.baseDir != null ? options.baseDir : mainDir;
+    var outputDir = options.outputDir != null ? options.outputDir : mainDir;
+    var packageRoot = options.packageRoot != null ? options.packageRoot
+        : path.join(path.dirname(_mainPath), 'packages');
+
+    if (options.resetCssFile != null) {
+      _resetCssFile = options.resetCssFile;
+      if (path.isRelative(_resetCssFile)) {
+        // If CSS reset file path is relative from our current path.
+        _resetCssFile = path.resolve(_resetCssFile);
+      }
+    }
+
+    // Normalize paths - all should be relative or absolute paths.
+    if (path.isAbsolute(_mainPath) || path.isAbsolute(baseDir) ||
+        path.isAbsolute(outputDir) || path.isAbsolute(packageRoot)) {
+      if (path.isRelative(_mainPath)) _mainPath = path.resolve(_mainPath);
+      if (path.isRelative(baseDir)) baseDir = path.resolve(baseDir);
+      if (path.isRelative(outputDir)) outputDir = path.resolve(outputDir);
+      if (path.isRelative(packageRoot)) {
+        packageRoot = path.resolve(packageRoot);
+      }
+    }
+    _pathMapper = new PathMapper(
+        baseDir, outputDir, packageRoot, options.forceMangle,
+        options.rewriteUrls);
+  }
+
+  /** Compile the application starting from the given input file. */
+  Future run() {
+    if (path.basename(_mainPath).endsWith('.dart')) {
+      _messages.error("Please provide an HTML file as your entry point.",
+          null);
+      return new Future.value(null);
+    }
+    return _parseAndDiscover(_mainPath).then((_) {
+      _analyze();
+
+      // Analyze all CSS files.
+      _time('Analyzed Style Sheets', '', () =>
+          analyzeCss(_pathMapper.packageRoot, files, info,
+              global.pseudoElements, _messages,
+              warningsAsErrors: options.warningsAsErrors));
+
+      // TODO(jmesserly): need to go through our errors, and figure out if some
+      // of them should be warnings instead.
+      if (_messages.hasErrors || options.analysisOnly) return;
+      _transformDart();
+      _emit();
+    });
+  }
+
+  /**
+   * Asynchronously parse [inputFile] and transitively discover web components
+   * to load and parse. Returns a future that completes when all files are
+   * processed.
+   */
+  Future _parseAndDiscover(String inputFile) {
+    _tasks = new FutureGroup();
+    _processed = new Set();
+    _processed.add(inputFile);
+    _tasks.add(_parseHtmlFile(new UrlInfo(inputFile, inputFile, null)));
+    return _tasks.future;
+  }
+
+  void _processHtmlFile(UrlInfo inputUrl, SourceFile file) {
+    if (file == null) return;
+
+    bool isEntryPoint = _processed.length == 1;
+
+    files.add(file);
+
+    var fileInfo = _time('Analyzed definitions', inputUrl.url, () {
+      return analyzeDefinitions(global, inputUrl, file.document,
+        _pathMapper.packageRoot, _messages, isEntryPoint: isEntryPoint);
+    });
+    info[inputUrl.resolvedPath] = fileInfo;
+
+    if (isEntryPoint && _resetCssFile != null) {
+      _processed.add(_resetCssFile);
+      _tasks.add(_parseCssFile(new UrlInfo(_resetCssFile, _resetCssFile,
+          null)));
+    }
+
+    _setOutputFilenames(fileInfo);
+    _processImports(fileInfo);
+
+    // Load component files referenced by [file].
+    for (var link in fileInfo.componentLinks) {
+      _loadFile(link, _parseHtmlFile);
+    }
+
+    // Load stylesheet files referenced by [file].
+    for (var link in fileInfo.styleSheetHrefs) {
+      _loadFile(link, _parseCssFile);
+    }
+
+    // Load .dart files being referenced in the page.
+    _loadFile(fileInfo.externalFile, _parseDartFile);
+
+    // Process any @imports inside of a <style> tag.
+    var urlInfos = findUrlsImported(fileInfo, fileInfo.inputUrl,
+        _pathMapper.packageRoot, file.document, _messages, options);
+    for (var urlInfo in urlInfos) {
+      _loadFile(urlInfo, _parseCssFile);
+    }
+
+    // Load .dart files being referenced in components.
+    for (var component in fileInfo.declaredComponents) {
+      if (component.externalFile != null) {
+        _loadFile(component.externalFile, _parseDartFile);
+      } else if (component.userCode != null) {
+        _processImports(component);
+      }
+
+      // Process any @imports inside of the <style> tag in a component.
+      var urlInfos = findUrlsImported(component,
+          component.declaringFile.inputUrl, _pathMapper.packageRoot,
+          component.element, _messages, options);
+      for (var urlInfo in urlInfos) {
+        _loadFile(urlInfo, _parseCssFile);
+      }
+    }
+  }
+
+  /**
+   * Helper function to load [urlInfo] and parse it using [loadAndParse] if it
+   * hasn't been loaded before.
+   */
+  void _loadFile(UrlInfo urlInfo, Future loadAndParse(UrlInfo inputUrl)) {
+    if (urlInfo == null) return;
+    var resolvedPath = urlInfo.resolvedPath;
+    if (!_processed.contains(resolvedPath)) {
+      _processed.add(resolvedPath);
+      _tasks.add(loadAndParse(urlInfo));
+    }
+  }
+
+  void _setOutputFilenames(FileInfo fileInfo) {
+    var filePath = fileInfo.dartCodeUrl.resolvedPath;
+    fileInfo.outputFilename = _pathMapper.mangle(path.basename(filePath),
+        '.dart', path.extension(filePath) == '.html');
+    for (var component in fileInfo.declaredComponents) {
+      var externalFile = component.externalFile;
+      var name = null;
+      if (externalFile != null) {
+        name = _pathMapper.mangle(
+            path.basename(externalFile.resolvedPath), '.dart');
+      } else {
+        var declaringFile = component.declaringFile;
+        var prefix = path.basename(declaringFile.inputUrl.resolvedPath);
+        if (declaringFile.declaredComponents.length == 1
+            && !declaringFile.codeAttached && !declaringFile.isEntryPoint) {
+          name = _pathMapper.mangle(prefix, '.dart', true);
+        } else {
+          var componentName = component.tagName.replaceAll('-', '_');
+          name = _pathMapper.mangle('${prefix}_$componentName', '.dart', true);
+        }
+      }
+      component.outputFilename = name;
+    }
+  }
+
+  /** Parse an HTML file. */
+  Future _parseHtmlFile(UrlInfo inputUrl) {
+    if (!_pathMapper.checkInputPath(inputUrl, _messages)) {
+      return new Future<SourceFile>.value(null);
+    }
+    var filePath = inputUrl.resolvedPath;
+    return fileSystem.readTextOrBytes(filePath)
+        .catchError((e) => _readError(e, inputUrl))
+        .then((source) {
+          if (source == null) return;
+          var file = new SourceFile(filePath);
+          file.document = _time('Parsed', filePath,
+              () => parseHtml(source, filePath, _messages));
+          _processHtmlFile(inputUrl, file);
+        });
+  }
+
+  /** Parse a Dart file. */
+  Future _parseDartFile(UrlInfo inputUrl) {
+    if (!_pathMapper.checkInputPath(inputUrl, _messages)) {
+      return new Future<SourceFile>.value(null);
+    }
+    var filePath = inputUrl.resolvedPath;
+    return fileSystem.readText(filePath)
+        .catchError((e) => _readError(e, inputUrl))
+        .then((code) {
+          if (code == null) return;
+          var file = new SourceFile(filePath, type: SourceFile.DART);
+          file.code = code;
+          _processDartFile(inputUrl, file);
+        });
+  }
+
+  /** Parse a stylesheet file. */
+  Future _parseCssFile(UrlInfo inputUrl) {
+    if (!options.emulateScopedCss ||
+        !_pathMapper.checkInputPath(inputUrl, _messages)) {
+      return new Future<SourceFile>.value(null);
+    }
+    var filePath = inputUrl.resolvedPath;
+    return fileSystem.readText(filePath)
+        .catchError((e) => _readError(e, inputUrl, isWarning: true))
+        .then((code) {
+          if (code == null) return;
+          var file = new SourceFile(filePath, type: SourceFile.STYLESHEET);
+          file.code = code;
+          _processCssFile(inputUrl, file);
+        });
+  }
+
+
+  SourceFile _readError(error, UrlInfo inputUrl, {isWarning: false}) {
+    var message = 'unable to open file "${inputUrl.resolvedPath}"';
+    if (options.verbose) {
+      message = '$message. original message:\n $error';
+    }
+    if (isWarning) {
+      _messages.warning(message, inputUrl.sourceSpan);
+    } else {
+      _messages.error(message, inputUrl.sourceSpan);
+    }
+    return null;
+  }
+
+  void _processDartFile(UrlInfo inputUrl, SourceFile dartFile) {
+    if (dartFile == null) return;
+
+    files.add(dartFile);
+
+    var resolvedPath = inputUrl.resolvedPath;
+    var fileInfo = new FileInfo(inputUrl);
+    info[resolvedPath] = fileInfo;
+    fileInfo.inlinedCode = parseDartCode(resolvedPath, dartFile.code);
+    fileInfo.outputFilename =
+        _pathMapper.mangle(path.basename(resolvedPath), '.dart', false);
+
+    _processImports(fileInfo);
+  }
+
+  void _processImports(LibraryInfo library) {
+    if (library.userCode == null) return;
+
+    for (var directive in library.userCode.directives) {
+      _loadFile(_getDirectiveUrlInfo(library, directive), _parseDartFile);
+    }
+  }
+
+  void _processCssFile(UrlInfo inputUrl, SourceFile cssFile) {
+    if (cssFile == null) return;
+
+    files.add(cssFile);
+
+    var fileInfo = new FileInfo(inputUrl);
+    info[inputUrl.resolvedPath] = fileInfo;
+
+    var styleSheet = parseCss(cssFile.code, _messages, options);
+    if (inputUrl.url == _resetCssFile) {
+      _cssResetStyleSheet = styleSheet;
+    } else if (styleSheet != null) {
+      _resolveStyleSheetImports(inputUrl, cssFile.path, styleSheet);
+      fileInfo.styleSheets.add(styleSheet);
+    }
+  }
+
+  /** Load and parse all style sheets referenced with an @imports. */
+  void _resolveStyleSheetImports(UrlInfo inputUrl, String processingFile,
+      StyleSheet styleSheet) {
+    var urlInfos = _time('CSS imports', processingFile, () =>
+        findImportsInStyleSheet(styleSheet, _pathMapper.packageRoot, inputUrl,
+            _messages));
+
+    for (var urlInfo in urlInfos) {
+      if (urlInfo == null) break;
+      // Load any @imported stylesheet files referenced in this style sheet.
+      _loadFile(urlInfo, _parseCssFile);
+    }
+  }
+
+  String _directiveUri(Directive directive) {
+    var uriDirective = (directive as UriBasedDirective).uri;
+    return (uriDirective as dynamic).value;
+  }
+
+  UrlInfo _getDirectiveUrlInfo(LibraryInfo library, Directive directive) {
+    var uri = _directiveUri(directive);
+    if (uri.startsWith('dart:')) return null;
+    if (uri.startsWith('package:') && uri.startsWith('package:polymer/')) {
+      // Don't process our own package -- we'll implement @observable manually.
+      return null;
+    }
+
+    var span = library.userCode.sourceFile.span(
+        directive.offset, directive.end);
+    return UrlInfo.resolve(uri, library.dartCodeUrl, span,
+        _pathMapper.packageRoot, _messages);
+  }
+
+  /**
+   * Transform Dart source code.
+   * Currently, the only transformation is [transformObservables].
+   * Calls _emitModifiedDartFiles to write the transformed files.
+   */
+  void _transformDart() {
+    var libraries = _findAllDartLibraries();
+
+    var transformed = [];
+    for (var lib in libraries) {
+      var userCode = lib.userCode;
+      var transaction = transformObservables(userCode.compilationUnit,
+          userCode.sourceFile, userCode.code, _messages);
+      if (transaction != null) {
+        _edits[lib.userCode] = transaction;
+        if (transaction.hasEdits) {
+          transformed.add(lib);
+        } else if (lib.htmlFile != null) {
+          // All web components will be transformed too. Track that.
+          transformed.add(lib);
+        }
+      }
+    }
+
+    _findModifiedDartFiles(libraries, transformed);
+
+    libraries.forEach(_fixImports);
+
+    _emitModifiedDartFiles(libraries);
+  }
+
+  /**
+   * Finds all Dart code libraries.
+   * Each library will have [LibraryInfo.inlinedCode] that is non-null.
+   * Also each inlinedCode will be unique.
+   */
+  List<LibraryInfo> _findAllDartLibraries() {
+    var libs = <LibraryInfo>[];
+    void _addLibrary(LibraryInfo lib) {
+      if (lib.inlinedCode != null) libs.add(lib);
+    }
+
+    for (var sourceFile in files) {
+      var file = info[sourceFile.path];
+      _addLibrary(file);
+      file.declaredComponents.forEach(_addLibrary);
+    }
+
+    // Assert that each file path is unique.
+    assert(_uniquePaths(libs));
+    return libs;
+  }
+
+  bool _uniquePaths(List<LibraryInfo> libs) {
+    var seen = new Set();
+    for (var lib in libs) {
+      if (seen.contains(lib.inlinedCode)) {
+        throw new StateError('internal error: '
+            'duplicate user code for ${lib.dartCodeUrl.resolvedPath}.'
+            ' Files were: $files');
+      }
+      seen.add(lib.inlinedCode);
+    }
+    return true;
+  }
+
+  /**
+   * Queue modified Dart files to be written.
+   * This will not write files that are handled by [WebComponentEmitter] and
+   * [EntryPointEmitter].
+   */
+  void _emitModifiedDartFiles(List<LibraryInfo> libraries) {
+    for (var lib in libraries) {
+      // Components will get emitted by WebComponentEmitter, and the
+      // entry point will get emitted by MainPageEmitter.
+      // So we only need to worry about other .dart files.
+      if (lib.modified && lib is FileInfo &&
+          lib.htmlFile == null && !lib.isEntryPoint) {
+        var transaction = _edits[lib.userCode];
+
+        // Save imports that were modified by _fixImports.
+        for (var d in lib.userCode.directives) {
+          transaction.edit(d.offset, d.end, d.toString());
+        }
+
+        if (!lib.userCode.isPart) {
+          var pos = lib.userCode.firstPartOffset;
+          // Note: we use a different prefix than "autogenerated" to make
+          // ChangeRecord unambiguous. Otherwise it would be imported by this
+          // and polymer, resulting in a collision.
+          // TODO(jmesserly): only generate this for libraries that need it.
+          transaction.edit(pos, pos, "\nimport "
+              "'package:observe/observe.dart' as __observe;\n");
+        }
+        _emitFileAndSourceMaps(lib, transaction.commit(), lib.dartCodeUrl);
+      }
+    }
+  }
+
+  /**
+   * This method computes which Dart files have been modified, starting
+   * from [transformed] and marking recursively through all files that import
+   * the modified files.
+   */
+  void _findModifiedDartFiles(List<LibraryInfo> libraries,
+      List<FileInfo> transformed) {
+
+    if (transformed.length == 0) return;
+
+    // Compute files that reference each file, then use this information to
+    // flip the modified bit transitively. This is a lot simpler than trying
+    // to compute it the other way because of circular references.
+    for (var lib in libraries) {
+      for (var directive in lib.userCode.directives) {
+        var importPath = _getDirectiveUrlInfo(lib, directive);
+        if (importPath == null) continue;
+
+        var importInfo = info[importPath.resolvedPath];
+        if (importInfo != null) {
+          importInfo.referencedBy.add(lib);
+        }
+      }
+    }
+
+    // Propegate the modified bit to anything that references a modified file.
+    void setModified(LibraryInfo library) {
+      if (library.modified) return;
+      library.modified = true;
+      library.referencedBy.forEach(setModified);
+    }
+    transformed.forEach(setModified);
+
+    for (var lib in libraries) {
+      // We don't need this anymore, so free it.
+      lib.referencedBy = null;
+    }
+  }
+
+  void _fixImports(LibraryInfo library) {
+    // Fix imports. Modified files must use the generated path, otherwise
+    // we need to make the path relative to the input.
+    for (var directive in library.userCode.directives) {
+      var importPath = _getDirectiveUrlInfo(library, directive);
+      if (importPath == null) continue;
+      var importInfo = info[importPath.resolvedPath];
+      if (importInfo == null) continue;
+
+      String newUri = null;
+      if (importInfo.modified) {
+        // Use the generated URI for this file.
+        newUri = _pathMapper.importUrlFor(library, importInfo);
+      } else if (options.rewriteUrls) {
+        // Get the relative path to the input file.
+        newUri = _pathMapper.transformUrl(
+            library.dartCodeUrl.resolvedPath, directive.uri.value);
+      }
+      if (newUri != null) {
+        directive.uri = createStringLiteral(newUri);
+      }
+    }
+  }
+
+  /** Run the analyzer on every input html file. */
+  void _analyze() {
+    var uniqueIds = new IntIterator();
+    for (var file in files) {
+      if (file.isHtml) {
+        _time('Analyzed contents', file.path, () =>
+            analyzeFile(file, info, uniqueIds, global, _messages,
+              options.emulateScopedCss));
+      }
+    }
+  }
+
+  /** Emit the generated code corresponding to each input file. */
+  void _emit() {
+    for (var file in files) {
+      if (file.isDart || file.isStyleSheet) continue;
+      _time('Codegen', file.path, () {
+        var fileInfo = info[file.path];
+        _emitComponents(fileInfo);
+      });
+    }
+
+    var entryPoint = files[0];
+    assert(info[entryPoint.path].isEntryPoint);
+    _emitMainDart(entryPoint);
+    _emitMainHtml(entryPoint);
+
+    assert(_unqiueOutputs());
+  }
+
+  bool _unqiueOutputs() {
+    var seen = new Set();
+    for (var file in output) {
+      if (seen.contains(file.path)) {
+        throw new StateError('internal error: '
+            'duplicate output file ${file.path}. Files were: $output');
+      }
+      seen.add(file.path);
+    }
+    return true;
+  }
+
+  /** Emit the main .dart file. */
+  void _emitMainDart(SourceFile file) {
+    var fileInfo = info[file.path];
+
+    var codeInfo = fileInfo.userCode;
+    if (codeInfo != null) {
+      var printer = new NestedPrinter(0);
+      if (codeInfo.libraryName == null) {
+        printer.addLine('library ${fileInfo.libraryName};');
+      }
+      printer.add(codeInfo.code);
+      _emitFileAndSourceMaps(fileInfo, printer, fileInfo.dartCodeUrl);
+    }
+  }
+
+  // TODO(jmesserly): refactor this out of Compiler.
+  /** Generate an html file with the (trimmed down) main html page. */
+  void _emitMainHtml(SourceFile file) {
+    var fileInfo = info[file.path];
+
+    var bootstrapName = '${path.basename(file.path)}_bootstrap.dart';
+    var bootstrapPath = path.join(path.dirname(file.path), bootstrapName);
+    var bootstrapOutPath = _pathMapper.outputPath(bootstrapPath, '');
+    var bootstrapOutName = path.basename(bootstrapOutPath);
+    var bootstrapInfo = new FileInfo(new UrlInfo('', bootstrapPath, null));
+    var printer = generateBootstrapCode(bootstrapInfo, fileInfo, global,
+        _pathMapper, options);
+    printer.build(bootstrapOutPath);
+    output.add(new OutputFile(
+          bootstrapOutPath, printer.text, source: file.path));
+
+    var document = file.document;
+    var hasCss = _emitAllCss();
+    transformMainHtml(document, fileInfo, _pathMapper, hasCss,
+        options.rewriteUrls, _messages, global, bootstrapOutName);
+    output.add(new OutputFile(_pathMapper.outputPath(file.path, '.html'),
+        document.outerHtml, source: file.path));
+  }
+
+  // TODO(jmesserly): refactor this and other CSS related transforms out of
+  // Compiler.
+  /**
+   * Generate an CSS file for all style sheets (main and components).
+   * Returns true if a file was generated, otherwise false.
+   */
+  bool _emitAllCss() {
+    if (!options.emulateScopedCss) return false;
+
+    var buff = new StringBuffer();
+
+    // Emit all linked style sheet files first.
+    for (var file in files) {
+      var css = new StringBuffer();
+      var fileInfo = info[file.path];
+      if (file.isStyleSheet) {
+        for (var styleSheet in fileInfo.styleSheets) {
+          // Translate any URIs in CSS.
+          rewriteCssUris(_pathMapper, fileInfo.inputUrl.resolvedPath,
+              options.rewriteUrls, styleSheet);
+          css.write(
+              '/* Auto-generated from style sheet href = ${file.path} */\n'
+              '/* DO NOT EDIT. */\n\n');
+          css.write(emitStyleSheet(styleSheet, fileInfo));
+          css.write('\n\n');
+        }
+
+        // Emit the linked style sheet in the output directory.
+        if (fileInfo.inputUrl.url != _resetCssFile) {
+          var outCss = _pathMapper.outputPath(fileInfo.inputUrl.resolvedPath,
+              '');
+          output.add(new OutputFile(outCss, css.toString()));
+        }
+      }
+    }
+
+    // Emit all CSS for each component (style scoped).
+    for (var file in files) {
+      if (file.isHtml) {
+        var fileInfo = info[file.path];
+        for (var component in fileInfo.declaredComponents) {
+          for (var styleSheet in component.styleSheets) {
+            // Translate any URIs in CSS.
+            rewriteCssUris(_pathMapper, fileInfo.inputUrl.resolvedPath,
+                options.rewriteUrls, styleSheet);
+
+            if (buff.isEmpty) {
+              buff.write(
+                  '/* Auto-generated from components style tags. */\n'
+                  '/* DO NOT EDIT. */\n\n');
+            }
+            buff.write(
+                '/* ==================================================== \n'
+                '   Component ${component.tagName} stylesheet \n'
+                '   ==================================================== */\n');
+
+            var tagName = component.tagName;
+            if (!component.hasAuthorStyles) {
+              if (_cssResetStyleSheet != null) {
+                // If component doesn't have apply-author-styles then we need to
+                // reset the CSS the styles for the component (if css-reset file
+                // option was passed).
+                buff.write('\n/* Start CSS Reset */\n');
+                var style;
+                if (options.emulateScopedCss) {
+                  style = emitComponentStyleSheet(_cssResetStyleSheet, tagName);
+                } else {
+                  style = emitOriginalCss(_cssResetStyleSheet);
+                }
+                buff.write(style);
+                buff.write('/* End CSS Reset */\n\n');
+              }
+            }
+            if (options.emulateScopedCss) {
+              buff.write(emitComponentStyleSheet(styleSheet, tagName));
+            } else {
+              buff.write(emitOriginalCss(styleSheet));
+            }
+            buff.write('\n\n');
+          }
+        }
+      }
+    }
+
+    if (buff.isEmpty) return false;
+
+    var cssPath = _pathMapper.outputPath(_mainPath, '.css', true);
+    output.add(new OutputFile(cssPath, buff.toString()));
+    return true;
+  }
+
+  /** Emits the Dart code for all components in [fileInfo]. */
+  void _emitComponents(FileInfo fileInfo) {
+    for (var component in fileInfo.declaredComponents) {
+      // TODO(terry): Handle more than one stylesheet per component
+      if (component.styleSheets.length > 1 && options.emulateScopedCss) {
+        var span = component.externalFile != null
+            ? component.externalFile.sourceSpan : null;
+        _messages.warning(
+            'Component has more than one stylesheet - first stylesheet used.',
+            span);
+      }
+      var printer = emitPolymerElement(
+          component, _pathMapper, _edits[component.userCode], options);
+      _emitFileAndSourceMaps(component, printer, component.externalFile);
+    }
+  }
+
+  /**
+   * Emits a file that was created using [NestedPrinter] and it's corresponding
+   * source map file.
+   */
+  void _emitFileAndSourceMaps(
+      LibraryInfo lib, NestedPrinter printer, UrlInfo dartCodeUrl) {
+    // Bail if we had an error generating the code for the file.
+    if (printer == null) return;
+
+    var libPath = _pathMapper.outputLibraryPath(lib);
+    var dir = path.dirname(libPath);
+    var filename = path.basename(libPath);
+    printer.add('\n//# sourceMappingURL=$filename.map');
+    printer.build(libPath);
+    var sourcePath = dartCodeUrl != null ? dartCodeUrl.resolvedPath : null;
+    output.add(new OutputFile(libPath, printer.text, source: sourcePath));
+    // Fix-up the paths in the source map file
+    var sourceMap = json.parse(printer.map);
+    var urls = sourceMap['sources'];
+    for (int i = 0; i < urls.length; i++) {
+      urls[i] = path.relative(urls[i], from: dir);
+    }
+    output.add(new OutputFile(path.join(dir, '$filename.map'),
+          json.stringify(sourceMap)));
+  }
+
+  _time(String logMessage, String filePath, callback(),
+      {bool printTime: false}) {
+    var message = new StringBuffer();
+    message.write(logMessage);
+    var filename = path.basename(filePath);
+    for (int i = (60 - logMessage.length - filename.length); i > 0 ; i--) {
+      message.write(' ');
+    }
+    message.write(filename);
+    return time(message.toString(), callback,
+        printTime: options.verbose || printTime);
+  }
+}
diff --git a/pkg/polymer/lib/src/compiler_options.dart b/pkg/polymer/lib/src/compiler_options.dart
new file mode 100644
index 0000000..4c1d317
--- /dev/null
+++ b/pkg/polymer/lib/src/compiler_options.dart
@@ -0,0 +1,148 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library polymer.src.compiler_options;
+
+import 'package:args/args.dart';
+
+class CompilerOptions {
+  /** Report warnings as errors. */
+  final bool warningsAsErrors;
+
+  /** True to show informational messages. The `--verbose` flag. */
+  final bool verbose;
+
+  /** Remove any generated files. */
+  final bool clean;
+
+  /** Whether to use colors to print messages on the terminal. */
+  final bool useColors;
+
+  /** Force mangling any generated name (even when --out is provided). */
+  final bool forceMangle;
+
+  /** Generate component's dart code, but not the main entry point file. */
+  final bool componentsOnly;
+
+  /** File to process by the compiler. */
+  String inputFile;
+
+  /** Directory where all sources are found. */
+  final String baseDir;
+
+  /** Directory where all output will be generated. */
+  final String outputDir;
+
+  /** Directory where to look for 'package:' imports. */
+  final String packageRoot;
+
+  /**
+   * Adjust resource URLs in the output HTML to point back to the original
+   * location in the file system. Commonly this is enabled during development,
+   * but disabled for deployment.
+   */
+  final bool rewriteUrls;
+
+  /**
+   * Whether to print error messages using the json format understood by the
+   * Dart editor.
+   */
+  final bool jsonFormat;
+
+  /** Emulate scoped styles using a CSS polyfill. */
+  final bool emulateScopedCss;
+
+  /** Use CSS file for CSS Reset. */
+  final String resetCssFile;
+
+  /** Whether to analyze the input for warnings without generating any code. */
+  final bool analysisOnly;
+
+  // We could make this faster, if it ever matters.
+  factory CompilerOptions() => parse(['']);
+
+  CompilerOptions.fromArgs(ArgResults args)
+    : warningsAsErrors = args['warnings_as_errors'],
+      verbose = args['verbose'],
+      clean = args['clean'],
+      useColors = args['colors'],
+      baseDir = args['basedir'],
+      outputDir = args['out'],
+      packageRoot = args['package-root'],
+      rewriteUrls = args['rewrite-urls'],
+      forceMangle = args['unique_output_filenames'],
+      jsonFormat = args['json_format'],
+      componentsOnly = args['components_only'],
+      emulateScopedCss = args['scoped-css'],
+      resetCssFile = args['css-reset'],
+      analysisOnly = !args['deploy'],
+      inputFile = args.rest.length > 0 ? args.rest[0] : null;
+
+  /**
+   * Returns the compiler options parsed from [arguments]. Set [checkUsage] to
+   * false to suppress checking of correct usage or printing help messages.
+   */
+  // TODO(sigmund): convert all flags to use dashes instead of underscores
+  static CompilerOptions parse(List<String> arguments,
+      {bool checkUsage: true}) {
+    var parser = new ArgParser()
+      ..addFlag('verbose', abbr: 'v')
+      ..addFlag('clean', help: 'Remove all generated files',
+          defaultsTo: false, negatable: false)
+      ..addFlag('warnings_as_errors', abbr: 'e',
+          help: 'Warnings handled as errors',
+          defaultsTo: false, negatable: false)
+      ..addFlag('colors', help: 'Display errors/warnings in colored text',
+          defaultsTo: true)
+      ..addFlag('rewrite-urls',
+          help: 'Adjust every resource url to point to the original location in'
+          ' the filesystem.\nThis on by default during development and can be'
+          ' disabled to make the generated code easier to deploy.',
+          defaultsTo: true)
+      ..addFlag('unique_output_filenames', abbr: 'u',
+          help: 'Use unique names for all generated files, so they will not '
+                'have the\nsame name as your input files, even if they are in a'
+                ' different directory',
+          defaultsTo: false, negatable: false)
+      ..addFlag('json_format',
+          help: 'Print error messsages in a json format easy to parse by tools,'
+                ' such as the Dart editor',
+          defaultsTo: false, negatable: false)
+      ..addFlag('components_only',
+          help: 'Generate only the code for component classes, do not generate '
+                'HTML files or the main bootstrap code.',
+          defaultsTo: false, negatable: false)
+      ..addFlag('scoped-css', help: 'Emulate scoped styles with CSS polyfill',
+          defaultsTo: false)
+      ..addOption('css-reset', abbr: 'r', help: 'CSS file used to reset CSS')
+      ..addFlag('deploy', help: 'Emit code used for deploying a polymer app,'
+          ' if false just show warnings and errors (default)',
+          defaultsTo: false, negatable: false)
+      ..addOption('out', abbr: 'o', help: 'Directory where to generate files'
+          ' (defaults to the same directory as the source file)')
+      ..addOption('basedir', help: 'Base directory where to find all source '
+          'files (defaults to the source file\'s directory)')
+      ..addOption('package-root', help: 'Where to find "package:" imports'
+          '(defaults to the "packages/" subdirectory next to the source file)')
+      ..addFlag('help', abbr: 'h', help: 'Displays this help message',
+          defaultsTo: false, negatable: false);
+    try {
+      var results = parser.parse(arguments);
+      if (checkUsage && (results['help'] || results.rest.length == 0)) {
+        showUsage(parser);
+        return null;
+      }
+      return new CompilerOptions.fromArgs(results);
+    } on FormatException catch (e) {
+      print(e.message);
+      showUsage(parser);
+      return null;
+    }
+  }
+
+  static showUsage(parser) {
+    print('Usage: dwc [options...] input.html');
+    print(parser.getUsage());
+  }
+}
diff --git a/pkg/polymer/lib/src/css_analyzer.dart b/pkg/polymer/lib/src/css_analyzer.dart
new file mode 100644
index 0000000..270af5d
--- /dev/null
+++ b/pkg/polymer/lib/src/css_analyzer.dart
@@ -0,0 +1,507 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/** Portion of the analyzer dealing with CSS sources. */
+library polymer.src.css_analyzer;
+
+import 'package:csslib/parser.dart' as css;
+import 'package:csslib/visitor.dart';
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/dom_parsing.dart';
+
+import 'info.dart';
+import 'files.dart' show SourceFile;
+import 'messages.dart';
+import 'compiler_options.dart';
+
+void analyzeCss(String packageRoot, List<SourceFile> files,
+                Map<String, FileInfo> info, Map<String, String> pseudoElements,
+                Messages messages, {warningsAsErrors: false}) {
+  var analyzer = new _AnalyzerCss(packageRoot, info, pseudoElements, messages,
+      warningsAsErrors);
+  for (var file in files) analyzer.process(file);
+  analyzer.normalize();
+}
+
+class _AnalyzerCss {
+  final String packageRoot;
+  final Map<String, FileInfo> info;
+  final Map<String, String> _pseudoElements;
+  final Messages _messages;
+  final bool _warningsAsErrors;
+
+  Set<StyleSheet> allStyleSheets = new Set<StyleSheet>();
+
+  /**
+   * [_pseudoElements] list of known pseudo attributes found in HTML, any
+   * CSS pseudo-elements 'name::custom-element' is mapped to the manged name
+   * associated with the pseudo-element key.
+   */
+  _AnalyzerCss(this.packageRoot, this.info, this._pseudoElements,
+               this._messages, this._warningsAsErrors);
+
+  /**
+   * Run the analyzer on every file that is a style sheet or any component that
+   * has a style tag.
+   */
+  void process(SourceFile file) {
+    var fileInfo = info[file.path];
+    if (file.isStyleSheet || fileInfo.styleSheets.length > 0) {
+      var styleSheets = processVars(fileInfo);
+
+      // Add to list of all style sheets analyzed.
+      allStyleSheets.addAll(styleSheets);
+    }
+
+    // Process any components.
+    for (var component in fileInfo.declaredComponents) {
+      var all = processVars(component);
+
+      // Add to list of all style sheets analyzed.
+      allStyleSheets.addAll(all);
+    }
+
+    processCustomPseudoElements();
+  }
+
+  void normalize() {
+    // Remove all var definitions for all style sheets analyzed.
+    for (var tree in allStyleSheets) new _RemoveVarDefinitions().visitTree(tree);
+  }
+
+  List<StyleSheet> processVars(var libraryInfo) {
+    // Get list of all stylesheet(s) dependencies referenced from this file.
+    var styleSheets = _dependencies(libraryInfo).toList();
+
+    var errors = [];
+    css.analyze(styleSheets, errors: errors, options:
+      [_warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
+
+    // Print errors as warnings.
+    for (var e in errors) {
+      _messages.warning(e.message, e.span);
+    }
+
+    // Build list of all var definitions.
+    Map varDefs = new Map();
+    for (var tree in styleSheets) {
+      var allDefs = (new _VarDefinitions()..visitTree(tree)).found;
+      allDefs.forEach((key, value) {
+        varDefs[key] = value;
+      });
+    }
+
+    // Resolve all definitions to a non-VarUsage (terminal expression).
+    varDefs.forEach((key, value) {
+      for (var expr in (value.expression as Expressions).expressions) {
+        var def = _findTerminalVarDefinition(varDefs, value);
+        varDefs[key] = def;
+      }
+    });
+
+    // Resolve all var usages.
+    for (var tree in styleSheets) new _ResolveVarUsages(varDefs).visitTree(tree);
+
+    return styleSheets;
+  }
+
+  processCustomPseudoElements() {
+    var polyFiller = new _PseudoElementExpander(_pseudoElements);
+    for (var tree in allStyleSheets) {
+      polyFiller.visitTree(tree);
+    }
+  }
+
+  /**
+   * Given a component or file check if any stylesheets referenced.  If so then
+   * return a list of all referenced stylesheet dependencies (@imports or <link
+   * rel="stylesheet" ..>).
+   */
+  Set<StyleSheet> _dependencies(var libraryInfo, {Set<StyleSheet> seen}) {
+    if (seen == null) seen = new Set();
+
+    // Used to resolve all pathing information.
+    var inputUrl = libraryInfo is FileInfo
+        ? libraryInfo.inputUrl
+        : (libraryInfo as ComponentInfo).declaringFile.inputUrl;
+
+    for (var styleSheet in libraryInfo.styleSheets) {
+      if (!seen.contains(styleSheet)) {
+        // TODO(terry): VM uses expandos to implement hashes.  Currently, it's a
+        //              linear (not constant) time cost (see dartbug.com/5746).
+        //              If this bug isn't fixed and performance show's this a
+        //              a problem we'll need to implement our own hashCode or
+        //              use a different key for better perf.
+        // Add the stylesheet.
+        seen.add(styleSheet);
+
+        // Any other imports in this stylesheet?
+        var urlInfos = findImportsInStyleSheet(styleSheet, packageRoot,
+            inputUrl, _messages);
+
+        // Process other imports in this stylesheets.
+        for (var importSS in urlInfos) {
+          var importInfo = info[importSS.resolvedPath];
+          if (importInfo != null) {
+            // Add all known stylesheets processed.
+            seen.addAll(importInfo.styleSheets);
+            // Find dependencies for stylesheet referenced with a
+            // @import
+            for (var ss in importInfo.styleSheets) {
+              var urls = findImportsInStyleSheet(ss, packageRoot, inputUrl,
+                  _messages);
+              for (var url in urls) {
+                _dependencies(info[url.resolvedPath], seen: seen);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return seen;
+  }
+}
+
+/**
+ * Find var- definitions in a style sheet.
+ * [found] list of known definitions.
+ */
+class _VarDefinitions extends Visitor {
+  final Map<String, VarDefinition> found = new Map();
+
+  void visitTree(StyleSheet tree) {
+    visitStyleSheet(tree);
+  }
+
+  visitVarDefinition(VarDefinition node) {
+    // Replace with latest variable definition.
+    found[node.definedName] = node;
+    super.visitVarDefinition(node);
+  }
+
+  void visitVarDefinitionDirective(VarDefinitionDirective node) {
+    visitVarDefinition(node.def);
+  }
+}
+
+/**
+ * Resolve any CSS expression which contains a var() usage to the ultimate real
+ * CSS expression value e.g.,
+ *
+ *    var-one: var(two);
+ *    var-two: #ff00ff;
+ *
+ *    .test {
+ *      color: var(one);
+ *    }
+ *
+ * then .test's color would be #ff00ff
+ */
+class _ResolveVarUsages extends Visitor {
+  final Map<String, VarDefinition> varDefs;
+  bool inVarDefinition = false;
+  bool inUsage = false;
+  Expressions currentExpressions;
+
+  _ResolveVarUsages(this.varDefs);
+
+  void visitTree(StyleSheet tree) {
+    visitStyleSheet(tree);
+  }
+
+  void visitVarDefinition(VarDefinition varDef) {
+    inVarDefinition = true;
+    super.visitVarDefinition(varDef);
+    inVarDefinition = false;
+  }
+
+  void visitExpressions(Expressions node) {
+    currentExpressions = node;
+    super.visitExpressions(node);
+    currentExpressions = null;
+  }
+
+  void visitVarUsage(VarUsage node) {
+    // Don't process other var() inside of a varUsage.  That implies that the
+    // default is a var() too.  Also, don't process any var() inside of a
+    // varDefinition (they're just place holders until we've resolved all real
+    // usages.
+    if (!inUsage && !inVarDefinition && currentExpressions != null) {
+      var expressions = currentExpressions.expressions;
+      var index = expressions.indexOf(node);
+      assert(index >= 0);
+      var def = varDefs[node.name];
+      if (def != null) {
+        // Found a VarDefinition use it.
+        _resolveVarUsage(currentExpressions.expressions, index, def);
+      } else if (node.defaultValues.any((e) => e is VarUsage)) {
+        // Don't have a VarDefinition need to use default values resolve all
+        // default values.
+        var terminalDefaults = [];
+        for (var defaultValue in node.defaultValues) {
+          terminalDefaults.addAll(resolveUsageTerminal(defaultValue));
+        }
+        expressions.replaceRange(index, index + 1, terminalDefaults);
+      } else {
+        // No VarDefinition but default value is a terminal expression; use it.
+        expressions.replaceRange(index, index + 1, node.defaultValues);
+      }
+    }
+
+    inUsage = true;
+    super.visitVarUsage(node);
+    inUsage = false;
+  }
+
+  List<Expression> resolveUsageTerminal(VarUsage usage) {
+    var result = [];
+
+    var varDef = varDefs[usage.name];
+    var expressions;
+    if (varDef == null) {
+      // VarDefinition not found try the defaultValues.
+      expressions = usage.defaultValues;
+    } else {
+      // Use the VarDefinition found.
+      expressions = (varDef.expression as Expressions).expressions;
+    }
+
+    for (var expr in expressions) {
+      if (expr is VarUsage) {
+        // Get terminal value.
+        result.addAll(resolveUsageTerminal(expr));
+      }
+    }
+
+    // We're at a terminal just return the VarDefinition expression.
+    if (result.isEmpty && varDef != null) {
+      result = (varDef.expression as Expressions).expressions;
+    }
+
+    return result;
+  }
+
+  _resolveVarUsage(List<Expressions> expressions, int index,
+                   VarDefinition def) {
+    var defExpressions = (def.expression as Expressions).expressions;
+    expressions.replaceRange(index, index + 1, defExpressions);
+  }
+}
+
+/** Remove all var definitions. */
+class _RemoveVarDefinitions extends Visitor {
+  void visitTree(StyleSheet tree) {
+    visitStyleSheet(tree);
+  }
+
+  void visitStyleSheet(StyleSheet ss) {
+    ss.topLevels.removeWhere((e) => e is VarDefinitionDirective);
+    super.visitStyleSheet(ss);
+  }
+
+  void visitDeclarationGroup(DeclarationGroup node) {
+    node.declarations.removeWhere((e) => e is VarDefinition);
+    super.visitDeclarationGroup(node);
+  }
+}
+
+/**
+ * Process all selectors looking for a pseudo-element in a selector.  If the
+ * name is found in our list of known pseudo-elements.  Known pseudo-elements
+ * are built when parsing a component looking for an attribute named "pseudo".
+ * The value of the pseudo attribute is the name of the custom pseudo-element.
+ * The name is mangled so Dart/JS can't directly access the pseudo-element only
+ * CSS can access a custom pseudo-element (and see issue #510, querying needs
+ * access to custom pseudo-elements).
+ *
+ * Change the custom pseudo-element to be a child of the pseudo attribute's
+ * mangled custom pseudo element name. e.g,
+ *
+ *    .test::x-box
+ *
+ * would become:
+ *
+ *    .test > *[pseudo="x-box_2"]
+ */
+class _PseudoElementExpander extends Visitor {
+  final Map<String, String> _pseudoElements;
+
+  _PseudoElementExpander(this._pseudoElements);
+
+  void visitTree(StyleSheet tree) => visitStyleSheet(tree);
+
+  visitSelector(Selector node) {
+    var selectors = node.simpleSelectorSequences;
+    for (var index = 0; index < selectors.length; index++) {
+      var selector = selectors[index].simpleSelector;
+      if (selector is PseudoElementSelector) {
+        if (_pseudoElements.containsKey(selector.name)) {
+          // Pseudo Element is a custom element.
+          var mangledName = _pseudoElements[selector.name];
+
+          var span = selectors[index].span;
+
+          var attrSelector = new AttributeSelector(
+              new Identifier('pseudo', span), css.TokenKind.EQUALS,
+              mangledName, span);
+          // The wildcard * namespace selector.
+          var wildCard = new ElementSelector(new Wildcard(span), span);
+          selectors[index] = new SimpleSelectorSequence(wildCard, span,
+                  css.TokenKind.COMBINATOR_GREATER);
+          selectors.insert(++index,
+              new SimpleSelectorSequence(attrSelector, span));
+        }
+      }
+    }
+  }
+}
+
+List<UrlInfo> findImportsInStyleSheet(StyleSheet styleSheet,
+    String packageRoot, UrlInfo inputUrl, Messages messages) {
+  var visitor = new _CssImports(packageRoot, inputUrl, messages);
+  visitor.visitTree(styleSheet);
+  return visitor.urlInfos;
+}
+
+/**
+ * Find any imports in the style sheet; normalize the style sheet href and
+ * return a list of all fully qualified CSS files.
+ */
+class _CssImports extends Visitor {
+  final String packageRoot;
+
+  /** Input url of the css file, used to normalize relative import urls. */
+  final UrlInfo inputUrl;
+
+  /** List of all imported style sheets. */
+  final List<UrlInfo> urlInfos = [];
+
+  final Messages _messages;
+
+  _CssImports(this.packageRoot, this.inputUrl, this._messages);
+
+  void visitTree(StyleSheet tree) {
+    visitStyleSheet(tree);
+  }
+
+  void visitImportDirective(ImportDirective node) {
+    var urlInfo = UrlInfo.resolve(node.import, inputUrl,
+        node.span, packageRoot, _messages, ignoreAbsolute: true);
+    if (urlInfo == null) return;
+    urlInfos.add(urlInfo);
+  }
+}
+
+StyleSheet parseCss(String content, Messages messages,
+    CompilerOptions options) {
+  if (content.trim().isEmpty) return null;
+
+  var errors = [];
+
+  // TODO(terry): Add --checked when fully implemented and error handling.
+  var stylesheet = css.parse(content, errors: errors, options:
+      [options.warningsAsErrors ? '--warnings_as_errors' : '', 'memory']);
+
+  // Note: errors aren't fatal in HTML (unless strict mode is on).
+  // So just print them as warnings.
+  for (var e in errors) {
+    messages.warning(e.message, e.span);
+  }
+
+  return stylesheet;
+}
+
+/** Find terminal definition (non VarUsage implies real CSS value). */
+VarDefinition _findTerminalVarDefinition(Map<String, VarDefinition> varDefs,
+                                        VarDefinition varDef) {
+  var expressions = varDef.expression as Expressions;
+  for (var expr in expressions.expressions) {
+    if (expr is VarUsage) {
+      var usageName = (expr as VarUsage).name;
+      var foundDef = varDefs[usageName];
+
+      // If foundDef is unknown check if defaultValues; if it exist then resolve
+      // to terminal value.
+      if (foundDef == null) {
+        // We're either a VarUsage or terminal definition if in varDefs;
+        // either way replace VarUsage with it's default value because the
+        // VarDefinition isn't found.
+        var defaultValues = (expr as VarUsage).defaultValues;
+        var replaceExprs = expressions.expressions;
+        assert(replaceExprs.length == 1);
+        replaceExprs.replaceRange(0, 1, defaultValues);
+        return varDef;
+      }
+      if (foundDef is VarDefinition) {
+        return _findTerminalVarDefinition(varDefs, foundDef);
+      }
+    } else {
+      // Return real CSS property.
+      return varDef;
+    }
+  }
+
+  // Didn't point to a var definition that existed.
+  return varDef;
+}
+
+/**
+ * Find urls imported inside style tags under [info].  If [info] is a FileInfo
+ * then process only style tags in the body (don't process any style tags in a
+ * component).  If [info] is a ComponentInfo only process style tags inside of
+ * the element are processed.  For an [info] of type FileInfo [node] is the
+ * file's document and for an [info] of type ComponentInfo then [node] is the
+ * component's element tag.
+ */
+List<UrlInfo> findUrlsImported(LibraryInfo info, UrlInfo inputUrl,
+    String packageRoot, Node node, Messages messages, CompilerOptions options) {
+  // Process any @imports inside of the <style> tag.
+  var styleProcessor =
+      new _CssStyleTag(packageRoot, info, inputUrl, messages, options);
+  styleProcessor.visit(node);
+  return styleProcessor.imports;
+}
+
+/* Process CSS inside of a style tag. */
+class _CssStyleTag extends TreeVisitor {
+  final String _packageRoot;
+
+  /** Either a FileInfo or ComponentInfo. */
+  final LibraryInfo _info;
+  final Messages _messages;
+  final CompilerOptions _options;
+
+  /**
+   * Path of the declaring file, for a [_info] of type FileInfo it's the file's
+   * path for a type ComponentInfo it's the declaring file path.
+   */
+  final UrlInfo _inputUrl;
+
+  /** List of @imports found. */
+  List<UrlInfo> imports = [];
+
+  _CssStyleTag(this._packageRoot, this._info, this._inputUrl, this._messages,
+      this._options);
+
+  void visitElement(Element node) {
+    // Don't process any style tags inside of element if we're processing a
+    // FileInfo.  The style tags inside of a component defintion will be
+    // processed when _info is a ComponentInfo.
+    if (node.tagName == 'polymer-element' && _info is FileInfo) return;
+    if (node.tagName == 'style') {
+      // Parse the contents of the scoped style tag.
+      var styleSheet = parseCss(node.nodes.single.value, _messages, _options);
+      if (styleSheet != null) {
+        _info.styleSheets.add(styleSheet);
+
+        // Find all imports return list of @imports in this style tag.
+        var urlInfos = findImportsInStyleSheet(styleSheet, _packageRoot,
+            _inputUrl, _messages);
+        imports.addAll(urlInfos);
+      }
+    }
+    super.visitElement(node);
+  }
+}
diff --git a/pkg/polymer/lib/src/css_emitters.dart b/pkg/polymer/lib/src/css_emitters.dart
new file mode 100644
index 0000000..d1863f5
--- /dev/null
+++ b/pkg/polymer/lib/src/css_emitters.dart
@@ -0,0 +1,155 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library polymer.src.css_emitters;
+
+import 'package:csslib/visitor.dart' show Visitor, CssPrinter, ElementSelector,
+       UriTerm, Selector, HostDirective, SimpleSelectorSequence, StyleSheet;
+
+import 'info.dart';
+import 'paths.dart' show PathMapper;
+import 'utils.dart';
+
+void rewriteCssUris(PathMapper pathMapper, String cssPath, bool rewriteUrls,
+    StyleSheet styleSheet) {
+  new _UriVisitor(pathMapper, cssPath, rewriteUrls).visitTree(styleSheet);
+}
+
+/** Compute each CSS URI resource relative from the generated CSS file. */
+class _UriVisitor extends Visitor {
+  /**
+   * Relative path from the output css file to the location of the original
+   * css file that contained the URI to each resource.
+   */
+  final String _pathToOriginalCss;
+
+  factory _UriVisitor(PathMapper pathMapper, String cssPath, bool rewriteUrl) {
+    var cssDir = path.dirname(cssPath);
+    var outCssDir = rewriteUrl ? pathMapper.outputDirPath(cssPath)
+        : path.dirname(cssPath);
+    return new _UriVisitor._internal(path.relative(cssDir, from: outCssDir));
+  }
+
+  _UriVisitor._internal(this._pathToOriginalCss);
+
+  void visitUriTerm(UriTerm node) {
+    // Don't touch URIs that have any scheme (http, etc.).
+    var uri = Uri.parse(node.text);
+    if (uri.host != '') return;
+    if (uri.scheme != '' && uri.scheme != 'package') return;
+
+    node.text = pathToUrl(
+        path.normalize(path.join(_pathToOriginalCss, node.text)));
+  }
+}
+
+
+/** Emit the contents of the style tag outside of a component. */
+String emitStyleSheet(StyleSheet ss, FileInfo file) =>
+  (new _CssEmitter(file.components.keys.toSet())
+      ..visitTree(ss, pretty: true)).toString();
+
+/** Emit a component's style tag content emulating scoped css. */
+String emitComponentStyleSheet(StyleSheet ss, String tagName) =>
+    (new _ComponentCssEmitter(tagName)..visitTree(ss, pretty: true)).toString();
+
+String emitOriginalCss(StyleSheet css) =>
+    (new CssPrinter()..visitTree(css)).toString();
+
+/** Only x-tag name element selectors are emitted as [is="x-"]. */
+class _CssEmitter extends CssPrinter {
+  final Set _componentsTag;
+  _CssEmitter(this._componentsTag);
+
+  void visitElementSelector(ElementSelector node) {
+    // If element selector is a component's tag name, then change selector to
+    // find element who's is attribute's the component's name.
+    if (_componentsTag.contains(node.name)) {
+      emit('[is="${node.name}"]');
+      return;
+    }
+    super.visitElementSelector(node);
+  }
+}
+
+/**
+ * Emits a css stylesheet applying rules to emulate scoped css. The rules adjust
+ * element selectors to include the component's tag name.
+ */
+class _ComponentCssEmitter extends CssPrinter {
+  final String _componentTagName;
+  bool _inHostDirective = false;
+  bool _selectorStartInHostDirective = false;
+
+  _ComponentCssEmitter(this._componentTagName);
+
+  /** Is the element selector an x-tag name. */
+  bool _isSelectorElementXTag(Selector node) {
+    if (node.simpleSelectorSequences.length > 0) {
+      var selector = node.simpleSelectorSequences[0].simpleSelector;
+      return selector is ElementSelector && selector.name == _componentTagName;
+    }
+    return false;
+  }
+
+  void visitSelector(Selector node) {
+    // If the selector starts with an x-tag name don't emit it twice.
+    if (!_isSelectorElementXTag(node)) {
+      if (_inHostDirective) {
+        // Style the element that's hosting the component, therefore don't emit
+        // the descendent combinator (first space after the [is="x-..."]).
+        emit('[is="$_componentTagName"]');
+        // Signal that first simpleSelector must be checked.
+        _selectorStartInHostDirective = true;
+      } else {
+        // Emit its scoped as a descendent (space at end).
+        emit('[is="$_componentTagName"] ');
+      }
+    }
+    super.visitSelector(node);
+  }
+
+  /**
+   * If first simple selector of a ruleset in a @host directive is a wildcard
+   * then don't emit the wildcard.
+   */
+  void visitSimpleSelectorSequence(SimpleSelectorSequence node) {
+    if (_selectorStartInHostDirective) {
+      _selectorStartInHostDirective = false;
+      if (node.simpleSelector.isWildcard) {
+        // Skip the wildcard if first item in the sequence.
+        return;
+      }
+      assert(node.isCombinatorNone);
+    }
+
+    super.visitSimpleSelectorSequence(node);
+  }
+
+  void visitElementSelector(ElementSelector node) {
+    // If element selector is the component's tag name, then change selector to
+    // find element who's is attribute is the component's name.
+    if (_componentTagName == node.name) {
+      emit('[is="$_componentTagName"]');
+      return;
+    }
+    super.visitElementSelector(node);
+  }
+
+  /**
+   * If we're polyfilling scoped styles the @host directive is stripped.  Any
+   * ruleset(s) processed in an @host will fixup the first selector.  See
+   * visitSelector and visitSimpleSelectorSequence in this class, they adjust
+   * the selectors so it styles the element hosting the compopnent.
+   */
+  void visitHostDirective(HostDirective node) {
+    _inHostDirective = true;
+    emit('/* @host */');
+    for (var ruleset in node.rulesets) {
+      ruleset.visit(this);
+    }
+    _inHostDirective = false;
+    emit('/* end of @host */\n');
+  }
+}
diff --git a/pkg/polymer/lib/src/custom_tag_name.dart b/pkg/polymer/lib/src/custom_tag_name.dart
new file mode 100644
index 0000000..637b937
--- /dev/null
+++ b/pkg/polymer/lib/src/custom_tag_name.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library polymer.src.custom_tag_name;
+
+/**
+ * Returns true if this is a valid custom element name. See:
+ * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html#dfn-custom-element-name>
+ */
+bool isCustomTag(String name) {
+  if (name == null || !name.contains('-')) return false;
+
+  // These names have meaning in SVG or MathML, so they aren't allowed as custom
+  // tags.
+  var invalidNames = const {
+    'annotation-xml': '',
+    'color-profile': '',
+    'font-face': '',
+    'font-face-src': '',
+    'font-face-uri': '',
+    'font-face-format': '',
+    'font-face-name': '',
+    'missing-glyph': '',
+  };
+  return !invalidNames.containsKey(name);
+}
diff --git a/pkg/polymer/lib/src/dart_parser.dart b/pkg/polymer/lib/src/dart_parser.dart
new file mode 100644
index 0000000..a2f44c3
--- /dev/null
+++ b/pkg/polymer/lib/src/dart_parser.dart
@@ -0,0 +1,132 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Parser for Dart code based on the experimental analyzer.
+ */
+library dart_parser;
+
+import 'package:analyzer_experimental/src/generated/ast.dart';
+import 'package:analyzer_experimental/src/generated/error.dart';
+import 'package:analyzer_experimental/src/generated/parser.dart';
+import 'package:analyzer_experimental/src/generated/scanner.dart';
+import 'package:source_maps/span.dart' show SourceFile, SourceFileSegment, Location;
+import 'utils.dart' show escapeDartString;
+
+/** Information extracted from a source Dart file. */
+class DartCodeInfo {
+
+  /** Library qualified identifier, if any. */
+  final String libraryName;
+
+  /** Library which the code is part-of, if any. */
+  final String partOf;
+
+  /** Declared imports, exports, and parts. */
+  final List<Directive> directives;
+
+  /** Source file representation used to compute source map information. */
+  final SourceFile sourceFile;
+
+  /** The parsed code. */
+  final CompilationUnit compilationUnit;
+
+  /** The full source code. */
+  final String code;
+
+  DartCodeInfo(this.libraryName, this.partOf, this.directives, code,
+      this.sourceFile, [compilationUnit])
+      : this.code = code,
+        this.compilationUnit = compilationUnit == null
+          ? _parseCompilationUnit(code) : compilationUnit;
+
+  bool get isPart =>
+      compilationUnit.directives.any((d) => d is PartOfDirective);
+
+  int get directivesEnd {
+    if (compilationUnit.directives.length == 0) return 0;
+    return compilationUnit.directives.last.end;
+  }
+
+  /**
+   * The position of the first "part" directive. If none is found,
+   * this behaves like [directivesEnd].
+   */
+  int get firstPartOffset {
+    for (var directive in compilationUnit.directives) {
+      if (directive is PartDirective) return directive.offset;
+    }
+    // No part directives, just return directives end.
+    return directivesEnd;
+  }
+
+  /** Gets the code after the [directives]. */
+  String codeAfterDirectives() => code.substring(directivesEnd);
+
+  ClassDeclaration findClass(String name) {
+    for (var decl in compilationUnit.declarations) {
+      if (decl is ClassDeclaration) {
+        if (decl.name.name == name) return decl;
+      }
+    }
+    return null;
+  }
+}
+
+SimpleStringLiteral createStringLiteral(String contents) {
+  var lexeme = "'${escapeDartString(contents)}'";
+  var token = new StringToken(TokenType.STRING, lexeme, null);
+  return new SimpleStringLiteral.full(token, contents);
+}
+
+
+/**
+ * Parse and extract top-level directives from [code].
+ *
+ */
+// TODO(sigmund): log emitted error/warning messages
+DartCodeInfo parseDartCode(String path, String code, [Location offset]) {
+  var unit = _parseCompilationUnit(code);
+
+  // Extract some information from the compilation unit.
+  String libraryName, partName;
+  var directives = [];
+  int directiveEnd = 0;
+  for (var directive in unit.directives) {
+    if (directive is LibraryDirective) {
+      libraryName = directive.name.name;
+    } else if (directive is PartOfDirective) {
+      partName = directive.libraryName.name;
+    } else {
+      assert(directive is UriBasedDirective);
+      // Normalize the library URI.
+      var uriNode = directive.uri;
+      if (uriNode is! SimpleStringLiteral) {
+        String uri = uriNode.accept(new ConstantEvaluator());
+        directive.uri = createStringLiteral(uri);
+      }
+      directives.add(directive);
+    }
+  }
+
+  var sourceFile = offset == null
+      ? new SourceFile.text(path, code)
+      : new SourceFileSegment(path, code, offset);
+
+  return new DartCodeInfo(libraryName, partName, directives, code,
+      sourceFile, unit);
+}
+
+CompilationUnit _parseCompilationUnit(String code) {
+  var errorListener = new _ErrorCollector();
+  var scanner = new StringScanner(null, code, errorListener);
+  var token = scanner.tokenize();
+  var parser = new Parser(null, errorListener);
+  return parser.parseCompilationUnit(token);
+}
+
+class _ErrorCollector extends AnalysisErrorListener {
+  final errors = new List<AnalysisError>();
+  onError(error) => errors.add(error);
+}
diff --git a/pkg/polymer/lib/src/emitters.dart b/pkg/polymer/lib/src/emitters.dart
new file mode 100644
index 0000000..dbd6816
--- /dev/null
+++ b/pkg/polymer/lib/src/emitters.dart
@@ -0,0 +1,249 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/** Collects several code emitters for the template tool. */
+library emitters;
+
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/dom_parsing.dart' show TreeVisitor;
+import 'package:html5lib/parser.dart' show parseFragment;
+import 'package:source_maps/printer.dart';
+import 'package:source_maps/refactor.dart';
+
+import 'compiler_options.dart';
+import 'css_emitters.dart' show emitStyleSheet, emitOriginalCss;
+import 'html5_utils.dart';
+import 'info.dart' show ComponentInfo, FileInfo, GlobalInfo;
+import 'messages.dart';
+import 'paths.dart' show PathMapper;
+import 'utils.dart' show escapeDartString, path;
+
+/** Generates the class corresponding to a single web component. */
+NestedPrinter emitPolymerElement(ComponentInfo info, PathMapper pathMapper,
+    TextEditTransaction transaction, CompilerOptions options) {
+  if (info.classDeclaration == null) return null;
+
+  var codeInfo = info.userCode;
+  if (transaction == null) {
+    // TODO(sigmund): avoid emitting this file if we don't need to do any
+    // modifications (e.g. no @observable and not adding the libraryName).
+    transaction = new TextEditTransaction(codeInfo.code, codeInfo.sourceFile);
+  }
+  if (codeInfo.libraryName == null) {
+    // For deploy, we need to import the library associated with the component,
+    // so we need to ensure there is a library directive.
+    var libraryName = info.tagName.replaceAll(new RegExp('[-./]'), '_');
+    transaction.edit(0, 0, 'library $libraryName;');
+  }
+  return transaction.commit();
+}
+
+/** The code that will be used to bootstrap the application. */
+NestedPrinter generateBootstrapCode(
+    FileInfo info, FileInfo userMainInfo, GlobalInfo global,
+    PathMapper pathMapper, CompilerOptions options) {
+
+  var printer = new NestedPrinter(0)
+      ..addLine('library app_bootstrap;')
+      ..addLine('')
+      ..addLine("import 'package:polymer/polymer.dart';")
+      ..addLine("import 'dart:mirrors' show currentMirrorSystem;");
+
+  int i = 0;
+  for (var c in global.components.values) {
+    if (c.hasConflict) continue;
+    printer.addLine("import '${pathMapper.importUrlFor(info, c)}' as i$i;");
+    i++;
+  }
+  if (userMainInfo.userCode != null) {
+    printer..addLine("import '${pathMapper.importUrlFor(info, userMainInfo)}' "
+        "as i$i;\n");
+  }
+
+  printer..addLine('')
+      ..addLine('void main() {')
+      ..indent += 1
+      ..addLine("initPolymer([")
+      ..indent += 2;
+
+  for (var c in global.components.values) {
+    if (c.hasConflict) continue;
+    printer.addLine("'${pathMapper.importUrlFor(info, c)}',");
+  }
+
+  if (userMainInfo.userCode != null) {
+    printer.addLine("'${pathMapper.importUrlFor(info, userMainInfo)}',");
+  }
+
+  return printer
+      ..indent -= 1
+      ..addLine('],')
+      ..addLine(
+          "currentMirrorSystem().findLibrary(const Symbol('app_bootstrap'))")
+      ..indent += 2
+      ..addLine(".first.uri.toString());")
+      ..indent -= 4
+      ..addLine('}');
+}
+
+
+/**
+ * Rewrites attributes that contain relative URL (excluding src urls in script
+ * and link tags which are already rewritten by other parts of the compiler).
+*/
+class _AttributeUrlTransform extends TreeVisitor {
+  final String filePath;
+  final PathMapper pathMapper;
+
+  _AttributeUrlTransform(this.filePath, this.pathMapper);
+
+  visitElement(Element node) {
+    if (node.tagName == 'script') return;
+    if (node.tagName == 'link') return;
+
+    for (var key in node.attributes.keys) {
+      if (urlAttributes.contains(key)) {
+        node.attributes[key] =
+            pathMapper.transformUrl(filePath, node.attributes[key]);
+      }
+    }
+    super.visitElement(node);
+  }
+}
+
+final _shadowDomJS = new RegExp(r'shadowdom\..*\.js', caseSensitive: false);
+final _bootJS = new RegExp(r'.*/polymer/boot.js', caseSensitive: false);
+
+/** Trim down the html for the main html page. */
+void transformMainHtml(Document document, FileInfo fileInfo,
+    PathMapper pathMapper, bool hasCss, bool rewriteUrls,
+    Messages messages, GlobalInfo global, String bootstrapOutName) {
+  var filePath = fileInfo.inputUrl.resolvedPath;
+
+  var dartLoaderTag = null;
+  bool shadowDomFound = false;
+  for (var tag in document.queryAll('script')) {
+    var src = tag.attributes['src'];
+    if (src != null) {
+      var last = src.split('/').last;
+      if (last == 'dart.js' || last == 'testing.js') {
+        dartLoaderTag = tag;
+      } else if (_shadowDomJS.hasMatch(last)) {
+        shadowDomFound = true;
+      }
+    }
+    if (tag.attributes['type'] == 'application/dart') {
+      tag.remove();
+    } else if (src != null) {
+      if (_bootJS.hasMatch(src)) {
+        tag.remove();
+      } else if (rewriteUrls) {
+        tag.attributes["src"] = pathMapper.transformUrl(filePath, src);
+      }
+    }
+  }
+
+  for (var tag in document.queryAll('link')) {
+    var href = tag.attributes['href'];
+    var rel = tag.attributes['rel'];
+    if (rel == 'component' || rel == 'components' || rel == 'import') {
+      tag.remove();
+    } else if (href != null && rewriteUrls && !hasCss) {
+      // Only rewrite URL if rewrite on and we're not CSS polyfilling.
+      tag.attributes['href'] = pathMapper.transformUrl(filePath, href);
+    }
+  }
+
+  if (rewriteUrls) {
+    // Transform any element's attribute which is a relative URL.
+    new _AttributeUrlTransform(filePath, pathMapper).visit(document);
+  }
+
+  if (hasCss) {
+    var newCss = pathMapper.mangle(path.basename(filePath), '.css', true);
+    var linkElem = new Element.html(
+        '<link rel="stylesheet" type="text/css" href="$newCss">');
+    document.head.insertBefore(linkElem, null);
+  }
+
+  var styles = document.queryAll('style');
+  if (styles.length > 0) {
+    var allCss = new StringBuffer();
+    fileInfo.styleSheets.forEach((styleSheet) =>
+        allCss.write(emitStyleSheet(styleSheet, fileInfo)));
+    styles[0].nodes.clear();
+    styles[0].nodes.add(new Text(allCss.toString()));
+    for (var i = styles.length - 1; i > 0 ; i--) {
+      styles[i].remove();
+    }
+  }
+
+  // TODO(jmesserly): put this in the global CSS file?
+  // http://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#css-additions
+  document.head.nodes.insert(0, parseFragment(
+      '<style>template { display: none; }</style>'));
+
+  // Move all <element> declarations to the main HTML file
+  // TODO(sigmund): remove this once we have HTMLImports implemented.
+  for (var c in global.components.values) {
+    document.body.nodes.insert(0, new Text('\n'));
+    var fragment = c.element;
+    for (var tag in fragment.queryAll('script')) {
+      // TODO(sigmund): leave script tags around when we start using "boot.js"
+      if (tag.attributes['type'] == 'application/dart') {
+        tag.remove();
+      }
+    }
+    document.body.nodes.insert(0, fragment);
+  }
+
+  if (!shadowDomFound) {
+    // TODO(jmesserly): we probably shouldn't add this automatically.
+    document.body.nodes.add(parseFragment('<script type="text/javascript" '
+        'src="packages/shadow_dom/shadow_dom.debug.js"></script>\n'));
+
+    // JS interop code required for Polymer CSS shimming.
+    document.body.nodes.add(parseFragment('<script type="text/javascript" '
+        'src="packages/browser/interop.js"></script>\n'));
+  }
+
+  var bootstrapScript = parseFragment(
+        '<script type="application/dart" src="$bootstrapOutName"></script>');
+  if (dartLoaderTag == null) {
+    document.body.nodes.add(bootstrapScript);
+    // TODO(jmesserly): turn this warning on.
+    //messages.warning('Missing script to load Dart. '
+    //    'Please add this line to your HTML file: $dartLoader',
+    //    document.body.sourceSpan);
+    // TODO(sigmund): switch to 'boot.js'
+    document.body.nodes.add(parseFragment('<script type="text/javascript" '
+        'src="packages/browser/dart.js"></script>\n'));
+  } else if (dartLoaderTag.parent != document.body) {
+    document.body.nodes.add(bootstrapScript);
+  } else {
+    document.body.insertBefore(bootstrapScript, dartLoaderTag);
+  }
+
+  // Insert the "auto-generated" comment after the doctype, otherwise IE will
+  // go into quirks mode.
+  int commentIndex = 0;
+  DocumentType doctype =
+      document.nodes.firstWhere((n) => n is DocumentType, orElse: () => null);
+  if (doctype != null) {
+    commentIndex = document.nodes.indexOf(doctype) + 1;
+    // TODO(jmesserly): the html5lib parser emits a warning for missing
+    // doctype, but it allows you to put it after comments. Presumably they do
+    // this because some comments won't force IE into quirks mode (sigh). See
+    // this link for more info:
+    //     http://bugzilla.validator.nu/show_bug.cgi?id=836
+    // For simplicity we emit the warning always, like validator.nu does.
+    if (doctype.tagName != 'html' || commentIndex != 1) {
+      messages.warning('file should start with <!DOCTYPE html> '
+          'to avoid the possibility of it being parsed in quirks mode in IE. '
+          'See http://www.w3.org/TR/html5-diff/#doctype', doctype.sourceSpan);
+    }
+  }
+  document.nodes.insert(commentIndex, parseFragment(
+      '\n<!-- This file was auto-generated from $filePath. -->\n'));
+}
diff --git a/pkg/polymer/lib/src/file_system.dart b/pkg/polymer/lib/src/file_system.dart
new file mode 100644
index 0000000..c6af55c
--- /dev/null
+++ b/pkg/polymer/lib/src/file_system.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/** Abstraction for file systems and utility functions to manipulate paths. */
+library file_system;
+
+import 'dart:async';
+
+/**
+ * Abstraction around file system access to work in a variety of different
+ * environments.
+ */
+abstract class FileSystem {
+  /**
+   * Apply all pending writes.  Until this method is called, writeString is not
+   * guaranteed to have any observable impact.
+   */
+  Future flush();
+
+  /**
+   * Reads bytes if possible, but falls back to text if running in a browser.
+   * Return type is either [Future<List<int>>] or [Future<String>].
+   */
+  Future readTextOrBytes(String path);
+
+  /* Like [readTextOrBytes], but decodes bytes as UTF-8. Used for Dart code. */
+  Future<String> readText(String path);
+
+  /**
+   * Writes [text] to file at [path]. Call flush to insure that changes are
+   * visible.
+   */
+  void writeString(String path, String text);
+}
diff --git a/pkg/polymer/lib/src/file_system/console.dart b/pkg/polymer/lib/src/file_system/console.dart
new file mode 100644
index 0000000..6776745
--- /dev/null
+++ b/pkg/polymer/lib/src/file_system/console.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library console;
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:utf';
+import 'package:polymer/src/file_system.dart';
+
+/** File system implementation for console VM (i.e. no browser). */
+class ConsoleFileSystem implements FileSystem {
+
+  /** Pending futures for file write requests. */
+  final _pending = <String, Future>{};
+
+  Future flush() => Future.wait(_pending.values.toList());
+
+  void writeString(String path, String text) {
+    if(!_pending.containsKey(path)) {
+      _pending[path] = new File(path).open(mode: FileMode.WRITE)
+          .then((file) => file.writeString(text))
+          .then((file) => file.close())
+          .whenComplete(() { _pending.remove(path); });
+    }
+  }
+
+  // TODO(jmesserly): even better would be to pass the RandomAccessFile directly
+  // to html5lib. This will require a further restructuring of FileSystem.
+  // Probably it just needs "readHtml" and "readText" methods.
+  Future<List<int>> readTextOrBytes(String path) {
+    return new File(path).open().then(
+        (file) => file.length().then((length) {
+      // TODO(jmesserly): is this guaranteed to read all of the bytes?
+      var buffer = new List<int>(length);
+      return file.readInto(buffer, 0, length)
+          .then((_) => file.close())
+          .then((_) => buffer);
+    }));
+  }
+
+  // TODO(jmesserly): do we support any encoding other than UTF-8 for Dart?
+  Future<String> readText(String path) {
+    return readTextOrBytes(path).then(decodeUtf8);
+  }
+}
diff --git a/pkg/polymer/lib/src/files.dart b/pkg/polymer/lib/src/files.dart
new file mode 100644
index 0000000..9fea763
--- /dev/null
+++ b/pkg/polymer/lib/src/files.dart
@@ -0,0 +1,47 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library files;
+
+import 'package:html5lib/dom.dart';
+
+/** An input file to process by the template compiler. */
+class SourceFile {
+  static const int HTML = 1;
+  static const int DART = 2;
+  static const int STYLESHEET = 3;
+
+  final String path;
+  final int type;
+
+  Document document;
+
+  /** Dart code or contents of a linked style sheet. */
+  String code;
+
+  SourceFile(this.path, {this.type: HTML});
+
+  bool get isDart => type == DART;
+  bool get isHtml => type == HTML;
+  bool get isStyleSheet => type == STYLESHEET;
+
+  String toString() => "#<SourceFile $path>";
+}
+
+/** An output file to generated by the template compiler. */
+class OutputFile {
+  final String path;
+  final String contents;
+
+  /**
+   * Path to the source file that was transformed into this OutputFile, `null`
+   * for files that are generated and do not correspond to an input
+   * [SourceFile].
+   */
+  final String source;
+
+  OutputFile(this.path, this.contents, {this.source});
+
+  String toString() => "#<OutputFile $path>";
+}
diff --git a/pkg/polymer/lib/src/html5_utils.dart b/pkg/polymer/lib/src/html5_utils.dart
new file mode 100644
index 0000000..391c12b
--- /dev/null
+++ b/pkg/polymer/lib/src/html5_utils.dart
@@ -0,0 +1,29 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// TODO(jmesserly): html5lib might be a better home for this.
+// But at the moment we only need it here.
+
+library html5_utils;
+
+/**
+ * HTML attributes that expect a URL value.
+ * <http://dev.w3.org/html5/spec/section-index.html#attributes-1>
+ *
+ * Every one of these attributes is a URL in every context where it is used in
+ * the DOM. The comments show every DOM element where an attribute can be used.
+ */
+const urlAttributes = const [
+  'action',     // in form
+  'background', // in body
+  'cite',       // in blockquote, del, ins, q
+  'data',       // in object
+  'formaction', // in button, input
+  'href',       // in a, area, link, base, command
+  'icon',       // in command
+  'manifest',   // in html
+  'poster',     // in video
+  'src',        // in audio, embed, iframe, img, input, script, source, track,
+                //    video
+];
diff --git a/pkg/polymer/lib/src/info.dart b/pkg/polymer/lib/src/info.dart
new file mode 100644
index 0000000..9a7b530
--- /dev/null
+++ b/pkg/polymer/lib/src/info.dart
@@ -0,0 +1,330 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Datatypes holding information extracted by the analyzer and used by later
+ * phases of the compiler.
+ */
+library polymer.src.info;
+
+import 'dart:collection' show SplayTreeMap, LinkedHashMap;
+
+import 'package:analyzer_experimental/src/generated/ast.dart';
+import 'package:csslib/visitor.dart';
+import 'package:html5lib/dom.dart';
+import 'package:source_maps/span.dart' show Span;
+
+import 'dart_parser.dart' show DartCodeInfo;
+import 'messages.dart';
+import 'summary.dart';
+import 'utils.dart';
+
+/**
+ * Information that is global. Roughly corresponds to `window` and `document`.
+ */
+class GlobalInfo {
+  /**
+   * Pseudo-element names exposed in a component via a pseudo attribute.
+   * The name is only available from CSS (not Dart code) so they're mangled.
+   * The same pseudo-element in different components maps to the same
+   * mangled name (as the pseudo-element is scoped inside of the component).
+   */
+  final Map<String, String> pseudoElements = <String, String>{};
+
+  /** All components declared in the application. */
+  final Map<String, ComponentInfo> components = new SplayTreeMap();
+}
+
+/**
+ * Information for any library-like input. We consider each HTML file a library,
+ * and each component declaration a library as well. Hence we use this as a base
+ * class for both [FileInfo] and [ComponentInfo]. Both HTML files and components
+ * can have .dart code provided by the user for top-level user scripts and
+ * component-level behavior code. This code can either be inlined in the HTML
+ * file or included in a script tag with the "src" attribute.
+ */
+abstract class LibraryInfo implements LibrarySummary {
+
+  /** Whether there is any code associated with the page/component. */
+  bool get codeAttached => inlinedCode != null || externalFile != null;
+
+  /**
+   * The actual inlined code. Use [userCode] if you want the code from this file
+   * or from an external file.
+   */
+  DartCodeInfo inlinedCode;
+
+  /**
+   * If this library's code was loaded using a script tag (e.g. in a component),
+   * [externalFile] has the path to such Dart file relative from the compiler's
+   * base directory.
+   */
+  UrlInfo externalFile;
+
+  /** Info asscociated with [externalFile], if any. */
+  FileInfo externalCode;
+
+  /**
+   * The inverse of [externalCode]. If this .dart file was imported via a script
+   * tag, this refers to the HTML file that imported it.
+   */
+  LibraryInfo htmlFile;
+
+  /** File where the top-level code was defined. */
+  UrlInfo get dartCodeUrl;
+
+  /**
+   * Name of the file that will hold any generated Dart code for this library
+   * unit. Note this is initialized after parsing.
+   */
+  String outputFilename;
+
+  /** Parsed cssSource. */
+  List<StyleSheet> styleSheets = [];
+
+  /** This is used in transforming Dart code to track modified files. */
+  bool modified = false;
+
+  /**
+   * This is used in transforming Dart code to compute files that reference
+   * [modified] files.
+   */
+  List<FileInfo> referencedBy = [];
+
+  /**
+   * Components used within this library unit. For [FileInfo] these are
+   * components used directly in the page. For [ComponentInfo] these are
+   * components used within their shadowed template.
+   */
+  final Map<ComponentSummary, bool> usedComponents =
+      new LinkedHashMap<ComponentSummary, bool>();
+
+  /**
+   * The actual code, either inlined or from an external file, or `null` if none
+   * was defined.
+   */
+  DartCodeInfo get userCode =>
+      externalCode != null ? externalCode.inlinedCode : inlinedCode;
+}
+
+/** Information extracted at the file-level. */
+class FileInfo extends LibraryInfo implements HtmlFileSummary {
+  /** Relative path to this file from the compiler's base directory. */
+  final UrlInfo inputUrl;
+
+  /**
+   * Whether this file should be treated as the entry point of the web app, i.e.
+   * the file users navigate to in their browser. This will be true if this file
+   * was passed in the command line to the dwc compiler, and the
+   * `--components_only` flag was omitted.
+   */
+  final bool isEntryPoint;
+
+  // TODO(terry): Ensure that that the libraryName is a valid identifier:
+  //              a..z || A..Z || _ [a..z || A..Z || 0..9 || _]*
+  String get libraryName =>
+      path.basename(inputUrl.resolvedPath).replaceAll('.', '_');
+
+  /** File where the top-level code was defined. */
+  UrlInfo get dartCodeUrl => externalFile != null ? externalFile : inputUrl;
+
+  /**
+   * All custom element definitions in this file. This may contain duplicates.
+   * Normally you should use [components] for lookup.
+   */
+  final List<ComponentInfo> declaredComponents = new List<ComponentInfo>();
+
+  /**
+   * All custom element definitions defined in this file or imported via
+   *`<link rel='components'>` tag. Maps from the tag name to the component
+   * information. This map is sorted by the tag name.
+   */
+  final Map<String, ComponentSummary> components =
+      new SplayTreeMap<String, ComponentSummary>();
+
+  /** Files imported with `<link rel="import">` */
+  final List<UrlInfo> componentLinks = <UrlInfo>[];
+
+  /** Files imported with `<link rel="stylesheet">` */
+  final List<UrlInfo> styleSheetHrefs = <UrlInfo>[];
+
+  /** Root is associated with the body node. */
+  Element body;
+
+  FileInfo(this.inputUrl, [this.isEntryPoint = false]);
+
+  /**
+   * Query for an [Element] matching the provided [tag], starting from the
+   * [body].
+   */
+  Element query(String tag) => body.query(tag);
+}
+
+
+/** Information about a web component definition declared locally. */
+// TODO(sigmund): use a mixin to pull in ComponentSummary.
+class ComponentInfo extends LibraryInfo implements ComponentSummary {
+  /** The file that declares this component. */
+  final FileInfo declaringFile;
+
+  /** The component tag name, defined with the `name` attribute on `element`. */
+  final String tagName;
+
+  /**
+   * The tag name that this component extends, defined with the `extends`
+   * attribute on `element`.
+   */
+  final String extendsTag;
+
+  /**
+   * The component info associated with the [extendsTag] name, if any.
+   * This will be `null` if the component extends a built-in HTML tag, or
+   * if the analyzer has not run yet.
+   */
+  ComponentSummary extendsComponent;
+
+  /** The Dart class containing the component's behavior. */
+  String className;
+
+  /** The Dart class declaration. */
+  ClassDeclaration get classDeclaration => _classDeclaration;
+  ClassDeclaration _classDeclaration;
+
+  /** The declaring `<element>` tag. */
+  final Node element;
+
+  /** File where this component was defined. */
+  UrlInfo get dartCodeUrl => externalFile != null
+      ? externalFile : declaringFile.inputUrl;
+
+  /**
+   * True if [tagName] was defined by more than one component. If this happened
+   * we will skip over the component.
+   */
+  bool hasConflict = false;
+
+  ComponentInfo(this.element, this.declaringFile, this.tagName,
+      this.extendsTag);
+
+  /**
+   * Gets the HTML tag extended by the base of the component hierarchy.
+   * Equivalent to [extendsTag] if this inherits directly from an HTML element,
+   * in other words, if [extendsComponent] is null.
+   */
+  String get baseExtendsTag =>
+      extendsComponent == null ? extendsTag : extendsComponent.baseExtendsTag;
+
+  Span get sourceSpan => element.sourceSpan;
+
+  /** Is apply-author-styles enabled. */
+  bool get hasAuthorStyles =>
+      element.attributes.containsKey('apply-author-styles');
+
+  /**
+   * Finds the declaring class, and initializes [className] and
+   * [classDeclaration]. Also [userCode] is generated if there was no script.
+   */
+  void findClassDeclaration(Messages messages) {
+    var constructor = element.attributes['constructor'];
+    className = constructor != null ? constructor :
+        toCamelCase(tagName, startUppercase: true);
+
+    // If we don't have any code, generate a small class definition, and
+    // pretend the user wrote it as inlined code.
+    if (userCode == null) {
+      var superclass = extendsComponent != null ? extendsComponent.className
+          : 'autogenerated.PolymerElement';
+      inlinedCode = new DartCodeInfo(null, null, [],
+          'class $className extends $superclass {\n}', null);
+    }
+
+    var code = userCode.code;
+    _classDeclaration = userCode.findClass(className);
+    if (_classDeclaration == null) {
+      // Check for deprecated x-tags implied constructor.
+      if (tagName.startsWith('x-') && constructor == null) {
+        var oldCtor = toCamelCase(tagName.substring(2), startUppercase: true);
+        _classDeclaration = userCode.findClass(oldCtor);
+        if (_classDeclaration != null) {
+          messages.warning('Implied constructor name for x-tags has changed to '
+              '"$className". You should rename your class or add a '
+              'constructor="$oldCtor" attribute to the element declaration. '
+              'Also custom tags are not required to start with "x-" if their '
+              'name has at least one dash.',
+              element.sourceSpan);
+          className = oldCtor;
+        }
+      }
+
+      if (_classDeclaration == null) {
+        messages.error('please provide a class definition '
+            'for $className:\n $code', element.sourceSpan);
+        return;
+      }
+    }
+  }
+
+  String toString() => '#<ComponentInfo $tagName '
+      '${inlinedCode != null ? "inline" : "from ${dartCodeUrl.resolvedPath}"}>';
+}
+
+
+/**
+ * Information extracted about a URL that refers to another file. This is
+ * mainly introduced to be able to trace back where URLs come from when
+ * reporting errors.
+ */
+class UrlInfo {
+  /** Original url. */
+  final String url;
+
+  /** Path that the URL points to. */
+  final String resolvedPath;
+
+  /** Original source location where the URL was extracted from. */
+  final Span sourceSpan;
+
+  UrlInfo(this.url, this.resolvedPath, this.sourceSpan);
+
+  /**
+   * Resolve a path from an [url] found in a file located at [inputUrl].
+   * Returns null for absolute [url]. Unless [ignoreAbsolute] is true, reports
+   * an error message if the url is an absolute url.
+   */
+  static UrlInfo resolve(String url, UrlInfo inputUrl, Span span,
+      String packageRoot, Messages messages, {bool ignoreAbsolute: false}) {
+
+    var uri = Uri.parse(url);
+    if (uri.host != '' || (uri.scheme != '' && uri.scheme != 'package')) {
+      if (!ignoreAbsolute) {
+        messages.error('absolute paths not allowed here: "$url"', span);
+      }
+      return null;
+    }
+
+    var target;
+    if (url.startsWith('package:')) {
+      target = path.join(packageRoot, url.substring(8));
+    } else if (path.isAbsolute(url)) {
+      if (!ignoreAbsolute) {
+        messages.error('absolute paths not allowed here: "$url"', span);
+      }
+      return null;
+    } else {
+      target = path.join(path.dirname(inputUrl.resolvedPath), url);
+      url = pathToUrl(path.normalize(path.join(
+          path.dirname(inputUrl.url), url)));
+    }
+    target = path.normalize(target);
+
+    return new UrlInfo(url, target, span);
+  }
+
+  bool operator ==(UrlInfo other) =>
+      url == other.url && resolvedPath == other.resolvedPath;
+
+  int get hashCode => resolvedPath.hashCode;
+
+  String toString() => "#<UrlInfo url: $url, resolvedPath: $resolvedPath>";
+}
diff --git a/pkg/polymer/lib/src/messages.dart b/pkg/polymer/lib/src/messages.dart
new file mode 100644
index 0000000..5dc6247
--- /dev/null
+++ b/pkg/polymer/lib/src/messages.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library messages;
+
+import 'dart:json' as json;
+
+import 'package:barback/barback.dart' show TransformLogger;
+import 'package:source_maps/span.dart' show Span;
+import 'package:logging/logging.dart' show Level;
+
+import 'compiler_options.dart';
+import 'utils.dart';
+
+/** Map between error levels and their display color. */
+final Map<Level, String> _ERROR_COLORS = (() {
+  var colorsMap = new Map<Level, String>();
+  colorsMap[Level.SEVERE] = RED_COLOR;
+  colorsMap[Level.WARNING] = MAGENTA_COLOR;
+  colorsMap[Level.INFO] = GREEN_COLOR;
+  return colorsMap;
+})();
+
+/** A single message from the compiler. */
+class Message {
+  final Level level;
+  final String message;
+  final Span span;
+  final bool useColors;
+
+  Message(this.level, this.message, {this.span, this.useColors: false});
+
+  String get kind => level == Level.SEVERE ? 'error' :
+      (level == Level.WARNING ? 'warning' : 'info');
+
+  String toString() {
+    var output = new StringBuffer();
+    bool colors = useColors && _ERROR_COLORS.containsKey(level);
+    var levelColor =  _ERROR_COLORS[level];
+    if (colors) output.write(levelColor);
+    output..write(kind)..write(' ');
+    if (colors) output.write(NO_COLOR);
+
+    if (span == null) {
+      output.write(message);
+    } else {
+      output.write(span.getLocationMessage(message, useColors: colors,
+          color: levelColor));
+    }
+
+    return output.toString();
+  }
+
+  String toJson() {
+    if (span == null) return toString();
+    return json.stringify([{
+      'method': kind,
+      'params': {
+        'file': span.sourceUrl,
+        'message': message,
+        'line': span.start.line + 1,
+        'charStart': span.start.offset,
+        'charEnd': span.end.offset,
+      }
+    }]);
+  }
+}
+
+/**
+ * This class tracks and prints information, warnings, and errors emitted by the
+ * compiler.
+ */
+class Messages implements TransformLogger {
+  final CompilerOptions options;
+  final bool shouldPrint;
+
+  final List<Message> messages = <Message>[];
+
+  Messages({CompilerOptions options, this.shouldPrint: true})
+      : options = options != null ? options : new CompilerOptions();
+
+  /**
+   * Creates a new instance of [Messages] which doesn't write messages to
+   * the console.
+   */
+  Messages.silent(): this(shouldPrint: false);
+
+  /**
+   * True if we have an error that prevents correct codegen.
+   * For example, if we failed to read an input file.
+   */
+  bool get hasErrors => messages.any((m) => m.level == Level.SEVERE);
+
+  // Convenience methods for testing
+  int get length => messages.length;
+
+  Message operator[](int index) => messages[index];
+
+  void clear() {
+    messages.clear();
+  }
+
+  /** [message] is considered a static compile-time error by the Dart lang. */
+  void error(String message, [Span span]) {
+    var msg = new Message(Level.SEVERE, message, span: span,
+        useColors: options.useColors);
+
+    messages.add(msg);
+    printMessage(msg);
+  }
+
+  /** [message] is considered a type warning by the Dart lang. */
+  void warning(String message, [Span span]) {
+    if (options.warningsAsErrors) {
+      error(message, span);
+    } else {
+      var msg = new Message(Level.WARNING, message,
+          span: span, useColors: options.useColors);
+
+      messages.add(msg);
+      printMessage(msg);
+    }
+  }
+
+  /// the list of error messages. Empty list, if there are no error messages.
+  List<Message> get errors =>
+        messages.where((m) => m.level == Level.SEVERE).toList();
+
+  /// the list of warning messages. Empty list if there are no warning messages.
+  List<Message> get warnings =>
+        messages.where((m) => m.level == Level.WARNING).toList();
+
+  /**
+   * [message] at [span] will tell the user about what the compiler
+   * is doing.
+   */
+  void info(String message, [Span span]) {
+    var msg = new Message(Level.INFO, message, span: span,
+        useColors: options.useColors);
+
+    messages.add(msg);
+    if (options.verbose) printMessage(msg);
+  }
+
+  void printMessage(msg) {
+    if (shouldPrint) print(options.jsonFormat ? msg.toJson() : msg);
+  }
+}
diff --git a/pkg/polymer/lib/src/paths.dart b/pkg/polymer/lib/src/paths.dart
new file mode 100644
index 0000000..34037d9
--- /dev/null
+++ b/pkg/polymer/lib/src/paths.dart
@@ -0,0 +1,170 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Holds path information that is used by the WebUI compiler to find files,
+ * compute their output location and relative paths between them.
+ */
+library polymer.src.paths;
+
+import 'info.dart' show UrlInfo;
+import 'messages.dart';
+import 'summary.dart';
+import 'utils.dart' show path, pathToUrl;
+
+/**
+ * Stores information about paths and computes mappings between input and output
+ * path locations.
+ */
+class PathMapper {
+  /**
+   * Common prefix to all input paths that are read from the file system. The
+   * output generated by the compiler will reflect the directory structure
+   * starting from [_baseDir]. For instance, if [_baseDir] is `a/b/c` and
+   * [_outputDir] is `g/h/`, then the corresponding output file for
+   * `a/b/c/e/f.html` will be under `g/h/e/f.html.dart`.
+   */
+  final String _baseDir;
+
+  /** Base path where all output is generated. */
+  final String _outputDir;
+
+  /** The package root directory. */
+  final String packageRoot;
+
+  /** Whether to add prefixes and to output file names. */
+  final bool _mangleFilenames;
+
+  final bool _rewriteUrls;
+
+  bool get _rewritePackageImports => _rewriteUrls || !_mangleFilenames;
+
+  /** Default prefix added to all filenames. */
+  static const String _DEFAULT_PREFIX = '_';
+
+  PathMapper(String baseDir, String outputDir, this.packageRoot,
+      bool forceMangle, this._rewriteUrls)
+      : _baseDir = baseDir,
+        _outputDir = outputDir,
+        _mangleFilenames = forceMangle || (baseDir == outputDir);
+
+  /** Add a prefix and [suffix] if [_mangleFilenames] is true */
+  String mangle(String name, String suffix, [bool forceSuffix = false]) =>
+    _mangleFilenames ? "$_DEFAULT_PREFIX$name$suffix"
+        : (forceSuffix ? "$name$suffix" : name);
+
+  /**
+   * Checks that `input.resolvedPath` is a valid input path. It must be in
+   * [_baseDir] and must not be in the [_outputDir]. If not, an error message
+   * is added to [messages].
+   */
+  bool checkInputPath(UrlInfo input, Messages messages) {
+    if (_mangleFilenames) return true;
+    var canonicalized = path.normalize(input.resolvedPath);
+    var parentDir = '..${path.separator}';
+    if (!path.relative(canonicalized, from: _outputDir).startsWith(parentDir)) {
+      messages.error(
+          'The file ${input.resolvedPath} cannot be processed. '
+          'Files cannot be under the output folder (${_outputDir}).',
+          input.sourceSpan);
+      return false;
+    }
+    if (path.relative(canonicalized, from: _baseDir).startsWith(parentDir)) {
+      messages.error(
+          'The file ${input.resolvedPath} cannot be processed. '
+          'All processed files must be under the base folder (${_baseDir}), you'
+          ' can specify the base folder using the --basedir flag.',
+          input.sourceSpan);
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * The path to the output file corresponding to [input], by adding
+   * [_DEFAULT_PREFIX] and a [suffix] to its file name.
+   */
+  String outputPath(String input, String suffix, [bool forceSuffix = false]) =>
+      path.join(outputDirPath(input),
+          mangle(path.basename(input), suffix, forceSuffix));
+
+  /** The path to the output file corresponding to [info]. */
+  String outputLibraryPath(LibrarySummary lib) =>
+      path.join(outputDirPath(lib.dartCodeUrl.resolvedPath),
+          lib.outputFilename);
+
+  /** The corresponding output directory for [input]'s directory. */
+  String outputDirPath(String input) {
+    return _rewritePackages(path.normalize(
+          path.join(_outputDir, path.relative(
+              path.dirname(input), from: _baseDir))));
+  }
+
+  /**
+   * We deal with `packages/` directories in a very special way. We assume it
+   * points to resources loaded from other pub packages. If an output directory
+   * is specified, the compiler will create a packages symlink so that
+   * `package:` imports work.
+   *
+   * To make it possible to share components through pub, we allow using tags of
+   * the form `<link rel="import" href="packages/...">`, so that you can
+   * refer to components within the packages symlink.  Regardless of whether an
+   * --out option was given to the compiler, we don't want to generate files
+   * inside `packages/` for those components.  Instead we will generate such
+   * code in a special directory called `_from_packages/`.
+   */
+  String _rewritePackages(String outputPath) {
+    // TODO(jmesserly): this should match against packageRoot instead.
+    if (!outputPath.contains('packages')) return outputPath;
+    if (!_rewritePackageImports) return outputPath;
+    var segments = path.split(outputPath);
+    return path.joinAll(
+        segments.map((s) => s == 'packages' ? '_from_packages' : s));
+  }
+
+  /**
+   * Returns a url to import/export the output library represented by [target]
+   * from the output library of [src]. In other words, a url to import or export
+   * `target.outputFilename` from `src.outputFilename`.
+   */
+  String importUrlFor(LibrarySummary src, LibrarySummary target) {
+    if (!_rewritePackageImports &&
+        target.dartCodeUrl.url.startsWith('package:')) {
+      return pathToUrl(path.join(path.dirname(target.dartCodeUrl.url),
+            target.outputFilename));
+    }
+    var srcDir = path.dirname(src.dartCodeUrl.resolvedPath);
+    var relDir = path.relative(
+        path.dirname(target.dartCodeUrl.resolvedPath), from: srcDir);
+    return pathToUrl(_rewritePackages(path.normalize(
+          path.join(relDir, target.outputFilename))));
+  }
+
+  /**
+   * Transforms a [target] url seen in [src] (e.g. a Dart import, a .css href in
+   * an HTML file, etc) into a corresponding url from the output file associated
+   * with [src]. This will keep 'package:', 'dart:', path-absolute, and absolute
+   * urls intact, but it will fix relative paths to walk from the output
+   * directory back to the input directory. An exception will be thrown if
+   * [target] is not under [_baseDir].
+   */
+  String transformUrl(String src, String target) {
+    var uri = Uri.parse(target);
+    if (uri.isAbsolute) return target;
+    if (!uri.scheme.isEmpty) return target;
+    if (!uri.host.isEmpty) return target;
+    if (uri.path.isEmpty) return target;  // Implies standalone ? or # in URI.
+    if (path.isAbsolute(target)) return target;
+
+    return pathToUrl(path.normalize(path.relative(
+          path.join(path.dirname(src), target), from: outputDirPath(src))));
+  }
+}
+
+/**
+ * Returns a "mangled" name, with a prefix and [suffix] depending on the
+ * compiler's settings. [forceSuffix] causes [suffix] to be appended even if
+ * the compiler is not mangling names.
+ */
+typedef String NameMangler(String name, String suffix, [bool forceSuffix]);
diff --git a/pkg/polymer/lib/src/summary.dart b/pkg/polymer/lib/src/summary.dart
new file mode 100644
index 0000000..841870a
--- /dev/null
+++ b/pkg/polymer/lib/src/summary.dart
@@ -0,0 +1,88 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Summary information for components and libraries.
+ *
+ * These classes are used for modular compilation. Summaries are a subset of the
+ * information collected by Info objects (see `info.dart`). When we are
+ * compiling a single file, the information extracted from that file is stored
+ * as info objects, but any information that is needed from other files (like
+ * imported components) is stored as a summary.
+ */
+library polymer.src.summary;
+
+import 'package:source_maps/span.dart' show Span;
+
+// TODO(sigmund): consider moving UrlInfo out of info.dart
+import 'info.dart' show UrlInfo;
+
+/**
+ * Summary information from other library-like objects, which includes HTML
+ * components and dart libraries).
+ */
+class LibrarySummary {
+  /** Path to the sources represented by this summary. */
+  final UrlInfo dartCodeUrl;
+
+  /** Name given to this source after it was compiled. */
+  final String outputFilename;
+
+  LibrarySummary(this.dartCodeUrl, this.outputFilename);
+}
+
+/** Summary information for an HTML file that defines custom elements. */
+class HtmlFileSummary extends LibrarySummary {
+  /**
+   * Summary of each component defined either explicitly the HTML file or
+   * included transitively from `<link rel="import">` tags.
+   */
+  final Map<String, ComponentSummary> components;
+
+  HtmlFileSummary(UrlInfo dartCodeUrl, String outputFilename, this.components)
+      : super(dartCodeUrl, outputFilename);
+}
+
+/** Information about a web component definition. */
+class ComponentSummary extends LibrarySummary {
+  /** The component tag name, defined with the `name` attribute on `element`. */
+  final String tagName;
+
+  /**
+   * The tag name that this component extends, defined with the `extends`
+   * attribute on `element`.
+   */
+  final String extendsTag;
+
+  /**
+   * The Dart class containing the component's behavior, derived from tagName or
+   * defined in the `constructor` attribute on `element`.
+   */
+  final String className;
+
+  /** Summary of the base component, if any. */
+  final ComponentSummary extendsComponent;
+
+  /**
+   * True if [tagName] was defined by more than one component. Used internally
+   * by the analyzer. Conflicting component will be skipped by the compiler.
+   */
+  bool hasConflict;
+
+  /** Original span where this component is declared. */
+  final Span sourceSpan;
+
+  ComponentSummary(UrlInfo dartCodeUrl, String outputFilename,
+      this.tagName, this.extendsTag, this.className, this.extendsComponent,
+      this.sourceSpan, [this.hasConflict = false])
+      : super(dartCodeUrl, outputFilename);
+
+  /**
+   * Gets the HTML tag extended by the base of the component hierarchy.
+   * Equivalent to [extendsTag] if this inherits directly from an HTML element,
+   * in other words, if [extendsComponent] is null.
+   */
+  String get baseExtendsTag =>
+      extendsComponent == null ? extendsTag : extendsComponent.baseExtendsTag;
+}
diff --git a/pkg/polymer/lib/src/utils.dart b/pkg/polymer/lib/src/utils.dart
new file mode 100644
index 0000000..da79dc2
--- /dev/null
+++ b/pkg/polymer/lib/src/utils.dart
@@ -0,0 +1,177 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library polymer.src.utils;
+
+import 'dart:async';
+import 'package:path/path.dart' show Builder;
+export 'utils_observe.dart' show toCamelCase, toHyphenedName;
+
+/**
+ * An instance of the pathos library builder. We could just use the default
+ * builder in pathos, but we add this indirection to make it possible to run
+ * unittest for windows paths.
+ */
+Builder path = new Builder();
+
+/** Convert a OS specific path into a url. */
+String pathToUrl(String relPath) =>
+  (path.separator == '/') ? relPath : path.split(relPath).join('/');
+
+/**
+ * Invokes [callback], logs how long it took to execute in ms, and returns
+ * whatever [callback] returns. The log message will be printed if [printTime]
+ * is true.
+ */
+time(String logMessage, callback(),
+     {bool printTime: false, bool useColors: false}) {
+  final watch = new Stopwatch();
+  watch.start();
+  var result = callback();
+  watch.stop();
+  final duration = watch.elapsedMilliseconds;
+  if (printTime) {
+    _printMessage(logMessage, duration, useColors);
+  }
+  return result;
+}
+
+/**
+ * Invokes [callback], logs how long it takes from the moment [callback] is
+ * executed until the future it returns is completed. Returns the future
+ * returned by [callback]. The log message will be printed if [printTime]
+ * is true.
+ */
+Future asyncTime(String logMessage, Future callback(),
+                 {bool printTime: false, bool useColors: false}) {
+  final watch = new Stopwatch();
+  watch.start();
+  return callback()..then((_) {
+    watch.stop();
+    final duration = watch.elapsedMilliseconds;
+    if (printTime) {
+      _printMessage(logMessage, duration, useColors);
+    }
+  });
+}
+
+void _printMessage(String logMessage, int duration, bool useColors) {
+  var buf = new StringBuffer();
+  buf.write(logMessage);
+  for (int i = logMessage.length; i < 60; i++) buf.write(' ');
+  buf.write(' -- ');
+  if (useColors) {
+    buf.write(GREEN_COLOR);
+  }
+  if (duration < 10) buf.write(' ');
+  if (duration < 100) buf.write(' ');
+  buf..write(duration)..write(' ms');
+  if (useColors) {
+    buf.write(NO_COLOR);
+  }
+  print(buf.toString());
+}
+
+// Color constants used for generating messages.
+final String GREEN_COLOR = '\u001b[32m';
+final String RED_COLOR = '\u001b[31m';
+final String MAGENTA_COLOR = '\u001b[35m';
+final String NO_COLOR = '\u001b[0m';
+
+/** A future that waits until all added [Future]s complete. */
+// TODO(sigmund): this should be part of the futures/core libraries.
+class FutureGroup {
+  static const _FINISHED = -1;
+
+  int _pending = 0;
+  Future _failedTask;
+  final Completer<List> _completer = new Completer<List>();
+  final List results = [];
+
+  /** Gets the task that failed, if any. */
+  Future get failedTask => _failedTask;
+
+  /**
+   * Wait for [task] to complete.
+   *
+   * If this group has already been marked as completed, you'll get a
+   * [StateError].
+   *
+   * If this group has a [failedTask], new tasks will be ignored, because the
+   * error has already been signaled.
+   */
+  void add(Future task) {
+    if (_failedTask != null) return;
+    if (_pending == _FINISHED) throw new StateError("Future already completed");
+
+    _pending++;
+    var i = results.length;
+    results.add(null);
+    task.then((res) {
+      results[i] = res;
+      if (_failedTask != null) return;
+      _pending--;
+      if (_pending == 0) {
+        _pending = _FINISHED;
+        _completer.complete(results);
+      }
+    }, onError: (e) {
+      if (_failedTask != null) return;
+      _failedTask = task;
+      _completer.completeError(e, getAttachedStackTrace(e));
+    });
+  }
+
+  Future<List> get future => _completer.future;
+}
+
+
+/**
+ * Escapes [text] for use in a Dart string.
+ * [single] specifies single quote `'` vs double quote `"`.
+ * [triple] indicates that a triple-quoted string, such as `'''` or `"""`.
+ */
+String escapeDartString(String text, {bool single: true, bool triple: false}) {
+  // Note: don't allocate anything until we know we need it.
+  StringBuffer result = null;
+
+  for (int i = 0; i < text.length; i++) {
+    int code = text.codeUnitAt(i);
+    var replace = null;
+    switch (code) {
+      case 92/*'\\'*/: replace = r'\\'; break;
+      case 36/*r'$'*/: replace = r'\$'; break;
+      case 34/*'"'*/:  if (!single) replace = r'\"'; break;
+      case 39/*"'"*/:  if (single) replace = r"\'"; break;
+      case 10/*'\n'*/: if (!triple) replace = r'\n'; break;
+      case 13/*'\r'*/: if (!triple) replace = r'\r'; break;
+
+      // Note: we don't escape unicode characters, under the assumption that
+      // writing the file in UTF-8 will take care of this.
+
+      // TODO(jmesserly): do we want to replace any other non-printable
+      // characters (such as \f) for readability?
+    }
+
+    if (replace != null && result == null) {
+      result = new StringBuffer(text.substring(0, i));
+    }
+
+    if (result != null) result.write(replace != null ? replace : text[i]);
+  }
+
+  return result == null ? text : result.toString();
+}
+
+/** Iterates through an infinite sequence, starting from zero. */
+class IntIterator implements Iterator<int> {
+  int _next = -1;
+
+  int get current => _next < 0 ? null : _next;
+
+  bool moveNext() {
+    _next++;
+    return true;
+  }
+}
diff --git a/pkg/polymer/lib/src/utils_observe.dart b/pkg/polymer/lib/src/utils_observe.dart
new file mode 100644
index 0000000..b568d0c
--- /dev/null
+++ b/pkg/polymer/lib/src/utils_observe.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library polymer.src.utils_observe;
+
+/**
+ * Converts a string name with hyphens into an identifier, by removing hyphens
+ * and capitalizing the following letter. Optionally [startUppercase] to
+ * captialize the first letter.
+ */
+String toCamelCase(String hyphenedName, {bool startUppercase: false}) {
+  var segments = hyphenedName.split('-');
+  int start = startUppercase ? 0 : 1;
+  for (int i = start; i < segments.length; i++) {
+    var segment = segments[i];
+    if (segment.length > 0) {
+      // Character between 'a'..'z' mapped to 'A'..'Z'
+      segments[i] = '${segment[0].toUpperCase()}${segment.substring(1)}';
+    }
+  }
+  return segments.join('');
+}
+
+String toHyphenedName(String word) {
+  var sb = new StringBuffer();
+  for (int i = 0; i < word.length; i++) {
+    var lower = word[i].toLowerCase();
+    if (word[i] != lower && i > 0) sb.write('-');
+    sb.write(lower);
+  }
+  return sb.toString();
+}
diff --git a/pkg/polymer/lib/testing/content_shell_test.dart b/pkg/polymer/lib/testing/content_shell_test.dart
new file mode 100644
index 0000000..97398be
--- /dev/null
+++ b/pkg/polymer/lib/testing/content_shell_test.dart
@@ -0,0 +1,229 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * Helper library to run tests in content_shell
+ */
+library polymer.testing.end2end;
+
+import 'dart:io';
+import 'package:args/args.dart';
+import 'package:path/path.dart' as path;
+import 'package:unittest/unittest.dart';
+import 'package:polymer/dwc.dart' as dwc;
+
+
+/**
+ * Compiles [testFile] with the web-ui compiler, and then runs the output as a
+ * unit test in content_shell.
+ */
+void endToEndTests(String inputDir, String outDir, {List<String> arguments}) {
+  _testHelper(new _TestOptions(inputDir, inputDir, null, outDir,
+        arguments: arguments));
+}
+
+/**
+ * Compiles [testFile] with the web-ui compiler, and then runs the output as a
+ * render test in content_shell.
+ */
+void renderTests(String baseDir, String inputDir, String expectedDir,
+    String outDir, {List<String> arguments, String script, String pattern,
+    bool deleteDir: true}) {
+  _testHelper(new _TestOptions(baseDir, inputDir, expectedDir, outDir,
+      arguments: arguments, script: script, pattern: pattern,
+      deleteDir: deleteDir));
+}
+
+void _testHelper(_TestOptions options) {
+  expect(options, isNotNull);
+
+  var paths = new Directory(options.inputDir).listSync()
+      .where((f) => f is File).map((f) => f.path)
+      .where((p) => p.endsWith('_test.html') && options.pattern.hasMatch(p));
+
+  if (paths.isEmpty) return;
+
+  // First clear the output folder. Otherwise we can miss bugs when we fail to
+  // generate a file.
+  var dir = new Directory(options.outDir);
+  if (dir.existsSync() && options.deleteDir) {
+    print('Cleaning old output for ${path.normalize(options.outDir)}');
+    dir.deleteSync(recursive: true);
+  }
+  dir.createSync();
+
+  for (var filePath in paths) {
+    var filename = path.basename(filePath);
+    test('compile $filename', () {
+      var testArgs = ['-o', options.outDir, '--basedir', options.baseDir,
+          '--deploy']
+          ..addAll(options.compilerArgs)
+          ..add(filePath);
+      expect(dwc.run(testArgs, printTime: false).then((res) {
+        expect(res.messages.length, 0, reason: res.messages.join('\n'));
+      }), completes);
+    });
+  }
+
+  var filenames = paths.map(path.basename).toList();
+  // Sort files to match the order in which run.sh runs diff.
+  filenames.sort();
+
+  // Get the path from "input" relative to "baseDir"
+  var relativeToBase = path.relative(options.inputDir, from: options.baseDir);
+  var finalOutDir = path.join(options.outDir, relativeToBase);
+
+  runTests(String search) {
+    var output;
+
+    for (var filename in filenames) {
+      test('content_shell run $filename$search', () {
+        var args = ['--dump-render-tree',
+            'file://$finalOutDir/$filename$search'];
+        var env = {'DART_FLAGS': '--checked'};
+        expect(Process.run('content_shell', args, environment: env).then((res) {
+          expect(res.exitCode, 0, reason: 'content_shell exit code: '
+              '${res.exitCode}. Contents of stderr: \n${res.stderr}');
+          var outs = res.stdout.split('#EOF\n')
+            .where((s) => !s.trim().isEmpty).toList();
+          expect(outs.length, 1);
+          output = outs.first;
+        }), completes);
+      });
+
+      test('verify $filename $search', () {
+        expect(output, isNotNull, reason:
+          'Output not available, maybe content_shell failed to run.');
+        var outPath = path.join(options.outDir, '$filename.txt');
+        new File(outPath).writeAsStringSync(output);
+        if (options.isRenderTest) {
+          var expectedPath = path.join(options.expectedDir, '$filename.txt');
+          var expected = new File(expectedPath).readAsStringSync();
+          expect(output, expected, reason: 'unexpected output for <$filename>');
+        } else {
+          bool passes = matches(
+              new RegExp('All .* tests passed')).matches(output, {});
+          expect(passes, true, reason: 'unit test failed:\n$output');
+        }
+      });
+    }
+  }
+
+  bool compiled = false;
+  ensureCompileToJs() {
+    if (compiled) return;
+    compiled = true;
+
+    for (var filename in filenames) {
+      test('dart2js $filename', () {
+        // TODO(jmesserly): this depends on DWC's output scheme.
+        // Alternatively we could use html5lib to find the script tag.
+        var inPath = '${filename}_bootstrap.dart';
+        var outPath = '${inPath}.js';
+
+        inPath = path.join(finalOutDir, inPath);
+        outPath = path.join(finalOutDir, outPath);
+
+        expect(Process.run('dart2js', ['-o$outPath', inPath]).then((res) {
+          expect(res.exitCode, 0, reason: 'dart2js exit code: '
+            '${res.exitCode}. Contents of stderr: \n${res.stderr}. '
+            'Contents of stdout: \n${res.stdout}.');
+          expect(new File(outPath).existsSync(), true, reason: 'input file '
+            '$inPath should have been compiled to $outPath.');
+        }), completes);
+      });
+    }
+  }
+
+  if (options.runAsDart) {
+    runTests('');
+  }
+  if (options.runAsJs) {
+    ensureCompileToJs();
+    runTests('?js=1');
+  }
+  if (options.forcePolyfillShadowDom) {
+    ensureCompileToJs();
+    runTests('?js=1&shadowdomjs=1');
+  }
+}
+
+class _TestOptions {
+  final String baseDir;
+  final String inputDir;
+
+  final String expectedDir;
+  bool get isRenderTest => expectedDir != null;
+
+  final String outDir;
+  final bool deleteDir;
+
+  final bool runAsDart;
+  final bool runAsJs;
+  final bool forcePolyfillShadowDom;
+
+  final List<String> compilerArgs;
+  final RegExp pattern;
+
+  factory _TestOptions(String baseDir, String inputDir, String expectedDir,
+      String outDir, {List<String> arguments, String script, String pattern,
+      bool deleteDir: true}) {
+    if (arguments == null) arguments = new Options().arguments;
+    if (script == null) script = new Options().script;
+
+    var args = _parseArgs(arguments, script);
+    if (args == null) return null;
+    var compilerArgs = args.rest;
+    var filePattern;
+    if (pattern != null) {
+      filePattern = new RegExp(pattern);
+    } else if (compilerArgs.length > 0) {
+      filePattern = new RegExp(compilerArgs[0]);
+      compilerArgs = compilerArgs.sublist(1);
+    } else {
+      filePattern = new RegExp('.');
+    }
+
+    var scriptDir = path.absolute(path.dirname(script));
+    baseDir = path.join(scriptDir, baseDir);
+    inputDir = path.join(scriptDir, inputDir);
+    outDir = path.join(scriptDir, outDir);
+    if (expectedDir != null) {
+      expectedDir = path.join(scriptDir, expectedDir);
+    }
+
+    return new _TestOptions._(baseDir, inputDir, expectedDir, outDir, deleteDir,
+        args['dart'] == true, args['js'] == true, args['shadowdom'] == true,
+        compilerArgs, filePattern);
+  }
+
+  _TestOptions._(this.baseDir, this.inputDir, this.expectedDir, this.outDir,
+      this.deleteDir, this.runAsDart, this.runAsJs,
+      this.forcePolyfillShadowDom, this.compilerArgs, this.pattern);
+}
+
+ArgResults _parseArgs(List<String> arguments, String script) {
+  var parser = new ArgParser()
+    ..addFlag('dart', abbr: 'd', help: 'run on Dart VM', defaultsTo: true)
+    ..addFlag('js', abbr: 'j', help: 'run compiled dart2js', defaultsTo: true)
+    ..addFlag('shadowdom', abbr: 's',
+        help: 'run dart2js and polyfilled ShadowDOM', defaultsTo: true)
+    ..addFlag('help', abbr: 'h', help: 'Displays this help message',
+        defaultsTo: false, negatable: false);
+
+  showUsage() {
+    print('Usage: $script [options...] [test_name_regexp]');
+    print(parser.getUsage());
+    return null;
+  }
+
+  try {
+    var results = parser.parse(arguments);
+    if (results['help']) return showUsage();
+    return results;
+  } on FormatException catch (e) {
+    print(e.message);
+    return showUsage();
+  }
+}
diff --git a/pkg/polymer/lib/testing/testing.js b/pkg/polymer/lib/testing/testing.js
new file mode 100644
index 0000000..de27047
--- /dev/null
+++ b/pkg/polymer/lib/testing/testing.js
@@ -0,0 +1,147 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+(function() {
+  var undoReplaceScripts = [];
+
+  var flags = {};
+  // populate flags from location
+  location.search.slice(1).split('&').forEach(function(o) {
+    o = o.split('=');
+    o[0] && (flags[o[0]] = o[1] || true);
+  });
+
+  // Webkit is migrating from layoutTestController to testRunner, we use
+  // layoutTestController as a fallback until that settles in.
+  var runner = window.testRunner || window.layoutTestController;
+
+  if (runner) {
+    runner.dumpAsText();
+    runner.waitUntilDone();
+  }
+
+  function dumpDOM() {
+    // Undo any scripts that were modified.
+    undoReplaceScripts.forEach(function(undo) { undo(); });
+
+    function expandShadowRoot(node) {
+      for (var n = node.firstChild; n; n = n.nextSibling) {
+        expandShadowRoot(n);
+      }
+      var shadow = node.shadowRoot || node.webkitShadowRoot ||
+          node.olderShadowRoot;
+
+      if (shadow) {
+        expandShadowRoot(shadow);
+
+        var name = 'shadow-root';
+        if (shadow == node.olderShadowRoot) name = 'older-' + name;
+
+        var fakeShadow = document.createElement(name);
+        while (shadow.firstChild) fakeShadow.appendChild(shadow.firstChild);
+        node.insertBefore(fakeShadow, node.firstChild);
+      }
+    }
+
+    // TODO(jmesserly): use querySelector to workaround unwrapped "document".
+    expandShadowRoot(document.querySelector('body'));
+
+    // Clean up all of the junk added to the DOM by js-interop and shadow CSS
+    // TODO(jmesserly): it seems like we're leaking lots of dart-port attributes
+    // for the document elemenet
+    function cleanTree(node) {
+      for (var n = node.firstChild; n; n = n.nextSibling) {
+        cleanTree(n);
+      }
+
+      // Remove dart-port attributes
+      if (node.attributes) {
+        for (var i = 0; i < node.attributes.length; i++) {
+          if (node.attributes[i].value.indexOf('dart-port') == 0) {
+            node.removeAttribute(i);
+          }
+        }
+      }
+
+      if (node.tagName == 'script' &&
+          node.textContent.indexOf('_DART_TEMPORARY_ATTACHED') >= 0)  {
+        node.parentNode.removeChild(node);
+      }
+    }
+
+    // TODO(jmesserly): use querySelector to workaround unwrapped "document".
+    cleanTree(document.querySelector('html'));
+
+    var out = document.createElement('pre');
+    out.textContent = document.documentElement.outerHTML;
+    document.body.innerHTML = '';
+    document.body.appendChild(out);
+  }
+
+  function messageHandler(e) {
+    if (e.data == 'done' && runner) {
+      // On success, dump the DOM. Convert shadowRoot contents into
+      // <shadow-root>
+      dumpDOM();
+      runner.notifyDone();
+    }
+  }
+
+  window.addEventListener('message', messageHandler, false);
+
+  function errorHandler(e) {
+    if (runner) {
+      window.setTimeout(function() { runner.notifyDone(); }, 0);
+    }
+    window.console.log('FAIL');
+  }
+
+  window.addEventListener('error', errorHandler, false);
+
+  if (navigator.webkitStartDart && !flags.js) {
+    // TODO(jmesserly): fix this so we don't need to copy from browser/dart.js
+    if (!navigator.webkitStartDart()) {
+      document.body.innerHTML = 'This build has expired.  Please download a new Dartium at http://www.dartlang.org/dartium/index.html';
+    }
+  } else {
+    if (flags.shadowdomjs) {
+      // Allow flags to force polyfill of ShadowDOM so we can test it.
+      window.__forceShadowDomPolyfill = true;
+    }
+
+    // TODO:
+    // - Support in-browser compilation.
+    // - Handle inline Dart scripts.
+    window.addEventListener("DOMContentLoaded", function (e) {
+      // Fall back to compiled JS. Run through all the scripts and
+      // replace them if they have a type that indicate that they source
+      // in Dart code.
+      //
+      //   <script type="application/dart" src="..."></script>
+      //
+      var scripts = document.getElementsByTagName("script");
+      var length = scripts.length;
+      for (var i = 0; i < length; ++i) {
+        var script = scripts[i];
+        if (script.type == "application/dart") {
+          // Remap foo.dart to foo.dart.js.
+          if (script.src && script.src != '') {
+            var jsScript = document.createElement('script');
+            jsScript.src = script.src.replace(/\.dart(?=\?|$)/, '.dart.js');
+            var parent = script.parentNode;
+            // TODO(vsm): Find a solution for issue 8455 that works with more
+            // than one script.
+            document.currentScript = jsScript;
+
+            undoReplaceScripts.push(function() {
+              parent.replaceChild(script, jsScript);
+            });
+            parent.replaceChild(jsScript, script);
+          }
+        }
+      }
+    }, false);
+  }
+
+})();
diff --git a/pkg/polymer/pubspec.yaml b/pkg/polymer/pubspec.yaml
new file mode 100644
index 0000000..78b2b66
--- /dev/null
+++ b/pkg/polymer/pubspec.yaml
@@ -0,0 +1,26 @@
+name: polymer
+author: Web UI Authors <web-ui-dev@dartlang.org>
+description: >
+  Polymer.dart is a new type of library for the web, built on top of Web
+  Components, and designed to leverage the evolving web platform on modern
+  browsers.
+homepage: https://www.dartlang.org
+dependencies:
+  analyzer_experimental: any
+  args: any
+  barback: any
+  browser: any
+  csslib: any
+  custom_element: any
+  html_import: any
+  html5lib: any
+  js: any
+  logging: any
+  mdv: any
+  observe: any
+  path: any
+  polymer_expressions: any
+  shadow_dom: any
+  source_maps: any
+  # TODO(jmesserly): make this a dev_dependency
+  unittest: any
diff --git a/pkg/polymer/test/compiler_test.dart b/pkg/polymer/test/compiler_test.dart
new file mode 100644
index 0000000..d5d47ba
--- /dev/null
+++ b/pkg/polymer/test/compiler_test.dart
@@ -0,0 +1,141 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/** End-to-end tests for the [Compiler] API. */
+library compiler_test;
+
+import 'package:logging/logging.dart' show Level;
+import 'package:path/path.dart' as path;
+import 'package:polymer/src/messages.dart';
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+
+import 'testing.dart';
+
+main() {
+  useCompactVMConfiguration();
+
+  test('recursive dependencies', () {
+    var messages = new Messages.silent();
+    var compiler = createCompiler({
+      'index.html': '<head>'
+                    '<link rel="import" href="foo.html">'
+                    '<link rel="import" href="bar.html">'
+                    '<body><x-foo></x-foo><x-bar></x-bar>'
+                    '<script type="application/dart">main() {}</script>',
+      'foo.html': '<head><link rel="import" href="bar.html">'
+                  '<body><polymer-element name="x-foo" constructor="Foo">'
+                  '<template><x-bar>',
+      'bar.html': '<head><link rel="import" href="foo.html">'
+                  '<body><polymer-element name="x-bar" constructor="Boo">'
+                  '<template><x-foo>',
+    }, messages);
+
+    compiler.run().then(expectAsync1((e) {
+      MockFileSystem fs = compiler.fileSystem;
+      expect(fs.readCount, equals({
+        'index.html': 1,
+        'foo.html': 1,
+        'bar.html': 1
+      }), reason: 'Actual:\n  ${fs.readCount}');
+
+      var outputs = compiler.output.map((o) => o.path);
+      expect(outputs, equals([
+        'foo.html.dart',
+        'foo.html.dart.map',
+        'bar.html.dart',
+        'bar.html.dart.map',
+        'index.html.dart',
+        'index.html.dart.map',
+        'index.html_bootstrap.dart',
+        'index.html',
+      ].map((p) => path.join('out', p))));
+    }));
+  });
+
+  group('missing files', () {
+    test('main script', () {
+      var messages = new Messages.silent();
+      var compiler = createCompiler({
+        'index.html': '<head></head><body>'
+            '<script type="application/dart" src="notfound.dart"></script>'
+            '</body>',
+      }, messages);
+
+      compiler.run().then(expectAsync1((e) {
+        var msgs = messages.messages.where((m) =>
+            m.message.contains('unable')).toList();
+
+        expect(msgs.length, 1);
+        expect(msgs[0].level, Level.SEVERE);
+        expect(msgs[0].message, contains('unable to open file'));
+        expect(msgs[0].span, isNotNull);
+        expect(msgs[0].span.sourceUrl, 'index.html');
+
+        MockFileSystem fs = compiler.fileSystem;
+        expect(fs.readCount, { 'index.html': 1, 'notfound.dart': 1 });
+
+        var outputs = compiler.output.map((o) => o.path.toString());
+        expect(outputs, []);
+      }));
+    });
+
+    test('component html', () {
+      var messages = new Messages.silent();
+      var compiler = createCompiler({
+        'index.html': '<head>'
+            '<link rel="import" href="notfound.html">'
+            '<body><x-foo>'
+            '<script type="application/dart">main() {}</script>',
+      }, messages);
+
+      compiler.run().then(expectAsync1((e) {
+        var msgs = messages.messages.where((m) =>
+            m.message.contains('unable')).toList();
+
+        expect(msgs.length, 1);
+        expect(msgs[0].level, Level.SEVERE);
+        expect(msgs[0].message, contains('unable to open file'));
+        expect(msgs[0].span, isNotNull);
+        expect(msgs[0].span.sourceUrl, 'index.html');
+
+        MockFileSystem fs = compiler.fileSystem;
+        expect(fs.readCount, { 'index.html': 1, 'notfound.html': 1 });
+
+        var outputs = compiler.output.map((o) => o.path.toString());
+        expect(outputs, []);
+      }));
+    });
+
+    test('component script', () {
+      var messages = new Messages.silent();
+      var compiler = createCompiler({
+        'index.html': '<head>'
+            '<link rel="import" href="foo.html">'
+            '<body><x-foo></x-foo>'
+            '<script type="application/dart">main() {}</script>'
+            '</body>',
+        'foo.html': '<body><polymer-element name="x-foo" constructor="Foo">'
+            '<template></template>'
+            '<script type="application/dart" src="notfound.dart"></script>',
+      }, messages);
+
+      compiler.run().then(expectAsync1((e) {
+        var msgs = messages.messages.where((m) =>
+            m.message.contains('unable')).toList();
+
+        expect(msgs.length, 1);
+        expect(msgs[0].level, Level.SEVERE);
+        expect(msgs[0].message, contains('unable to open file'));
+
+        MockFileSystem fs = compiler.fileSystem;
+        expect(fs.readCount,
+            { 'index.html': 1, 'foo.html': 1, 'notfound.dart': 1  });
+
+        var outputs = compiler.output.map((o) => o.path.toString());
+        expect(outputs, []);
+      }));
+    });
+  });
+}
diff --git a/pkg/polymer/test/css_test.dart b/pkg/polymer/test/css_test.dart
new file mode 100644
index 0000000..1702312
--- /dev/null
+++ b/pkg/polymer/test/css_test.dart
@@ -0,0 +1,559 @@
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library css_test;
+
+import 'package:path/path.dart' as path;
+import 'package:polymer/src/messages.dart';
+import 'package:polymer/src/utils.dart' as utils;
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+
+import 'testing.dart';
+
+test_simple_var() {
+  Map createFiles() {
+    return {
+      'index.html':
+        '<!DOCTYPE html>'
+        '<html lang="en">'
+          '<head>'
+            '<meta charset="utf-8">'
+          '</head>'
+          '<body>'
+            '<style>'
+              '@main_color: var(b);'
+              '@b: var(c);'
+              '@c: red;'
+            '</style>'
+            '<style>'
+              '.test { color: var(main_color); }'
+            '</style>'
+            '<script type="application/dart">main() {}</script>'
+          '</body>'
+        '</html>',
+    };
+  }
+
+  var messages = new Messages.silent();
+  var compiler = createCompiler(createFiles(), messages, errors: true,
+      scopedCss: true);
+
+  compiler.run().then(expectAsync1((e) {
+    MockFileSystem fs = compiler.fileSystem;
+    expect(fs.readCount, equals({
+      'index.html': 1,
+    }), reason: 'Actual:\n  ${fs.readCount}');
+
+    var htmlInfo = compiler.info['index.html'];
+    expect(htmlInfo.styleSheets.length, 2);
+    expect(prettyPrintCss(htmlInfo.styleSheets[0]), '');
+    expect(prettyPrintCss(htmlInfo.styleSheets[1]), '.test { color: red; }');
+
+    var outputs = compiler.output.map((o) => o.path);
+    expect(outputs, equals([
+      'out/index.html.dart',
+      'out/index.html.dart.map',
+      'out/index.html_bootstrap.dart',
+      'out/index.html',
+    ]));
+  }));
+}
+
+test_var() {
+  Map createFiles() {
+    return {
+      'index.html':
+        '<!DOCTYPE html>'
+        '<html lang="en">'
+          '<head>'
+            '<meta charset="utf-8">'
+          '</head>'
+          '<body>'
+            '<style>'
+              '@main-color: var(b);'
+              '@b: var(c);'
+              '@c: red;'
+              '@d: var(main-color-1, green);'
+              '@border-pen: solid;'
+              '@inset: 5px;'
+              '@frame-color: solid orange;'
+              '@big-border: 2px 2px 2px;'
+              '@border-stuff: 3px dashed var(main-color);'
+              '@border-2: 3px var(big-border) dashed var(main-color-1, green);'
+              '@blue-border: bold var(not-found, 1px 10px blue)'
+            '</style>'
+            '<style>'
+              '.test-1 { color: var(main-color-1, blue); }'
+              '.test-2 { color: var(main-color-1, var(main-color)); }'
+              '.test-3 { color: var(d, yellow); }'
+              '.test-4 { color: var(d-1, yellow); }'
+              '.test-5 { color: var(d-1, var(d)); }'
+              '.test-6 { border: var(inset) var(border-pen) var(d); }'
+              '.test-7 { border: 10px var(border-pen) var(d); }'
+              '.test-8 { border: 20px var(border-pen) yellow; }'
+              '.test-9 { border: 30px dashed var(d); }'
+              '.test-10 { border: 40px var(frame-color);}'
+              '.test-11 { border: 40px var(frame-color-1, blue);}'
+              '.test-12 { border: 40px var(frame-color-1, solid blue);}'
+              '.test-13 {'
+                 'border: 40px var(x1, var(x2, var(x3, var(frame-color)));'
+              '}'
+              '.test-14 { border: 40px var(x1, var(frame-color); }'
+              '.test-15 { border: 40px var(x1, solid blue);}'
+              '.test-16 { border: 1px 1px 2px 3px var(frame-color);}'
+              '.test-17 { border: 1px 1px 2px 3px var(x1, solid blue);}'
+              '.test-18 { border: 1px 1px 2px var(border-stuff);}'
+              '.test-19 { border: var(big-border) var(border-stuff);}'
+              '.test-20 { border: var(border-2);}'
+              '.test-21 { border: var(blue-border);}'
+            '</style>'
+            '<script type="application/dart">main() {}</script>'
+          '</body>'
+        '</html>',
+    };
+  }
+
+  var messages = new Messages.silent();
+  var compiler = createCompiler(createFiles(), messages, errors: true,
+    scopedCss: true);
+
+  compiler.run().then(expectAsync1((e) {
+    MockFileSystem fs = compiler.fileSystem;
+    expect(fs.readCount, equals({
+      'index.html': 1,
+    }), reason: 'Actual:\n  ${fs.readCount}');
+
+    var htmlInfo = compiler.info['index.html'];
+    expect(htmlInfo.styleSheets.length, 2);
+    expect(prettyPrintCss(htmlInfo.styleSheets[0]), '');
+    expect(prettyPrintCss(htmlInfo.styleSheets[1]),
+        '.test-1 { color: blue; } '
+        '.test-2 { color: red; } '
+        '.test-3 { color: green; } '
+        '.test-4 { color: yellow; } '
+        '.test-5 { color: green; } '
+        '.test-6 { border: 5px solid green; } '
+        '.test-7 { border: 10px solid green; } '
+        '.test-8 { border: 20px solid yellow; } '
+        '.test-9 { border: 30px dashed green; } '
+        '.test-10 { border: 40px solid orange; } '
+        '.test-11 { border: 40px blue; } '
+        '.test-12 { border: 40px solid blue; } '
+        '.test-13 { border: 40px solid orange; } '
+        '.test-14 { border: 40px solid orange; } '
+        '.test-15 { border: 40px solid blue; } '
+        '.test-16 { border: 1px 1px 2px 3px solid orange; } '
+        '.test-17 { border: 1px 1px 2px 3px solid blue; } '
+        '.test-18 { border: 1px 1px 2px 3px dashed red; } '
+        '.test-19 { border: 2px 2px 2px 3px dashed red; } '
+        '.test-20 { border: 3px 2px 2px 2px dashed green; } '
+        '.test-21 { border: bold 1px 10px blue; }');
+    var outputs = compiler.output.map((o) => o.path);
+    expect(outputs, equals([
+      'out/index.html.dart',
+      'out/index.html.dart.map',
+      'out/index.html_bootstrap.dart',
+      'out/index.html',
+    ]));
+  }));
+}
+
+test_simple_import() {
+  Map createFiles() {
+    return {
+      'foo.css':  r'''@main_color: var(b);
+        @b: var(c);
+        @c: red;''',
+      'index.html':
+        '<!DOCTYPE html>'
+        '<html lang="en">'
+          '<head>'
+             '<meta charset="utf-8">'
+          '</head>'
+          '<body>'
+            '<style>'
+              '@import "foo.css";'
+              '.test { color: var(main_color); }'
+            '</style>'
+            '<script type="application/dart">main() {}</script>'
+          '</body>'
+        '</html>',
+    };
+  }
+
+  var messages = new Messages.silent();
+  var compiler = createCompiler(createFiles(), messages, errors: true,
+      scopedCss: true);
+
+  compiler.run().then(expectAsync1((e) {
+    MockFileSystem fs = compiler.fileSystem;
+    expect(fs.readCount, equals({
+      'foo.css': 1,
+      'index.html': 1,
+    }), reason: 'Actual:\n  ${fs.readCount}');
+
+    var cssInfo = compiler.info['foo.css'];
+    expect(cssInfo.styleSheets.length, 1);
+    expect(prettyPrintCss(cssInfo.styleSheets[0]), '');
+
+    var htmlInfo = compiler.info['index.html'];
+    expect(htmlInfo.styleSheets.length, 1);
+    expect(prettyPrintCss(htmlInfo.styleSheets[0]),
+        '@import url(foo.css); .test { color: red; }');
+
+    var outputs = compiler.output.map((o) => o.path);
+    expect(outputs, equals([
+      'out/index.html.dart',
+      'out/index.html.dart.map',
+      'out/index.html_bootstrap.dart',
+      'out/foo.css',
+      'out/index.html',
+    ]));
+  }));
+}
+
+test_imports() {
+  Map createFiles() {
+    return {
+      'first.css':
+        '@import "third.css";'
+        '@main-width: var(main-width-b);'
+        '@main-width-b: var(main-width-c);'
+        '@main-width-c: var(wide-width);',
+      'second.css':
+        '@import "fourth.css";'
+        '@main-color: var(main-color-b);'
+        '@main-color-b: var(main-color-c);'
+        '@main-color-c: var(color-value);',
+      'third.css':
+        '@wide-width: var(wide-width-b);'
+        '@wide-width-b: var(wide-width-c);'
+        '@wide-width-c: 100px;',
+      'fourth.css':
+        '@color-value: var(color-value-b);'
+        '@color-value-b: var(color-value-c);'
+        '@color-value-c: red;',
+      'index.html':
+        '<!DOCTYPE html>'
+        '<html lang="en">'
+          '<head>'
+            '<meta charset="utf-8">'
+            '<link rel="stylesheet" href="first.css">'
+          '</head>'
+          '<body>'
+            '<style>'
+              '@import "first.css";'
+              '@import "second.css";'
+              '.test-1 { color: var(main-color); }'
+              '.test-2 { width: var(main-width); }'
+            '</style>'
+            '<script type="application/dart">main() {}</script>'
+          '</body>'
+        '</html>',
+    };
+  }
+
+  var messages = new Messages.silent();
+  var compiler = createCompiler(createFiles(), messages, errors: true,
+      scopedCss: true);
+
+  compiler.run().then(expectAsync1((e) {
+    MockFileSystem fs = compiler.fileSystem;
+    expect(fs.readCount, equals({
+      'first.css': 1,
+      'second.css': 1,
+      'third.css': 1,
+      'fourth.css': 1,
+      'index.html': 1,
+    }), reason: 'Actual:\n  ${fs.readCount}');
+
+    var firstInfo = compiler.info['first.css'];
+    expect(firstInfo.styleSheets.length, 1);
+    expect(prettyPrintCss(firstInfo.styleSheets[0]), '@import url(third.css);');
+
+    var secondInfo = compiler.info['second.css'];
+    expect(secondInfo.styleSheets.length, 1);
+    expect(prettyPrintCss(secondInfo.styleSheets[0]),
+        '@import url(fourth.css);');
+
+    var thirdInfo = compiler.info['third.css'];
+    expect(thirdInfo.styleSheets.length, 1);
+    expect(prettyPrintCss(thirdInfo.styleSheets[0]), '');
+
+    var fourthInfo = compiler.info['fourth.css'];
+    expect(fourthInfo.styleSheets.length, 1);
+    expect(prettyPrintCss(fourthInfo.styleSheets[0]), '');
+
+    var htmlInfo = compiler.info['index.html'];
+    expect(htmlInfo.styleSheets.length, 1);
+    expect(prettyPrintCss(htmlInfo.styleSheets[0]),
+        '@import url(first.css); '
+        '@import url(second.css); '
+        '.test-1 { color: red; } '
+        '.test-2 { width: 100px; }');
+
+    var outputs = compiler.output.map((o) => o.path);
+    expect(outputs, equals([
+      'out/index.html.dart',
+      'out/index.html.dart.map',
+      'out/index.html_bootstrap.dart',
+      'out/first.css',
+      'out/second.css',
+      'out/third.css',
+      'out/fourth.css',
+      'out/index.html',
+    ]));
+  }));
+}
+
+test_component_var() {
+  Map createFiles() {
+    return {
+      'index.html': '<!DOCTYPE html>'
+                    '<html lang="en">'
+                      '<head>'
+                        '<meta charset="utf-8">'
+                        '<link rel="import" href="foo.html">'
+                      '</head>'
+                      '<body>'
+                        '<x-foo></x-foo>'
+                        '<script type="application/dart">main() {}</script>'
+                      '</body>'
+                    '</html>',
+      'foo.html': '<!DOCTYPE html>'
+                  '<html lang="en">'
+                    '<head>'
+                      '<meta charset="utf-8">'
+                    '</head>'
+                    '<body>'
+                      '<polymer-element name="x-foo" constructor="Foo">'
+                        '<template>'
+                          '<style scoped>'
+                            '@import "foo.css";'
+                            '.main { color: var(main_color); }'
+                            '.test-background { '
+                              'background:  url(http://www.foo.com/bar.png);'
+                            '}'
+                          '</style>'
+                        '</template>'
+                      '</polymer-element>'
+                    '</body>'
+                  '</html>',
+      'foo.css':  r'''@main_color: var(b);
+                      @b: var(c);
+                      @c: red;
+
+                      @one: var(two);
+                      @two: var(one);
+
+                      @four: var(five);
+                      @five: var(six);
+                      @six: var(four);
+
+                      @def-1: var(def-2);
+                      @def-2: var(def-3);
+                      @def-3: var(def-2);''',
+    };
+  }
+
+  test('var- and Less @define', () {
+    var messages = new Messages.silent();
+    var compiler = createCompiler(createFiles(), messages, errors: true,
+      scopedCss: true);
+
+    compiler.run().then(expectAsync1((e) {
+      MockFileSystem fs = compiler.fileSystem;
+      expect(fs.readCount, equals({
+        'index.html': 1,
+        'foo.html': 1,
+        'foo.css': 1
+      }), reason: 'Actual:\n  ${fs.readCount}');
+
+      var cssInfo = compiler.info['foo.css'];
+      expect(cssInfo.styleSheets.length, 1);
+      var htmlInfo = compiler.info['foo.html'];
+      expect(htmlInfo.styleSheets.length, 0);
+      expect(htmlInfo.declaredComponents.length, 1);
+      expect(htmlInfo.declaredComponents[0].styleSheets.length, 1);
+
+      var outputs = compiler.output.map((o) => o.path);
+      expect(outputs, equals([
+        'out/foo.html.dart',
+        'out/foo.html.dart.map',
+        'out/index.html.dart',
+        'out/index.html.dart.map',
+        'out/index.html_bootstrap.dart',
+        'out/foo.css',
+        'out/index.html.css',
+        'out/index.html',
+      ]));
+
+      for (var file in compiler.output) {
+        if (file.path == 'out/index.html.css') {
+          expect(file.contents,
+              '/* Auto-generated from components style tags. */\n'
+              '/* DO NOT EDIT. */\n\n'
+              '/* ==================================================== \n'
+              '   Component x-foo stylesheet \n'
+              '   ==================================================== */\n'
+              '@import "foo.css";\n'
+              '[is="x-foo"] .main {\n'
+              '  color: #f00;\n'
+              '}\n'
+              '[is="x-foo"] .test-background {\n'
+              '  background: url("http://www.foo.com/bar.png");\n'
+              '}\n\n');
+        } else if (file.path == 'out/foo.css') {
+          expect(file.contents,
+              '/* Auto-generated from style sheet href = foo.css */\n'
+              '/* DO NOT EDIT. */\n\n\n\n');
+        }
+      }
+
+      // Check for warning messages about var- cycles.
+      expect(messages.messages.length, 8);
+
+      var errorMessage = messages.messages[0];
+      expect(errorMessage.message, contains('var cycle detected var-def-1'));
+      expect(errorMessage.span, isNotNull);
+      expect(errorMessage.span.start.line, 11);
+      expect(errorMessage.span.start.column, 22);
+      expect(errorMessage.span.text, '@def-1: var(def-2)');
+
+      errorMessage = messages.messages[1];
+      expect(errorMessage.message, contains('var cycle detected var-five'));
+      expect(errorMessage.span, isNotNull);
+      expect(errorMessage.span.start.line, 8);
+      expect(errorMessage.span.start.column, 22);
+      expect(errorMessage.span.text, '@five: var(six)');
+
+      errorMessage = messages.messages[2];
+      expect(errorMessage.message, contains('var cycle detected var-six'));
+      expect(errorMessage.span, isNotNull);
+      expect(errorMessage.span.start.line, 9);
+      expect(errorMessage.span.start.column, 22);
+      expect(errorMessage.span.text, '@six: var(four)');
+
+      errorMessage = messages.messages[3];
+      expect(errorMessage.message, contains('var cycle detected var-def-3'));
+      expect(errorMessage.span, isNotNull);
+      expect(errorMessage.span.start.line, 13);
+      expect(errorMessage.span.start.column, 22);
+      expect(errorMessage.span.text, '@def-3: var(def-2)');
+
+      errorMessage = messages.messages[4];
+      expect(errorMessage.message, contains('var cycle detected var-two'));
+      expect(errorMessage.span, isNotNull);
+      expect(errorMessage.span.start.line, 5);
+      expect(errorMessage.span.start.column, 22);
+      expect(errorMessage.span.text, '@two: var(one)');
+
+      errorMessage = messages.messages[5];
+      expect(errorMessage.message, contains('var cycle detected var-def-2'));
+      expect(errorMessage.span, isNotNull);
+      expect(errorMessage.span.start.line, 12);
+      expect(errorMessage.span.start.column, 22);
+      expect(errorMessage.span.text, '@def-2: var(def-3)');
+
+      errorMessage = messages.messages[6];
+      expect(errorMessage.message, contains('var cycle detected var-one'));
+      expect(errorMessage.span, isNotNull);
+      expect(errorMessage.span.start.line, 4);
+      expect(errorMessage.span.start.column, 22);
+      expect(errorMessage.span.text, '@one: var(two)');
+
+      errorMessage = messages.messages[7];
+      expect(errorMessage.message, contains('var cycle detected var-four'));
+      expect(errorMessage.span, isNotNull);
+      expect(errorMessage.span.start.line, 7);
+      expect(errorMessage.span.start.column, 22);
+      expect(errorMessage.span.text, '@four: var(five)');
+    }));
+  });
+}
+
+test_pseudo_element() {
+  var messages = new Messages.silent();
+  var compiler = createCompiler({
+    'index.html': '<head>'
+                  '<link rel="import" href="foo.html">'
+                  '<style>'
+                    '.test::x-foo { background-color: red; }'
+                    '.test::x-foo1 { color: blue; }'
+                    '.test::x-foo2 { color: green; }'
+                  '</style>'
+                  '<body>'
+                    '<x-foo class=test></x-foo>'
+                    '<x-foo></x-foo>'
+                  '<script type="application/dart">main() {}</script>',
+    'foo.html': '<head>'
+                '<body><polymer-element name="x-foo" constructor="Foo">'
+                '<template>'
+                  '<div pseudo="x-foo">'
+                    '<div>Test</div>'
+                  '</div>'
+                  '<div pseudo="x-foo1 x-foo2">'
+                  '<div>Test</div>'
+                  '</div>'
+                '</template>',
+    }, messages, scopedCss: true);
+
+    compiler.run().then(expectAsync1((e) {
+      MockFileSystem fs = compiler.fileSystem;
+      expect(fs.readCount, equals({
+        'index.html': 1,
+        'foo.html': 1,
+      }), reason: 'Actual:\n  ${fs.readCount}');
+
+      var outputs = compiler.output.map((o) => o.path);
+      expect(outputs, equals([
+        'out/foo.html.dart',
+        'out/foo.html.dart.map',
+        'out/index.html.dart',
+        'out/index.html.dart.map',
+        'out/index.html_bootstrap.dart',
+        'out/index.html',
+      ]));
+      expect(compiler.output.last.contents, contains(
+          '<div pseudo="x-foo_0">'
+            '<div>Test</div>'
+          '</div>'
+          '<div pseudo="x-foo1_1 x-foo2_2">'
+          '<div>Test</div>'
+          '</div>'));
+      expect(compiler.output.last.contents, contains(
+          '<style>.test > *[pseudo="x-foo_0"] {\n'
+            '  background-color: #f00;\n'
+          '}\n'
+          '.test > *[pseudo="x-foo1_1"] {\n'
+          '  color: #00f;\n'
+          '}\n'
+          '.test > *[pseudo="x-foo2_2"] {\n'
+          '  color: #008000;\n'
+          '}'
+          '</style>'));
+    }));
+}
+
+main() {
+  useCompactVMConfiguration();
+
+  group('css', () {
+    setUp(() {
+      utils.path = new path.Builder(style: path.Style.posix);
+    });
+
+    tearDown(() {
+      utils.path = new path.Builder();
+    });
+
+    test('test_simple_var', test_simple_var);
+    test('test_var', test_var);
+    test('test_simple_import', test_simple_import);
+    test('test_imports', test_imports);
+    group('test_component_var', test_component_var);
+    test('test_pseudo_element', test_pseudo_element);
+  });
+}
diff --git a/pkg/polymer/test/data/unit/event_path_test.html b/pkg/polymer/test/data/unit/event_path_test.html
new file mode 100644
index 0000000..811479d
--- /dev/null
+++ b/pkg/polymer/test/data/unit/event_path_test.html
@@ -0,0 +1,142 @@
+<!doctype html>
+<!--
+   Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+   for details. All rights reserved. Use of this source code is governed by a
+   BSD-style license that can be found in the LICENSE file.
+-->
+<html>
+  <head>
+    <title>event path</title>
+    <script src="packages/polymer/testing/testing.js"></script>
+    <script src="packages/unittest/test_controller.js"></script>
+    <!--
+    Test ported from:
+    https://github.com/Polymer/polymer/blob/7936ff8/test/html/event-path.html
+
+    This test actually doesn't test the polymer's event layer. It just ensures
+    that tests are propagated in the right order when using Shadow DOM.
+    -->
+  </head>
+  <body>
+
+  <polymer-element name="x-selector">
+    <template>
+      <div id="selectorDiv">
+        <content id="selectorContent"></content>
+      </div>
+    </template>
+    <script type="application/dart">
+      import 'package:polymer/polymer.dart';
+      @CustomTag("x-selector")
+      class XSelector extends PolymerElement {}
+    </script>
+  </polymer-element>
+
+  <polymer-element name="x-overlay">
+    <template>
+      <content id="overlayContent"></content>
+    </template>
+    <script type="application/dart">
+      import 'package:polymer/polymer.dart';
+      @CustomTag("x-overlay")
+      class XOverlay extends PolymerElement {}
+    </script>
+  </polymer-element>
+
+  <polymer-element name="x-menu" extends="x-selector">
+    <template>
+      <div id="menuDiv">
+        <shadow id="menuShadow"></shadow>
+      </div>
+    </template>
+    <script type="application/dart">
+      import 'package:polymer/polymer.dart';
+      @CustomTag("x-menu")
+      class XMenu extends PolymerElement {}
+    </script>
+  </polymer-element>
+
+  <polymer-element name="x-menu-button">
+    <template>
+    <div>
+      <x-overlay id="overlay">
+        <div id="menuButtonDiv">
+          <x-menu id="menu">
+            <content id="menuButtonContent"></content>
+          </x-menu>
+        </div>
+      </x-overlay>
+      </div>
+    </template>
+    <script type="application/dart">
+      import 'package:polymer/polymer.dart';
+      @CustomTag("x-menu-button")
+      class XMenuButton extends PolymerElement {}
+    </script>
+  </polymer-element>
+
+  <x-menu-button id="menuButton">
+    <div id="item1"><div id="source"></div>Item1</div>
+    <div id="item2">Item2</div>
+  </x-menu-button>
+
+
+  <script type="application/dart">
+    import 'dart:html';
+    import 'dart:async';
+    import 'package:unittest/unittest.dart';
+    import 'package:unittest/html_config.dart';
+
+    main() {
+      useHtmlConfiguration();
+      test('bubbling in the right order', () {
+        // TODO(sigmund): this should change once we port over the
+        // 'WebComponentsReady' event.
+        runAsync(expectAsync0(() {
+          var item1 = query('#item1');
+          var menuButton = query('#menuButton');
+          // Note: polymer uses automatic node finding (menuButton.$.menu)
+          // also note that their node finding code also reachs into the ids
+          // from the parent shadow (menu.$.selectorContent instead of
+          // menu.$.menuShadow.$.selectorContent)
+          var menu = menuButton.shadowRoot.query('#menu');
+          var selector = menu.shadowRoot.query("#menuShadow");
+          var overlay = menuButton.shadowRoot.query('#overlay');
+          var expectedPath = <Node>[
+              item1,
+              menuButton.shadowRoot.query('#menuButtonContent'),
+              selector.olderShadowRoot.query('#selectorContent'),
+              selector.olderShadowRoot.query('#selectorDiv'),
+              menu.shadowRoot.query('#menuShadow').olderShadowRoot,
+              menu.shadowRoot.query('#menuShadow'),
+              menu.shadowRoot.query('#menuDiv'),
+              menu.shadowRoot,
+              menu,
+              menuButton.shadowRoot.query('#menuButtonDiv'),
+              // TODO(sigmund): this test is currently broken because currently
+              // registerElement is sensitive to the order in which each custom
+              // element is registered. When fixed, we should be able to add the
+              // following three targets:
+              //   overlay.shadowRoot.query('#overlayContent'),
+              //   overlay.shadowRoot,
+              //   overlay,
+              menuButton.shadowRoot,
+              menuButton
+          ];
+          var x = 0;
+          for (int i = 0; i < expectedPath.length; i++) {
+            var node = expectedPath[i];
+            expect(node, isNotNull, reason: "Should not be null at $i");
+            node.on['x'].listen(expectAsync1((e) {
+              expect(e.currentTarget, node);
+              expect(x++, i);
+            }));
+          }
+
+          item1.dispatchEvent(new Event('x', canBubble: true));
+        }));
+      });
+    }
+  </script>
+  </body>
+</html>
diff --git a/pkg/polymer/test/data/unit/events_test.html b/pkg/polymer/test/data/unit/events_test.html
new file mode 100644
index 0000000..cbf33d1
--- /dev/null
+++ b/pkg/polymer/test/data/unit/events_test.html
@@ -0,0 +1,97 @@
+<!doctype html>
+<!--
+   Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
+   for details. All rights reserved. Use of this source code is governed by a
+   BSD-style license that can be found in the LICENSE file.
+-->
+<html>
+  <head>
+    <title>event path</title>
+    <script src="packages/polymer/testing/testing.js"></script>
+    <script src="packages/unittest/test_controller.js"></script>
+    <!--
+    Test ported from:
+    https://github.com/Polymer/polymer/blob/7936ff8/test/js/events.js
+
+    TODO(sigmund): when we have support for mutation observers, render all of
+    the test in Dart (like events.js does in JS)
+    -->
+  </head>
+  <body>
+
+  <polymer-element name="test-a" on-click="clickHandler">
+    <template></template>
+    <script type="application/dart">
+      import 'package:polymer/polymer.dart';
+
+      @CustomTag("test-a")
+      class TestA extends PolymerElement {
+        List clicks = [];
+        void clickHandler() {
+          clicks.add('host click on: $localName (id $id)');
+        }
+      }
+    </script>
+  </polymer-element>
+
+  <polymer-element name="test-b">
+    <template>
+      <div>
+        <span id="b-1">1</span>
+        <span id="b-2" on-click="clickHandler">2</span>
+      </div>
+    </template>
+    <script type="application/dart">
+      import 'package:polymer/polymer.dart';
+
+      @CustomTag("test-b")
+      class TestB extends PolymerElement {
+        List clicks = [];
+        void clickHandler(event, detail, target) {
+          clicks.add('local click under $localName (id $id) on ${target.id}');
+        }
+      }
+    </script>
+  </polymer-element>
+
+  <test-a id="a"></test-a>
+  <test-b id="b"></test-b>
+
+  <script type="application/dart">
+    import 'dart:html';
+    import 'dart:async';
+    import 'package:unittest/unittest.dart';
+    import 'package:unittest/html_config.dart';
+
+    main() {
+      useHtmlConfiguration();
+
+      test('host event', () {
+        // Note: this test is currently the only event in
+        // polymer/test/js/events.js at commit #7936ff8
+        Timer.run(expectAsync0(() {
+          var testA = query('#a');
+          expect(testA.xtag.clicks, isEmpty);
+          testA.click();
+          expect(testA.xtag.clicks, ['host click on: test-a (id a)']);
+        }));
+      });
+
+      test('local event', () {
+        Timer.run(expectAsync0(() {
+          var testB = query('#b');
+          expect(testB.xtag.clicks, isEmpty);
+          testB.click();
+          expect(testB.xtag.clicks, []);
+          var b1 = testB.shadowRoot.query('#b-1');
+          b1.click();
+          expect(testB.xtag.clicks, []);
+          var b2 = testB.shadowRoot.query('#b-2');
+          b2.click();
+          expect(testB.xtag.clicks, ['local click under test-b (id b) on b-2']);
+        }));
+      });
+    }
+  </script>
+  </body>
+</html>
diff --git a/pkg/polymer/test/paths_test.dart b/pkg/polymer/test/paths_test.dart
new file mode 100644
index 0000000..4a54a9d
--- /dev/null
+++ b/pkg/polymer/test/paths_test.dart
@@ -0,0 +1,339 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/** Tests for [PathMapper]. */
+library path_info_test;
+
+import 'package:path/path.dart' as path;
+import 'package:polymer/src/info.dart';
+import 'package:polymer/src/paths.dart';
+import 'package:polymer/src/utils.dart' as utils;
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+
+main() {
+  useCompactVMConfiguration();
+  group('paths', () {
+    setUp(() {
+      utils.path = new path.Builder(style: path.Style.posix);
+    });
+
+    tearDown(() {
+      utils.path = new path.Builder();
+    });
+
+    testPaths();
+  });
+}
+
+testPaths() {
+  group('outdir == basedir:', () {
+    group('outputPath', () {
+      test('mangle automatic', () {
+        var pathMapper = _newPathMapper('a', 'a', false);
+        var file = _mockFile('a/b.dart', pathMapper);
+        expect(file.dartCodeUrl.resolvedPath, 'a/b.dart');
+        expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+            'a/_b.dart.dart');
+      });
+
+      test('within packages/', () {
+        var pathMapper = _newPathMapper('a', 'a', false);
+        var file = _mockFile('a/packages/b.dart', pathMapper);
+        expect(file.dartCodeUrl.resolvedPath, 'a/packages/b.dart');
+        expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+            'a/_from_packages/_b.dart.dart');
+      });
+    });
+
+    group('importUrlFor', () {
+      test('simple pathMapper', () {
+        var pathMapper = _newPathMapper('a', 'a', false);
+        var file1 = _mockFile('a/b.dart', pathMapper);
+        var file2 = _mockFile('a/c/d.dart', pathMapper);
+        var file3 = _mockFile('a/e/f.dart', pathMapper);
+        expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+        expect(pathMapper.importUrlFor(file1, file3), 'e/_f.dart.dart');
+        expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+        expect(pathMapper.importUrlFor(file2, file3), '../e/_f.dart.dart');
+        expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
+        expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
+      });
+
+      test('include packages/', () {
+        var pathMapper = _newPathMapper('a', 'a', false);
+        var file1 = _mockFile('a/b.dart', pathMapper);
+        var file2 = _mockFile('a/c/d.dart', pathMapper);
+        var file3 = _mockFile('a/packages/f.dart', pathMapper);
+        expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+        expect(pathMapper.importUrlFor(file1, file3),
+            '_from_packages/_f.dart.dart');
+        expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+        expect(pathMapper.importUrlFor(file2, file3),
+            '../_from_packages/_f.dart.dart');
+        expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
+        expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
+      });
+
+      test('packages, but no rewrite', () {
+        var pathMapper = _newPathMapper('a', 'a', false, rewriteUrls: false);
+        var file1 = _mockFile('a/b.dart', pathMapper);
+        var file2 = _mockFile('a/c/d.dart', pathMapper);
+        var file3 = _mockFile('a/packages/c/f.dart', pathMapper,
+            url: 'package:e/f.dart');
+        expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+        expect(pathMapper.importUrlFor(file1, file3),
+          'package:e/_f.dart.dart');
+        expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+        expect(pathMapper.importUrlFor(file2, file3),
+            'package:e/_f.dart.dart');
+      });
+
+      test('windows paths', () {
+        try {
+          utils.path = new path.Builder(style: path.Style.windows);
+          var pathMapper = _newPathMapper('a', 'a', false);
+          var file1 = _mockFile('a\\b.dart', pathMapper);
+          var file2 = _mockFile('a\\c\\d.dart', pathMapper);
+          var file3 = _mockFile('a\\packages\\f.dart', pathMapper);
+          expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+          expect(pathMapper.importUrlFor(file1, file3),
+              '_from_packages/_f.dart.dart');
+          expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+          expect(pathMapper.importUrlFor(file2, file3),
+              '../_from_packages/_f.dart.dart');
+          expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
+          expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
+        } finally {
+          utils.path = new path.Builder();
+        }
+      });
+    });
+
+    test('transformUrl simple paths', () {
+      var pathMapper = _newPathMapper('a', 'a', false);
+      var file1 = 'a/b.dart';
+      var file2 = 'a/c/d.html';
+      // when the output == input directory, no paths should be rewritten
+      expect(pathMapper.transformUrl(file1, '/a.dart'), '/a.dart');
+      expect(pathMapper.transformUrl(file1, 'c.dart'), 'c.dart');
+      expect(pathMapper.transformUrl(file1, '../c/d.dart'), '../c/d.dart');
+      expect(pathMapper.transformUrl(file1, 'packages/c.dart'),
+          'packages/c.dart');
+      expect(pathMapper.transformUrl(file2, 'e.css'), 'e.css');
+      expect(pathMapper.transformUrl(file2, '../c/e.css'), 'e.css');
+      expect(pathMapper.transformUrl(file2, '../q/e.css'), '../q/e.css');
+      expect(pathMapper.transformUrl(file2, 'packages/c.css'),
+          'packages/c.css');
+      expect(pathMapper.transformUrl(file2, '../packages/c.css'),
+          '../packages/c.css');
+    });
+
+    test('transformUrl with source in packages/', () {
+      var pathMapper = _newPathMapper('a', 'a', false);
+      var file = 'a/packages/e.html';
+      // Even when output == base, files under packages/ are moved to
+      // _from_packages, so all imports are affected:
+      expect(pathMapper.transformUrl(file, 'e.css'), '../packages/e.css');
+      expect(pathMapper.transformUrl(file, '../packages/e.css'),
+          '../packages/e.css');
+      expect(pathMapper.transformUrl(file, '../q/e.css'), '../q/e.css');
+      expect(pathMapper.transformUrl(file, 'packages/c.css'),
+          '../packages/packages/c.css');
+    });
+  });
+
+  group('outdir != basedir:', () {
+    group('outputPath', (){
+      test('no force mangle', () {
+        var pathMapper = _newPathMapper('a', 'out', false);
+        var file = _mockFile('a/b.dart', pathMapper);
+        expect(file.dartCodeUrl.resolvedPath, 'a/b.dart');
+        expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+            'out/b.dart');
+      });
+
+      test('force mangling', () {
+        var pathMapper = _newPathMapper('a', 'out', true);
+        var file = _mockFile('a/b.dart', pathMapper);
+        expect(file.dartCodeUrl.resolvedPath, 'a/b.dart');
+        expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+            'out/_b.dart.dart');
+      });
+
+      test('within packages/, no mangle', () {
+        var pathMapper = _newPathMapper('a', 'out', false);
+        var file = _mockFile('a/packages/b.dart', pathMapper);
+        expect(file.dartCodeUrl.resolvedPath, 'a/packages/b.dart');
+        expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+            'out/_from_packages/b.dart');
+      });
+
+      test('within packages/, mangle', () {
+        var pathMapper = _newPathMapper('a', 'out', true);
+        var file = _mockFile('a/packages/b.dart', pathMapper);
+        expect(file.dartCodeUrl.resolvedPath, 'a/packages/b.dart');
+        expect(pathMapper.outputPath(file.dartCodeUrl.resolvedPath, '.dart'),
+            'out/_from_packages/_b.dart.dart');
+      });
+    });
+
+    group('importUrlFor', (){
+      test('simple paths, no mangle', () {
+        var pathMapper = _newPathMapper('a', 'out', false);
+        var file1 = _mockFile('a/b.dart', pathMapper);
+        var file2 = _mockFile('a/c/d.dart', pathMapper);
+        var file3 = _mockFile('a/e/f.dart', pathMapper);
+        expect(pathMapper.importUrlFor(file1, file2), 'c/d.dart');
+        expect(pathMapper.importUrlFor(file1, file3), 'e/f.dart');
+        expect(pathMapper.importUrlFor(file2, file1), '../b.dart');
+        expect(pathMapper.importUrlFor(file2, file3), '../e/f.dart');
+        expect(pathMapper.importUrlFor(file3, file2), '../c/d.dart');
+        expect(pathMapper.importUrlFor(file3, file1), '../b.dart');
+      });
+
+      test('simple paths, mangle', () {
+        var pathMapper = _newPathMapper('a', 'out', true);
+        var file1 = _mockFile('a/b.dart', pathMapper);
+        var file2 = _mockFile('a/c/d.dart', pathMapper);
+        var file3 = _mockFile('a/e/f.dart', pathMapper);
+        expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+        expect(pathMapper.importUrlFor(file1, file3), 'e/_f.dart.dart');
+        expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+        expect(pathMapper.importUrlFor(file2, file3), '../e/_f.dart.dart');
+        expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
+        expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
+      });
+
+      test('include packages/, no mangle', () {
+        var pathMapper = _newPathMapper('a', 'out', false);
+        var file1 = _mockFile('a/b.dart', pathMapper);
+        var file2 = _mockFile('a/c/d.dart', pathMapper);
+        var file3 = _mockFile('a/packages/e/f.dart', pathMapper,
+            url: 'package:e/f.dart');
+        expect(pathMapper.importUrlFor(file1, file2), 'c/d.dart');
+        expect(pathMapper.importUrlFor(file1, file3),
+            '_from_packages/e/f.dart');
+        expect(pathMapper.importUrlFor(file2, file1), '../b.dart');
+        expect(pathMapper.importUrlFor(file2, file3),
+            '../_from_packages/e/f.dart');
+        expect(pathMapper.importUrlFor(file3, file2), '../../c/d.dart');
+        expect(pathMapper.importUrlFor(file3, file1), '../../b.dart');
+      });
+
+      test('include packages/, mangle', () {
+        var pathMapper = _newPathMapper('a', 'out', true);
+        var file1 = _mockFile('a/b.dart', pathMapper);
+        var file2 = _mockFile('a/c/d.dart', pathMapper);
+        var file3 = _mockFile('a/packages/e/f.dart', pathMapper,
+            url: 'package:e/f.dart');
+        expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+        expect(pathMapper.importUrlFor(file1, file3),
+          '_from_packages/e/_f.dart.dart');
+        expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+        expect(pathMapper.importUrlFor(file2, file3),
+            '../_from_packages/e/_f.dart.dart');
+        expect(pathMapper.importUrlFor(file3, file2), '../../c/_d.dart.dart');
+        expect(pathMapper.importUrlFor(file3, file1), '../../_b.dart.dart');
+      });
+
+      test('windows paths', () {
+        try {
+          utils.path = new path.Builder(style: path.Style.windows);
+          var pathMapper = _newPathMapper('a', 'out', true);
+          var file1 = _mockFile('a\\b.dart', pathMapper);
+          var file2 = _mockFile('a\\c\\d.dart', pathMapper);
+          var file3 = _mockFile('a\\packages\\f.dart', pathMapper);
+          expect(pathMapper.importUrlFor(file1, file2), 'c/_d.dart.dart');
+          expect(pathMapper.importUrlFor(file1, file3),
+              '_from_packages/_f.dart.dart');
+          expect(pathMapper.importUrlFor(file2, file1), '../_b.dart.dart');
+          expect(pathMapper.importUrlFor(file2, file3),
+              '../_from_packages/_f.dart.dart');
+          expect(pathMapper.importUrlFor(file3, file2), '../c/_d.dart.dart');
+          expect(pathMapper.importUrlFor(file3, file1), '../_b.dart.dart');
+        } finally {
+          utils.path = new path.Builder();
+        }
+      });
+    });
+
+    group('transformUrl', () {
+      test('simple source, not in packages/', () {
+        var pathMapper = _newPathMapper('a', 'out', false);
+        var file1 = 'a/b.dart';
+        var file2 = 'a/c/d.html';
+        // when the output == input directory, no paths should be rewritten
+        expect(pathMapper.transformUrl(file1, '/a.dart'), '/a.dart');
+        expect(pathMapper.transformUrl(file1, 'c.dart'), '../a/c.dart');
+
+        // reach out from basedir:
+        expect(pathMapper.transformUrl(file1, '../c/d.dart'), '../c/d.dart');
+
+        // reach into packages dir:
+        expect(pathMapper.transformUrl(file1, 'packages/c.dart'),
+            '../a/packages/c.dart');
+
+        expect(pathMapper.transformUrl(file2, 'e.css'), '../../a/c/e.css');
+
+        _checkPath('../../a/c/../c/e.css', '../../a/c/e.css');
+        expect(pathMapper.transformUrl(file2, '../c/e.css'), '../../a/c/e.css');
+
+        _checkPath('../../a/c/../q/e.css', '../../a/q/e.css');
+        expect(pathMapper.transformUrl(file2, '../q/e.css'), '../../a/q/e.css');
+
+        expect(pathMapper.transformUrl(file2, 'packages/c.css'),
+            '../../a/c/packages/c.css');
+        _checkPath('../../a/c/../packages/c.css', '../../a/packages/c.css');
+        expect(pathMapper.transformUrl(file2, '../packages/c.css'),
+            '../../a/packages/c.css');
+      });
+
+      test('input in packages/', () {
+        var pathMapper = _newPathMapper('a', 'out', true);
+        var file = 'a/packages/e.html';
+        expect(pathMapper.transformUrl(file, 'e.css'),
+            '../../a/packages/e.css');
+        expect(pathMapper.transformUrl(file, '../packages/e.css'),
+            '../../a/packages/e.css');
+        expect(pathMapper.transformUrl(file, '../q/e.css'), '../../a/q/e.css');
+        expect(pathMapper.transformUrl(file, 'packages/c.css'),
+            '../../a/packages/packages/c.css');
+      });
+
+      test('src fragments', () {
+        var pathMapper = _newPathMapper('a', 'out', false);
+        var file1 = 'a/b.dart';
+        var file2 = 'a/c/html.html';
+        // when the output == input directory, no paths should be rewritten
+        expect(pathMapper.transformUrl(file1, '#tips'), '#tips');
+        expect(pathMapper.transformUrl(file1,
+            'http://www.w3schools.com/html_links.htm#tips'),
+            'http://www.w3schools.com/html_links.htm#tips');
+        expect(pathMapper.transformUrl(file2,
+          'html_links.html'),
+          '../../a/c/html_links.html');
+        expect(pathMapper.transformUrl(file2,
+            'html_links.html#tips'),
+            '../../a/c/html_links.html#tips');
+      });
+    });
+  });
+}
+
+_newPathMapper(String baseDir, String outDir, bool forceMangle,
+    {bool rewriteUrls: true}) =>
+  new PathMapper(baseDir, outDir, 'packages', forceMangle, rewriteUrls);
+
+_mockFile(String filePath, PathMapper pathMapper, {String url}) {
+  var file = new FileInfo(new UrlInfo(
+        url == null ? filePath : url, filePath, null));
+  file.outputFilename = pathMapper.mangle(
+      utils.path.basename(filePath), '.dart', false);
+  return file;
+}
+
+_checkPath(String filePath, String expected) {
+  expect(utils.path.normalize(filePath), expected);
+}
diff --git a/pkg/polymer/test/run.sh b/pkg/polymer/test/run.sh
new file mode 100755
index 0000000..0d2b569
--- /dev/null
+++ b/pkg/polymer/test/run.sh
@@ -0,0 +1,134 @@
+#!/bin/bash
+# Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Usage: call directly in the commandline as test/run.sh ensuring that you have
+# both 'dart' and 'content_shell' in your path. Filter tests by passing a
+# pattern as an argument to this script.
+
+# TODO(sigmund): replace with a real test runner
+
+# bail on error
+set -e
+
+# print commands executed by this script
+# set -x
+
+DIR=$( cd $( dirname "${BASH_SOURCE[0]}" ) && pwd )
+export DART_FLAGS="--checked"
+TEST_PATTERN=$1
+
+function fail {
+  return 1
+}
+
+function show_diff {
+  diff -u -N $1 $2 | \
+    sed -e "s/^\(+.*\)/\1/" |\
+    sed -e "s/^\(-.*\)/\1/"
+  return 1
+}
+
+function update {
+  read -p "Would you like to update the expectations? [y/N]: " answer
+  if [[ $answer == 'y' || $answer == 'Y' ]]; then
+    cp $2 $1
+    return 0
+  fi
+  return 1
+}
+
+function pass {
+  echo -e "OK"
+}
+
+function compare {
+  # use a standard diff, if they are not identical, format the diff nicely to
+  # see what's the error and prompt to see if they wish to update it. If they
+  # do, continue running more tests.
+  diff -q $1 $2 && pass || show_diff $1 $2 || update $1 $2
+}
+
+if [[ ($TEST_PATTERN == "") ]]; then
+  # Note: dartanalyzer needs to be run from the root directory for proper path
+  # canonicalization.
+  pushd $DIR/.. > /dev/null
+
+  echo Analyzing compiler for warnings or type errors
+  dartanalyzer --hints --fatal-warnings --fatal-type-errors bin/dwc.dart
+
+  echo -e "\nAnalyzing runtime for warnings or type errors"
+  dartanalyzer --hints --fatal-warnings --fatal-type-errors lib/polymer.dart
+
+  popd > /dev/null
+fi
+
+function compare_all {
+# TODO(jmesserly): bash and dart regexp might not be 100% the same. Ideally we
+# could do all the heavy lifting in Dart code, and keep this script as a thin
+# wrapper that sets `--enable-type-checks --enable-asserts`
+  for input in $DIR/../example/component/news/test/*_test.html \
+               $DIR/../../../samples/third_party/todomvc/test/*_test.html; do
+    if [[ ($TEST_PATTERN == "") || ($input =~ $TEST_PATTERN) ]]; then
+      FILENAME=`basename $input`
+      DIRNAME=`dirname $input`
+      if [[ `basename $DIRNAME` == 'input' ]]; then
+        DIRNAME=`dirname $DIRNAME`
+      fi
+      echo -e -n "Checking diff for $FILENAME "
+      DUMP="$DIRNAME/out/$FILENAME.txt"
+      EXPECTATION="$DIRNAME/expected/$FILENAME.txt"
+
+      compare $EXPECTATION $DUMP
+    fi
+  done
+  echo -e "Some tests failed"
+  fail
+}
+
+if [[ -e $DIR/data/input/example ]]; then
+  echo "WARNING: detected old data/input/example symlink."
+  echo "Removing it and rerunning pub install to fix broken example symlinks."
+  echo "See http://dartbug.com/9418 for more information."
+  echo "You should only see this message once."
+  if [[ -e $DIR/packages ]]; then
+    find . -name packages -type l | xargs rm
+  fi
+  rm $DIR/data/input/example
+  pushd $DIR/..
+  pub install
+  popd
+fi
+
+# TODO(jmesserly): dart:io fails if we run the Dart scripts with an absolute
+# path. So use pushd/popd to change the working directory.
+if [[ ($TEST_PATTERN == "") ]]; then
+  pushd $DIR/.. > /dev/null
+  echo -e "\nTesting build.dart... "
+  dart $DART_FLAGS build.dart
+  # Run it the way the editor does. Hide stdout because it is in noisy machine
+  # format. Show stderr in case something breaks.
+  # NOTE: not using --checked because the editor doesn't use it, and to workaround
+  # http://dartbug.com/9637
+  dart build.dart --machine --clean > /dev/null
+  dart build.dart --machine --full > /dev/null
+  dart build.dart --machine --changed ../../samples/third_party/todomvc/web/index.html > /dev/null
+  popd > /dev/null
+fi
+
+pushd $DIR > /dev/null
+echo -e "\nRunning unit tests... "
+dart $DART_FLAGS run_all.dart $@ || compare_all
+popd > /dev/null
+
+# Run Dart analyzer to check that we're generating warning clean code.
+# It's a bit slow, so only do this for TodoMVC and html5_utils tests.
+OUT_PATTERN="$DIR/../../../third_party/samples/todomvc/test/out/test/*$TEST_PATTERN*_bootstrap.dart"
+if [[ `ls $OUT_PATTERN 2>/dev/null` != "" ]]; then
+  echo -e "\nAnalyzing generated code for warnings or type errors."
+  ls $OUT_PATTERN 2>/dev/null | dartanalyzer --package-root=packages \
+      --fatal-warnings --fatal-type-errors -batch
+fi
+
+echo -e "All tests pass"
diff --git a/pkg/polymer/test/run_all.dart b/pkg/polymer/test/run_all.dart
new file mode 100644
index 0000000..b149502
--- /dev/null
+++ b/pkg/polymer/test/run_all.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/**
+ * This is a helper for run.sh. We try to run all of the Dart code in one
+ * instance of the Dart VM to reduce warm-up time.
+ */
+library run_impl;
+
+import 'dart:io';
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+import 'package:polymer/testing/content_shell_test.dart';
+
+import 'css_test.dart' as css_test;
+import 'compiler_test.dart' as compiler_test;
+import 'paths_test.dart' as paths_test;
+import 'utils_test.dart' as utils_test;
+
+main() {
+  var args = new Options().arguments;
+  var pattern = new RegExp(args.length > 0 ? args[0] : '.');
+
+  useCompactVMConfiguration();
+
+  void addGroup(testFile, testMain) {
+    if (pattern.hasMatch(testFile)) {
+      group(testFile.replaceAll('_test.dart', ':'), testMain);
+    }
+  }
+
+  addGroup('compiler_test.dart', compiler_test.main);
+  addGroup('css_test.dart', css_test.main);
+  addGroup('paths_test.dart', paths_test.main);
+  addGroup('utils_test.dart', utils_test.main);
+
+  endToEndTests('data/unit/', 'data/out');
+
+  // Note: if you're adding more render test suites, make sure to update run.sh
+  // as well for convenient baseline diff/updating.
+
+  // TODO(jmesserly): figure out why this fails in content_shell but works in
+  // Dartium and Firefox when using the ShadowDOM polyfill.
+  exampleTest('../example/component/news', ['--no-shadowdom']..addAll(args));
+
+  exampleTest('../../../samples/third_party/todomvc');
+}
+
+void exampleTest(String path, [List<String> args]) {
+  renderTests(path, '$path/test', '$path/test/expected', '$path/test/out',
+      arguments: args);
+}
+
+void cssCompileMangleTest(String path, String pattern,
+    [bool deleteDirectory = true]) {
+  renderTests(path, path, '$path/expected', '$path/out',
+      arguments: ['--css-mangle'], pattern: pattern,
+      deleteDir: deleteDirectory);
+}
+
+void cssCompilePolyFillTest(String path, String pattern, String cssReset,
+    [bool deleteDirectory = true]) {
+  var args = ['--no-css-mangle'];
+  if (cssReset != null) {
+    args.addAll(['--css-reset', '${path}/${cssReset}']);
+  }
+  renderTests(path, path, '$path/expected', '$path/out',
+      arguments: args, pattern: pattern, deleteDir: deleteDirectory);
+}
+
+void cssCompileShadowDOMTest(String path, String pattern,
+    [bool deleteDirectory = true]) {
+  var args = ['--no-css'];
+  renderTests(path, path, '$path/expected', '$path/out',
+      arguments: args, pattern: pattern,
+      deleteDir: deleteDirectory);
+}
diff --git a/pkg/polymer/test/testing.dart b/pkg/polymer/test/testing.dart
new file mode 100644
index 0000000..c89b416
--- /dev/null
+++ b/pkg/polymer/test/testing.dart
@@ -0,0 +1,111 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/** Common definitions used for setting up the test environment. */
+library testing;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:csslib/visitor.dart';
+import 'package:html5lib/dom.dart';
+import 'package:html5lib/parser.dart';
+import 'package:polymer/src/analyzer.dart';
+import 'package:polymer/src/compiler.dart';
+import 'package:polymer/src/file_system.dart';
+import 'package:polymer/src/info.dart';
+import 'package:polymer/src/messages.dart';
+import 'package:polymer/src/compiler_options.dart';
+import 'package:polymer/src/files.dart';
+import 'package:polymer/src/utils.dart';
+
+
+Document parseDocument(String html) => parse(html);
+
+Element parseSubtree(String html) => parseFragment(html).nodes[0];
+
+FileInfo analyzeDefinitionsInTree(Document doc, Messages messages,
+    {String packageRoot: 'packages'}) {
+
+  return analyzeDefinitions(new GlobalInfo(), new UrlInfo('', '', null),
+      doc, packageRoot, messages);
+}
+
+/** Parses files in [fileContents], with [mainHtmlFile] being the main file. */
+List<SourceFile> parseFiles(Map<String, String> fileContents,
+    [String mainHtmlFile = 'index.html']) {
+
+  var result = <SourceFile>[];
+  fileContents.forEach((filename, contents) {
+    var src = new SourceFile(filename);
+    src.document = parse(contents);
+    result.add(src);
+  });
+
+  return result;
+}
+
+/** Analyze all files. */
+Map<String, FileInfo> analyzeFiles(List<SourceFile> files,
+    {Messages messages, String packageRoot: 'packages'}) {
+  messages = messages == null ? new Messages.silent() : messages;
+  var result = new Map<String, FileInfo>();
+
+  // analyze definitions
+  var global = new GlobalInfo();
+  for (var file in files) {
+    var path = file.path;
+    result[path] = analyzeDefinitions(global, new UrlInfo(path, path, null),
+        file.document, packageRoot, messages);
+  }
+
+  // analyze file contents
+  var uniqueIds = new IntIterator();
+  var pseudoElements = new Map();
+  for (var file in files) {
+    analyzeFile(file, result, uniqueIds, pseudoElements, messages, true);
+  }
+  return result;
+}
+
+Compiler createCompiler(Map files, Messages messages, {bool errors: false,
+    bool scopedCss: false}) {
+  List baseOptions = ['--no-colors', '-o', 'out', '--deploy', 'index.html'];
+  if (errors) baseOptions.insert(0, '--warnings_as_errors');
+  if (scopedCss) baseOptions.insert(0, '--scoped-css');
+  var options = CompilerOptions.parse(baseOptions);
+  var fs = new MockFileSystem(files);
+  return new Compiler(fs, options, messages);
+}
+
+String prettyPrintCss(StyleSheet styleSheet) =>
+    ((new CssPrinter())..visitTree(styleSheet)).toString();
+
+/**
+ * Abstraction around file system access to work in a variety of different
+ * environments.
+ */
+class MockFileSystem extends FileSystem {
+  final Map _files;
+  final Map readCount = {};
+
+  MockFileSystem(this._files);
+
+  Future readTextOrBytes(String filename) => readText(filename);
+
+  Future<String> readText(String path) {
+    readCount[path] = readCount.putIfAbsent(path, () => 0) + 1;
+    var file = _files[path];
+    if (file != null) {
+      return new Future.value(file);
+    } else {
+      return new Future.error(
+          new FileException('MockFileSystem: $path not found'));
+    }
+  }
+
+  // Compiler doesn't call these
+  void writeString(String outfile, String text) {}
+  Future flush() {}
+}
diff --git a/pkg/polymer/test/utils_test.dart b/pkg/polymer/test/utils_test.dart
new file mode 100644
index 0000000..393d215
--- /dev/null
+++ b/pkg/polymer/test/utils_test.dart
@@ -0,0 +1,86 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/** Tests for some of the utility helper functions used by the compiler. */
+library utils_test;
+
+import 'package:unittest/compact_vm_config.dart';
+import 'package:unittest/unittest.dart';
+import 'package:polymer/src/utils.dart';
+
+main() {
+  useCompactVMConfiguration();
+
+  for (bool startUppercase in [false, true]) {
+    Matcher caseEquals(String str) {
+      if (startUppercase) str = str[0].toUpperCase() + str.substring(1);
+      return equals(str);
+    }
+
+    camelCase(str) => toCamelCase(str, startUppercase: startUppercase);
+
+    group('toCamelCase startUppercase=$startUppercase', () {
+      test('empty', () {
+        expect(camelCase(''), equals(''));
+      });
+
+      test('single token', () {
+        expect(camelCase('a'), caseEquals('a'));
+        expect(camelCase('ab'), caseEquals('ab'));
+        expect(camelCase('Ab'), caseEquals('Ab'));
+        expect(camelCase('AB'), caseEquals('AB'));
+        expect(camelCase('long_word'), caseEquals('long_word'));
+      });
+
+      test('dashes in the middle', () {
+        expect(camelCase('a-b'), caseEquals('aB'));
+        expect(camelCase('a-B'), caseEquals('aB'));
+        expect(camelCase('A-b'), caseEquals('AB'));
+        expect(camelCase('long-word'), caseEquals('longWord'));
+      });
+
+      test('leading/trailing dashes', () {
+        expect(camelCase('-hi'), caseEquals('Hi'));
+        expect(camelCase('hi-'), caseEquals('hi'));
+        expect(camelCase('hi-friend-'), caseEquals('hiFriend'));
+      });
+
+      test('consecutive dashes', () {
+        expect(camelCase('--hi-friend'), caseEquals('HiFriend'));
+        expect(camelCase('hi--friend'), caseEquals('hiFriend'));
+        expect(camelCase('hi-friend--'), caseEquals('hiFriend'));
+      });
+    });
+  }
+
+  group('toHyphenedName', () {
+    test('empty', () {
+      expect(toHyphenedName(''), '');
+    });
+
+    test('all lower case', () {
+      expect(toHyphenedName('a'), 'a');
+      expect(toHyphenedName('a-b'), 'a-b');
+      expect(toHyphenedName('aBc'), 'a-bc');
+      expect(toHyphenedName('abC'), 'ab-c');
+      expect(toHyphenedName('abc-d'), 'abc-d');
+      expect(toHyphenedName('long_word'), 'long_word');
+    });
+
+    test('capitalized letters in the middle/end', () {
+      expect(toHyphenedName('aB'), 'a-b');
+      expect(toHyphenedName('longWord'), 'long-word');
+    });
+
+    test('leading capital letters', () {
+      expect(toHyphenedName('Hi'), 'hi');
+      expect(toHyphenedName('Hi-'), 'hi-');
+      expect(toHyphenedName('HiFriend'), 'hi-friend');
+    });
+
+    test('consecutive capital letters', () {
+      expect(toHyphenedName('aBC'), 'a-b-c');
+    });
+  });
+}
diff --git a/pkg/scheduled_test/lib/scheduled_process.dart b/pkg/scheduled_test/lib/scheduled_process.dart
index 3ff903c..27ebc17 100644
--- a/pkg/scheduled_test/lib/scheduled_process.dart
+++ b/pkg/scheduled_test/lib/scheduled_process.dart
@@ -5,6 +5,7 @@
 library scheduled_test.scheduled_process;
 
 import 'dart:async';
+import 'dart:convert';
 import 'dart:io';
 
 import 'scheduled_test.dart';
@@ -203,7 +204,7 @@
       return chunk;
     })
         .transform(new StringDecoder(_encoding))
-        .transform(new LineTransformer()));
+        .transform(new LineSplitter()));
   }
 
   /// Schedule an exception handler that will clean up the process and provide
diff --git a/pkg/scheduled_test/test/metatest.dart b/pkg/scheduled_test/test/metatest.dart
index 4834b6e..9e94d07 100644
--- a/pkg/scheduled_test/test/metatest.dart
+++ b/pkg/scheduled_test/test/metatest.dart
@@ -200,10 +200,9 @@
 
 /// Special test configuration for use within the child isolates. This hides all
 /// output and reports data back to the parent isolate.
-class _MetaConfiguration extends SimpleConfiguration {
-  final name = "MetaConfiguration";
+class _MetaConfiguration extends Configuration {
 
-  void logTestCaseMesssage(TestCase testCase, String message) {}
+  _MetaConfiguration() : super.blank();
 
   void onSummary(int passed, int failed, int errors, List<TestCase> results,
       String uncaughtError) {
@@ -220,7 +219,4 @@
       }).toList()
     });
   }
-
-  void onInit() {}
-  void onDone(bool success) {}
 }
diff --git a/pkg/scheduled_test/test/scheduled_process_test.dart b/pkg/scheduled_test/test/scheduled_process_test.dart
index b855e02..33cfa7c 100644
--- a/pkg/scheduled_test/test/scheduled_process_test.dart
+++ b/pkg/scheduled_test/test/scheduled_process_test.dart
@@ -5,6 +5,7 @@
 library scheduled_process_test;
 
 import 'dart:async';
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:path/path.dart' as path;
@@ -367,11 +368,12 @@
       var utilsPath = path.absolute(path.join(Platform.script, 'utils.dart'));
       return new File(path.join(dir, 'test.dart')).writeAsString('''
           import 'dart:async';
+          import 'dart:convert';
           import 'dart:io';
 
           var stdinLines = stdin
               .transform(new StringDecoder())
-              .transform(new LineTransformer());
+              .transform(new LineSplitter());
 
           void main() {
             $script
diff --git a/pkg/stack_trace/lib/src/frame.dart b/pkg/stack_trace/lib/src/frame.dart
index 3db4ab5..b222737 100644
--- a/pkg/stack_trace/lib/src/frame.dart
+++ b/pkg/stack_trace/lib/src/frame.dart
@@ -134,6 +134,12 @@
     }
   }
 
+  /// Parses a string representation of an IE stack frame.
+  ///
+  /// IE10+ frames look just like V8 frames. Prior to IE10, stack traces can't
+  /// be retrieved.
+  factory Frame.parseIE(String frame) => new Frame.parseV8(frame);
+
   /// Parses a string representation of a Firefox stack frame.
   factory Frame.parseFirefox(String frame) {
     var match = _firefoxFrame.firstMatch(frame);
@@ -155,6 +161,12 @@
     return new Frame(uri, int.parse(match[4]), null, member);
   }
 
+  /// Parses a string representation of a Safari stack frame.
+  ///
+  /// Safari 6+ frames look just like Firefox frames. Prior to Safari 6, stack
+  /// traces can't be retrieved.
+  factory Frame.parseSafari(String frame) => new Frame.parseFirefox(frame);
+
   /// Parses this package's string representation of a stack frame.
   factory Frame.parseFriendly(String frame) {
     var match = _friendlyFrame.firstMatch(frame);
diff --git a/pkg/stack_trace/lib/src/trace.dart b/pkg/stack_trace/lib/src/trace.dart
index 863d64e..19f0a12 100644
--- a/pkg/stack_trace/lib/src/trace.dart
+++ b/pkg/stack_trace/lib/src/trace.dart
@@ -14,6 +14,14 @@
 
 final _terseRegExp = new RegExp(r"(-patch)?(/.*)?$");
 
+/// A RegExp to match V8's stack traces.
+///
+/// V8's traces start with a line that's either just "Error" or else is a
+/// description of the exception that occurred. That description can be multiple
+/// lines, so we just look for any line other than the first that begins with
+/// four spaces and "at".
+final _v8Trace = new RegExp(r"\n    at ");
+
 /// A RegExp to match Firefox's stack traces.
 ///
 /// Firefox's trace frames start with the name of the function in which the
@@ -74,8 +82,9 @@
   factory Trace.parse(String trace) {
     try {
       if (trace.isEmpty) return new Trace(<Frame>[]);
-      if (trace.startsWith("Error\n")) return new Trace.parseV8(trace);
-      if (trace.contains(_firefoxTrace)) return new Trace.parseFirefox(trace);
+      if (trace.contains(_v8Trace)) return new Trace.parseV8(trace);
+      // Valid Safari traces are a superset of valid Firefox traces.
+      if (trace.contains(_firefoxTrace)) return new Trace.parseSafari(trace);
       if (trace.contains(_friendlyTrace)) return new Trace.parseFriendly(trace);
 
       // Default to parsing the stack trace as a VM trace. This is also hit on
@@ -93,13 +102,36 @@
 
   /// Parses a string representation of a Chrome/V8 stack trace.
   Trace.parseV8(String trace)
-      : this(trace.split("\n").skip(1).map((line) => new Frame.parseV8(line)));
+      : this(trace.split("\n").skip(1)
+          // It's possible that an Exception's description contains a line that
+          // looks like a V8 trace line, which will screw this up.
+          // Unfortunately, that's impossible to detect.
+          .skipWhile((line) => !line.startsWith("    at "))
+          .map((line) => new Frame.parseV8(line)));
+
+  /// Parses a string representation of an Internet Explorer stack trace.
+  ///
+  /// IE10+ traces look just like V8 traces. Prior to IE10, stack traces can't
+  /// be retrieved.
+  Trace.parseIE(String trace)
+      : this.parseV8(trace);
 
   /// Parses a string representation of a Firefox stack trace.
   Trace.parseFirefox(String trace)
       : this(trace.trim().split("\n")
           .map((line) => new Frame.parseFirefox(line)));
 
+  /// Parses a string representation of a Safari stack trace.
+  ///
+  /// Safari 6+ stack traces look just like Firefox traces, except that they
+  /// sometimes (e.g. in isolates) have a "[native code]" frame. We just ignore
+  /// this frame to make the stack format more consistent between browsers.
+  /// Prior to Safari 6, stack traces can't be retrieved.
+  Trace.parseSafari(String trace)
+      : this(trace.trim().split("\n")
+          .where((line) => line != '[native code]')
+          .map((line) => new Frame.parseFirefox(line)));
+
   /// Parses this package's a string representation of a stack trace.
   Trace.parseFriendly(String trace)
       : this(trace.trim().split("\n")
diff --git a/pkg/stack_trace/test/trace_test.dart b/pkg/stack_trace/test/trace_test.dart
index 92b2b20..c3ce039 100644
--- a/pkg/stack_trace/test/trace_test.dart
+++ b/pkg/stack_trace/test/trace_test.dart
@@ -61,6 +61,35 @@
           equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
       expect(trace.frames[2].uri,
           equals(Uri.parse("http://pub.dartlang.org/thing.js")));
+
+      trace = new Trace.parse(
+          "Exception: foo\n"
+          '    at Foo._bar (http://pub.dartlang.org/stuff.js:42:21)\n'
+          '    at http://pub.dartlang.org/stuff.js:0:2\n'
+          '    at zip.<anonymous>.zap '
+              '(http://pub.dartlang.org/thing.js:1:100)');
+
+      expect(trace.frames[0].uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+      expect(trace.frames[1].uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+      expect(trace.frames[2].uri,
+          equals(Uri.parse("http://pub.dartlang.org/thing.js")));
+
+      trace = new Trace.parse(
+          'Exception: foo\n'
+          '    bar\n'
+          '    at Foo._bar (http://pub.dartlang.org/stuff.js:42:21)\n'
+          '    at http://pub.dartlang.org/stuff.js:0:2\n'
+          '    at zip.<anonymous>.zap '
+              '(http://pub.dartlang.org/thing.js:1:100)');
+
+      expect(trace.frames[0].uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+      expect(trace.frames[1].uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+      expect(trace.frames[2].uri,
+          equals(Uri.parse("http://pub.dartlang.org/thing.js")));
     });
 
     test('parses a Firefox stack trace correctly', () {
@@ -101,6 +130,22 @@
           equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
     });
 
+    test('.parseSafari', () {
+      var trace = new Trace.parse(
+          'Foo._bar@http://pub.dartlang.org/stuff.js:42\n'
+          'zip/<@http://pub.dartlang.org/stuff.js:0\n'
+          'zip.zap(12, "@)()/<")@http://pub.dartlang.org/thing.js:1\n'
+          '[native code]');
+
+      expect(trace.frames[0].uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+      expect(trace.frames[1].uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+      expect(trace.frames[2].uri,
+          equals(Uri.parse("http://pub.dartlang.org/thing.js")));
+      expect(trace.frames.length, equals(3));
+    });
+
     test('parses a package:stack_trace stack trace correctly', () {
       var trace = new Trace.parse(
           'http://dartlang.org/foo/bar.dart 10:11  Foo.<fn>.bar\n'
diff --git a/pkg/unittest/lib/src/configuration.dart b/pkg/unittest/lib/src/configuration.dart
index 4571b90..c9ec1c0 100644
--- a/pkg/unittest/lib/src/configuration.dart
+++ b/pkg/unittest/lib/src/configuration.dart
@@ -1,4 +1,4 @@
-// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
+// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
@@ -16,10 +16,17 @@
   factory Configuration() => new SimpleConfiguration();
 
   /**
+   * Creates an [Configuration] instances that does nothing.
+   *
+   * For use by subclasses which wish to implement only a subset of features.
+   */
+  Configuration.blank();
+
+  /**
    * If [true], tests are started automatically. Otherwise [runTests]
    * must be called explicitly after tests are set up.
    */
-  bool get autoStart;
+  bool get autoStart => true;
 
   /**
    * Called as soon as the unittest framework becomes initialized. This is done
@@ -28,7 +35,7 @@
    * It is also used to tell the vm or browser that tests are going to be run
    * asynchronously and that the process should wait until they are done.
    */
-  void onInit();
+  void onInit() {}
 
   /** Called as soon as the unittest framework starts running. */
   void onStart() {}
@@ -37,31 +44,31 @@
    * Called when each test starts. Useful to show intermediate progress on
    * a test suite.
    */
-  void onTestStart(TestCase testCase);
+  void onTestStart(TestCase testCase) {}
 
   /**
    * Called when each test is first completed. Useful to show intermediate
    * progress on a test suite.
    */
-  void onTestResult(TestCase testCase);
+  void onTestResult(TestCase testCase) {}
 
   /**
    * Called when an already completed test changes state. For example: a test
    * that was marked as passing may later be marked as being in error because
    * it still had callbacks being invoked.
    */
-  void onTestResultChanged(TestCase testCase);
+  void onTestResultChanged(TestCase testCase) {}
 
   /**
    * Handles the logging of messages by a test case.
    */
-  void onLogMessage(TestCase testCase, String message);
+  void onLogMessage(TestCase testCase, String message) {}
 
   /**
    * Called when the unittest framework is done running. [success] indicates
    * whether all tests passed successfully.
    */
-  void onDone(bool success);
+  void onDone(bool success) {}
 
   /**
    * Called with the result of all test cases. Browser tests commonly override
@@ -71,6 +78,6 @@
    * of tests (e.g. setting up the test).
    */
   void onSummary(int passed, int failed, int errors, List<TestCase> results,
-      String uncaughtError);
+      String uncaughtError) {}
 }
 
diff --git a/pkg/unittest/lib/src/simple_configuration.dart b/pkg/unittest/lib/src/simple_configuration.dart
index b9d3a02..d4a0f6e 100644
--- a/pkg/unittest/lib/src/simple_configuration.dart
+++ b/pkg/unittest/lib/src/simple_configuration.dart
@@ -22,7 +22,7 @@
  * advantage of the platform can create a subclass and override methods from
  * this class.
  */
-class SimpleConfiguration implements Configuration {
+class SimpleConfiguration extends Configuration {
   // The VM won't shut down if a receive port is open. Use this to make sure
   // we correctly wait for asynchronous tests.
   ReceivePort _receivePort;
@@ -32,7 +32,7 @@
    * Particularly useful in cases where we have parent/child configurations
    * such as layout tests.
    */
-  final String name = 'Configuration';
+  String get name => 'Configuration';
 
   bool get autoStart => true;
 
@@ -56,7 +56,7 @@
    * The constructor sets up a failure handler for [expect] that redirects
    * [expect] failures to [onExpectFailure].
    */
-  SimpleConfiguration() {
+  SimpleConfiguration() : super.blank() {
     configureExpectFailureHandler(new _ExpectFailureHandler(this));
   }
 
@@ -65,8 +65,6 @@
     _postMessage('unittest-suite-wait-for-done');
   }
 
-  void onStart() {}
-
   /**
    * Called when each test starts. Useful to show intermediate progress on
    * a test suite. Derived classes should call this first before their own
diff --git a/pkg/unittest/test/unittest_test_utils.dart b/pkg/unittest/test/unittest_test_utils.dart
index 03fc8d6..7d9a922 100644
--- a/pkg/unittest/test/unittest_test_utils.dart
+++ b/pkg/unittest/test/unittest_test_utils.dart
@@ -33,7 +33,7 @@
       '$setup:$teardown:$uncaughtError$testDetails';
 }
 
-class TestConfiguration extends SimpleConfiguration {
+class TestConfiguration extends Configuration {
 
   // Some test state that is captured.
   int count = 0; // A count of callbacks.
@@ -44,9 +44,7 @@
   final SendPort _port;
   String _result;
 
-  TestConfiguration(this._port);
-
-  void onInit() {}
+  TestConfiguration(this._port) : super.blank();
 
   void onSummary(int passed, int failed, int errors, List<TestCase> results,
       String uncaughtError) {
diff --git a/runtime/bin/dbg_connection.cc b/runtime/bin/dbg_connection.cc
index e5d4f6b..e5abc5e 100644
--- a/runtime/bin/dbg_connection.cc
+++ b/runtime/bin/dbg_connection.cc
@@ -260,7 +260,9 @@
                                           int msg_id,
                                           const char* err_msg) {
   dart::TextBuffer msg(64);
-  msg.Printf("{\"id\": %d, \"error\": \"Error: %s\"}", msg_id, err_msg);
+  msg.Printf("{\"id\": %d, \"error\": \"Error: ", msg_id);
+  msg.AddEscapedString(err_msg);
+  msg.Printf("\"}");
   SendMsg(debug_fd, &msg);
 }
 
diff --git a/runtime/bin/dbg_message.cc b/runtime/bin/dbg_message.cc
index caec295..36cfeba 100644
--- a/runtime/bin/dbg_message.cc
+++ b/runtime/bin/dbg_message.cc
@@ -50,6 +50,14 @@
 }
 
 
+bool MessageParser::HasParam(const char* name) const {
+  const char* params = Params();
+  ASSERT(params != NULL);
+  dart::JSONReader r(params);
+  return r.Seek(name);
+}
+
+
 intptr_t MessageParser::GetIntParam(const char* name) const {
   const char* params = Params();
   ASSERT(params != NULL);
@@ -224,13 +232,13 @@
     intptr_t len = 0;
     Dart_Handle res = Dart_ListLength(object, &len);
     ASSERT_NOT_ERROR(res);
-    buf->Printf("\"kind\":\"list\",\"length\":%"Pd",", len);
+    buf->Printf("\"kind\":\"list\",\"length\":%" Pd ",", len);
   } else {
     buf->Printf("\"kind\":\"object\",");
     intptr_t class_id = 0;
     Dart_Handle res = Dart_GetObjClassId(object, &class_id);
     if (!Dart_IsError(res)) {
-      buf->Printf("\"classId\":%"Pd",", class_id);
+      buf->Printf("\"classId\":%" Pd ",", class_id);
     }
   }
   buf->Printf("\"text\":\"");
@@ -250,7 +258,7 @@
 static void FormatRemoteObj(dart::TextBuffer* buf, Dart_Handle object) {
   intptr_t obj_id = Dart_CacheObject(object);
   ASSERT(obj_id >= 0);
-  buf->Printf("{\"objectId\":%"Pd",", obj_id);
+  buf->Printf("{\"objectId\":%" Pd ",", obj_id);
   FormatValue(buf, object);
   buf->Printf("}");
 }
@@ -300,9 +308,9 @@
   RETURN_IF_ERROR(name);
   buf->Printf("{\"name\":\"%s\",", GetStringChars(name));
   if (super_id > 0) {
-    buf->Printf("\"superclassId\":%"Pd",", super_id);
+    buf->Printf("\"superclassId\":%" Pd ",", super_id);
   }
-  buf->Printf("\"libraryId\":%"Pd",", library_id);
+  buf->Printf("\"libraryId\":%" Pd ",", library_id);
   RETURN_IF_ERROR(static_fields);
   buf->Printf("\"fields\":");
   FormatNamedValueList(buf, static_fields);
@@ -366,7 +374,7 @@
   }
   Dart_Handle res = Dart_GetObjClassId(object, &class_id);
   RETURN_IF_ERROR(res);
-  buf->Printf("{\"classId\": %"Pd",", class_id);
+  buf->Printf("{\"classId\": %" Pd ",", class_id);
   buf->Printf("\"kind\":\"object\",\"fields\":");
   Dart_Handle fields = Dart_GetInstanceFields(object);
   RETURN_IF_ERROR(fields);
@@ -383,8 +391,8 @@
                                    intptr_t slice_length) {
   intptr_t end_index = index + slice_length;
   ASSERT(end_index <= list_length);
-  buf->Printf("{\"index\":%"Pd",", index);
-  buf->Printf("\"length\":%"Pd",", slice_length);
+  buf->Printf("{\"index\":%" Pd ",", index);
+  buf->Printf("\"length\":%" Pd ",", slice_length);
   buf->Printf("\"elements\":[");
   for (intptr_t i = index; i < end_index; i++) {
     Dart_Handle value = Dart_ListGetAt(list, i);
@@ -470,6 +478,7 @@
   { "getClassProperties", DbgMessage::HandleGetClassPropsCmd },
   { "getLibraryProperties", DbgMessage::HandleGetLibPropsCmd },
   { "setLibraryProperties", DbgMessage::HandleSetLibPropsCmd },
+  { "evaluateExpr", DbgMessage::HandleEvaluateExprCmd },
   { "getObjectProperties", DbgMessage::HandleGetObjPropsCmd },
   { "getListElements", DbgMessage::HandleGetListCmd },
   { "getGlobalVariables", DbgMessage::HandleGetGlobalsCmd },
@@ -633,6 +642,54 @@
 }
 
 
+bool DbgMessage::HandleEvaluateExprCmd(DbgMessage* in_msg) {
+  ASSERT(in_msg != NULL);
+  MessageParser msg_parser(in_msg->buffer(), in_msg->buffer_len());
+  int msg_id = msg_parser.MessageId();
+  Dart_Handle target;
+
+  if (msg_parser.HasParam("libraryId")) {
+    in_msg->SendErrorReply(msg_id,
+                           "libararyId evaluation target not supported");
+    return false;
+  } else if (msg_parser.HasParam("classId")) {
+    in_msg->SendErrorReply(msg_id,
+                           "classId evaluation target not supported");
+    return false;
+  } else if (msg_parser.HasParam("objectId")) {
+    intptr_t obj_id = msg_parser.GetIntParam("objectId");
+    target = Dart_GetCachedObject(obj_id);
+  } else {
+    in_msg->SendErrorReply(msg_id, "illegal evaluation target");
+    return false;
+  }
+
+  if (Dart_IsError(target)) {
+    in_msg->SendErrorReply(msg_id, Dart_GetError(target));
+    return false;
+  }
+  char* expr_chars = msg_parser.GetStringParam("expression");
+  Dart_Handle expr = Dart_NewStringFromCString(expr_chars);
+  if (Dart_IsError(expr)) {
+    in_msg->SendErrorReply(msg_id, Dart_GetError(expr));
+    return false;
+  }
+
+  Dart_Handle value = Dart_EvaluateExpr(target, expr);
+  if (Dart_IsError(value)) {
+    in_msg->SendErrorReply(msg_id, Dart_GetError(value));
+    return false;
+  }
+
+  dart::TextBuffer msg(64);
+  msg.Printf("{\"id\":%d, \"result\":", msg_id);
+  FormatRemoteObj(&msg, value);
+  msg.Printf("}");
+  in_msg->SendReply(&msg);
+  return false;
+}
+
+
 bool DbgMessage::HandleGetObjPropsCmd(DbgMessage* in_msg) {
   ASSERT(in_msg != NULL);
   MessageParser msg_parser(in_msg->buffer(), in_msg->buffer_len());
@@ -869,7 +926,7 @@
   Dart_Handle res = Dart_IntegerToUint64(bp_id, &bp_id_value);
   ASSERT_NOT_ERROR(res);
   dart::TextBuffer msg(64);
-  msg.Printf("{ \"id\": %d, \"result\": { \"breakpointId\": %"Pu64" }}",
+  msg.Printf("{ \"id\": %d, \"result\": { \"breakpointId\": %" Pu64 " }}",
              msg_id, bp_id_value);
   in_msg->SendReply(&msg);
   return false;
@@ -992,7 +1049,7 @@
   dart::TextBuffer msg(128);
   msg.Printf("{ \"event\": \"paused\", \"params\": { ");
   msg.Printf("\"reason\": \"breakpoint\", ");
-  msg.Printf("\"isolateId\": %"Pd64"", isolate_id_);
+  msg.Printf("\"isolateId\": %" Pd64 "", isolate_id_);
   if (!Dart_IsNull(location.script_url)) {
     ASSERT(Dart_IsString(location.script_url));
     msg.Printf(",\"location\": { \"url\":");
@@ -1014,7 +1071,7 @@
   dart::TextBuffer msg(128);
   msg.Printf("{ \"event\": \"paused\", \"params\": {");
   msg.Printf("\"reason\": \"exception\", ");
-  msg.Printf("\"isolateId\": %"Pd64", ", isolate_id_);
+  msg.Printf("\"isolateId\": %" Pd64 ", ", isolate_id_);
   msg.Printf("\"exception\":");
   FormatRemoteObj(&msg, exception);
   FormatLocationFromTrace(&msg, stack_trace, ", ");
@@ -1034,7 +1091,7 @@
     ASSERT_NOT_ERROR(res);
     msg.Printf("{ \"event\": \"paused\", \"params\": { ");
     msg.Printf("\"reason\": \"interrupted\", ");
-    msg.Printf("\"isolateId\": %"Pd64"", isolate_id);
+    msg.Printf("\"isolateId\": %" Pd64 "", isolate_id);
     FormatLocationFromTrace(&msg, trace, ", ");
     msg.Printf("}}");
   } else {
@@ -1045,7 +1102,7 @@
       ASSERT(kind == kShutdown);
       msg.Printf("\"reason\": \"shutdown\", ");
     }
-    msg.Printf("\"id\": %"Pd64" ", isolate_id);
+    msg.Printf("\"id\": %" Pd64 " ", isolate_id);
     msg.Printf("}}");
   }
   DebuggerConnectionHandler::BroadcastMsg(&msg);
@@ -1170,10 +1227,10 @@
     return;  // No items in the list.
   }
   DbgMsgQueue* queue = list_;
-  msg->Printf("%"Pd64"", queue->isolate_id());
+  msg->Printf("%" Pd64 "", queue->isolate_id());
   queue = queue->next();
   while (queue != NULL) {
-    msg->Printf(",%"Pd64"", queue->isolate_id());
+    msg->Printf(",%" Pd64 "", queue->isolate_id());
     queue = queue->next();
   }
 }
@@ -1185,9 +1242,9 @@
   Dart_EnterScope();
   dart::TextBuffer msg(128);
   msg.Printf("{ \"event\": \"breakpointResolved\", \"params\": {");
-  msg.Printf("\"breakpointId\": %"Pd"", bp_id);
+  msg.Printf("\"breakpointId\": %" Pd "", bp_id);
 
-  msg.Printf(", \"isolateId\":%"Pd64"", isolate_id);
+  msg.Printf(", \"isolateId\":%" Pd64 "", isolate_id);
   ASSERT(!Dart_IsNull(location.script_url));
   ASSERT(Dart_IsString(location.script_url));
   msg.Printf(", \"location\":{\"url\":");
diff --git a/runtime/bin/dbg_message.h b/runtime/bin/dbg_message.h
index ca05255..5386318 100644
--- a/runtime/bin/dbg_message.h
+++ b/runtime/bin/dbg_message.h
@@ -43,6 +43,7 @@
   int MessageId() const;
 
   const char* Params() const;
+  bool HasParam(const char* name) const;
   intptr_t GetIntParam(const char* name) const;
   intptr_t GetOptIntParam(const char* name, intptr_t default_val) const;
 
@@ -103,6 +104,7 @@
   static bool HandleGetLibPropsCmd(DbgMessage* msg);
   static bool HandleSetLibPropsCmd(DbgMessage* msg);
   static bool HandleGetGlobalsCmd(DbgMessage* msg);
+  static bool HandleEvaluateExprCmd(DbgMessage* msg);
   static bool HandleGetObjPropsCmd(DbgMessage* msg);
   static bool HandleGetListCmd(DbgMessage* msg);
   static bool HandleGetScriptURLsCmd(DbgMessage* msg);
diff --git a/runtime/bin/eventhandler_linux.cc b/runtime/bin/eventhandler_linux.cc
index 111e7e6..6d15c9e 100644
--- a/runtime/bin/eventhandler_linux.cc
+++ b/runtime/bin/eventhandler_linux.cc
@@ -171,7 +171,7 @@
     if (result == -1) {
       perror("Interrupt message failure:");
     }
-    FATAL1("Interrupt message failure. Wrote %"Pd" bytes.", result);
+    FATAL1("Interrupt message failure. Wrote %" Pd " bytes.", result);
   }
 }
 
diff --git a/runtime/bin/eventhandler_macos.cc b/runtime/bin/eventhandler_macos.cc
index 971ef1b..419c93e 100644
--- a/runtime/bin/eventhandler_macos.cc
+++ b/runtime/bin/eventhandler_macos.cc
@@ -190,7 +190,7 @@
     if (result == -1) {
       perror("Interrupt message failure:");
     }
-    FATAL1("Interrupt message failure. Wrote %"Pd" bytes.", result);
+    FATAL1("Interrupt message failure. Wrote %" Pd " bytes.", result);
   }
 }
 
diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc
index ff790d6..2948ce8 100644
--- a/runtime/bin/io_natives.cc
+++ b/runtime/bin/io_natives.cc
@@ -35,6 +35,8 @@
   V(Platform_LocalHostname, 0)                                                 \
   V(Platform_ExecutableName, 0)                                                \
   V(Platform_Environment, 0)                                                   \
+  V(Platform_ExecutableArguments, 0)                                           \
+  V(Platform_PackageRoot, 0)                                                   \
   V(Platform_GetVersion, 0)                                                    \
   V(Process_Start, 10)                                                         \
   V(Process_Wait, 5)                                                           \
diff --git a/runtime/bin/main.cc b/runtime/bin/main.cc
index 57c5168..7e5138e 100644
--- a/runtime/bin/main.cc
+++ b/runtime/bin/main.cc
@@ -339,6 +339,8 @@
     }
   }
 
+  // The arguments to the VM are at positions 1 through i-1 in argv.
+  Platform::SetExecutableArguments(i, argv);
 
   // Get the script name.
   if (i < argc) {
@@ -461,6 +463,7 @@
     return NULL;
   }
 
+  Platform::SetPackageRoot(package_root);
   Dart_Handle io_lib_url = DartUtils::NewString("dart:io");
   CHECK_RESULT(io_lib_url);
   Dart_Handle io_lib = Dart_LookupLibrary(io_lib_url);
diff --git a/runtime/bin/platform.cc b/runtime/bin/platform.cc
index 4d1bbed..f7a2920 100644
--- a/runtime/bin/platform.cc
+++ b/runtime/bin/platform.cc
@@ -13,6 +13,9 @@
 namespace bin {
 
 const char* Platform::executable_name_ = NULL;
+const char* Platform::package_root_ = NULL;
+int Platform::script_index_ = 1;
+char** Platform::argv_ = NULL;
 
 void FUNCTION_NAME(Platform_NumberOfProcessors)(Dart_NativeArguments args) {
   Dart_SetReturnValue(args, Dart_NewInteger(Platform::NumberOfProcessors()));
@@ -46,6 +49,31 @@
       args, Dart_NewStringFromCString(Platform::GetExecutableName()));
 }
 
+
+void FUNCTION_NAME(Platform_ExecutableArguments)(Dart_NativeArguments args) {
+  int end = Platform::GetScriptIndex();
+  char** argv = Platform::GetArgv();
+  Dart_Handle result = Dart_NewList(end - 1);
+  for (intptr_t i = 1; i < end; i++) {
+    Dart_Handle str = DartUtils::NewString(argv[i]);
+    Dart_Handle error = Dart_ListSetAt(result, i - 1, str);
+    if (Dart_IsError(error)) {
+      Dart_PropagateError(error);
+    }
+  }
+  Dart_SetReturnValue(args, result);
+}
+
+
+void FUNCTION_NAME(Platform_PackageRoot)(Dart_NativeArguments args) {
+  const char* package_root = Platform::GetPackageRoot();
+  if (package_root == NULL) {
+    package_root = "";
+  }
+  Dart_SetReturnValue(args, Dart_NewStringFromCString(package_root));
+}
+
+
 void FUNCTION_NAME(Platform_Environment)(Dart_NativeArguments args) {
   intptr_t count = 0;
   char** env = Platform::Environment(&count);
diff --git a/runtime/bin/platform.h b/runtime/bin/platform.h
index dd195c3..ca723ae3 100644
--- a/runtime/bin/platform.h
+++ b/runtime/bin/platform.h
@@ -42,8 +42,31 @@
     return executable_name_;
   }
 
+  // Stores and gets the package root.
+  static void SetPackageRoot(const char* package_root) {
+    package_root_ = package_root;
+  }
+  static const char* GetPackageRoot() {
+    return package_root_;
+  }
+
+  // Stores and gets the flags passed to the executable.
+  static void SetExecutableArguments(int script_index, char** argv) {
+    script_index_ = script_index;
+    argv_ = argv;
+  }
+  static int GetScriptIndex() {
+    return script_index_;
+  }
+  static char** GetArgv() {
+    return argv_;
+  }
+
  private:
   static const char* executable_name_;
+  static const char* package_root_;
+  static int script_index_;
+  static char** argv_;  // VM flags are argv_[1 ... script_index_ - 1]
 
   DISALLOW_ALLOCATION();
   DISALLOW_IMPLICIT_CONSTRUCTORS(Platform);
diff --git a/runtime/bin/platform_patch.dart b/runtime/bin/platform_patch.dart
index cfe7a80..fe1e90c 100644
--- a/runtime/bin/platform_patch.dart
+++ b/runtime/bin/platform_patch.dart
@@ -11,5 +11,8 @@
   /* patch */ static _localHostname() native "Platform_LocalHostname";
   /* patch */ static _executable() native "Platform_ExecutableName";
   /* patch */ static _environment() native "Platform_Environment";
+  /* patch */ static List<String> _executableArguments()
+       native "Platform_ExecutableArguments";
+  /* patch */ static String _packageRoot() native "Platform_PackageRoot";
   /* patch */ static String _version() native "Platform_GetVersion";
 }
diff --git a/runtime/bin/run_vm_tests.cc b/runtime/bin/run_vm_tests.cc
index 9daac7d..7ff4198 100644
--- a/runtime/bin/run_vm_tests.cc
+++ b/runtime/bin/run_vm_tests.cc
@@ -52,7 +52,7 @@
       (run_filter == kAllBenchmarks) ||
       (strcmp(run_filter, this->name()) == 0)) {
     this->Run();
-    OS::Print("%s(RunTime): %"Pd"\n", this->name(), this->score());
+    OS::Print("%s(RunTime): %" Pd "\n", this->name(), this->score());
     run_matches++;
   } else if (run_filter == kList) {
     fprintf(stdout, "%s\n", this->name());
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index aa3963a..4f35fab 100755
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -2010,6 +2010,39 @@
                                                      void** peer);
 
 /**
+ * Gets an integer native argument at some index.
+ * \param args Native arguments structure.
+ * \param arg_index Index of the desired argument in the structure above.
+ * \param value Returns the integer value if the argument is an Integer.
+ * \return Success if no error occurs. Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeIntegerArgument(Dart_NativeArguments args,
+                                                      int index,
+                                                      int64_t* value);
+
+/**
+ * Gets a boolean native argument at some index.
+ * \param args Native arguments structure.
+ * \param arg_index Index of the desired argument in the structure above.
+ * \param value Returns the boolean value if the argument is a Boolean.
+ * \return Success if no error occurs. Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeBooleanArgument(Dart_NativeArguments args,
+                                                      int index,
+                                                      bool* value);
+
+/**
+ * Gets a double native argument at some index.
+ * \param args Native arguments structure.
+ * \param arg_index Index of the desired argument in the structure above.
+ * \param value Returns the double value if the argument is a double.
+ * \return Success if no error occurs. Otherwise returns an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_GetNativeDoubleArgument(Dart_NativeArguments args,
+                                                     int index,
+                                                     double* value);
+
+/**
  * Sets the return value for a native function.
  */
 DART_EXPORT void Dart_SetReturnValue(Dart_NativeArguments args,
@@ -2022,7 +2055,7 @@
                                             bool retval);
 
 DART_EXPORT void Dart_SetIntegerReturnValue(Dart_NativeArguments args,
-                                            intptr_t retval);
+                                            int64_t retval);
 
 DART_EXPORT void Dart_SetDoubleReturnValue(Dart_NativeArguments args,
                                            double retval);
diff --git a/runtime/include/dart_debugger_api.h b/runtime/include/dart_debugger_api.h
index 36249ff..b847a91 100755
--- a/runtime/include/dart_debugger_api.h
+++ b/runtime/include/dart_debugger_api.h
@@ -472,6 +472,25 @@
 
 
 /**
+ * Execute the expression given in string \expr in the context
+ * of \target.
+ *
+ * Requires there to be a current isolate.
+ *
+ * The expression is evaluated in the context of \target.
+ * If \target is a Dart object, the expression is evaluated as if
+ * it were an instance method of the class of the object.
+ * TODO(hausner): add other execution contexts, e.g. library and class.
+ * 
+ * \return A handle to the computed value, or an error object if
+ * the compilation of the expression fails, or if the evaluation throws
+ * an error.
+ */
+DART_EXPORT Dart_Handle Dart_EvaluateExpr(Dart_Handle target,
+                                          Dart_Handle expr);
+
+
+/**
  * Returns the class of the given \object.
  *
  * Requires there to be a current isolate.
diff --git a/runtime/lib/array.cc b/runtime/lib/array.cc
index da55de9..7116f5b 100644
--- a/runtime/lib/array.cc
+++ b/runtime/lib/array.cc
@@ -19,7 +19,7 @@
       isolate, arguments->NativeArgAt(1));
   if (!length.IsSmi()) {
     const String& error = String::Handle(String::NewFormatted(
-        "Length must be an integer in the range [0..%"Pd"].",
+        "Length must be an integer in the range [0..%" Pd "].",
         Array::kMaxElements));
     const Array& args = Array::Handle(Array::New(1));
     args.SetAt(0, error);
@@ -28,7 +28,7 @@
   intptr_t len = Smi::Cast(length).Value();
   if (len < 0 || len > Array::kMaxElements) {
     const String& error = String::Handle(String::NewFormatted(
-        "Length (%"Pd") must be an integer in the range [0..%"Pd"].",
+        "Length (%" Pd ") must be an integer in the range [0..%" Pd "].",
         len, Array::kMaxElements));
     const Array& args = Array::Handle(Array::New(1));
     args.SetAt(0, error);
diff --git a/runtime/lib/double.cc b/runtime/lib/double.cc
index 04006e5..f69e963 100644
--- a/runtime/lib/double.cc
+++ b/runtime/lib/double.cc
@@ -172,16 +172,6 @@
 }
 
 
-DEFINE_NATIVE_ENTRY(Double_pow, 2) {
-  const double operand =
-      Double::CheckedHandle(arguments->NativeArgAt(0)).value();
-  GET_NON_NULL_NATIVE_ARGUMENT(
-      Double, exponent_object, arguments->NativeArgAt(1));
-  const double exponent = exponent_object.value();
-  return Double::New(pow(operand, exponent));
-}
-
-
 #if defined(TARGET_OS_MACOS)
 // MAC OSX math library produces old style cast warning.
 #pragma GCC diagnostic ignored "-Wold-style-cast"
diff --git a/runtime/lib/double.dart b/runtime/lib/double.dart
index 29247ad..166d731 100644
--- a/runtime/lib/double.dart
+++ b/runtime/lib/double.dart
@@ -126,21 +126,6 @@
   int toInt() native "Double_toInt";
   double toDouble() { return this; }
 
-  double pow(num exponent) {
-    if (exponent == 0) {
-      return 1.0;  // ECMA-262 15.8.2.13
-    }
-    if (exponent is! num) {
-      throw new ArgumentError(null);
-    }
-    double doubleExponent = exponent.toDouble();
-    if (isNaN || exponent.isNaN) {
-      return double.NAN;
-    }
-    return _pow(doubleExponent);
-  }
-  double _pow(double exponent) native "Double_pow";
-
   String toStringAsFixed(int fractionDigits) {
     // See ECMAScript-262, 15.7.4.5 for details.
 
diff --git a/runtime/lib/integers.dart b/runtime/lib/integers.dart
index aaf5712..dca7ef8 100644
--- a/runtime/lib/integers.dart
+++ b/runtime/lib/integers.dart
@@ -163,23 +163,6 @@
   int toInt() { return this; }
   double toDouble() { return new _Double.fromInteger(this); }
 
-  int pow(int exponent) {
-    // Exponentiation by squaring.
-    int base = this;
-    int result = 1;
-    while (exponent != 0) {
-      if ((exponent & 1) == 1) {
-        result *= base;
-      }
-      exponent >>= 1;
-      // Skip unnecessary operation (can overflow to Mint or Bigint).
-      if (exponent != 0) {
-        base *= base;
-      }
-    }
-    return result;
-  }
-
   String toStringAsFixed(int fractionDigits) {
     return this.toDouble().toStringAsFixed(fractionDigits);
   }
diff --git a/runtime/lib/math.cc b/runtime/lib/math.cc
index 317045d..ca7c7e6 100644
--- a/runtime/lib/math.cc
+++ b/runtime/lib/math.cc
@@ -66,6 +66,15 @@
   return Double::New(log(operand.value()));
 }
 
+DEFINE_NATIVE_ENTRY(Math_doublePow, 2) {
+  const double operand =
+      Double::CheckedHandle(arguments->NativeArgAt(0)).value();
+  GET_NON_NULL_NATIVE_ARGUMENT(
+      Double, exponent_object, arguments->NativeArgAt(1));
+  const double exponent = exponent_object.value();
+  return Double::New(pow(operand, exponent));
+}
+
 
 // Returns the typed-data array store in '_Random._state' field.
 static RawTypedData* GetRandomStateArray(const Instance& receiver) {
diff --git a/runtime/lib/math_patch.dart b/runtime/lib/math_patch.dart
index 1bf701d..00cacdf 100644
--- a/runtime/lib/math_patch.dart
+++ b/runtime/lib/math_patch.dart
@@ -10,10 +10,38 @@
 // an [int], otherwise the result is a [double].
 patch num pow(num x, num exponent) {
   if ((x is int) && (exponent is int) && (exponent >= 0)) {
-    return x.pow(exponent);
+    return _intPow(x, exponent);
   }
-  // Double.pow will call exponent.toDouble().
-  return x.toDouble().pow(exponent);
+  return _doublePow(x.toDouble(), exponent.toDouble());
+}
+
+double _doublePow(double base, double exponent) {
+  if (exponent == 0.0) {
+    return 1.0;  // ECMA-262 15.8.2.13
+  }
+  if (base == 1.0) return 1.0;
+  if (base.isNaN || exponent.isNaN) {
+    return double.NAN;
+  }
+  return _pow(base, exponent);
+}
+
+double _pow(double base, double exponent) native "Math_doublePow";
+
+int _intPow(int base, int exponent) {
+  // Exponentiation by squaring.
+  int result = 1;
+  while (exponent != 0) {
+    if ((exponent & 1) == 1) {
+      result *= base;
+    }
+    exponent >>= 1;
+    // Skip unnecessary operation (can overflow to Mint or Bigint).
+    if (exponent != 0) {
+      base *= base;
+    }
+  }
+  return result;
 }
 
 patch double atan2(num a, num b) => _atan2(a.toDouble(), b.toDouble());
diff --git a/runtime/lib/mirrors.cc b/runtime/lib/mirrors.cc
index 7ab68b6..e0c8b23 100644
--- a/runtime/lib/mirrors.cc
+++ b/runtime/lib/mirrors.cc
@@ -305,9 +305,16 @@
   GET_NON_NULL_NATIVE_ARGUMENT(Type, type, arguments->NativeArgAt(0));
   const Class& cls = Class::Handle(type.type_class());
   ASSERT(!cls.IsNull());
-  return CreateClassMirror(cls,
-                           AbstractType::Handle(),
-                           Instance::null_instance());
+  // Strip the type for generics only.
+  if (cls.NumTypeParameters() == 0) {
+    return CreateClassMirror(cls,
+                             type,
+                             Object::null_instance());
+  } else {
+    return CreateClassMirror(cls,
+                             AbstractType::Handle(),
+                             Object::null_instance());
+  }
 }
 
 
diff --git a/runtime/lib/mirrors_impl.dart b/runtime/lib/mirrors_impl.dart
index 88a89cc..ea63f90 100644
--- a/runtime/lib/mirrors_impl.dart
+++ b/runtime/lib/mirrors_impl.dart
@@ -369,6 +369,15 @@
   final Type _reflectedType;
   final bool _isGeneric;
 
+  bool get hasReflectedType => _reflectedType != null;
+  Type get reflectedType {
+    if (!hasReflectedType) {
+      throw new UnsupportedError(
+          "Declarations of generics have no reflected type");
+    }
+    return _reflectedType;
+  }
+
   Symbol _simpleName;
   Symbol get simpleName {
     // dynamic, void and the function types have their names set eagerly in the
@@ -417,7 +426,7 @@
         // Object has no superclass.
         return null;
       }
-      _superclass = reflectClass(supertype);
+      _superclass = _Mirrors._reflectType(supertype);
     }
     return _superclass;
   }
@@ -924,7 +933,22 @@
                          this.isGenerativeConstructor,
                          this.isRedirectingConstructor,
                          this.isFactoryConstructor)
-      : super(reflectee, _s(simpleName));
+      : this.isOperator = _operators.contains(simpleName),
+        super(reflectee, _s(simpleName));
+
+  static const _operators = const ["%", "&", "*", "+", "-", "/", "<", "<<",
+      "<=", "==", ">", ">=", ">>", "[]", "[]=", "^", "|", "~", "unary-", "~/"];
+
+  final bool isStatic;
+  final bool isAbstract;
+  final bool isGetter;
+  final bool isSetter;
+  final bool isConstructor;
+  final bool isConstConstructor;
+  final bool isGenerativeConstructor;
+  final bool isRedirectingConstructor;
+  final bool isFactoryConstructor;
+  final bool isOperator;
 
   DeclarationMirror _owner;
   DeclarationMirror get owner {
@@ -969,20 +993,8 @@
     return _parameters;
   }
 
-  final bool isStatic;
-  final bool isAbstract;
-
   bool get isRegularMethod => !isGetter && !isSetter && !isConstructor;
 
-  TypeMirror get isOperator {
-    throw new UnimplementedError(
-        'MethodMirror.isOperator is not implemented');
-  }
-
-  final bool isGetter;
-  final bool isSetter;
-  final bool isConstructor;
-
   Symbol _constructorName = null;
   Symbol get constructorName {
     if (_constructorName == null) {
@@ -1004,11 +1016,6 @@
     return _constructorName;
   }
 
-  final bool isConstConstructor;
-  final bool isGenerativeConstructor;
-  final bool isRedirectingConstructor;
-  final bool isFactoryConstructor;
-
   String toString() => "MethodMirror on '${_n(simpleName)}'";
 
   static dynamic _MethodMirror_owner(reflectee)
diff --git a/runtime/lib/simd128.cc b/runtime/lib/simd128.cc
index 9b79412..1d4d551 100644
--- a/runtime/lib/simd128.cc
+++ b/runtime/lib/simd128.cc
@@ -229,13 +229,36 @@
 }
 
 
+DEFINE_NATIVE_ENTRY(Float32x4_getSignMask, 1) {
+  GET_NON_NULL_NATIVE_ARGUMENT(Float32x4, self, arguments->NativeArgAt(0));
+  uint32_t mx = (bit_cast<uint32_t>(self.x()) & 0x80000000) >> 31;
+  uint32_t my = (bit_cast<uint32_t>(self.y()) & 0x80000000) >> 31;
+  uint32_t mz = (bit_cast<uint32_t>(self.z()) & 0x80000000) >> 31;
+  uint32_t mw = (bit_cast<uint32_t>(self.w()) & 0x80000000) >> 31;
+  uint32_t value = mx | (my << 1) | (mz << 2) | (mw << 3);
+  return Integer::New(value);
+}
+
+
+DEFINE_NATIVE_ENTRY(Uint32x4_getSignMask, 1) {
+  GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, self, arguments->NativeArgAt(0));
+  uint32_t mx = (self.x() & 0x80000000) >> 31;
+  uint32_t my = (self.y() & 0x80000000) >> 31;
+  uint32_t mz = (self.z() & 0x80000000) >> 31;
+  uint32_t mw = (self.w() & 0x80000000) >> 31;
+  uint32_t value = mx | (my << 1) | (mz << 2) | (mw << 3);
+  return Integer::New(value);
+}
+
+
 DEFINE_NATIVE_ENTRY(Float32x4_shuffle, 2) {
   GET_NON_NULL_NATIVE_ARGUMENT(Float32x4, self, arguments->NativeArgAt(0));
   GET_NON_NULL_NATIVE_ARGUMENT(Integer, mask, arguments->NativeArgAt(1));
   int64_t m = mask.AsInt64Value();
   if (m < 0 || m > 255) {
     const String& error = String::Handle(
-      String::NewFormatted("mask (%"Pd64") must be in the range [0..256)", m));
+      String::NewFormatted("mask (%" Pd64 ") must be in the range [0..256)",
+                           m));
     const Array& args = Array::Handle(Array::New(1));
     args.SetAt(0, error);
     Exceptions::ThrowByType(Exceptions::kRange, args);
@@ -471,6 +494,28 @@
 }
 
 
+DEFINE_NATIVE_ENTRY(Uint32x4_add, 2) {
+  GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, self, arguments->NativeArgAt(0));
+  GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, other, arguments->NativeArgAt(1));
+  uint32_t _x = self.x() + other.x();
+  uint32_t _y = self.y() + other.y();
+  uint32_t _z = self.z() + other.z();
+  uint32_t _w = self.w() + other.w();
+  return Uint32x4::New(_x, _y, _z, _w);
+}
+
+
+DEFINE_NATIVE_ENTRY(Uint32x4_sub, 2) {
+  GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, self, arguments->NativeArgAt(0));
+  GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, other, arguments->NativeArgAt(1));
+  uint32_t _x = self.x() - other.x();
+  uint32_t _y = self.y() - other.y();
+  uint32_t _z = self.z() - other.z();
+  uint32_t _w = self.w() - other.w();
+  return Uint32x4::New(_x, _y, _z, _w);
+}
+
+
 DEFINE_NATIVE_ENTRY(Uint32x4_getX, 1) {
   GET_NON_NULL_NATIVE_ARGUMENT(Uint32x4, self, arguments->NativeArgAt(0));
   uint32_t value = self.x();
diff --git a/runtime/lib/typed_data.cc b/runtime/lib/typed_data.cc
index 23f0b83..bd7a266 100644
--- a/runtime/lib/typed_data.cc
+++ b/runtime/lib/typed_data.cc
@@ -30,7 +30,7 @@
                           intptr_t element_size_in_bytes) {
   if (!Utils::RangeCheck(offset_in_bytes, num_bytes, length_in_bytes)) {
     const String& error = String::Handle(String::NewFormatted(
-        "index (%"Pd") must be in the range [0..%"Pd")",
+        "index (%" Pd ") must be in the range [0..%" Pd ")",
         (offset_in_bytes / element_size_in_bytes),
         (length_in_bytes / element_size_in_bytes)));
     const Array& args = Array::Handle(Array::New(1));
@@ -44,7 +44,7 @@
 static void LengthCheck(intptr_t len, intptr_t max) {
   if (len < 0 || len > max) {
     const String& error = String::Handle(String::NewFormatted(
-        "Length (%"Pd") of object must be in range [0..%"Pd"]",
+        "Length (%" Pd ") of object must be in range [0..%" Pd "]",
         len, max));
     const Array& args = Array::Handle(Array::New(1));
     args.SetAt(0, error);
@@ -114,7 +114,7 @@
 
   if (length.Value() < 0) {
     const String& error = String::Handle(String::NewFormatted(
-        "length (%"Pd") must be non-negative", length.Value()));
+        "length (%" Pd ") must be non-negative", length.Value()));
     const Array& args = Array::Handle(Array::New(1));
     args.SetAt(0, error);
     Exceptions::ThrowByType(Exceptions::kArgument, args);
diff --git a/runtime/lib/typed_data.dart b/runtime/lib/typed_data.dart
index 4e76c27..e60c145 100644
--- a/runtime/lib/typed_data.dart
+++ b/runtime/lib/typed_data.dart
@@ -2022,6 +2022,7 @@
   double get y native "Float32x4_getY";
   double get z native "Float32x4_getZ";
   double get w native "Float32x4_getW";
+  int get signMask native "Float32x4_getSignMask";
 
   Float32x4 shuffle(int mask) native "Float32x4_shuffle";
 
@@ -2081,10 +2082,19 @@
     return _xor(other);
   }
   Uint32x4 _xor(Uint32x4 other) native "Uint32x4_xor";
+  Uint32x4 operator +(Uint32x4 other) {
+    return _add(other);
+  }
+  Uint32x4 _add(Uint32x4 other) native "Uint32x4_add";
+  Uint32x4 operator -(Uint32x4 other) {
+    return _sub(other);
+  }
+  Uint32x4 _sub(Uint32x4 other) native "Uint32x4_sub";
   int get x native "Uint32x4_getX";
   int get y native "Uint32x4_getY";
   int get z native "Uint32x4_getZ";
   int get w native "Uint32x4_getW";
+  int get signMask native "Uint32x4_getSignMask";
   Uint32x4 withX(int x) native "Uint32x4_setX";
   Uint32x4 withY(int y) native "Uint32x4_setY";
   Uint32x4 withZ(int z) native "Uint32x4_setZ";
diff --git a/runtime/platform/thread_win.cc b/runtime/platform/thread_win.cc
index 7b3ca56..e8be678 100644
--- a/runtime/platform/thread_win.cc
+++ b/runtime/platform/thread_win.cc
@@ -140,7 +140,7 @@
 void Mutex::Unlock() {
   BOOL result = ReleaseSemaphore(data_.semaphore_, 1, NULL);
   if (result == 0) {
-    FATAL1("Mutex unlock failed", GetLastError());
+    FATAL1("Mutex unlock failed %d", GetLastError());
   }
 }
 
@@ -244,7 +244,7 @@
     // Signal event.
     BOOL result = SetEvent(first->event_);
     if (result == 0) {
-      FATAL1("Monitor::Notify failed to signal event", GetLastError());
+      FATAL1("Monitor::Notify failed to signal event %d", GetLastError());
     }
   }
   LeaveCriticalSection(&waiters_cs_);
@@ -261,7 +261,7 @@
   while (current != NULL) {
     BOOL result = SetEvent(current->event_);
     if (result == 0) {
-      FATAL1("Failed to set event for NotifyAll", GetLastError());
+      FATAL1("Failed to set event for NotifyAll %d", GetLastError());
     }
     current = current->next_;
   }
@@ -315,14 +315,14 @@
     // Wait forever for a Notify or a NotifyAll event.
     result = WaitForSingleObject(wait_data->event_, INFINITE);
     if (result == WAIT_FAILED) {
-      FATAL1("Monitor::Wait failed", GetLastError());
+      FATAL1("Monitor::Wait failed %d", GetLastError());
     }
   } else {
     // Wait for the given period of time for a Notify or a NotifyAll
     // event.
     result = WaitForSingleObject(wait_data->event_, millis);
     if (result == WAIT_FAILED) {
-      FATAL1("Monitor::Wait with timeout failed", GetLastError());
+      FATAL1("Monitor::Wait with timeout failed %d", GetLastError());
     }
     if (result == WAIT_TIMEOUT) {
       // No longer waiting. Remove from the list of waiters.
diff --git a/runtime/vm/allocation.cc b/runtime/vm/allocation.cc
index 0e97ed0..1c88436 100644
--- a/runtime/vm/allocation.cc
+++ b/runtime/vm/allocation.cc
@@ -19,7 +19,7 @@
   ASSERT(isolate != NULL);
   ASSERT(isolate->current_zone() != NULL);
   if (size > static_cast<uword>(kIntptrMax)) {
-    FATAL1("ZoneAllocated object has unexpectedly large size %"Pu"", size);
+    FATAL1("ZoneAllocated object has unexpectedly large size %" Pu "", size);
   }
   return reinterpret_cast<void*>(isolate->current_zone()->AllocUnsafe(size));
 }
diff --git a/runtime/vm/assembler_ia32.cc b/runtime/vm/assembler_ia32.cc
index c04347c..a1dfc13 100644
--- a/runtime/vm/assembler_ia32.cc
+++ b/runtime/vm/assembler_ia32.cc
@@ -571,6 +571,24 @@
 }
 
 
+void Assembler::addpl(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xFE);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void Assembler::subpl(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xFA);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
 void Assembler::addps(XmmRegister dst, XmmRegister src) {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitUint8(0x0F);
diff --git a/runtime/vm/assembler_ia32.h b/runtime/vm/assembler_ia32.h
index 83b1c49..d0972ea 100644
--- a/runtime/vm/assembler_ia32.h
+++ b/runtime/vm/assembler_ia32.h
@@ -411,7 +411,8 @@
   void divsd(XmmRegister dst, XmmRegister src);
   void divsd(XmmRegister dst, const Address& src);
 
-
+  void addpl(XmmRegister dst, XmmRegister src);
+  void subpl(XmmRegister dst, XmmRegister src);
   void addps(XmmRegister dst, XmmRegister src);
   void subps(XmmRegister dst, XmmRegister src);
   void divps(XmmRegister dst, XmmRegister src);
diff --git a/runtime/vm/assembler_ia32_test.cc b/runtime/vm/assembler_ia32_test.cc
index 3a7d4a5..2f3087f 100644
--- a/runtime/vm/assembler_ia32_test.cc
+++ b/runtime/vm/assembler_ia32_test.cc
@@ -874,6 +874,31 @@
 }
 
 
+ASSEMBLER_TEST_GENERATE(PackedIntOperations, assembler) {
+  __ movl(EAX, Immediate(0x2));
+  __ movd(XMM0, EAX);
+  __ shufps(XMM0, XMM0, Immediate(0x0));
+  __ movl(EAX, Immediate(0x1));
+  __ movd(XMM1, EAX);
+  __ shufps(XMM1, XMM1, Immediate(0x0));
+  __ addpl(XMM0, XMM1);  // 0x3
+  __ addpl(XMM0, XMM0);  // 0x6
+  __ subpl(XMM0, XMM1);  // 0x5
+  // Copy the low lane at ESP.
+  __ pushl(EAX);
+  __ movss(Address(ESP, 0), XMM0);
+  __ popl(EAX);
+  __ ret();
+}
+
+
+ASSEMBLER_TEST_RUN(PackedIntOperations, test) {
+  typedef uint32_t (*PackedIntOperationsCode)();
+  uint32_t res = reinterpret_cast<PackedIntOperationsCode>(test->entry())();
+  EXPECT_EQ(static_cast<uword>(0x5), res);
+}
+
+
 ASSEMBLER_TEST_GENERATE(PackedFPOperations2, assembler) {
   __ movl(EAX, Immediate(bit_cast<int32_t, float>(4.0f)));
   __ movd(XMM0, EAX);
diff --git a/runtime/vm/assembler_x64.cc b/runtime/vm/assembler_x64.cc
index 488cd55..38fad31 100644
--- a/runtime/vm/assembler_x64.cc
+++ b/runtime/vm/assembler_x64.cc
@@ -641,6 +641,27 @@
   EmitXmmRegisterOperand(dst & 7, src);
 }
 
+
+void Assembler::addpl(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitREX_RB(dst, src);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xFE);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
+void Assembler::subpl(XmmRegister dst, XmmRegister src) {
+  AssemblerBuffer::EnsureCapacity ensured(&buffer_);
+  EmitREX_RB(dst, src);
+  EmitUint8(0x66);
+  EmitUint8(0x0F);
+  EmitUint8(0xFA);
+  EmitXmmRegisterOperand(dst, src);
+}
+
+
 void Assembler::addps(XmmRegister dst, XmmRegister src) {
   AssemblerBuffer::EnsureCapacity ensured(&buffer_);
   EmitREX_RB(dst, src);
diff --git a/runtime/vm/assembler_x64.h b/runtime/vm/assembler_x64.h
index 17324c0..fd0d70b 100644
--- a/runtime/vm/assembler_x64.h
+++ b/runtime/vm/assembler_x64.h
@@ -415,6 +415,8 @@
   void mulsd(XmmRegister dst, XmmRegister src);
   void divsd(XmmRegister dst, XmmRegister src);
 
+  void addpl(XmmRegister dst, XmmRegister src);
+  void subpl(XmmRegister dst, XmmRegiste