Add GSS support.

Second patch concerning the support of GSS in GWT. This patch
introduce the generator that will parse and compile a GSS file
to a css string and generate the implementation of the
corresponding CssResource interface. GSS support is disabled
by default. To enable it, you have to set the configuration
property "CssResource.enableGss" to true.

Change-Id: Iae743842c5707c0231b1c4eb290f7914b6f7a501
diff --git a/tools/api-checker/config/gwt26_27userApi.conf b/tools/api-checker/config/gwt26_27userApi.conf
index f5f637a..427b038 100644
--- a/tools/api-checker/config/gwt26_27userApi.conf
+++ b/tools/api-checker/config/gwt26_27userApi.conf
@@ -109,6 +109,7 @@
 :user/src/com/google/gwt/junit/client/impl/GWTTestAccessor.java\
 :user/src/com/google/gwt/junit/remote/**\
 :user/src/com/google/gwt/resources/css/**\
+:user/src/com/google/gwt/resources/gss/**\
 :user/src/com/google/gwt/resources/converter/**\
 :user/src/com/google/gwt/resources/ext/**\
 :user/src/com/google/gwt/resources/rg/**\
diff --git a/user/src/com/google/gwt/resources/Resources.gwt.xml b/user/src/com/google/gwt/resources/Resources.gwt.xml
index 13b814b..fb31e71 100644
--- a/user/src/com/google/gwt/resources/Resources.gwt.xml
+++ b/user/src/com/google/gwt/resources/Resources.gwt.xml
@@ -76,4 +76,27 @@
   <!-- by setting the value to true. -->
   <define-configuration-property name="ExternalTextResource.useJsonp" is-multi-valued="false" />
   <set-configuration-property name="ExternalTextResource.useJsonp" value="false" />
+
+  <!-- Enable GSS with CssResource -->
+  <define-configuration-property name="CssResource.enableGss" is-multi-valued="false" />
+  <set-configuration-property name="CssResource.enableGss" value="false" />
+
+  <!-- A multi-valued configuration property that defines the list of allowed non standard -->
+  <!-- at-rules in the css files -->
+  <define-configuration-property name="CssResource.allowedAtRules" is-multi-valued="true" />
+  <extend-configuration-property name="CssResource.allowedAtRules" value="-moz-document" />
+  <extend-configuration-property name="CssResource.allowedAtRules" value="supports" />
+
+  <!-- A multi-valued configuration property that defines the list of allowed non standard -->
+  <!-- functions in the css files -->
+  <define-configuration-property name="CssResource.allowedFunctions" is-multi-valued="true" />
+  <extend-configuration-property name="CssResource.allowedFunctions" value="" />
+
+  <!-- Enable the in-memory conversion of css files with .css extension to GSS -->
+  <define-configuration-property name="CssResource.legacy" is-multi-valued="false" />
+  <set-configuration-property name="CssResource.legacy" value="false" />
+
+  <!-- Set the mode used by the in-memory conversion: strict or lenient -->
+  <define-configuration-property name="CssResource.conversionMode" is-multi-valued="false" />
+  <set-configuration-property name="CssResource.conversionMode" value="strict" />
 </module>
diff --git a/user/src/com/google/gwt/resources/client/CssResource.java b/user/src/com/google/gwt/resources/client/CssResource.java
index c55cb20..d92b2ab 100644
--- a/user/src/com/google/gwt/resources/client/CssResource.java
+++ b/user/src/com/google/gwt/resources/client/CssResource.java
@@ -146,7 +146,7 @@
  * @see <a href="http://code.google.com/p/google-web-toolkit/wiki/CssResource"
  *      >CssResource design doc</a>
  */
-@DefaultExtensions(value = {".css"})
+@DefaultExtensions(value = {".css", ".gss"})
 @ResourceGeneratorType(CssResourceGenerator.class)
 public interface CssResource extends CssResourceBase {
   /**
diff --git a/user/src/com/google/gwt/resources/gss/CreateRuntimeConditionalNodes.java b/user/src/com/google/gwt/resources/gss/CreateRuntimeConditionalNodes.java
new file mode 100644
index 0000000..794bf83
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/CreateRuntimeConditionalNodes.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.resources.gss.ast.CssJavaExpressionNode;
+import com.google.gwt.resources.gss.ast.CssRuntimeConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssAtRuleNode.Type;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssBooleanExpressionNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalBlockNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.DefaultTreeVisitor;
+import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Visitor that handles conditional nodes with conditions that need to be evaluated at runtime.
+ */
+public class CreateRuntimeConditionalNodes extends DefaultTreeVisitor implements CssCompilerPass {
+  private static final Pattern EVAL_FUNCTION = Pattern.compile("^eval\\(([\"'])(((?!\\1).)*)\\1\\)$");
+
+  private final MutatingVisitController visitController;
+
+  public CreateRuntimeConditionalNodes(MutatingVisitController visitController) {
+    this.visitController = visitController;
+  }
+
+  @Override
+  public boolean enterConditionalBlock(CssConditionalBlockNode block) {
+    // We have to visit all the CssConditionalRuleNode when we visit the CssConditionalBlockNode
+    // parent node because we are going to replace CssConditionalRuleNode by another node and
+    // unfortunately the visitController doesn't support to replace a CssConditionalRuleNode and
+    // we have to do it manually. That implies that the new nodes won't be visited by the
+    // visitor if we do that during the visit of the CssConditionalRuleNodes and they can contain
+    // other CssConditionalBlockNodes that won't be visited.
+    // Once MutatingVisitController supports replacement of CssConditionalRuleNode,
+    // we will be able to visit CssConditionalRuleNode directly.
+
+    // Make a copy in order to avoid ConcurrentModificationException
+    List<CssConditionalRuleNode> children = Lists.newArrayList(block.getChildren());
+    for (CssConditionalRuleNode ruleNode : children) {
+      visitConditionalRule(ruleNode, block);
+    }
+    return true;
+  }
+
+  private void visitConditionalRule(CssConditionalRuleNode node,
+      CssConditionalBlockNode parent) {
+
+    if (node.getType() != Type.ELSE) {
+      CssBooleanExpressionNode nodeCondition = node.getCondition();
+      String condition = extractRuntimeCondition(nodeCondition);
+
+      if (condition != null) {
+        CssJavaExpressionNode newNode = new CssJavaExpressionNode(condition,
+            nodeCondition.getSourceCodeLocation());
+
+        CssRuntimeConditionalRuleNode newRuleNode = new CssRuntimeConditionalRuleNode(node,
+            newNode);
+
+        // Unfortunately visitController.replaceCurrentBlockChildWith doesn't work with
+        // CssConditionnalRuleNode
+        int index = parent.getChildren().indexOf(node);
+        parent.replaceChildAt(index, Lists.newArrayList(newRuleNode));
+      }
+    }
+  }
+
+  private String extractRuntimeCondition(CssValueNode node) {
+    Matcher m = EVAL_FUNCTION.matcher(node.getValue());
+    return m.matches() ? m.group(2) : null;
+  }
+
+  @Override
+  public void runPass() {
+    visitController.startVisit(this);
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/CssPrinter.java b/user/src/com/google/gwt/resources/gss/CssPrinter.java
new file mode 100644
index 0000000..35c22df
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/CssPrinter.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.resources.gss.ast.CssDotPathNode;
+import com.google.gwt.resources.gss.ast.CssJavaExpressionNode;
+import com.google.gwt.resources.gss.ast.CssRuntimeConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssAtRuleNode.Type;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalBlockNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssRootNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CompactPrinter;
+
+import java.util.Stack;
+
+/**
+ * Visitor that converts the ast to a {@code String} chain. This string will contain the
+ * needed java code that will be used to build the final css at runtime.
+ */
+public class CssPrinter extends CompactPrinter {
+  /**
+   * This value is used by {@link #concat} to help create a more balanced AST
+   * tree by producing parenthetical expressions.
+   */
+  private static final int CONCAT_EXPRESSION_LIMIT = 20;
+  private static final String CONTATENATION = " + ";
+  private static final String COLON = " : ";
+  private static final String LEFT_PARENTHESIS = "(";
+  private static final String RIGHT_PARENTHESIS = ")";
+  private static final String DOUBLE_QUOTE = "\"";
+  private static final String CONTATENATION_BLOCK = ") + (";
+  private static final String CONDITIONAL_OPERATOR = ") ? (";
+
+  private final Stack<Boolean> elseNodeFound = new Stack<Boolean>();
+
+  private StringBuilder masterStringBuilder;
+  private String css;
+  private int concatenationNumber;
+
+  public CssPrinter(CssTree tree) {
+    super(tree);
+  }
+
+  public CssPrinter(CssNode node) {
+    super(node);
+  }
+
+  @Override
+  public boolean enterTree(CssRootNode root) {
+    masterStringBuilder.append(LEFT_PARENTHESIS);
+    return super.enterTree(root);
+  }
+
+  @Override
+  public String getCompactPrintedString() {
+    return css;
+  }
+
+  @Override
+  public void leaveTree(CssRootNode root) {
+    masterStringBuilder.append(flushInternalStringBuilder()).append(RIGHT_PARENTHESIS);
+    super.leaveTree(root);
+  }
+
+  @Override
+  public void runPass() {
+    masterStringBuilder = new StringBuilder();
+    concatenationNumber = 0;
+
+    super.runPass();
+
+    css = masterStringBuilder
+        .toString()
+        // remove empty string concatenation : '+ ("")'
+        .replaceAll(" \\+ \\(\"\"\\)", "")
+        // remove possible empty string concatenation '("") + ' at the  beginning
+        .replaceAll("^\\(\"\"\\) \\+ ", "");
+  }
+
+  @Override
+  public boolean enterConditionalBlock(CssConditionalBlockNode node) {
+    masterStringBuilder.append(flushInternalStringBuilder());
+
+    masterStringBuilder.append(CONTATENATION_BLOCK);
+
+    elseNodeFound.push(false);
+
+    return true;
+  }
+
+  @Override
+  public void leaveConditionalBlock(CssConditionalBlockNode block) {
+    if (!elseNodeFound.pop()) {
+      masterStringBuilder.append(DOUBLE_QUOTE).append(DOUBLE_QUOTE);
+    }
+    masterStringBuilder.append(CONTATENATION_BLOCK);
+
+    // Reset concatenation counter
+    concatenationNumber = 0;
+  }
+
+  @Override
+  public boolean enterConditionalRule(CssConditionalRuleNode node) {
+    if (node.getType() == Type.ELSE) {
+      elseNodeFound.pop();
+      elseNodeFound.push(true);
+
+      masterStringBuilder.append(LEFT_PARENTHESIS);
+    } else {
+      CssRuntimeConditionalRuleNode conditionalRuleNode = (CssRuntimeConditionalRuleNode) node;
+
+      masterStringBuilder.append(LEFT_PARENTHESIS);
+      masterStringBuilder.append(conditionalRuleNode.getRuntimeCondition().getValue());
+      masterStringBuilder.append(CONDITIONAL_OPERATOR);
+
+      // Reset concatenation counter
+      concatenationNumber = 0;
+    }
+
+    return true;
+  }
+
+  @Override
+  public void leaveConditionalRule(CssConditionalRuleNode node) {
+    masterStringBuilder.append(flushInternalStringBuilder()).append(RIGHT_PARENTHESIS);
+
+    if (node.getType() != Type.ELSE) {
+      masterStringBuilder.append(COLON);
+    }
+  }
+
+  @Override
+  protected void appendValueNode(CssValueNode node) {
+    if (node instanceof CssJavaExpressionNode || node instanceof CssDotPathNode) {
+      concat(LEFT_PARENTHESIS + node.getValue() + RIGHT_PARENTHESIS);
+    } else {
+      super.appendValueNode(node);
+    }
+  }
+
+  private void concat(String stringToAppend) {
+    masterStringBuilder.append(flushInternalStringBuilder());
+
+    appendConcatOperation();
+
+    masterStringBuilder.append(stringToAppend);
+
+    appendConcatOperation();
+  }
+
+  private void appendConcatOperation() {
+    // Avoid long string concatenation chain
+    if (concatenationNumber >= CONCAT_EXPRESSION_LIMIT) {
+      masterStringBuilder.append(CONTATENATION_BLOCK);
+      concatenationNumber = 0;
+    } else {
+      masterStringBuilder.append(CONTATENATION);
+      concatenationNumber++;
+    }
+  }
+
+  /**
+   * Read what the internal StringBuilder used by the CompactPrinter has already built. Escape it.
+   * and reset the internal StringBuilder
+   *
+   * @return
+   */
+  private String flushInternalStringBuilder() {
+    String content = DOUBLE_QUOTE + Generator.escape(sb.toString()) + DOUBLE_QUOTE;
+    sb = new StringBuilder();
+
+    return content;
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/EvalFunction.java b/user/src/com/google/gwt/resources/gss/EvalFunction.java
new file mode 100644
index 0000000..1fd8fc7
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/EvalFunction.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.resources.gss.ast.CssJavaExpressionNode;
+import com.google.gwt.thirdparty.common.css.SourceCodeLocation;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunction;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunctionException;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * GSS function that creates a {@link com.google.gwt.resources.gss.ast.CssJavaExpressionNode}
+ * in order to evaluate a Java expression at runtime.
+ */
+public class EvalFunction implements GssFunction {
+
+  public static String getName() {
+    return "eval";
+  }
+
+  @Override
+  public List<CssValueNode> getCallResultNodes(List<CssValueNode> args, ErrorManager errorManager)
+      throws GssFunctionException {
+    CssValueNode functionToEval = args.get(0);
+
+    SourceCodeLocation sourceCodeLocation = extractSourceCodeLocation(functionToEval);
+
+    CssJavaExpressionNode result = new CssJavaExpressionNode(functionToEval.getValue(),
+        sourceCodeLocation);
+
+    return ImmutableList.of((CssValueNode) result);
+  }
+
+  @Override
+  public String getCallResultString(List<String> args) throws GssFunctionException {
+    return args.get(0);
+  }
+
+  @Override
+  public Integer getNumExpectedArguments() {
+    return 1;
+  }
+
+  private SourceCodeLocation extractSourceCodeLocation(CssValueNode functionToEval) {
+    return functionToEval.getParent().getParent().getSourceCodeLocation();
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/ExtendedEliminateConditionalNodes.java b/user/src/com/google/gwt/resources/gss/ExtendedEliminateConditionalNodes.java
new file mode 100644
index 0000000..54fdb4a
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/ExtendedEliminateConditionalNodes.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.resources.gss.ast.CssRuntimeConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssAtRuleNode.Type;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssBooleanExpressionNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalBlockNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController;
+import com.google.gwt.thirdparty.common.css.compiler.passes.BooleanExpressionEvaluator;
+import com.google.gwt.thirdparty.common.css.compiler.passes.EliminateConditionalNodes;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A compiler pass that eliminates the conditional blocks for which the boolean
+ * expression does not evaluate to true.
+ * <p>This compiler pass does not deal with conditional nodes that need to be evaluated at runtime.
+ */
+public class ExtendedEliminateConditionalNodes extends EliminateConditionalNodes
+    implements CssCompilerPass {
+
+  private final MutatingVisitController visitController;
+  private final Set<String> trueConditions;
+  private final Set<CssConditionalBlockNode> runtimeConditionalNodes;
+  private final Set<CssConditionalBlockNode> alreadyTreatedNode =
+      new HashSet<CssConditionalBlockNode>();
+
+  public ExtendedEliminateConditionalNodes(MutatingVisitController visitController,
+      Set<String> trueConditions, Set<CssConditionalBlockNode> runtimeConditionalNodes) {
+    super(visitController, trueConditions);
+
+    this.visitController = visitController;
+    this.trueConditions = trueConditions;
+    this.runtimeConditionalNodes = runtimeConditionalNodes;
+  }
+
+  @Override
+  public boolean enterConditionalBlock(CssConditionalBlockNode block) {
+    if (alreadyTreatedNode.contains(block)) {
+      // don't visit this block again but visit its children
+      return true;
+    }
+
+    if (runtimeConditionalNodes.contains(block)) {
+      return enterRuntimeConditionalBlock(block);
+    } else {
+      // block without any runtime condition.
+      return super.enterConditionalBlock(block);
+    }
+  }
+
+  private boolean enterRuntimeConditionalBlock(CssConditionalBlockNode block) {
+    boolean runtimeEvaluationNodeFound = false;
+    List<CssConditionalRuleNode> newChildren =
+        new ArrayList<CssConditionalRuleNode>(block.numChildren());
+
+    for (CssConditionalRuleNode currentConditional : block.childIterable()) {
+      if (currentConditional.getType() == Type.ELSE) {
+        newChildren.add(currentConditional);
+        break;
+      }
+
+      if (currentConditional instanceof CssRuntimeConditionalRuleNode) {
+        runtimeEvaluationNodeFound = true;
+        newChildren.add(currentConditional);
+        continue;
+      }
+
+      // The node can be evaluated at compile time
+      BooleanExpressionEvaluator evaluator = new BooleanExpressionEvaluator(
+          currentConditional.getCondition(), trueConditions);
+
+      CssBooleanExpressionNode result = evaluator.evaluate();
+      boolean isTrue = CssBooleanExpressionNode.Type.TRUE_CONSTANT.equals(result.getValue());
+
+      if (!isTrue) {
+        // any node evaluated to false can be removed
+      } else if (!runtimeEvaluationNodeFound) {
+        // node evaluated to true before the runtime condition, replace the conditional block by the
+        // children of this current conditional node.
+        visitController.replaceCurrentBlockChildWith(currentConditional.getBlock().getChildren(),
+            true);
+        return true;
+      } else {
+        // node evaluated to true before the runtime condition, transform this node to an else node
+        CssConditionalRuleNode newNode = new CssConditionalRuleNode(Type.ELSE,
+            currentConditional.getName(), null, currentConditional.getBlock());
+
+        newChildren.add(newNode);
+        break;
+      }
+    }
+
+    CssConditionalBlockNode newNode = new CssConditionalBlockNode();
+    for (CssConditionalRuleNode child : newChildren) {
+      newNode.addChildToBack(child);
+    }
+
+    visitController.replaceCurrentBlockChildWith(Lists.newArrayList(newNode), true);
+
+    // mark this node as already visited.
+    alreadyTreatedNode.add(newNode);
+
+    return true;
+  }
+
+  @Override
+  public void runPass() {
+    alreadyTreatedNode.clear();
+
+    visitController.startVisit(this);
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/ExternalClassesCollector.java b/user/src/com/google/gwt/resources/gss/ExternalClassesCollector.java
new file mode 100644
index 0000000..08b3201
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/ExternalClassesCollector.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.thirdparty.common.css.SourceCodeLocation;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompositeValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssLiteralNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssStringNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssUnknownAtRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.DefaultTreeVisitor;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Visitor that collect style classes flagged as external.
+ */
+public class ExternalClassesCollector extends DefaultTreeVisitor implements CssCompilerPass {
+  public static final String EXTERNAL_AT_RULE = "external";
+  private static final String STAR_SUFFIX = "*";
+
+  private final MutatingVisitController visitController;
+  private final ErrorManager errorManager;
+
+  private Set<String> externalClassNames;
+  private List<String> externalClassPrefixes;
+  private boolean matchAll;
+
+  public ExternalClassesCollector(MutatingVisitController visitController,
+      ErrorManager errorManager) {
+    this.visitController = visitController;
+    this.errorManager = errorManager;
+  }
+
+  @Override
+  public void runPass() {
+    externalClassNames = new HashSet<String>();
+    externalClassPrefixes = new ArrayList<String>();
+
+    visitController.startVisit(this);
+  }
+
+  @Override
+  public void leaveUnknownAtRule(CssUnknownAtRuleNode node) {
+    if (EXTERNAL_AT_RULE.equals(node.getName().getValue())) {
+      if (!matchAll) {
+        processParameters(node.getParameters(), node.getSourceCodeLocation());
+      }
+      visitController.removeCurrentNode();
+    }
+  }
+
+  public Set<String> getExternalClassNames(Set<String> styleClassesSet) {
+    SortedSet<String> classNames = new TreeSet<String>(styleClassesSet);
+    if (matchAll) {
+      return classNames;
+    }
+
+    for (String prefix : externalClassPrefixes) {
+      for (String styleClass : classNames.tailSet(prefix)) {
+        if (styleClass.startsWith(prefix)) {
+          externalClassNames.add(styleClass);
+        } else {
+          break;
+        }
+      }
+    }
+    return externalClassNames;
+  }
+
+  private void processParameters(List<CssValueNode> values, SourceCodeLocation sourceCodeLocation) {
+    for (CssValueNode value : values) {
+      if (value instanceof CssCompositeValueNode) {
+        processParameters(((CssCompositeValueNode) value).getValues(), sourceCodeLocation);
+      } else if (value instanceof CssStringNode) {
+        String selector = ((CssStringNode) value).getConcreteValue();
+        if (STAR_SUFFIX.equals(selector)) {
+          matchAll = true;
+          return;
+        } else if (selector.endsWith(STAR_SUFFIX)) {
+          externalClassPrefixes.add(selector.substring(0, selector.length() - 1));
+        } else {
+          externalClassNames.add(selector);
+        }
+      } else if (value instanceof CssLiteralNode) {
+        externalClassNames.add(value.getValue());
+      } else {
+        errorManager.report(new GssError("External at-rule invalid. The following terms is not " +
+            "accepted in an external at-rule [" + value.getValue() + "]", sourceCodeLocation));
+      }
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/GwtGssFunctionMapProvider.java b/user/src/com/google/gwt/resources/gss/GwtGssFunctionMapProvider.java
new file mode 100644
index 0000000..4195748
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/GwtGssFunctionMapProvider.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.resources.ext.ResourceContext;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunction;
+import com.google.gwt.thirdparty.common.css.compiler.gssfunctions.DefaultGssFunctionMapProvider;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
+
+import java.util.Map;
+
+/**
+ * {@link GssFunctionMapProvider} that adds the mapping of GssFunction implemented for GWT.
+ */
+public class GwtGssFunctionMapProvider extends DefaultGssFunctionMapProvider {
+  private final ResourceContext context;
+
+  public GwtGssFunctionMapProvider(ResourceContext context) {
+    this.context = context;
+  }
+
+  @Override
+  public Map<String, GssFunction> get() {
+    Map<String, GssFunction> gssFunctionMap = super.get();
+
+    return ImmutableMap.<String, GssFunction>builder().putAll(gssFunctionMap)
+        // TODO add a namespace for gwt-specific function ?
+        .put(EvalFunction.getName(), new EvalFunction())
+        .put(ValueFunction.getName(), new ValueFunction())
+        .put(ResourceUrlFunction.getName(), new ResourceUrlFunction(context))
+        .build();
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/ImageSpriteCreator.java b/user/src/com/google/gwt/resources/gss/ImageSpriteCreator.java
new file mode 100644
index 0000000..fbd56b0
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/ImageSpriteCreator.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import static com.google.gwt.thirdparty.common.css.compiler.passes.PassUtil.ALTERNATE;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.resources.ext.ResourceContext;
+import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.resources.gss.ast.CssDotPathNode;
+import com.google.gwt.thirdparty.common.css.SourceCodeLocation;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCommentNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssDeclarationNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssFunctionArgumentsNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssFunctionNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssFunctionNode.Function;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssLiteralNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssPropertyNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssPropertyValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.DefaultTreeVisitor;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList.Builder;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Visitor that detects sprite definitions and replace them by several css rules in order to create
+ * the corresponding sprited image.
+ * <p>This visitor will replace the following gss:
+ * {@code
+ *   .foo {
+ *     padding: 5px;
+ *     gwt-sprite: imageResource;
+ *     width: 150px;
+ *   }
+ * }
+ * to the corresponding gss:
+ * {@code
+ *   .foo {
+ *     padding: 5px;
+ *     /* @alternate &#42;/ width: eval("imageResource.getWidth", "px");
+ *     /* @alternate &#42;/ height: eval("imageResource.getHeight", "px");
+ *     /* @alternate &#42;/ overflow: hidden;
+ *     /* @alternate &#42;/ background: resourceUrl("imageResource") eval("imageResource.getLeft",
+ *          "px") eval("imageResource.getTop", "px") no-repeat;
+ *     width: 150px;
+ *   }
+ * }
+ * <p>This visitor will also check the presence of the {@link ImageOptions} annotation on the
+ * image resource in order to support correctly horizontal or vertical repetition.
+ */
+public class ImageSpriteCreator extends DefaultTreeVisitor implements CssCompilerPass {
+  @VisibleForTesting
+  interface MethodByPathHelper {
+    JMethod getMethodByPath(ResourceContext context, List<String> pathElements,
+        JType expectedReturnType) throws NotFoundException;
+  }
+
+  private static class MethodByPathHelperImpl implements MethodByPathHelper {
+    @Override
+    public JMethod getMethodByPath(ResourceContext context, List<String> pathElements,
+        JType expectedReturnType) throws NotFoundException {
+      return ResourceGeneratorUtil.getMethodByPath(context.getClientBundleType(),
+          pathElements, expectedReturnType);
+    }
+  }
+
+  private static final String SPRITE_PROPERTY_NAME = "gwt-sprite";
+
+  private final MutatingVisitController visitController;
+  private final ErrorManager errorManager;
+  private final ResourceContext context;
+  private final ImageSpriteCreator.MethodByPathHelper methodByPathHelper;
+  private final JClassType imageResourceType;
+  private final String resourceThisPrefix;
+
+  public ImageSpriteCreator(MutatingVisitController visitController, ResourceContext context,
+      ErrorManager errorManager) {
+    this(visitController, context, errorManager, new MethodByPathHelperImpl());
+  }
+
+  @VisibleForTesting
+  ImageSpriteCreator(MutatingVisitController visitController, ResourceContext context,
+      ErrorManager errorManager, MethodByPathHelper methodByPathHelper) {
+    this.visitController = visitController;
+    this.errorManager = errorManager;
+    this.context = context;
+    this.methodByPathHelper = methodByPathHelper;
+    this.imageResourceType = context.getGeneratorContext().getTypeOracle().findType(
+        ImageResource.class.getName());
+    this.resourceThisPrefix = context.getImplementationSimpleSourceName() + ".this";
+  }
+
+  @Override
+  public boolean enterDeclaration(CssDeclarationNode declaration) {
+    String propertyName = declaration.getPropertyName().getPropertyName();
+
+    if (SPRITE_PROPERTY_NAME.equals(propertyName)) {
+      createSprite(declaration);
+      return true;
+    }
+
+    return super.enterDeclaration(declaration);
+  }
+
+  private void createSprite(CssDeclarationNode declaration) {
+    List<CssValueNode> valuesNodes = declaration.getPropertyValue().getChildren();
+
+    if (valuesNodes.size() != 1) {
+      errorManager.report(new GssError(SPRITE_PROPERTY_NAME + " must have exactly one value",
+          declaration.getSourceCodeLocation()));
+      return;
+    }
+
+    String imageResource = valuesNodes.get(0).getValue();
+
+    JMethod imageMethod;
+    try {
+      imageMethod = methodByPathHelper.getMethodByPath(context, getPathElement(imageResource),
+          imageResourceType);
+    } catch (NotFoundException e) {
+      errorManager.report(new GssError("Unable to find ImageResource method "
+          + imageResource + " in " + context.getClientBundleType().getQualifiedSourceName() + " : "
+          + e.getMessage(), declaration.getSourceCodeLocation()));
+      return;
+    }
+
+    ImageOptions options = imageMethod.getAnnotation(ImageOptions.class);
+    RepeatStyle repeatStyle = options != null ? options.repeatStyle() : RepeatStyle.None;
+
+    Builder<CssDeclarationNode> listBuilder = ImmutableList.builder();
+    SourceCodeLocation sourceCodeLocation = declaration.getSourceCodeLocation();
+
+    String repeatText;
+    switch (repeatStyle) {
+      case None:
+        repeatText = " no-repeat";
+        listBuilder.add(buildHeightDeclaration(imageResource, sourceCodeLocation));
+        listBuilder.add(buildWidthDeclaration(imageResource, sourceCodeLocation));
+        break;
+      case Horizontal:
+        repeatText = " repeat-x";
+        listBuilder.add(buildHeightDeclaration(imageResource, sourceCodeLocation));
+        break;
+      case Vertical:
+        repeatText = " repeat-y";
+        listBuilder.add(buildWidthDeclaration(imageResource, sourceCodeLocation));
+        break;
+      case Both:
+        repeatText = " repeat";
+        break;
+      default:
+        errorManager.report(new GssError("Unknown repeatStyle " + repeatStyle,
+            sourceCodeLocation));
+        return;
+    }
+
+    listBuilder.add(buildOverflowDeclaration(sourceCodeLocation));
+    listBuilder.add(buildBackgroundDeclaration(imageResource, repeatText, sourceCodeLocation));
+
+    visitController.replaceCurrentBlockChildWith(listBuilder.build(), false);
+  }
+
+  private CssDeclarationNode buildBackgroundDeclaration(String imageResource, String repeatText,
+      SourceCodeLocation location) {
+    // build the url function
+    CssFunctionNode urlFunction = new CssFunctionNode(Function.byName("url"), location);
+    CssDotPathNode imageUrl = new CssDotPathNode(resourceThisPrefix, imageResource + ".getSafeUri" +
+        ".asString", null, null, location);
+    CssFunctionArgumentsNode urlFunctionArguments = new CssFunctionArgumentsNode();
+    urlFunctionArguments.addChildToBack(imageUrl);
+    urlFunction.setArguments(urlFunctionArguments);
+
+    // build left offset
+    CssDotPathNode left = new CssDotPathNode(resourceThisPrefix, imageResource + ".getLeft", "-",
+        "px", location);
+
+    // build top offset
+    CssDotPathNode top = new CssDotPathNode(resourceThisPrefix, imageResource + ".getTop",
+        "-", "px", location);
+
+    // build repeat
+    CssLiteralNode repeat = new CssLiteralNode(repeatText, location);
+
+    CssPropertyNode propertyNode = new CssPropertyNode("background", location);
+    CssPropertyValueNode propertyValueNode = new CssPropertyValueNode(ImmutableList.of(urlFunction,
+        left, top, repeat));
+    propertyValueNode.setSourceCodeLocation(location);
+
+    return createDeclarationNode(propertyNode, propertyValueNode, location, true);
+  }
+
+  private CssDeclarationNode buildHeightDeclaration(String imageResource,
+      SourceCodeLocation location) {
+    CssPropertyNode propertyNode = new CssPropertyNode("height", location);
+    CssValueNode valueNode = new CssDotPathNode(resourceThisPrefix, imageResource + ".getHeight",
+        null, "px", location);
+
+    CssPropertyValueNode propertyValueNode = new CssPropertyValueNode(ImmutableList.of(valueNode));
+
+    return createDeclarationNode(propertyNode, propertyValueNode, location, true);
+  }
+
+  private CssDeclarationNode buildOverflowDeclaration(SourceCodeLocation location) {
+    CssPropertyNode propertyNode = new CssPropertyNode("overflow", location);
+    CssValueNode valueNode = new CssLiteralNode("hidden", location);
+
+    CssPropertyValueNode propertyValueNode = new CssPropertyValueNode(ImmutableList.of(valueNode));
+
+    return createDeclarationNode(propertyNode, propertyValueNode, location, true);
+  }
+
+  private CssDeclarationNode buildWidthDeclaration(String imageResource,
+      SourceCodeLocation location) {
+    CssPropertyNode propertyNode = new CssPropertyNode("width", location);
+    CssValueNode valueNode = new CssDotPathNode(resourceThisPrefix, imageResource + ".getWidth",
+        null, "px", location);
+    CssPropertyValueNode propertyValueNode = new CssPropertyValueNode(ImmutableList.of(valueNode));
+
+    return createDeclarationNode(propertyNode, propertyValueNode, location, true);
+  }
+
+  private List<String> getPathElement(String imageResourcePath) {
+    return Lists.newArrayList(imageResourcePath.split("\\."));
+  }
+
+  private CssDeclarationNode createDeclarationNode(CssPropertyNode propertyNode,
+      CssPropertyValueNode propertyValueNode, SourceCodeLocation location, boolean useAlternate) {
+    CssDeclarationNode replaceNode =  new CssDeclarationNode(propertyNode, propertyValueNode);
+    replaceNode.setSourceCodeLocation(location);
+
+    if (useAlternate) {
+      replaceNode.setComments(ImmutableList.of(new CssCommentNode(ALTERNATE, location)));
+    }
+
+    return replaceNode;
+  }
+
+  @Override
+  public void runPass() {
+    visitController.startVisit(this);
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/PermutationsCollector.java b/user/src/com/google/gwt/resources/gss/PermutationsCollector.java
new file mode 100644
index 0000000..8d328ee
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/PermutationsCollector.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssBooleanExpressionNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssBooleanExpressionNode.Type;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.DefaultTreeVisitor;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+import com.google.gwt.thirdparty.guava.common.collect.Queues;
+
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Visitor that collects the different permutation axis defined in a gss file.
+ */
+public class PermutationsCollector extends DefaultTreeVisitor implements CssCompilerPass {
+  private static final Pattern IS_FUNCTION = Pattern.compile("^is\\([\"']([^\"']*)[\"'](?:,[\"']([^\"']*)[\"'])?\\)$");
+  private static final String USER_AGENT_PERMUTATION = "user.agent";
+
+  private final MutatingVisitController delegate;
+  private final ErrorManager errorManager;
+  private Deque<CssBooleanExpressionNode> childrenStack;
+  private Set<String> permutationAxesSet;
+
+  public PermutationsCollector(MutatingVisitController delegate, ErrorManager errorManager) {
+    this.delegate = delegate;
+    this.errorManager = errorManager;
+  }
+
+  @Override
+  public boolean enterConditionalRule(CssConditionalRuleNode node) {
+    // unfortunately the VisitController doesn't visit the children of a CssConditionalRuleNode
+    for (CssValueNode children : node.getChildren()) {
+      if (children instanceof CssBooleanExpressionNode) {
+        childrenStack.addFirst((CssBooleanExpressionNode) children);
+      }
+    }
+
+    while (!childrenStack.isEmpty()) {
+      CssBooleanExpressionNode visitingNode = childrenStack.pop();
+      visitBooleanExpression(visitingNode);
+
+      if (visitingNode.getLeft() != null) {
+        childrenStack.addFirst(visitingNode.getLeft());
+      }
+      if (visitingNode.getRight() != null) {
+        childrenStack.addFirst(visitingNode.getRight());
+      }
+    }
+
+    return true;
+  }
+
+  public List<String> getPermutationAxes() {
+    return ImmutableList.copyOf(permutationAxesSet);
+  }
+
+  private void visitBooleanExpression(CssBooleanExpressionNode booleanExpressionNode) {
+    if (booleanExpressionNode.getType() == Type.CONSTANT && booleanExpressionNode.getValue() != null) {
+      Matcher m = IS_FUNCTION.matcher(booleanExpressionNode.getValue());
+
+      if (m.matches()) {
+        String permutationName = m.group(1);
+        String permutationValue = m.group(2);
+
+        if (permutationValue == null) {
+          permutationValue = permutationName;
+          permutationName = USER_AGENT_PERMUTATION;
+        }
+
+        booleanExpressionNode.setValue(permutationName + ":" + permutationValue);
+
+        if (!permutationAxesSet.contains(permutationName)) {
+          permutationAxesSet.add(permutationName);
+        }
+      } else {
+        GssError error = new GssError("The expression [" + booleanExpressionNode.getValue() +
+            "] is not valid condition.",
+            booleanExpressionNode.getSourceCodeLocation());
+        errorManager.report(error);
+      }
+    }
+  }
+
+  @Override
+  public void runPass() {
+    childrenStack  = Queues.newArrayDeque();
+    permutationAxesSet = new HashSet<String>();
+    delegate.startVisit(this);
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/RecordingBidiFlipper.java b/user/src/com/google/gwt/resources/gss/RecordingBidiFlipper.java
new file mode 100644
index 0000000..638f378
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/RecordingBidiFlipper.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompositeValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssDeclarationNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssFunctionNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssNumericNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssPropertyNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssPropertyValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTreeVisitor;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.DefaultTreeVisitor;
+import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController;
+import com.google.gwt.thirdparty.common.css.compiler.passes.BiDiFlipper;
+import com.google.gwt.thirdparty.guava.common.base.Objects;
+
+import java.util.List;
+
+/**
+ * Compiler pass that BiDi flips all the flippable nodes and records if nodes have been flipped.
+ */
+public class RecordingBidiFlipper extends DefaultTreeVisitor implements CssCompilerPass {
+
+  /**
+   * This {@link MutatingVisitController} will record if an effective mutation is done.
+   */
+  private static class RecordingMutatingVisitController implements MutatingVisitController {
+    private MutatingVisitController delegate;
+    private CssDeclarationNode visitingDeclarationNode;
+    private boolean hasMutation;
+
+    private RecordingMutatingVisitController(MutatingVisitController delegate) {
+      this.delegate = delegate;
+    }
+
+    @Override
+    public void removeCurrentNode() {
+      delegate.removeCurrentNode();
+    }
+
+    @Override
+    public <T extends CssNode> void replaceCurrentBlockChildWith(List<T> replacementNodes,
+        boolean visitTheReplacementNodes) {
+      // In our case, the list of replacement node should contain only one CssDeclarationNode
+      if (!hasMutation && visitingDeclarationNode != null && replacementNodes.size() == 1 &&
+          replacementNodes.get(0) instanceof CssDeclarationNode) {
+        CssDeclarationNode newDeclarationNode = (CssDeclarationNode) replacementNodes.get(0);
+        hasMutation |= isNotEqual(visitingDeclarationNode, newDeclarationNode);
+      }
+
+      delegate.replaceCurrentBlockChildWith(replacementNodes, visitTheReplacementNodes);
+    }
+
+    public void setVisitingDeclarationNode(CssDeclarationNode visitingDeclarationNode) {
+      this.visitingDeclarationNode = visitingDeclarationNode;
+    }
+
+    @Override
+    public void startVisit(CssTreeVisitor visitor) {
+      delegate.startVisit(visitor);
+    }
+
+    @Override
+    public void stopVisit() {
+      delegate.stopVisit();
+    }
+
+    private boolean compositeValueEqual(CssCompositeValueNode first, CssCompositeValueNode second) {
+      return valueNodeListEqual(first.getValues(), second.getValues());
+    }
+
+    private boolean functionNodeEqual(CssFunctionNode first, CssFunctionNode second) {
+      return valueNodeListEqual(first.getArguments().getChildren(), second.getArguments()
+          .getChildren());
+    }
+
+    private boolean isNotEqual(CssDeclarationNode first, CssDeclarationNode second) {
+      return !propertyNameEqual(first.getPropertyName(), second.getPropertyName()) ||
+          !propertyValuesEqual(first.getPropertyValue(), second.getPropertyValue());
+    }
+
+    private boolean numericNodeEqual(CssNumericNode first, CssNumericNode second) {
+      return Objects.equal(first.getNumericPart(), second.getNumericPart()) &&
+          Objects.equal(first.getUnit(), second.getUnit());
+    }
+
+    private boolean propertyNameEqual(CssPropertyNode first, CssPropertyNode second) {
+      return Objects.equal(first.getPropertyName(), second.getPropertyName());
+    }
+
+    private boolean propertyValuesEqual(CssPropertyValueNode first, CssPropertyValueNode second) {
+      return valueNodeListEqual(first.getChildren(), second.getChildren());
+    }
+
+    private boolean valueEqual(CssValueNode first, CssValueNode second) {
+      if (first.getClass() != second.getClass()) {
+        return false;
+      }
+
+      if (first instanceof CssCompositeValueNode) {
+        return compositeValueEqual((CssCompositeValueNode) first, (CssCompositeValueNode) second);
+      } else if (first instanceof CssFunctionNode) {
+        return functionNodeEqual((CssFunctionNode) first, (CssFunctionNode) second);
+      } else if (first instanceof CssNumericNode) {
+        return numericNodeEqual((CssNumericNode) first, (CssNumericNode) second);
+      } else {
+        return Objects.equal(first.getValue(), second.getValue());
+      }
+    }
+
+    private boolean valueNodeListEqual(List<CssValueNode> firstValues,
+        List<CssValueNode> secondValues) {
+      if (firstValues.size() != secondValues.size()) {
+        return false;
+      }
+
+      for (int i = 0; i < firstValues.size(); i++) {
+        CssValueNode firstNode = firstValues.get(i);
+        CssValueNode secondNode = secondValues.get(i);
+
+        if (!valueEqual(firstNode, secondNode)) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+  }
+
+  private BiDiFlipper delegate;
+  private RecordingMutatingVisitController mutatingVisitController;
+
+  public RecordingBidiFlipper(MutatingVisitController visitController, boolean swapLtrRtlInUrl,
+      boolean swapLeftRightInUrl, boolean shouldFlipConstantReferences) {
+
+    this.mutatingVisitController = new RecordingMutatingVisitController(visitController);
+    this.delegate = new BiDiFlipper(mutatingVisitController, swapLtrRtlInUrl, swapLeftRightInUrl,
+        shouldFlipConstantReferences);
+  }
+
+  @Override
+  public boolean enterDeclaration(CssDeclarationNode declaration) {
+    mutatingVisitController.setVisitingDeclarationNode(declaration);
+    return delegate.enterDeclaration(declaration);
+  }
+
+  /**
+   * return true if at least one node was flipped, false otherwise.
+   */
+  public boolean nodeFlipped() {
+    return mutatingVisitController.hasMutation;
+  }
+
+  @Override
+  public void runPass() {
+    mutatingVisitController.startVisit(this);
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/RenamingSubstitutionMap.java b/user/src/com/google/gwt/resources/gss/RenamingSubstitutionMap.java
new file mode 100644
index 0000000..c82be4d
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/RenamingSubstitutionMap.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.thirdparty.common.css.SubstitutionMap;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * This SubstitutionMap is used for renaming each style classes of the ast by its corresponding
+ * obfuscated name.
+ * <p/>
+ * It takes care of eventual prefix and does not rename external style classes.
+ * <p/>
+ * It lists the eventual external style class candidates (class without associated obfuscation
+ * name)
+ */
+public class RenamingSubstitutionMap implements SubstitutionMap {
+  private final Map<String, String> replacementMap;
+
+  private Set<String> classes;
+  private Set<String> externalClassCandidates;
+
+  public RenamingSubstitutionMap(Map<String, Map<String, String>> replacementsWithPrefix) {
+    this.replacementMap = computeReplacementMap(replacementsWithPrefix);
+
+    classes = new HashSet<String>();
+    externalClassCandidates = new HashSet<String>();
+  }
+
+  private Map<String, String> computeReplacementMap(
+      Map<String, Map<String, String>> replacementsWithPrefix) {
+
+    Map<String, String> result = new HashMap<String, String>();
+
+    for (Entry<String, Map<String, String>> entry : replacementsWithPrefix.entrySet()) {
+      final String prefix = entry.getKey();
+      Map<String, String> replacement = new HashMap<String, String>();
+
+      for (Entry<String, String> replacementEntry : entry.getValue().entrySet()) {
+        replacement.put(prefix + replacementEntry.getKey(), replacementEntry.getValue());
+      }
+
+      result.putAll(replacement);
+    }
+
+    return result;
+  }
+
+  @Override
+  public String get(String key) {
+    classes.add(key);
+
+    String replacement = replacementMap.get(key);
+
+    if (replacement == null) {
+      // could be an external style class
+      externalClassCandidates.add(key);
+      return key;
+    }
+
+    return replacement;
+  }
+
+  public Set<String> getStyleClasses() {
+    return classes;
+  }
+
+  public Set<String> getExternalClassCandidates() {
+    return externalClassCandidates;
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/ResourceUrlFunction.java b/user/src/com/google/gwt/resources/gss/ResourceUrlFunction.java
new file mode 100644
index 0000000..677ba79
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/ResourceUrlFunction.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.ext.ResourceContext;
+import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.resources.gss.ast.CssDotPathNode;
+import com.google.gwt.resources.gss.ast.CssJavaExpressionNode;
+import com.google.gwt.thirdparty.common.css.SourceCodeLocation;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssFunctionArgumentsNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssFunctionNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunction;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunctionException;
+import com.google.gwt.thirdparty.common.css.compiler.gssfunctions.GssFunctions;
+import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Gss function that create the needed nodes in order to correctly get the url of a resource.
+ */
+public class ResourceUrlFunction implements GssFunction {
+
+  @VisibleForTesting
+  interface MethodByPathHelper {
+    JType getReturnType(ResourceContext context, List<String> pathElements)
+        throws NotFoundException;
+  }
+
+  private static class MethodByPathHelperImpl implements MethodByPathHelper {
+    @Override
+    public JType getReturnType(ResourceContext context, List<String> pathElements)
+        throws NotFoundException {
+      return ResourceGeneratorUtil.getMethodByPath(context.getClientBundleType(),
+          pathElements, null).getReturnType();
+    }
+  }
+
+  private final ResourceContext context;
+  private final MethodByPathHelper methodByPathHelper;
+  private final JClassType dataResourceType;
+  private final JClassType imageResourceType;
+
+  public static String getName() {
+    return "resourceUrl";
+  }
+
+  public ResourceUrlFunction(ResourceContext context) {
+    this(context, new MethodByPathHelperImpl());
+  }
+
+  @VisibleForTesting
+  ResourceUrlFunction(ResourceContext context, MethodByPathHelper methodByPathHelper) {
+    this.context = context;
+    this.methodByPathHelper = methodByPathHelper;
+    this.dataResourceType = context.getGeneratorContext().getTypeOracle()
+        .findType(DataResource.class.getCanonicalName());
+    this.imageResourceType = context.getGeneratorContext().getTypeOracle()
+        .findType(ImageResource.class.getCanonicalName());
+  }
+
+  @Override
+  public Integer getNumExpectedArguments() {
+    return 1;
+  }
+
+  @Override
+  public List<CssValueNode> getCallResultNodes(List<CssValueNode> cssValueNodes,
+      ErrorManager errorManager) throws GssFunctionException {
+    CssValueNode functionToEval = cssValueNodes.get(0);
+    String value = functionToEval.getValue();
+    SourceCodeLocation location = functionToEval.getSourceCodeLocation();
+
+    String javaExpression = buildJavaExpression(value, location, errorManager);
+
+    CssFunctionNode urlNode = buildUrlNode(javaExpression, location);
+
+    return ImmutableList.<CssValueNode>of(urlNode);
+  }
+
+  @Override
+  public String getCallResultString(List<String> strings) throws GssFunctionException {
+    return strings.get(0);
+  }
+
+  private String buildJavaExpression(String value, SourceCodeLocation location,
+      ErrorManager errorManager) throws GssFunctionException {
+    CssDotPathNode dotPathValue = new CssDotPathNode(value, "", "", location);
+
+    assertMethodIsValidResource(location, dotPathValue.getPathElements(), errorManager);
+
+    return context.getImplementationSimpleSourceName() + ".this."
+        + dotPathValue.getValue() + ".getSafeUri().asString()";
+  }
+
+  private void assertMethodIsValidResource(SourceCodeLocation location, List<String> pathElements,
+      ErrorManager errorManager) throws GssFunctionException {
+    JType methodType;
+
+    try {
+      methodType = methodByPathHelper.getReturnType(context, pathElements);
+    } catch (NotFoundException e) {
+      String message = e.getMessage() != null ? e.getMessage() : "Invalid path";
+      errorManager.report(new GssError(message, location));
+      throw new GssFunctionException(message, e);
+    }
+
+    if (!dataResourceType.isAssignableFrom((JClassType) methodType) &&
+        !imageResourceType.isAssignableFrom((JClassType) methodType)) {
+      String message = "Invalid method type for url substitution: " + methodType + ". " +
+          "Only DataResource and ImageResource are supported.";
+      errorManager.report(new GssError(message, location));
+      throw new GssFunctionException(message);
+    }
+  }
+
+  private CssFunctionNode buildUrlNode(String javaExpression, SourceCodeLocation location) {
+    CssFunctionNode urlNode = GssFunctions.createUrlNode("", location);
+    CssJavaExpressionNode cssJavaExpressionNode = new CssJavaExpressionNode(javaExpression);
+    CssFunctionArgumentsNode arguments =
+        new CssFunctionArgumentsNode(ImmutableList.<CssValueNode>of(cssJavaExpressionNode));
+    urlNode.setArguments(arguments);
+
+    return urlNode;
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/RuntimeConditionalBlockCollector.java b/user/src/com/google/gwt/resources/gss/RuntimeConditionalBlockCollector.java
new file mode 100644
index 0000000..5d633ee
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/RuntimeConditionalBlockCollector.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.resources.gss.ast.CssRuntimeConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalBlockNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.DefaultTreeVisitor;
+import com.google.gwt.thirdparty.common.css.compiler.ast.VisitController;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Visitor that collects conditional blocks containing conditions that need to be evaluated at
+ * runtime.
+ */
+public class RuntimeConditionalBlockCollector extends DefaultTreeVisitor implements
+    CssCompilerPass {
+
+  private final VisitController visitController;
+
+  private Set<CssConditionalBlockNode> runtimeConditionalBlock;
+
+  public RuntimeConditionalBlockCollector(VisitController visitController) {
+    this.visitController = visitController;
+  }
+
+  @Override
+  public boolean enterConditionalBlock(CssConditionalBlockNode block) {
+    for (CssConditionalRuleNode currentConditional : block.childIterable()) {
+      if (currentConditional instanceof CssRuntimeConditionalRuleNode) {
+        runtimeConditionalBlock.add(block);
+        return true;
+      }
+    }
+
+    return true;
+  }
+
+  @Override
+  public void runPass() {
+    runtimeConditionalBlock = new HashSet<CssConditionalBlockNode>();
+
+    visitController.startVisit(this);
+  }
+
+  public Set<CssConditionalBlockNode> getRuntimeConditionalBlock() {
+    return runtimeConditionalBlock;
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/ValidateRuntimeConditionalNode.java b/user/src/com/google/gwt/resources/gss/ValidateRuntimeConditionalNode.java
new file mode 100644
index 0000000..c676114
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/ValidateRuntimeConditionalNode.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssDefinitionNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssUnknownAtRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.DefaultTreeVisitor;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+import com.google.gwt.thirdparty.common.css.compiler.ast.VisitController;
+
+/**
+ * Visitor that validates runtime conditional node.
+ * <p/>
+ * Runtime conditional node shouldn't contain any constant definitions nor external at-rule.
+ */
+public class ValidateRuntimeConditionalNode extends DefaultTreeVisitor implements
+    CssCompilerPass {
+
+  private final VisitController visitController;
+  private final ErrorManager errorManager;
+  private final boolean lenient;
+
+  private int cssConditionalRuleNodes;
+
+  public ValidateRuntimeConditionalNode(VisitController visitController,
+      ErrorManager errorManager, boolean lenient) {
+    this.visitController = visitController;
+    this.errorManager = errorManager;
+    this.lenient = lenient;
+  }
+
+  @Override
+  public boolean enterDefinition(CssDefinitionNode node) {
+    if (inConditionalRule()) {
+      if (lenient) {
+        errorManager.reportWarning(new GssError("You should not define a constant inside a " +
+            "ConditionalNode that will be evaluated at runtime. This will be disallowed in " +
+            "the next version of GWT.", node.getSourceCodeLocation()));
+      } else {
+        errorManager.report(new GssError("You cannot define a constant inside a ConditionalNode " +
+            "that will be evaluated at runtime.", node.getSourceCodeLocation()));
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public boolean enterUnknownAtRule(CssUnknownAtRuleNode node) {
+    if (inConditionalRule() && "external".equals(node.getName().getValue())) {
+      if (lenient) {
+        errorManager.reportWarning(new GssError("You should not define a external at-rule inside" +
+            " a  ConditionalNode that will be evaluated at runtime. This will be disallowed in " +
+            "the next version of GWT.", node.getSourceCodeLocation()));
+      } else {
+        errorManager.report(new GssError("You cannot define a external at-rule inside a " +
+            "ConditionalNode that will be evaluated at runtime.", node.getSourceCodeLocation()));
+      }
+    }
+    return super.enterUnknownAtRule(node);
+  }
+
+  @Override
+  public boolean enterConditionalRule(CssConditionalRuleNode node) {
+    cssConditionalRuleNodes++;
+    return true;
+  }
+
+  @Override
+  public void leaveConditionalRule(CssConditionalRuleNode node) {
+    cssConditionalRuleNodes--;
+  }
+
+  @Override
+  public void runPass() {
+    cssConditionalRuleNodes = 0;
+
+    visitController.startVisit(this);
+  }
+
+  private boolean inConditionalRule() {
+    return cssConditionalRuleNodes > 0;
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/ValueFunction.java b/user/src/com/google/gwt/resources/gss/ValueFunction.java
new file mode 100644
index 0000000..4c1e08b
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/ValueFunction.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.resources.gss.ast.CssDotPathNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunction;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunctionException;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Handles the value() function. This function takes a sequence of dot-separated identifiers
+ * interpreted as zero-arg method invocations. It can takes a optional prefix and suffix.
+ */
+public class ValueFunction implements GssFunction {
+  public static String getName() {
+    return "value";
+  }
+
+  @Override
+  public List<CssValueNode> getCallResultNodes(List<CssValueNode> args, ErrorManager errorManager)
+      throws GssFunctionException {
+    if (args.size() == 0 || args.size() > 3) {
+      throw new GssFunctionException(getName() + " function takes one, two or three arguments");
+    }
+
+    String functionPath = args.get(0).getValue();
+    String prefix = null;
+    String suffix = null;
+
+    if (args.size() > 1) {
+      suffix = args.get(1).getValue();
+    }
+
+    if (args.size() > 2) {
+      prefix = args.get(2).getValue();
+    }
+
+    CssDotPathNode cssDotPathNode = new CssDotPathNode(functionPath, prefix, suffix,
+        args.get(0).getSourceCodeLocation());
+
+    return ImmutableList.of((CssValueNode) cssDotPathNode);
+  }
+
+  @Override
+  public String getCallResultString(List<String> args) throws GssFunctionException {
+
+    String functionPath = args.get(0);
+    String prefix = null;
+    String suffix = null;
+
+    if (args.size() > 1) {
+      suffix = args.get(1);
+    }
+
+    if (args.size() > 2) {
+      prefix = args.get(2);
+    }
+
+    return CssDotPathNode.resolveExpression(null, functionPath, prefix, suffix);
+  }
+
+  @Override
+  public Integer getNumExpectedArguments() {
+    // number of arguments is variable
+    return null;
+  }
+}
+
diff --git a/user/src/com/google/gwt/resources/gss/ast/CssDotPathNode.java b/user/src/com/google/gwt/resources/gss/ast/CssDotPathNode.java
new file mode 100644
index 0000000..ed40dd4
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/ast/CssDotPathNode.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss.ast;
+
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.thirdparty.common.css.SourceCodeLocation;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.guava.common.base.Strings;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a sequence of no-arg method invocations.
+ */
+public class CssDotPathNode extends CssValueNode {
+
+  public static String resolveExpression(String instance, String path, String prefix, String suffix) {
+    String expression = path.replace(".", "().") + "()";
+
+    if (!Strings.isNullOrEmpty(instance)) {
+      expression = instance + "." + expression;
+    }
+
+    if (!Strings.isNullOrEmpty(prefix)) {
+      expression = "\"" + Generator.escape(prefix) + "\" + " + expression;
+    }
+
+    if (!Strings.isNullOrEmpty(suffix)) {
+      expression += " + \"" + Generator.escape(suffix) + "\"";
+    }
+
+    return expression;
+  }
+
+  private String suffix;
+  private String prefix;
+  private String path;
+  private String instance;
+
+  public CssDotPathNode(String dotPath, String prefix, String suffix, SourceCodeLocation sourceCodeLocation) {
+    this(null, dotPath, prefix, suffix, sourceCodeLocation);
+  }
+
+  public CssDotPathNode(String instance, String dotPath, String prefix, String suffix,
+      SourceCodeLocation sourceCodeLocation) {
+    super(resolveExpression(instance, dotPath, prefix, suffix), sourceCodeLocation);
+
+    this.prefix = prefix;
+    this.suffix = suffix;
+    this.path = dotPath;
+    this.instance = instance;
+  }
+
+  @Override
+  public CssValueNode deepCopy() {
+    return new CssDotPathNode(instance, path, prefix, suffix, getSourceCodeLocation());
+  }
+
+  public String getPath() {
+    return path;
+  }
+
+  public String getSuffix() {
+    return suffix;
+  }
+
+  public String getPrefix() {
+    return prefix;
+  }
+
+  public String getInstance() {
+    return instance;
+  }
+
+  public List<String> getPathElements() {
+    return Arrays.asList(path.split("\\."));
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/ast/CssJavaExpressionNode.java b/user/src/com/google/gwt/resources/gss/ast/CssJavaExpressionNode.java
new file mode 100644
index 0000000..3f664c7
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/ast/CssJavaExpressionNode.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss.ast;
+
+import com.google.gwt.thirdparty.common.css.SourceCodeLocation;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+
+/**
+ * Represents a Java expression.
+ */
+public class CssJavaExpressionNode extends CssValueNode {
+
+  public CssJavaExpressionNode(String expression) {
+    this(expression, null);
+  }
+
+  public CssJavaExpressionNode(String expression, SourceCodeLocation sourceCodeLocation) {
+    super(expression, sourceCodeLocation);
+  }
+
+  @Override
+  public CssValueNode deepCopy() {
+    return new CssJavaExpressionNode(getValue());
+  }
+
+  @Override
+  public String toString() {
+    return "Java expression : " + getValue();
+  }
+}
diff --git a/user/src/com/google/gwt/resources/gss/ast/CssRuntimeConditionalRuleNode.java b/user/src/com/google/gwt/resources/gss/ast/CssRuntimeConditionalRuleNode.java
new file mode 100644
index 0000000..04c914a
--- /dev/null
+++ b/user/src/com/google/gwt/resources/gss/ast/CssRuntimeConditionalRuleNode.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss.ast;
+
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.guava.common.base.Preconditions;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+
+/**
+ * Represents a conditional rule that needs to be evaluated at runtime.
+ */
+public class CssRuntimeConditionalRuleNode extends CssConditionalRuleNode {
+
+  public CssRuntimeConditionalRuleNode(CssConditionalRuleNode node,
+      CssJavaExpressionNode condition) {
+    super(node.getType(), node.getName().deepCopy(), null,
+        node.getBlock() != null ? node.getBlock().deepCopy() : null);
+    setSourceCodeLocation(node.getSourceCodeLocation());
+
+    setRuntimeCondition(condition);
+  }
+
+  /**
+   * Copy constructor.
+   *
+   * @param node
+   */
+  public CssRuntimeConditionalRuleNode(CssRuntimeConditionalRuleNode node) {
+    this(node, node.getRuntimeCondition());
+  }
+
+  public CssJavaExpressionNode getRuntimeCondition() {
+    if (getType() == Type.ELSE) {
+      Preconditions.checkState(getParametersCount() == 0);
+      return null;
+    }
+
+    Preconditions.checkState(getParametersCount() == 1);
+    return (CssJavaExpressionNode) this.getParameters().get(0);
+  }
+
+  private void setRuntimeCondition(CssJavaExpressionNode condition) {
+    Preconditions.checkState(getType() != Type.ELSE);
+    Preconditions.checkState(getParametersCount() <= 1);
+    this.setParameters(ImmutableList.<CssValueNode>of(condition));
+  }
+
+  @Override
+  public CssConditionalRuleNode deepCopy() {
+    return new CssRuntimeConditionalRuleNode(this);
+  }
+}
diff --git a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
index f24f117..8206f98 100644
--- a/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java
@@ -20,6 +20,7 @@
 import com.google.gwt.core.ext.Generator;
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.EmittedArtifact.Visibility;
 import com.google.gwt.core.ext.typeinfo.JClassType;
@@ -141,6 +142,7 @@
   private static final String KEY_RESERVED_PREFIXES = "CssResource.reservedClassPrefixes";
   private static final String KEY_SHARED_METHODS = "sharedMethods";
   private static final String KEY_STYLE = "CssResource.style";
+  private static final String KEY_ENABLE_GSS = "CssResource.enableGss";
 
   /**
    * This character must not appear in {@link #BASE32_CHARS}.
@@ -207,12 +209,6 @@
     return false;
   }
 
-  public static void main(String[] args) {
-    for (int i = 0; i < 1000; i++) {
-      System.out.println(makeIdent(i));
-    }
-  }
-
   /**
    * Compute an obfuscated CSS class name that is guaranteed not to conflict
    * with a set of reserved prefixes. Visible for testing.
@@ -432,6 +428,8 @@
   protected CssObfuscationStyle obfuscationStyle;
   private Counter classCounter;
   private boolean enableMerge;
+  private boolean gssEnabled;
+  private GssResourceGenerator gssResourceGenerator;
   private List<String> ignoredMethods = new ArrayList<String>();
   private Map<JClassType, Map<JMethod, String>> replacementsByClassAndMethod;
   private Map<JMethod, String> replacementsForSharedMethods;
@@ -440,6 +438,12 @@
   @Override
   public String createAssignment(TreeLogger logger, ResourceContext context,
       JMethod method) throws UnableToCompleteException {
+
+    // if Gss is enabled, defer the call to the Gss generator.
+    if (gssEnabled) {
+      return gssResourceGenerator.createAssignment(logger, context, method);
+    }
+
     JClassType cssResourceSubtype = method.getReturnType().isInterface();
     assert cssResourceSubtype != null;
     CssStylesheet stylesheet = stylesheetMap.get(method);
@@ -459,6 +463,17 @@
   @Override
   public void init(TreeLogger logger, ResourceContext context)
       throws UnableToCompleteException {
+
+    // if Gss is enabled, defer the call to the Gss generator.
+    if (checkIfGssEnabled(context, logger)) {
+      gssEnabled = true;
+      gssResourceGenerator = new GssResourceGenerator();
+      gssResourceGenerator.init(logger, context);
+      return;
+    }
+
+    gssEnabled = false;
+
     String classPrefix;
     try {
       PropertyOracle propertyOracle =
@@ -507,6 +522,12 @@
       ClientBundleRequirements requirements, JMethod method)
       throws UnableToCompleteException {
 
+    // if Gss is enabled, defer the call to the Gss generator.
+    if (gssEnabled) {
+      gssResourceGenerator.prepare(logger, context, requirements, method);
+      return;
+    }
+
     if (method.getReturnType().isInterface() == null) {
       logger.log(TreeLogger.ERROR, "Return type must be an interface");
       throw new UnableToCompleteException();
@@ -1187,4 +1208,22 @@
 
     writeSimpleGetter(toImplement, returnExpr, sw);
   }
+
+  private boolean checkIfGssEnabled(ResourceContext context, TreeLogger logger)
+      throws UnableToCompleteException {
+    try {
+      PropertyOracle propertyOracle =
+          context.getGeneratorContext().getPropertyOracle();
+
+      ConfigurationProperty enableGssProp =
+          propertyOracle.getConfigurationProperty(KEY_ENABLE_GSS);
+      String enableGss = enableGssProp.getValues().get(0);
+
+      return "true".equals(enableGss);
+
+    } catch (BadPropertyValueException ex) {
+      logger.log(Type.ERROR, "Unable to determine if GSS need to be used");
+      throw new UnableToCompleteException();
+    }
+  }
 }
diff --git a/user/src/com/google/gwt/resources/rg/GssResourceGenerator.java b/user/src/com/google/gwt/resources/rg/GssResourceGenerator.java
new file mode 100644
index 0000000..8093068
--- /dev/null
+++ b/user/src/com/google/gwt/resources/rg/GssResourceGenerator.java
@@ -0,0 +1,1155 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.rg;
+
+import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.ConfigurationProperty;
+import com.google.gwt.core.ext.Generator;
+import com.google.gwt.core.ext.PropertyOracle;
+import com.google.gwt.core.ext.SelectionProperty;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
+import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.util.Util;
+import com.google.gwt.i18n.client.LocaleInfo;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.ClassName;
+import com.google.gwt.resources.client.CssResource.Import;
+import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
+import com.google.gwt.resources.client.CssResource.NotStrict;
+import com.google.gwt.resources.client.CssResource.Shared;
+import com.google.gwt.resources.client.ResourcePrototype;
+import com.google.gwt.resources.converter.Css2Gss;
+import com.google.gwt.resources.converter.Css2GssConversionException;
+import com.google.gwt.resources.ext.ClientBundleRequirements;
+import com.google.gwt.resources.ext.ResourceContext;
+import com.google.gwt.resources.ext.ResourceGeneratorUtil;
+import com.google.gwt.resources.ext.SupportsGeneratorResultCaching;
+import com.google.gwt.resources.gss.CreateRuntimeConditionalNodes;
+import com.google.gwt.resources.gss.CssPrinter;
+import com.google.gwt.resources.gss.ExtendedEliminateConditionalNodes;
+import com.google.gwt.resources.gss.ExternalClassesCollector;
+import com.google.gwt.resources.gss.GwtGssFunctionMapProvider;
+import com.google.gwt.resources.gss.ImageSpriteCreator;
+import com.google.gwt.resources.gss.PermutationsCollector;
+import com.google.gwt.resources.gss.RecordingBidiFlipper;
+import com.google.gwt.resources.gss.RenamingSubstitutionMap;
+import com.google.gwt.resources.gss.RuntimeConditionalBlockCollector;
+import com.google.gwt.resources.gss.ValidateRuntimeConditionalNode;
+import com.google.gwt.resources.rg.CssResourceGenerator.JClassOrderComparator;
+import com.google.gwt.thirdparty.common.css.MinimalSubstitutionMap;
+import com.google.gwt.thirdparty.common.css.PrefixingSubstitutionMap;
+import com.google.gwt.thirdparty.common.css.SourceCode;
+import com.google.gwt.thirdparty.common.css.SourceCodeLocation;
+import com.google.gwt.thirdparty.common.css.SubstitutionMap;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssDefinitionNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssNumericNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunction;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssParser;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssParserException;
+import com.google.gwt.thirdparty.common.css.compiler.passes.AbbreviatePositionalValues;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CheckDependencyNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CollectConstantDefinitions;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CollectMixinDefinitions;
+import com.google.gwt.thirdparty.common.css.compiler.passes.ColorValueOptimizer;
+import com.google.gwt.thirdparty.common.css.compiler.passes.ConstantDefinitions;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateComponentNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateConditionalNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateConstantReferences;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateDefinitionNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateMixins;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateStandardAtRuleNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CssClassRenaming;
+import com.google.gwt.thirdparty.common.css.compiler.passes.DisallowDuplicateDeclarations;
+import com.google.gwt.thirdparty.common.css.compiler.passes.EliminateEmptyRulesetNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.EliminateUnitsFromZeroNumericValues;
+import com.google.gwt.thirdparty.common.css.compiler.passes.EliminateUselessRulesetNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.HandleUnknownAtRuleNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.MarkRemovableRulesetNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.MergeAdjacentRulesetNodesWithSameDeclarations;
+import com.google.gwt.thirdparty.common.css.compiler.passes.MergeAdjacentRulesetNodesWithSameSelector;
+import com.google.gwt.thirdparty.common.css.compiler.passes.ProcessComponents;
+import com.google.gwt.thirdparty.common.css.compiler.passes.ProcessKeyframes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.ProcessRefiners;
+import com.google.gwt.thirdparty.common.css.compiler.passes.ReplaceConstantReferences;
+import com.google.gwt.thirdparty.common.css.compiler.passes.ReplaceMixins;
+import com.google.gwt.thirdparty.common.css.compiler.passes.ResolveCustomFunctionNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.SplitRulesetNodes;
+import com.google.gwt.thirdparty.guava.common.base.CaseFormat;
+import com.google.gwt.thirdparty.guava.common.base.Charsets;
+import com.google.gwt.thirdparty.guava.common.base.Joiner;
+import com.google.gwt.thirdparty.guava.common.base.Predicates;
+import com.google.gwt.thirdparty.guava.common.cache.Cache;
+import com.google.gwt.thirdparty.guava.common.cache.CacheBuilder;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet.Builder;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Maps;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+import com.google.gwt.thirdparty.guava.common.io.Resources;
+import com.google.gwt.user.rebind.SourceWriter;
+import com.google.gwt.user.rebind.StringSourceWriter;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.zip.Adler32;
+
+/**
+ * This generator parses and compiles a GSS file to a css string and generates the implementation
+ * of the corresponding CssResource interface.
+ */
+public class GssResourceGenerator extends AbstractCssResourceGenerator implements
+    SupportsGeneratorResultCaching {
+  /**
+   * {@link ErrorManager} used to log the errors and warning messages produced by the different
+   * {@link com.google.gwt.thirdparty.common.css.compiler.ast.CssCompilerPass}.
+   */
+  private static class LoggerErrorManager implements ErrorManager {
+    private final TreeLogger logger;
+    private boolean hasErrors;
+
+    private LoggerErrorManager(TreeLogger logger) {
+      this.logger = logger;
+    }
+
+    @Override
+    public void generateReport() {
+      // do nothing
+    }
+
+    @Override
+    public boolean hasErrors() {
+      return hasErrors;
+    }
+
+    @Override
+    public void report(GssError error) {
+      String fileName = "";
+      String location = "";
+      SourceCodeLocation codeLocation = error.getLocation();
+
+      if (codeLocation != null) {
+        fileName = codeLocation.getSourceCode().getFileName();
+        location = "[line: " + codeLocation.getBeginLineNumber() + " column: " + codeLocation
+            .getBeginIndexInLine() + "]";
+      }
+
+      logger.log(Type.ERROR, "Error in " + fileName + location + ": " + error.getMessage());
+      hasErrors = true;
+    }
+
+    @Override
+    public void reportWarning(GssError warning) {
+      logger.log(Type.WARN, warning.getMessage());
+    }
+  }
+
+  private static class ConversionResult {
+    final String gss;
+    final Map<String, String> defNameMapping;
+
+    private ConversionResult(String gss, Map<String, String> defNameMapping) {
+      this.gss = gss;
+      this.defNameMapping = defNameMapping;
+    }
+  }
+
+  private static class RenamingResult {
+    final Map<String, String> mapping;
+    final Set<String> externalClassCandidate;
+
+    private RenamingResult(Map<String, String> mapping, Set<String> externalClassCandidate) {
+      this.mapping = mapping;
+      this.externalClassCandidate = externalClassCandidate;
+    }
+  }
+
+  private static class ExtendedCssTree {
+    final CssTree tree;
+    final List<String> permutationAxes;
+    final Map<String, String> originalConstantNameMapping;
+
+    private ExtendedCssTree(CssTree tree, List<String> permutationAxis,
+        Map<String, String> originalConstantNameMapping) {
+      this.tree = tree;
+      this.permutationAxes = permutationAxis;
+      this.originalConstantNameMapping = originalConstantNameMapping;
+    }
+  }
+
+  private static final Cache<List<URL>, ExtendedCssTree> TREE_CACHE = CacheBuilder.newBuilder()
+      .softValues().build();
+  private static final Cache<List<URL>, Long> LAST_MODIFIED_CACHE = CacheBuilder.newBuilder()
+      .build();
+
+  // To be sure to avoid conflict during the style classes renaming between different GssResource,
+  // we will create a different prefix for each GssResource. We use a MinimalSubstitutionMap
+  // that will create a String with 1-6 characters in length but keeping the length of the prefix
+  // as short as possible. For instance if we have two GssResources to compile, the  prefix
+  // for the first resource will be 'a' and the prefix for the second resource will be 'b' and so on
+  private static final SubstitutionMap resourcePrefixBuilder = new MinimalSubstitutionMap();
+  private static final String KEY_LEGACY = "CssResource.legacy";
+  private static final String KEY_CONVERSION_MODE = "CssResource.conversionMode";
+  private static final String KEY_STYLE = "CssResource.style";
+  private static final String ALLOWED_AT_RULE = "CssResource.allowedAtRules";
+  private static final String ALLOWED_FUNCTIONS = "CssResource.allowedFunctions";
+  private static final String KEY_OBFUSCATION_PREFIX = "CssResource.obfuscationPrefix";
+  private static final String KEY_CLASS_PREFIX = "cssResourcePrefix";
+  private static final String KEY_BY_CLASS_AND_METHOD = "cssResourceClassAndMethod";
+  private static final String KEY_HAS_CACHED_DATA = "hasCachedData";
+  private static final String KEY_SHARED_METHODS = "sharedMethods";
+  private static final char[] BASE32_CHARS = new char[]{
+      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+      'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', '0', '1',
+      '2', '3', '4', '5', '6'};
+
+  private static String encode(long id) {
+    assert id >= 0;
+
+    StringBuilder b = new StringBuilder();
+
+    // Use only guaranteed-alpha characters for the first character
+    b.append(BASE32_CHARS[(int) (id & 0xf)]);
+    id >>= 4;
+
+    while (id != 0) {
+      b.append(BASE32_CHARS[(int) (id & 0x1f)]);
+      id >>= 5;
+    }
+
+    return b.toString();
+  }
+
+  private Map<JMethod, ExtendedCssTree> cssTreeMap;
+  private Set<String> allowedNonStandardFunctions;
+  private LoggerErrorManager errorManager;
+  private JMethod getTextMethod;
+  private JMethod ensuredInjectedMethod;
+  private JMethod getNameMethod;
+  private String obfuscationPrefix;
+  private CssObfuscationStyle obfuscationStyle;
+  private Set<String> allowedAtRules;
+  private Map<JClassType, Map<String, String>> replacementsByClassAndMethod;
+  private Map<JMethod, String> replacementsForSharedMethods;
+  private boolean allowLegacy;
+  private boolean lenientConversion;
+
+  @Override
+  public String createAssignment(TreeLogger logger, ResourceContext context, JMethod method)
+      throws UnableToCompleteException {
+    ExtendedCssTree extendedCssTree = cssTreeMap.get(method);
+
+    RenamingResult renamingResult = doClassRenaming(extendedCssTree.tree,
+        method, logger, context);
+
+    // TODO : Should we foresee configuration properties for simplifyCss and eliminateDeadCode
+    // booleans ?
+    ConstantDefinitions constantDefinitions = optimize(extendedCssTree, context, true, true,
+        logger);
+
+    checkErrors();
+
+    Set<String> externalClasses = revertRenamingOfExternalClasses(extendedCssTree.tree,
+        renamingResult.mapping);
+
+    checkErrors();
+
+    // Validate that classes not assigned to one of the interface methods are external
+    validateExternalClasses(externalClasses, renamingResult.externalClassCandidate, method, logger);
+
+    SourceWriter sw = new StringSourceWriter();
+    sw.println("new " + method.getReturnType().getQualifiedSourceName() + "() {");
+    sw.indent();
+
+    writeMethods(logger, context, method, sw, constantDefinitions,
+        extendedCssTree.originalConstantNameMapping, renamingResult.mapping);
+
+    sw.outdent();
+    sw.println("}");
+
+    return sw.toString();
+  }
+
+  private void validateExternalClasses(Set<String> externalClasses,
+      Set<String> externalClassCandidates, JMethod method,
+      TreeLogger logger) throws UnableToCompleteException {
+    if (!isStrictResource(method)) {
+      return;
+    }
+
+    boolean hasError = false;
+
+    for (String candidate : externalClassCandidates) {
+      if (!externalClasses.contains(candidate)) {
+        logger.log(Type.ERROR, "The following non-obfuscated class is present in a strict " +
+            "CssResource: " + candidate);
+        hasError = true;
+      }
+    }
+
+    if (hasError) {
+      throw new UnableToCompleteException();
+    }
+  }
+
+  @Override
+  public void init(TreeLogger logger, ResourceContext context) throws UnableToCompleteException {
+    cssTreeMap = new IdentityHashMap<JMethod, ExtendedCssTree>();
+    errorManager = new LoggerErrorManager(logger);
+
+    allowedNonStandardFunctions = new HashSet<String>();
+    allowedAtRules = Sets.newHashSet(ExternalClassesCollector.EXTERNAL_AT_RULE);
+
+    try {
+      PropertyOracle propertyOracle = context.getGeneratorContext().getPropertyOracle();
+
+      ConfigurationProperty styleProp = propertyOracle.getConfigurationProperty(KEY_STYLE);
+      obfuscationStyle = CssObfuscationStyle.getObfuscationStyle(styleProp.getValues().get(0));
+      obfuscationPrefix = getObfuscationPrefix(propertyOracle, context);
+
+      ConfigurationProperty allowedAtRuleProperty = propertyOracle
+          .getConfigurationProperty(ALLOWED_AT_RULE);
+      allowedAtRules.addAll(allowedAtRuleProperty.getValues());
+
+      ConfigurationProperty allowedFunctionsProperty = propertyOracle
+          .getConfigurationProperty(ALLOWED_FUNCTIONS);
+      allowedNonStandardFunctions.addAll(allowedFunctionsProperty.getValues());
+
+      allowLegacy = "true".equals(propertyOracle.getConfigurationProperty(KEY_LEGACY).getValues()
+          .get(0));
+
+      // enable lenient conversion when legacy mode is enabled
+      lenientConversion = allowLegacy && "lenient".equals(propertyOracle
+          .getConfigurationProperty(KEY_CONVERSION_MODE).getValues().get(0));
+
+      ClientBundleRequirements requirements = context.getRequirements();
+      requirements.addConfigurationProperty(KEY_STYLE);
+      requirements.addConfigurationProperty(KEY_OBFUSCATION_PREFIX);
+      requirements.addConfigurationProperty(ALLOWED_AT_RULE);
+      requirements.addConfigurationProperty(ALLOWED_FUNCTIONS);
+      requirements.addConfigurationProperty(KEY_LEGACY);
+      requirements.addConfigurationProperty(KEY_CONVERSION_MODE);
+    } catch (BadPropertyValueException e) {
+      logger.log(TreeLogger.ERROR, "Unable to query module property", e);
+      throw new UnableToCompleteException();
+    }
+
+    TypeOracle typeOracle = context.getGeneratorContext().getTypeOracle();
+    JClassType cssResourceInterface = typeOracle.findType(CssResource.class.getCanonicalName());
+    JClassType resourcePrototypeInterface = typeOracle.findType(ResourcePrototype.class
+        .getCanonicalName());
+
+    try {
+      getTextMethod = cssResourceInterface.getMethod("getText", new JType[0]);
+      ensuredInjectedMethod = cssResourceInterface.getMethod("ensureInjected", new JType[0]);
+      getNameMethod = resourcePrototypeInterface.getMethod("getName", new JType[0]);
+    } catch (NotFoundException e) {
+      logger.log(TreeLogger.ERROR, "Unable to lookup methods from CssResource and " +
+          "ResourcePrototype interface", e);
+      throw new UnableToCompleteException();
+    }
+
+    initReplacement(context);
+  }
+
+  private void initReplacement(ResourceContext context) {
+    if (context.getCachedData(KEY_HAS_CACHED_DATA, Boolean.class) != Boolean.TRUE) {
+
+      context.putCachedData(KEY_SHARED_METHODS, new IdentityHashMap<JMethod, String>());
+      context.putCachedData(KEY_BY_CLASS_AND_METHOD, new IdentityHashMap<JClassType, Map<String,
+          String>>());
+      context.putCachedData(KEY_HAS_CACHED_DATA, Boolean.TRUE);
+    }
+
+    replacementsByClassAndMethod = context.getCachedData(KEY_BY_CLASS_AND_METHOD, Map.class);
+    replacementsForSharedMethods = context.getCachedData(KEY_SHARED_METHODS,
+        Map.class);
+  }
+
+  private String getObfuscationPrefix(PropertyOracle propertyOracle, ResourceContext context)
+      throws BadPropertyValueException {
+    String prefix = propertyOracle.getConfigurationProperty(KEY_OBFUSCATION_PREFIX)
+        .getValues().get(0);
+    if ("empty".equalsIgnoreCase(prefix)) {
+      return "";
+    } else if ("default".equalsIgnoreCase(prefix)) {
+      return getDefaultObfuscationPrefix(context);
+    }
+
+    return prefix;
+  }
+
+  private String getDefaultObfuscationPrefix(ResourceContext context) {
+    String prefix = context.getCachedData(KEY_CLASS_PREFIX, String.class);
+    if (prefix == null) {
+      prefix = computeDefaultPrefix(context);
+      context.putCachedData(KEY_CLASS_PREFIX, prefix);
+    }
+
+    return prefix;
+  }
+
+  private String computeDefaultPrefix(ResourceContext context) {
+    SortedSet<JClassType> gssResources = computeOperableTypes(context);
+
+    Adler32 checksum = new Adler32();
+
+    for (JClassType type : gssResources) {
+      checksum.update(Util.getBytes(type.getQualifiedSourceName()));
+    }
+
+    int seed = Math.abs((int) checksum.getValue());
+
+    return encode(seed) + "-";
+  }
+
+  private SortedSet<JClassType> computeOperableTypes(ResourceContext context) {
+    TypeOracle typeOracle = context.getGeneratorContext().getTypeOracle();
+    JClassType baseInterface = typeOracle.findType(CssResource.class.getCanonicalName());
+
+    SortedSet<JClassType> toReturn = new TreeSet<JClassType>(new JClassOrderComparator());
+
+    JClassType[] cssResourceSubtypes = baseInterface.getSubtypes();
+    for (JClassType type : cssResourceSubtypes) {
+      if (type.isInterface() != null) {
+        toReturn.add(type);
+      }
+    }
+
+    return toReturn;
+  }
+
+  @Override
+  public void prepare(final TreeLogger logger, final ResourceContext context,
+      ClientBundleRequirements requirements, JMethod method) throws UnableToCompleteException {
+
+    if (method.getReturnType().isInterface() == null) {
+      logger.log(TreeLogger.ERROR, "Return type must be an interface");
+      throw new UnableToCompleteException();
+    }
+
+    URL[] resourceUrls = ResourceGeneratorUtil.findResources(logger, context, method);
+    if (resourceUrls.length == 0) {
+      logger.log(TreeLogger.ERROR, "At least one source must be specified");
+      throw new UnableToCompleteException();
+    }
+
+    final long lastModified = ResourceGeneratorUtil.getLastModified(resourceUrls, logger);
+    final List<URL> resources = Lists.newArrayList(resourceUrls);
+
+    maybeInvalidateCacheFor(resources, lastModified, logger);
+
+    ExtendedCssTree extTree;
+
+    try {
+      extTree = TREE_CACHE.get(resources, new Callable<ExtendedCssTree>() {
+        @Override
+        public ExtendedCssTree call() throws Exception {
+          ExtendedCssTree tree = parseResources(resources, logger);
+          // add last modified time in cache
+          LAST_MODIFIED_CACHE.put(resources, lastModified);
+          return tree;
+        }
+      });
+    } catch (ExecutionException e) {
+      if (e.getCause() instanceof UnableToCompleteException) {
+        throw (UnableToCompleteException) e.getCause();
+      } else {
+        logger.log(Type.ERROR, "Unexpected error occurred", e.getCause());
+        throw new UnableToCompleteException();
+      }
+    }
+
+    ExtendedCssTree finalTree = new ExtendedCssTree(deepCopy(extTree.tree),
+        extTree.permutationAxes, extTree.originalConstantNameMapping);
+    cssTreeMap.put(method, finalTree);
+
+    for (String permutationAxis : extTree.permutationAxes) {
+      try {
+        context.getRequirements().addPermutationAxis(permutationAxis);
+      } catch (BadPropertyValueException e) {
+        logger.log(TreeLogger.ERROR, "Unknown deferred-binding property " + permutationAxis, e);
+        throw new UnableToCompleteException();
+      }
+    }
+  }
+
+  @Override
+  protected String getCssExpression(TreeLogger logger, ResourceContext context,
+      JMethod method) throws UnableToCompleteException {
+    CssTree cssTree = cssTreeMap.get(method).tree;
+
+    String standard = printCssTree(cssTree);
+
+    // TODO add configuration properties for swapLtrRtlInUrl, swapLeftRightInUrl and
+    // shouldFlipConstantReferences booleans
+    RecordingBidiFlipper recordingBidiFlipper =
+        new RecordingBidiFlipper(cssTree.getMutatingVisitController(), false, false, true);
+    recordingBidiFlipper.runPass();
+
+    if (recordingBidiFlipper.nodeFlipped()) {
+      String reversed = printCssTree(cssTree);
+      return LocaleInfo.class.getName() + ".getCurrentLocale().isRTL() ? "
+          + reversed + " : " + standard;
+    } else {
+      return standard;
+    }
+  }
+
+  private void checkErrors() throws UnableToCompleteException {
+    if (errorManager.hasErrors()) {
+      throw new UnableToCompleteException();
+    }
+  }
+
+  private CssTree deepCopy(CssTree cssTree) {
+    return new CssTree(cssTree.getSourceCode(), cssTree.getRoot().deepCopy());
+  }
+
+  private RenamingResult doClassRenaming(CssTree cssTree, JMethod method, TreeLogger logger,
+      ResourceContext context) throws UnableToCompleteException {
+    Map<String, Map<String, String>> replacementsWithPrefix = computeReplacements(method, logger,
+        context);
+
+    RenamingSubstitutionMap substitutionMap = new RenamingSubstitutionMap(replacementsWithPrefix);
+
+    new CssClassRenaming(cssTree.getMutatingVisitController(), substitutionMap, null).runPass();
+
+    Map<String, String> mapping = replacementsWithPrefix.get("");
+
+    mapping = Maps.newHashMap(Maps.filterKeys(mapping, Predicates.in(substitutionMap
+        .getStyleClasses())));
+
+    return new RenamingResult(mapping, substitutionMap.getExternalClassCandidates());
+  }
+
+  /**
+   * When the tree is fully processed, we can now collect the external classes and revert the
+   * renaming for these classes. We cannot collect the external classes during the original renaming
+   * because some external at-rule could be located inside a conditional block and could be
+   * removed when these blocks are evaluated.
+   */
+  private Set<String> revertRenamingOfExternalClasses(CssTree cssTree, Map<String,
+      String> styleClassesMapping) {
+    ExternalClassesCollector externalClassesCollector = new ExternalClassesCollector(cssTree
+        .getMutatingVisitController(), errorManager);
+
+    externalClassesCollector.runPass();
+
+    Set<String> styleClassSet = styleClassesMapping.keySet();
+    Set<String> externalClasses = externalClassesCollector.getExternalClassNames(styleClassSet);
+
+    final Map<String, String> revertMap = new HashMap<String, String>(externalClasses.size());
+
+    for (String external : externalClasses) {
+      revertMap.put(styleClassesMapping.get(external), external);
+      // override the mapping
+      styleClassesMapping.put(external, external);
+    }
+
+    SubstitutionMap revertExternalClasses = new SubstitutionMap() {
+      @Override
+      public String get(String key) {
+        return revertMap.get(key);
+      }
+    };
+
+    new CssClassRenaming(cssTree.getMutatingVisitController(), revertExternalClasses, null)
+        .runPass();
+
+    return externalClasses;
+  }
+
+  private boolean isStrictResource(JMethod method) {
+    NotStrict notStrict = method.getAnnotation(NotStrict.class);
+    return notStrict == null;
+  }
+
+  private List<String> finalizeTree(CssTree cssTree) throws UnableToCompleteException {
+    new CheckDependencyNodes(cssTree.getMutatingVisitController(), errorManager, false).runPass();
+
+    // Don't continue if errors exist
+    checkErrors();
+
+    new CreateStandardAtRuleNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
+    new CreateMixins(cssTree.getMutatingVisitController(), errorManager).runPass();
+    new CreateDefinitionNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
+    new CreateConstantReferences(cssTree.getMutatingVisitController()).runPass();
+    new CreateConditionalNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
+    new CreateRuntimeConditionalNodes(cssTree.getMutatingVisitController()).runPass();
+    new CreateComponentNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
+
+    new HandleUnknownAtRuleNodes(cssTree.getMutatingVisitController(), errorManager,
+        allowedAtRules, true, false).runPass();
+    new ProcessKeyframes(cssTree.getMutatingVisitController(), errorManager, true, true).runPass();
+    new ProcessRefiners(cssTree.getMutatingVisitController(), errorManager, true).runPass();
+
+    PermutationsCollector permutationsCollector = new PermutationsCollector(cssTree
+        .getMutatingVisitController(), errorManager);
+    permutationsCollector.runPass();
+
+    return permutationsCollector.getPermutationAxes();
+  }
+
+  private void maybeInvalidateCacheFor(List<URL> resources, long lastModified, TreeLogger logger) {
+    Long lastModifiedFromCache = LAST_MODIFIED_CACHE.getIfPresent(resources);
+
+    if (lastModifiedFromCache == null || lastModified == 0 || (lastModified >
+        lastModifiedFromCache)) {
+      TREE_CACHE.invalidate(resources);
+    }
+  }
+
+  private ConstantDefinitions optimize(ExtendedCssTree extendedCssTree, ResourceContext context,
+      boolean simplifyCss, boolean eliminateDeadStyles, TreeLogger logger)
+      throws UnableToCompleteException {
+    CssTree cssTree = extendedCssTree.tree;
+
+    // Collect mixin definitions and replace mixins
+    CollectMixinDefinitions collectMixinDefinitions = new CollectMixinDefinitions(
+        cssTree.getMutatingVisitController(), errorManager);
+    collectMixinDefinitions.runPass();
+    new ReplaceMixins(cssTree.getMutatingVisitController(), errorManager,
+        collectMixinDefinitions.getDefinitions()).runPass();
+
+    new ProcessComponents<Object>(cssTree.getMutatingVisitController(), errorManager).runPass();
+
+    RuntimeConditionalBlockCollector runtimeConditionalBlockCollector = new
+        RuntimeConditionalBlockCollector(cssTree.getVisitController());
+    runtimeConditionalBlockCollector.runPass();
+
+    new ExtendedEliminateConditionalNodes(cssTree.getMutatingVisitController(),
+        getPermutationsConditions(context, extendedCssTree.permutationAxes, logger),
+        runtimeConditionalBlockCollector.getRuntimeConditionalBlock()).runPass();
+
+    new ValidateRuntimeConditionalNode(cssTree.getVisitController(), errorManager,
+        lenientConversion).runPass();
+
+    // Don't continue if errors exist
+    checkErrors();
+
+    CollectConstantDefinitions collectConstantDefinitionsPass = new CollectConstantDefinitions(
+        cssTree);
+    collectConstantDefinitionsPass.runPass();
+
+    ReplaceConstantReferences replaceConstantReferences = new ReplaceConstantReferences(cssTree,
+        collectConstantDefinitionsPass.getConstantDefinitions(), true, errorManager, false);
+    replaceConstantReferences.runPass();
+
+    new ImageSpriteCreator(cssTree.getMutatingVisitController(), context, errorManager).runPass();
+
+    Map<String, GssFunction> gssFunctionMap = new GwtGssFunctionMapProvider(context).get();
+    new ResolveCustomFunctionNodes(cssTree.getMutatingVisitController(), errorManager,
+        gssFunctionMap, true, allowedNonStandardFunctions).runPass();
+
+    if (simplifyCss) {
+      // Eliminate empty rules.
+      new EliminateEmptyRulesetNodes(cssTree.getMutatingVisitController()).runPass();
+      // Eliminating units for zero values.
+      new EliminateUnitsFromZeroNumericValues(cssTree.getMutatingVisitController()).runPass();
+      // Optimize color values.
+      new ColorValueOptimizer(cssTree.getMutatingVisitController()).runPass();
+      // Compress redundant top-right-bottom-left value lists.
+      new AbbreviatePositionalValues(cssTree.getMutatingVisitController()).runPass();
+    }
+
+    if (eliminateDeadStyles) {
+      // Report errors for duplicate declarations
+      new DisallowDuplicateDeclarations(cssTree.getVisitController(), errorManager).runPass();
+      // Split rules by selector and declaration.
+      new SplitRulesetNodes(cssTree.getMutatingVisitController()).runPass();
+      // Dead code elimination.
+      new MarkRemovableRulesetNodes(cssTree).runPass();
+      new EliminateUselessRulesetNodes(cssTree).runPass();
+      // Merge of rules with same selector.
+      new MergeAdjacentRulesetNodesWithSameSelector(cssTree).runPass();
+      new EliminateUselessRulesetNodes(cssTree).runPass();
+      // Merge of rules with same styles.
+      new MergeAdjacentRulesetNodesWithSameDeclarations(cssTree).runPass();
+      new EliminateUselessRulesetNodes(cssTree).runPass();
+    }
+
+    return collectConstantDefinitionsPass.getConstantDefinitions();
+  }
+
+  private Set<String> getPermutationsConditions(ResourceContext context,
+      List<String> permutationAxes, TreeLogger logger) throws UnableToCompleteException {
+    Builder<String> setBuilder = ImmutableSet.builder();
+    PropertyOracle oracle = context.getGeneratorContext().getPropertyOracle();
+
+    for (String permutationAxis : permutationAxes) {
+      String propValue = null;
+      try {
+        SelectionProperty selProp = oracle.getSelectionProperty(null,
+            permutationAxis);
+        propValue = selProp.getCurrentValue();
+      } catch (BadPropertyValueException e) {
+        try {
+          ConfigurationProperty confProp = oracle.getConfigurationProperty(permutationAxis);
+          propValue = confProp.getValues().get(0);
+        } catch (BadPropertyValueException e1) {
+          logger.log(Type.ERROR, "Unknown configuration property [" + permutationAxis + "]");
+          throw new UnableToCompleteException();
+        }
+      }
+
+      if (propValue != null) {
+        setBuilder.add(permutationAxis + ":" + propValue);
+      }
+    }
+    return setBuilder.build();
+  }
+
+  private ExtendedCssTree parseResources(List<URL> resources, TreeLogger logger)
+      throws UnableToCompleteException {
+    List<SourceCode> sourceCodes = new ArrayList<SourceCode>(resources.size());
+    ImmutableMap.Builder<String, String> constantNameMappingBuilder = ImmutableMap.builder();
+
+    // assert that we only support either gss or css on one resource.
+    boolean css = ensureEitherCssOrGss(resources, logger);
+
+    if (css && !allowLegacy) {
+      // TODO(dankurka): add link explaining the situation in detail.
+      logger.log(Type.ERROR,
+          "Your ClientBundle is referencing css files instead of gss. "
+              + "You will need to either convert these files to gss using the "
+              + "converter tool or turn on auto convertion in your gwt.xml file. "
+              + "Note: Autoconversion will be removed in the next version of GWT, "
+              + "you will need to move to gss."
+              + "Add this line to your gwt.xml file to temporary avoid this:"
+              + "<set-configuration-property name=\"CssResource.legacy\" value=\"true\" />");
+      throw new UnableToCompleteException();
+    }
+
+    if (css) {
+      String concatenatedCss = concatCssFiles(resources, logger);
+
+      ConversionResult result = convertToGss(concatenatedCss, logger);
+
+      String gss = result.gss;
+      sourceCodes.add(new SourceCode("[auto-converted gss files]", gss));
+
+      constantNameMappingBuilder.putAll(result.defNameMapping);
+    } else {
+      for (URL stylesheet : resources) {
+        TreeLogger branchLogger = logger.branch(TreeLogger.DEBUG,
+            "Parsing GSS stylesheet " + stylesheet.toExternalForm());
+        try {
+          // TODO : always use UTF-8 to read the file ?
+          String fileContent =
+              Resources.asByteSource(stylesheet).asCharSource(Charsets.UTF_8).read();
+          sourceCodes.add(new SourceCode(stylesheet.getFile(), fileContent));
+          continue;
+
+        } catch (IOException e) {
+          branchLogger.log(TreeLogger.ERROR, "Unable to parse CSS", e);
+        }
+        throw new UnableToCompleteException();
+      }
+    }
+
+    CssTree tree;
+
+    try {
+      tree = new GssParser(sourceCodes).parse();
+    } catch (GssParserException e) {
+      logger.log(TreeLogger.ERROR, "Unable to parse CSS", e);
+      throw new UnableToCompleteException();
+    }
+
+    List<String> permutationAxes = finalizeTree(tree);
+
+    checkErrors();
+
+    return new ExtendedCssTree(tree, permutationAxes, constantNameMappingBuilder.build());
+  }
+
+  private ConversionResult convertToGss(String concatenatedCss, TreeLogger logger)
+      throws UnableToCompleteException {
+    File tempFile = null;
+    FileOutputStream fos = null;
+    try {
+      // We actually need a URL for the old CssResource to work. So create a temp file.
+      tempFile = File.createTempFile(UUID.randomUUID() + "css_converter", "css.tmp");
+
+      fos = new FileOutputStream(tempFile);
+      IOUtils.write(concatenatedCss, fos);
+      fos.close();
+
+      Css2Gss converter = new Css2Gss(tempFile.toURI().toURL(), logger, lenientConversion);
+
+      return new ConversionResult(converter.toGss(), converter.getDefNameMapping());
+
+    } catch (Css2GssConversionException e) {
+      String message = "An error occurs during the automatic conversion: " + e.getMessage();
+      if (!lenientConversion) {
+        message += "\n You should try to change the faulty css to fix this error. If you are " +
+            "unable to change the css, you can setup the automatic conversion to be lenient. Add " +
+            "the following line to your gwt.xml file: " +
+            "<set-configuration-property name=\"CssResource.conversionMode\" value=\"lenient\" />";
+      }
+      logger.log(Type.ERROR, message, e);
+      throw new UnableToCompleteException();
+    } catch (IOException e) {
+      logger.log(Type.ERROR, "Error while writing temporary css file", e);
+      throw new UnableToCompleteException();
+    } finally {
+      if (tempFile != null) {
+        tempFile.delete();
+      }
+      if (fos != null) {
+        IOUtils.closeQuietly(fos);
+      }
+    }
+  }
+
+  private String concatCssFiles(List<URL> resources, TreeLogger logger)
+      throws UnableToCompleteException {
+    StringBuffer buffer = new StringBuffer();
+    for (URL stylesheet : resources) {
+      try {
+        String fileContent = Resources.asByteSource(stylesheet).asCharSource(Charsets.UTF_8)
+            .read();
+        buffer.append(fileContent);
+        buffer.append("\n");
+
+      } catch (IOException e) {
+        logger.log(TreeLogger.ERROR, "Unable to parse CSS", e);
+        throw new UnableToCompleteException();
+      }
+    }
+    return buffer.toString();
+  }
+
+  private boolean ensureEitherCssOrGss(List<URL> resources, TreeLogger logger)
+      throws UnableToCompleteException {
+    boolean css = resources.get(0).toString().endsWith(".css");
+    for (URL stylesheet : resources) {
+      if (css && !stylesheet.toString().endsWith(".css")) {
+        logger.log(Type.ERROR,
+            "Only either css files or gss files are supported on one interface");
+        throw new UnableToCompleteException();
+      } else if (!css && !stylesheet.toString().endsWith(".gss")) {
+        logger.log(Type.ERROR,
+            "Only either css files or gss files are supported on one interface");
+        throw new UnableToCompleteException();
+      }
+    }
+    return css;
+  }
+
+  private String printCssTree(CssTree tree) {
+    CssPrinter cssPrinterPass = new CssPrinter(tree);
+    cssPrinterPass.runPass();
+
+    return cssPrinterPass.getCompactPrintedString();
+  }
+
+  private boolean writeClassMethod(TreeLogger logger, JMethod userMethod,
+      Map<String, String> substitutionMap, SourceWriter sw) throws
+      UnableToCompleteException {
+
+    if (!isReturnTypeString(userMethod.getReturnType().isClass())) {
+      logger.log(Type.ERROR, "The return type of the method [" + userMethod.getName() + "] must " +
+          "be java.lang.String.");
+      throw new UnableToCompleteException();
+    }
+
+    if (userMethod.getParameters().length > 0) {
+      logger.log(Type.ERROR, "The method [" + userMethod.getName() + "] shouldn't contain any " +
+          "parameters");
+      throw new UnableToCompleteException();
+    }
+
+    String name = getClassName(userMethod);
+
+    String value = substitutionMap.get(name);
+
+    if (value == null) {
+      logger.log(Type.ERROR, "The following style class [" + name + "] is missing from the source" +
+          " CSS file");
+      return false;
+    } else {
+      writeSimpleGetter(userMethod, "\"" + value + "\"", sw);
+    }
+
+    return true;
+  }
+
+  private String getClassName(JMethod method) {
+    String name = method.getName();
+
+    ClassName classNameOverride = method.getAnnotation(ClassName.class);
+    if (classNameOverride != null) {
+      name = classNameOverride.value();
+    }
+    return name;
+  }
+
+  private boolean writeDefMethod(CssDefinitionNode definitionNode, TreeLogger logger,
+      JMethod userMethod, SourceWriter sw) throws UnableToCompleteException {
+
+    String name = userMethod.getName();
+
+    JClassType classReturnType = userMethod.getReturnType().isClass();
+    List<CssValueNode> params = definitionNode.getParameters();
+
+    if (params.size() != 1 && !isReturnTypeString(classReturnType)) {
+      logger.log(TreeLogger.ERROR, "@def rule " + name
+          + " must define exactly one value or return type must be String");
+      return false;
+    }
+
+    String returnExpr;
+    if (isReturnTypeString(classReturnType)) {
+      List<String> returnValues = new ArrayList<String>();
+      for (CssValueNode valueNode : params) {
+        returnValues.add(Generator.escape(valueNode.toString()));
+      }
+      returnExpr = "\"" + Joiner.on(" ").join(returnValues) + "\"";
+    } else {
+      JPrimitiveType returnType = userMethod.getReturnType().isPrimitive();
+      if (returnType == null) {
+        logger.log(TreeLogger.ERROR, name + ": Return type must be primitive type " +
+            "or String for @def accessors");
+        return false;
+      }
+      CssValueNode valueNode = params.get(0);
+      if (!(valueNode instanceof CssNumericNode)) {
+        logger.log(TreeLogger.ERROR, "The value of the constant defined by @" + name + " is not a" +
+            " numeric");
+        return false;
+      }
+      String numericValue = ((CssNumericNode) valueNode).getNumericPart();
+
+      if (returnType == JPrimitiveType.INT || returnType == JPrimitiveType.LONG) {
+        returnExpr = "" + Long.parseLong(numericValue);
+      } else if (returnType == JPrimitiveType.FLOAT) {
+        returnExpr = numericValue + "F";
+      } else if (returnType == JPrimitiveType.DOUBLE) {
+        returnExpr = "" + numericValue;
+      } else {
+        logger.log(TreeLogger.ERROR, returnType.getQualifiedSourceName()
+            + " is not a valid primitive return type for @def accessors");
+        return false;
+      }
+    }
+
+    writeSimpleGetter(userMethod, returnExpr, sw);
+
+    return true;
+  }
+
+  private void writeMethods(TreeLogger logger, ResourceContext context, JMethod method,
+      SourceWriter sw, ConstantDefinitions constantDefinitions,
+      Map<String, String> originalConstantNameMapping, Map<String, String> substitutionMap)
+      throws UnableToCompleteException {
+    JClassType gssResource = method.getReturnType().isInterface();
+
+    boolean success = true;
+
+    for (JMethod toImplement : gssResource.getOverridableMethods()) {
+      if (toImplement == getTextMethod) {
+        writeGetText(logger, context, method, sw);
+      } else if (toImplement == ensuredInjectedMethod) {
+        writeEnsureInjected(sw);
+      } else if (toImplement == getNameMethod) {
+        writeGetName(method, sw);
+      } else {
+        success &= writeUserMethod(logger, toImplement, sw, constantDefinitions,
+            originalConstantNameMapping, substitutionMap);
+      }
+    }
+
+    if (!success) {
+      throw new UnableToCompleteException();
+    }
+  }
+
+  private boolean writeUserMethod(TreeLogger logger, JMethod userMethod,
+      SourceWriter sw, ConstantDefinitions constantDefinitions,
+      Map<String, String> originalConstantNameMapping, Map<String, String> substitutionMap)
+      throws UnableToCompleteException {
+
+    String className = getClassName(userMethod);
+    // method to access style class ?
+    if (substitutionMap.containsKey(className)) {
+      return writeClassMethod(logger, userMethod, substitutionMap, sw);
+    }
+
+    // method to access constant value ?
+    CssDefinitionNode definitionNode;
+    String methodName = userMethod.getName();
+
+    if (originalConstantNameMapping.containsKey(methodName)) {
+      // method name maps a constant that has been renamed during the auto conversion
+      String constantName = originalConstantNameMapping.get(methodName);
+      definitionNode = constantDefinitions.getConstantDefinition(constantName);
+    } else {
+      definitionNode = constantDefinitions.getConstantDefinition(methodName);
+
+      if (definitionNode == null) {
+        // try with upper case
+        definitionNode = constantDefinitions.getConstantDefinition(toUpperCase(methodName));
+      }
+    }
+
+    if (definitionNode != null) {
+      return writeDefMethod(definitionNode, logger, userMethod, sw);
+    }
+
+    // the method doesn't match a style class nor a constant
+    logger.log(Type.ERROR,
+        "The following method [" + userMethod.getName() + "()] doesn't match a constant" +
+            " nor a style class. You could fix that by adding ." + className + " {}"
+    );
+    return false;
+  }
+
+  /**
+   * Transform a camel case string to upper case. Each word is separated by a '_'
+   *
+   * @param camelCase
+   * @return
+   */
+  private String toUpperCase(String camelCase) {
+    return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, camelCase);
+  }
+
+  private Map<String, Map<String, String>> computeReplacements(JMethod method, TreeLogger logger,
+      ResourceContext context) throws UnableToCompleteException {
+    Map<String, Map<String, String>> replacementsWithPrefix = new HashMap<String, Map<String,
+        String>>();
+
+    replacementsWithPrefix
+        .put("", computeReplacementsForType(method.getReturnType().isInterface()));
+
+    // Process the Import annotation if any
+    Import imp = method.getAnnotation(Import.class);
+
+    if (imp != null) {
+      boolean fail = false;
+      TypeOracle typeOracle = context.getGeneratorContext().getTypeOracle();
+
+      for (Class<? extends CssResource> clazz : imp.value()) {
+        JClassType importType = typeOracle.findType(clazz.getName().replace('$', '.'));
+        assert importType != null : "TypeOracle does not have type " + clazz.getName();
+
+        // add this import type as a requirement for this generator
+        context.getRequirements().addTypeHierarchy(importType);
+
+        String prefix = getImportPrefix(importType);
+
+        if (replacementsWithPrefix.put(prefix, computeReplacementsForType(importType)) != null) {
+          logger.log(TreeLogger.ERROR, "Multiple imports that would use the prefix " + prefix);
+          fail = true;
+        }
+      }
+
+      if (fail) {
+        throw new UnableToCompleteException();
+      }
+    }
+
+    return replacementsWithPrefix;
+  }
+
+  private Map<String, String> computeReplacementsForType(JClassType cssResource) {
+    Map<String, String> replacements = replacementsByClassAndMethod.get(cssResource);
+
+    if (replacements == null) {
+      replacements = new HashMap<String, String>();
+      replacementsByClassAndMethod.put(cssResource, replacements);
+
+      String resourcePrefix = resourcePrefixBuilder.get(cssResource.getQualifiedSourceName());
+
+      // This substitution map will prefix each renamed class with the resource prefix and use a
+      // MinimalSubstitutionMap for computing the obfuscated name.
+      SubstitutionMap prefixingSubstitutionMap = new PrefixingSubstitutionMap(
+          new MinimalSubstitutionMap(), obfuscationPrefix + resourcePrefix + "_");
+
+      for (JMethod method : cssResource.getOverridableMethods()) {
+        if (method == getNameMethod || method == getTextMethod || method == ensuredInjectedMethod) {
+          continue;
+        }
+
+        String styleClass = getClassName(method);
+
+        if (replacementsForSharedMethods.containsKey(method)) {
+          replacements.put(styleClass, replacementsForSharedMethods.get(method));
+        } else {
+          String obfuscatedClassName = prefixingSubstitutionMap.get(styleClass);
+          String replacement = obfuscationStyle.getPrettyName(styleClass, cssResource,
+              obfuscatedClassName);
+
+          replacements.put(styleClass, replacement);
+          maybeHandleSharedMethod(method, replacement);
+        }
+      }
+    }
+
+    return replacements;
+  }
+
+  private void maybeHandleSharedMethod(JMethod method, String obfuscatedClassName) {
+    JClassType enclosingType = method.getEnclosingType();
+    Shared shared = enclosingType.getAnnotation(Shared.class);
+
+    if (shared != null) {
+      replacementsForSharedMethods.put(method, obfuscatedClassName);
+    }
+  }
+
+  /**
+   * Returns the import prefix for a type, including the trailing hyphen.
+   */
+  private String getImportPrefix(JClassType importType) {
+    String prefix = importType.getSimpleSourceName();
+    ImportedWithPrefix exp = importType.getAnnotation(ImportedWithPrefix.class);
+    if (exp != null) {
+      prefix = exp.value();
+    }
+
+    return prefix + "-";
+  }
+}
diff --git a/user/test/com/google/gwt/resources/GssAutoConversion.gwt.xml b/user/test/com/google/gwt/resources/GssAutoConversion.gwt.xml
new file mode 100644
index 0000000..5c3d877
--- /dev/null
+++ b/user/test/com/google/gwt/resources/GssAutoConversion.gwt.xml
@@ -0,0 +1,11 @@
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0//EN"
+        "http://google-web-toolkit.googlecode.com/svn/releases/2.0/distro-source/core/src/gwt-module.dtd">
+<module>
+  <inherits name="com.google.gwt.resources.GssResourceTest" />
+
+  <!-- Enable auto-conversion -->
+  <set-configuration-property name="CssResource.legacy" value="true" />
+  <set-configuration-property name="CssResource.conversionMode" value="lenient" />
+  <define-configuration-property name="customProperty" is-multi-valued="false"/>
+  <set-configuration-property name="customProperty" value="true"/>
+</module>
diff --git a/user/test/com/google/gwt/resources/GssDebugStyle.gwt.xml b/user/test/com/google/gwt/resources/GssDebugStyle.gwt.xml
new file mode 100644
index 0000000..3d5a138
--- /dev/null
+++ b/user/test/com/google/gwt/resources/GssDebugStyle.gwt.xml
@@ -0,0 +1,7 @@
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0//EN"
+        "http://google-web-toolkit.googlecode.com/svn/releases/2.0/distro-source/core/src/gwt-module.dtd">
+<module>
+  <inherits name="com.google.gwt.resources.GssResourceTest" />
+
+  <set-configuration-property name="CssResource.style" value="debug" />
+</module>
diff --git a/user/test/com/google/gwt/resources/GssPrettyStyle.gwt.xml b/user/test/com/google/gwt/resources/GssPrettyStyle.gwt.xml
new file mode 100644
index 0000000..4f0e47e
--- /dev/null
+++ b/user/test/com/google/gwt/resources/GssPrettyStyle.gwt.xml
@@ -0,0 +1,7 @@
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0//EN"
+        "http://google-web-toolkit.googlecode.com/svn/releases/2.0/distro-source/core/src/gwt-module.dtd">
+<module>
+  <inherits name="com.google.gwt.resources.GssResourceTest" />
+
+  <set-configuration-property name="CssResource.style" value="pretty" />
+</module>
diff --git a/user/test/com/google/gwt/resources/GssResourceTest.gwt.xml b/user/test/com/google/gwt/resources/GssResourceTest.gwt.xml
new file mode 100644
index 0000000..e5c0338
--- /dev/null
+++ b/user/test/com/google/gwt/resources/GssResourceTest.gwt.xml
@@ -0,0 +1,29 @@
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0//EN"
+        "http://google-web-toolkit.googlecode.com/svn/releases/2.0/distro-source/core/src/gwt-module.dtd">
+<module>
+  <inherits name="com.google.gwt.resources.Resources" />
+
+  <!-- enable GSS -->
+  <set-configuration-property name="CssResource.enableGss" value="true" />
+
+  <!-- Define two other non standards at-rules -->
+  <extend-configuration-property name="CssResource.allowedAtRules" value="extenal" />
+
+  <!-- Define two other non standards functions -->
+  <extend-configuration-property name="CssResource.allowedFunctions" value="expression" />
+  <extend-configuration-property name="CssResource.allowedFunctions"
+                                 value="progid:DXImageTransform.Microsoft.gradient" />
+
+  <set-configuration-property name="CssResource.obfuscationPrefix" value="" />
+
+  <!-- Define a configuration property to test some if-else nodes -->
+  <define-configuration-property name="test.property" is-multi-valued="false" />
+  <set-configuration-property name="test.property" value="true" />
+
+  <!-- Define properties to test some if-else nodes -->
+  <define-property name="custom.one" values="foo,bar"/>
+  <set-property name="custom.one" value="bar"/>
+
+  <define-property name="custom.two" values="foo,bar"/>
+  <set-property name="custom.two" value="bar"/>
+</module>
diff --git a/user/test/com/google/gwt/resources/GssStableNoTypeStyle.gwt.xml b/user/test/com/google/gwt/resources/GssStableNoTypeStyle.gwt.xml
new file mode 100644
index 0000000..c44fbad
--- /dev/null
+++ b/user/test/com/google/gwt/resources/GssStableNoTypeStyle.gwt.xml
@@ -0,0 +1,7 @@
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0//EN"
+        "http://google-web-toolkit.googlecode.com/svn/releases/2.0/distro-source/core/src/gwt-module.dtd">
+<module>
+  <inherits name="com.google.gwt.resources.GssResourceTest" />
+
+  <set-configuration-property name="CssResource.style" value="stable-notype" />
+</module>
diff --git a/user/test/com/google/gwt/resources/GssStableShortTypeStyle.gwt.xml b/user/test/com/google/gwt/resources/GssStableShortTypeStyle.gwt.xml
new file mode 100644
index 0000000..8132de5
--- /dev/null
+++ b/user/test/com/google/gwt/resources/GssStableShortTypeStyle.gwt.xml
@@ -0,0 +1,7 @@
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0//EN"
+        "http://google-web-toolkit.googlecode.com/svn/releases/2.0/distro-source/core/src/gwt-module.dtd">
+<module>
+  <inherits name="com.google.gwt.resources.GssResourceTest" />
+
+  <set-configuration-property name="CssResource.style" value="stable-shorttype" />
+</module>
diff --git a/user/test/com/google/gwt/resources/GssStableStyle.gwt.xml b/user/test/com/google/gwt/resources/GssStableStyle.gwt.xml
new file mode 100644
index 0000000..31f26ad
--- /dev/null
+++ b/user/test/com/google/gwt/resources/GssStableStyle.gwt.xml
@@ -0,0 +1,7 @@
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0//EN"
+        "http://google-web-toolkit.googlecode.com/svn/releases/2.0/distro-source/core/src/gwt-module.dtd">
+<module>
+  <inherits name="com.google.gwt.resources.GssResourceTest" />
+
+  <set-configuration-property name="CssResource.style" value="stable" />
+</module>
diff --git a/user/test/com/google/gwt/resources/ResourcesSuite.java b/user/test/com/google/gwt/resources/ResourcesGwtSuite.java
similarity index 67%
rename from user/test/com/google/gwt/resources/ResourcesSuite.java
rename to user/test/com/google/gwt/resources/ResourcesGwtSuite.java
index c45cabc..abd15aa 100644
--- a/user/test/com/google/gwt/resources/ResourcesSuite.java
+++ b/user/test/com/google/gwt/resources/ResourcesGwtSuite.java
@@ -25,41 +25,41 @@
 import com.google.gwt.resources.client.ImageResourceTest;
 import com.google.gwt.resources.client.NestedBundleTest;
 import com.google.gwt.resources.client.TextResourceTest;
-import com.google.gwt.resources.css.CssExternalTest;
-import com.google.gwt.resources.css.CssNodeClonerTest;
-import com.google.gwt.resources.css.CssReorderTest;
-import com.google.gwt.resources.css.CssRtlTest;
-import com.google.gwt.resources.css.ExtractClassNamesVisitorTest;
-import com.google.gwt.resources.css.UnknownAtRuleTest;
-import com.google.gwt.resources.ext.ResourceGeneratorUtilTest;
-import com.google.gwt.resources.rg.CssClassNamesTestCase;
+import com.google.gwt.resources.client.gss.AutoConversionTest;
+import com.google.gwt.resources.client.gss.DebugObfuscationStyleTest;
+import com.google.gwt.resources.client.gss.GssResourceTest;
+import com.google.gwt.resources.client.gss.PrettyObfuscationStyleTest;
+import com.google.gwt.resources.client.gss.StableNoTypeObfuscationStyleTest;
+import com.google.gwt.resources.client.gss.StableObfuscationStyleTest;
+import com.google.gwt.resources.client.gss.StableShortTypeObfuscationStyleTest;
 
 import junit.framework.Test;
 
 /**
  * Tests the ClientBundle framework.
  */
-public class ResourcesSuite {
+public class ResourcesGwtSuite {
   public static Test suite() {
 
     GWTTestSuite suite = new GWTTestSuite("Test for com.google.gwt.resources");
-    suite.addTestSuite(CssClassNamesTestCase.class);
-    suite.addTestSuite(CssExternalTest.class);
-    suite.addTestSuite(CssNodeClonerTest.class);
-    suite.addTestSuite(CssReorderTest.class);
-    suite.addTestSuite(CSSResourceTest.class);
-    suite.addTestSuite(CssRtlTest.class);
     suite.addTestSuite(DataResourceDoNotEmbedTest.class);
     suite.addTestSuite(DataResourceMimeTypeTest.class);
     suite.addTestSuite(ExternalTextResourceJsonpTest.class);
     suite.addTestSuite(ExternalTextResourceTest.class);
-    suite.addTestSuite(ExtractClassNamesVisitorTest.class);
     suite.addTestSuite(ImageResourceNoInliningTest.class);
     suite.addTestSuite(ImageResourceTest.class);
     suite.addTestSuite(NestedBundleTest.class);
-    suite.addTestSuite(ResourceGeneratorUtilTest.class);
     suite.addTestSuite(TextResourceTest.class);
-    suite.addTestSuite(UnknownAtRuleTest.class);
+    suite.addTestSuite(CSSResourceTest.class);
+
+    // GSS
+    suite.addTestSuite(GssResourceTest.class);
+    suite.addTestSuite(DebugObfuscationStyleTest.class);
+    suite.addTestSuite(PrettyObfuscationStyleTest.class);
+    suite.addTestSuite(StableShortTypeObfuscationStyleTest.class);
+    suite.addTestSuite(StableNoTypeObfuscationStyleTest.class);
+    suite.addTestSuite(StableObfuscationStyleTest.class);
+    suite.addTestSuite(AutoConversionTest.class);
     return suite;
   }
 }
diff --git a/user/test/com/google/gwt/resources/ResourcesJreSuite.java b/user/test/com/google/gwt/resources/ResourcesJreSuite.java
index 1be6313..3cd9dac 100644
--- a/user/test/com/google/gwt/resources/ResourcesJreSuite.java
+++ b/user/test/com/google/gwt/resources/ResourcesJreSuite.java
@@ -15,16 +15,35 @@
  */
 package com.google.gwt.resources;
 
-import com.google.gwt.junit.tools.GWTTestSuite;
 import com.google.gwt.resources.converter.AlternateAnnotationCreatorVisitorTest;
 import com.google.gwt.resources.converter.Css2GssTest;
 import com.google.gwt.resources.converter.DefCollectorVisitorTest;
 import com.google.gwt.resources.converter.ElseNodeCreatorTest;
 import com.google.gwt.resources.converter.FontFamilyVisitorTest;
 import com.google.gwt.resources.converter.UndefinedConstantVisitorTest;
+import com.google.gwt.resources.css.CssExternalTest;
+import com.google.gwt.resources.css.CssNodeClonerTest;
+import com.google.gwt.resources.css.CssReorderTest;
+import com.google.gwt.resources.css.CssRtlTest;
+import com.google.gwt.resources.css.ExtractClassNamesVisitorTest;
+import com.google.gwt.resources.css.UnknownAtRuleTest;
+import com.google.gwt.resources.ext.ResourceGeneratorUtilTest;
+import com.google.gwt.resources.gss.CssPrinterTest;
+import com.google.gwt.resources.gss.ExtendedEliminateConditionalNodesTest;
+import com.google.gwt.resources.gss.ExternalClassesCollectorTest;
+import com.google.gwt.resources.gss.ImageSpriteCreatorTest;
+import com.google.gwt.resources.gss.PermutationsCollectorTest;
+import com.google.gwt.resources.gss.RecordingBidiFlipperTest;
+import com.google.gwt.resources.gss.RenamingSubstitutionMapTest;
+import com.google.gwt.resources.gss.ResourceUrlFunctionTest;
+import com.google.gwt.resources.gss.RuntimeConditionalBlockCollectorTest;
+import com.google.gwt.resources.gss.ValidateRuntimeConditionalNodeTest;
+import com.google.gwt.resources.gss.ValueFunctionTest;
+import com.google.gwt.resources.rg.CssClassNamesTestCase;
 import com.google.gwt.resources.rg.CssOutputTestCase;
 
 import junit.framework.Test;
+import junit.framework.TestSuite;
 
 /**
  * JRE tests of the ClientBundle framework.
@@ -32,7 +51,32 @@
 public class ResourcesJreSuite {
   public static Test suite() {
 
-    GWTTestSuite suite = new GWTTestSuite("JRE test for com.google.gwt.resources");
+    TestSuite suite = new TestSuite("JRE test for com.google.gwt.resources");
+    suite.addTestSuite(CssClassNamesTestCase.class);
+    suite.addTestSuite(CssExternalTest.class);
+    suite.addTestSuite(CssNodeClonerTest.class);
+    suite.addTestSuite(CssReorderTest.class);
+    suite.addTestSuite(CssRtlTest.class);
+    suite.addTestSuite(ExtractClassNamesVisitorTest.class);
+    suite.addTestSuite(ResourceGeneratorUtilTest.class);
+    suite.addTestSuite(UnknownAtRuleTest.class);
+
+    // GSS tests
+    suite.addTestSuite(ExternalClassesCollectorTest.class);
+    suite.addTestSuite(RenamingSubstitutionMapTest.class);
+    suite.addTestSuite(CssPrinterTest.class);
+    suite.addTestSuite(PermutationsCollectorTest.class);
+    suite.addTestSuite(RecordingBidiFlipperTest.class);
+    suite.addTestSuite(ResourceUrlFunctionTest.class);
+    suite.addTestSuite(ExtendedEliminateConditionalNodesTest.class);
+    suite.addTestSuite(PermutationsCollectorTest.class);
+    suite.addTestSuite(ResourceUrlFunctionTest.class);
+    suite.addTestSuite(RuntimeConditionalBlockCollectorTest.class);
+    suite.addTestSuite(ValidateRuntimeConditionalNodeTest.class);
+    suite.addTestSuite(ValueFunctionTest.class);
+    suite.addTestSuite(ImageSpriteCreatorTest.class);
+
+    // CSS to GSS converter tests
     suite.addTestSuite(Css2GssTest.class);
     suite.addTestSuite(CssOutputTestCase.class);
     suite.addTestSuite(DefCollectorVisitorTest.class);
diff --git a/user/test/com/google/gwt/resources/client/CSSResourceTest.java b/user/test/com/google/gwt/resources/client/CSSResourceTest.java
index fd5ffcf..5546b86 100644
--- a/user/test/com/google/gwt/resources/client/CSSResourceTest.java
+++ b/user/test/com/google/gwt/resources/client/CSSResourceTest.java
@@ -21,7 +21,6 @@
 import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
 import com.google.gwt.resources.client.CssResource.Shared;
 
-
 /**
  * Contains various full-stack tests of the CssResource system.
  */
diff --git a/user/test/com/google/gwt/resources/client/gss/AllGwtTests.java b/user/test/com/google/gwt/resources/client/gss/AllGwtTests.java
new file mode 100644
index 0000000..6789151
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/AllGwtTests.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Suite for all GWT tests.
+ */
+public class AllGwtTests extends GWTTestSuite {
+
+  public static Test suite() {
+    GWTTestSuite suite = new GWTTestSuite();
+    suite.addTestSuite(GssResourceTest.class);
+    suite.addTestSuite(DebugObfuscationStyleTest.class);
+    suite.addTestSuite(PrettyObfuscationStyleTest.class);
+    suite.addTestSuite(StableShortTypeObfuscationStyleTest.class);
+    suite.addTestSuite(StableNoTypeObfuscationStyleTest.class);
+    suite.addTestSuite(StableObfuscationStyleTest.class);
+    suite.addTestSuite(AutoConversionTest.class);
+    return suite;
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/AutoConversionBundle.java b/user/test/com/google/gwt/resources/client/gss/AutoConversionBundle.java
new file mode 100644
index 0000000..9c7dc48
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/AutoConversionBundle.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+
+/**
+ * ClientBundle containing defining several CssResource in order to be able to test the
+ * auto-conversion from CSS to GSS.
+ */
+public interface AutoConversionBundle extends ClientBundle {
+  /**
+   * For testing automatic constant renaming during the conversion.
+   */
+  interface ConstantRenaming extends CssResource {
+    int myConstant();
+    String my_constant();
+    int ie6();
+    int gecko1_8();
+  }
+
+  /**
+   * For testing conversion of constants inside conditional nodes.
+   */
+  interface ConstantConditional extends CssResource {
+    String foo();
+    String color();
+    int width();
+    int height();
+  }
+
+  /**
+   * For testing conversion of external at-rule definition inside conditional nodes.
+   */
+  interface LenientExternal extends CssResource {
+    String nonObfuscated();
+    String nonObfuscated2();
+    String nonObfuscated3();
+    String obfuscated();
+  }
+
+  ConstantRenaming constantRenaming();
+
+  ConstantConditional constantConditional();
+
+  LenientExternal lenientExternal();
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/AutoConversionTest.java b/user/test/com/google/gwt/resources/client/gss/AutoConversionTest.java
new file mode 100644
index 0000000..9a5aff7
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/AutoConversionTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.resources.client.gss.AutoConversionBundle.ConstantConditional;
+import com.google.gwt.resources.client.gss.AutoConversionBundle.ConstantRenaming;
+import com.google.gwt.resources.client.gss.AutoConversionBundle.LenientExternal;
+
+/**
+ * Test for automatic conversion from CSS to GSS.
+ */
+public class AutoConversionTest extends GWTTestCase {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.resources.GssAutoConversion";
+  }
+
+  public void testConstantRenaming() {
+    ConstantRenaming constantRenaming = res().constantRenaming();
+
+    assertEquals(45, constantRenaming.myConstant());
+    assertEquals("38px", constantRenaming.my_constant());
+    assertEquals(0, constantRenaming.ie6());
+    assertEquals(0, constantRenaming.gecko1_8());
+  }
+
+  public void testConstantConditional() {
+    ConstantConditional constantConditional = res().constantConditional();
+
+    String expectedCss = "." + constantConditional.foo() + "{width:15px;height:10px;color:black}";
+    assertEquals(expectedCss, constantConditional.getText());
+    assertEquals("black", constantConditional.color());
+    assertEquals(15, constantConditional.width());
+    assertEquals(10, constantConditional.height());
+  }
+
+  public void testLenientExternal() {
+    LenientExternal lenientExternal = res().lenientExternal();
+
+    assertNotSame("obfuscated", lenientExternal.obfuscated());
+    assertEquals("nonObfuscated", lenientExternal.nonObfuscated());
+    assertEquals("nonObfuscated2", lenientExternal.nonObfuscated2());
+    assertEquals("nonObfuscated3", lenientExternal.nonObfuscated3());
+  }
+
+  private AutoConversionBundle res() {
+    return GWT.create(AutoConversionBundle.class);
+  }
+
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/BooleanEval.java b/user/test/com/google/gwt/resources/client/gss/BooleanEval.java
new file mode 100644
index 0000000..0bd449f
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/BooleanEval.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+/**
+ * Class containing some static boolean field that can be evaluated in conditional nodes.
+ * <p/>
+ * Fields are voluntary not final so that the different test can define its own behavior.
+ */
+public class BooleanEval {
+  public static boolean FIRST = true;
+  public static boolean SECOND = true;
+  public static boolean THIRD = true;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/Constants.java b/user/test/com/google/gwt/resources/client/gss/Constants.java
new file mode 100644
index 0000000..18a2b0e
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/Constants.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+/**
+ * Class defining some static method that can be used in GSS files.
+ */
+public class Constants {
+  public static String white() {
+    return "#fff";
+  }
+
+  public static String red(String test) {
+    return "#f00";
+  }
+
+  public static int width() {
+    return 45;
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/DebugObfuscationStyleTest.java b/user/test/com/google/gwt/resources/client/gss/DebugObfuscationStyleTest.java
new file mode 100644
index 0000000..43362cf
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/DebugObfuscationStyleTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.resources.client.gss.TestResources.ClassNameAnnotation;
+
+/**
+ * Tests style classes renaming with the configuration property <code>CssResource.style</code> set
+ * to debug.
+ */
+public class DebugObfuscationStyleTest extends RenamingClassNameTest {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.resources.GssDebugStyle";
+  }
+
+  public void testClassesRenaming() {
+    ClassNameAnnotation classNameAnnotation = res().classNameAnnotation();
+    String renamedClass = classNameAnnotation.renamedClass();
+    String nonRenamedClass = classNameAnnotation.nonRenamedClass();
+
+    assertTrue(renamedClass.matches(OBFUSCATION_PATTERN + "-TestResources-ClassNameAnnotation-renamed-class"));
+    assertTrue(nonRenamedClass.matches(OBFUSCATION_PATTERN + "-TestResources-ClassNameAnnotation-nonRenamedClass"));
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/GssResourceTest.java b/user/test/com/google/gwt/resources/client/gss/GssResourceTest.java
new file mode 100644
index 0000000..66c7b2f
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/GssResourceTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.resources.client.gss.TestResources.ClassNameAnnotation;
+import com.google.gwt.resources.client.gss.TestResources.EmptyClass;
+import com.google.gwt.resources.client.gss.TestResources.NonStandardAtRules;
+import com.google.gwt.resources.client.gss.TestResources.NonStandardFunctions;
+import com.google.gwt.resources.client.gss.TestResources.RuntimeConditional;
+import com.google.gwt.resources.client.gss.TestResources.SomeGssResource;
+import com.google.gwt.resources.client.gss.TestResources.WithConstant;
+
+/**
+ * Contains various full-stack tests of the CssResource system with GSS.
+ */
+public class GssResourceTest extends RenamingClassNameTest {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.resources.GssResourceTest";
+  }
+
+  @Override
+  public void testClassesRenaming() {
+    ClassNameAnnotation classNameAnnotation = res().classNameAnnotation();
+    String renamedClass = classNameAnnotation.renamedClass();
+    String nonRenamedClass = classNameAnnotation.nonRenamedClass();
+
+    assertTrue(renamedClass.matches(OBFUSCATION_PATTERN));
+    assertTrue(nonRenamedClass.matches(OBFUSCATION_PATTERN));
+  }
+
+  public void testMixin() {
+    String text = res().mixin().getText();
+
+    assertTrue(text.contains("{width:120px;height:100px}"));
+  }
+
+  public void testAdd() {
+    String text = res().add().getText();
+
+    assertTrue(text.contains("{width:220px}"));
+  }
+
+  public void testEval() {
+    String text = res().eval().getText();
+
+    assertTrue(text.contains("{color:#fff;background-color:#f00;width:30px}"));
+  }
+
+  public void testSprite() {
+    String text = res().sprite().getText();
+
+    String expected = "{height:64px;width:64px;overflow:hidden;background:url(" + res()
+        .someImageResource().getSafeUri().asString() + ") -0px -0px  no-repeat}";
+
+    assertTrue(text.contains(expected));
+  }
+
+  public void testResourceUrl() {
+    String text = res().resourceUrl().getText();
+
+    String expected = "{cursor:url(" + res().someDataResource().getSafeUri().asString() + ");"
+        + "background-image:url(" + res().someImageResource().getSafeUri().asString() + ");"
+        + "cursor:url(" + res().someDataResource().getSafeUri().asString() + ");"
+        + "background-image:url(" + res().someImageResource().getSafeUri().asString() + ")}";
+    assertTrue(text.contains(expected));
+  }
+
+  /**
+   * Test that empty class definitions are removed from the resulting css.
+   */
+  public void testEmptyClass() {
+    EmptyClass emptyClass = res().emptyClass();
+
+    assertEquals("", emptyClass.getText());
+  }
+
+  public void testConstant() {
+    WithConstant withConstant = res().withConstant();
+
+    assertEquals("15px", withConstant.constantOne());
+
+    String expectedCss = "." + withConstant.classOne() + "{padding:" + withConstant.constantOne()
+        + "}";
+    assertEquals(expectedCss, withConstant.getText());
+  }
+
+  public void testClassNameAnnotation() {
+    ClassNameAnnotation css = res().classNameAnnotation();
+
+    String expectedCss = "." + css.renamedClass() + "{color:black}." + css.nonRenamedClass()
+        + "{color:white}";
+    assertEquals(expectedCss, css.getText());
+  }
+
+  public void testConstants() {
+    assertEquals("15px", res().cssWithConstant().constantOne());
+    assertEquals(5, res().cssWithConstant().constantTwo());
+    assertEquals("black", res().cssWithConstant().CONSTANT_THREE());
+
+    assertNotSame("white", res().cssWithConstant().conflictConstantClass());
+  }
+
+  public void testNotStrict() {
+    SomeGssResource notStrict = res().notstrict();
+
+    String expectedCss = "." + notStrict.someClass() + "{color:black}.otherNotStrictClass{" +
+        "color:white}";
+
+    assertEquals(expectedCss, notStrict.getText());
+  }
+
+  public void testRuntimeConditional() {
+    RuntimeConditional runtimeConditional = res().runtimeConditional();
+    String foo = runtimeConditional.foo();
+
+    BooleanEval.FIRST = true;
+    BooleanEval.SECOND = true;
+    BooleanEval.THIRD = true;
+
+    assertEquals(runtimeExpectedCss("purple", "10px", foo), runtimeConditional.getText());
+
+    BooleanEval.FIRST = false;
+    BooleanEval.SECOND = true;
+    BooleanEval.THIRD = true;
+
+    assertEquals(runtimeExpectedCss("black", "10px", foo), runtimeConditional.getText());
+
+    BooleanEval.FIRST = false;
+    BooleanEval.SECOND = true;
+    BooleanEval.THIRD = false;
+
+    assertEquals(runtimeExpectedCss("khaki", "10px", foo), runtimeConditional.getText());
+
+    BooleanEval.FIRST = false;
+    BooleanEval.SECOND = false;
+
+    assertEquals(runtimeExpectedCss("gray", "10px", foo), runtimeConditional.getText());
+  }
+
+  public void testNonStandardAtRules() {
+    NonStandardAtRules nonStandardAtRules = res().nonStandardAtRules();
+
+    String css = nonStandardAtRules.getText();
+    assertTrue(css.contains("@extenal"));
+    assertTrue(css.contains("@-moz-document"));
+    assertTrue(css.contains("@supports"));
+  }
+
+  public void testNonStandardFunctions() {
+    NonStandardFunctions nonStandardFunctions = res().nonStandardFunctions();
+
+    String css = nonStandardFunctions.getText();
+    assertTrue(css.contains("expression("));
+    assertTrue(css.contains("progid:DXImageTransform.Microsoft.gradient("));
+  }
+
+  private String runtimeExpectedCss(String color, String padding, String foo) {
+    String s = "." + foo + "{width:100%}" + "." + foo + "{color:" + color + "}";
+
+    if (padding != null) {
+      s += "." + foo + "{padding:" + padding + "}";
+    }
+
+    s += "." + foo + "{margin:100px}";
+
+    return s;
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/ImportResource.java b/user/test/com/google/gwt/resources/client/gss/ImportResource.java
new file mode 100644
index 0000000..3571dea
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/ImportResource.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
+
+/**
+ * ClientBundle used in order to test the {@code @import}
+ * and {@code @ImportedWithPrefix} annotations.
+ */
+public interface ImportResource extends ClientBundle {
+
+  /**
+   * CssResource that will be imported without any prefix.
+   */
+  public interface ImportCss extends CssResource {
+    String className();
+
+    String className2();
+  }
+
+  /**
+   * CssResource that will be imported with a prefix.
+   */
+  @ImportedWithPrefix("testPrefix")
+  public interface ImportWithPrefixCss extends CssResource {
+    String className();
+  }
+
+  ImportCss importCss();
+
+  ImportWithPrefixCss importWithPrefixCss();
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/PrettyObfuscationStyleTest.java b/user/test/com/google/gwt/resources/client/gss/PrettyObfuscationStyleTest.java
new file mode 100644
index 0000000..6a0bf99
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/PrettyObfuscationStyleTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.resources.client.gss.TestResources.ClassNameAnnotation;
+
+/**
+ * Tests style classes renaming with the configuration property <code>CssResource.style</code> set
+ * to pretty.
+ */
+public class PrettyObfuscationStyleTest extends RenamingClassNameTest {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.resources.GssPrettyStyle";
+  }
+
+  public void testClassesRenaming() {
+    ClassNameAnnotation classNameAnnotation = res().classNameAnnotation();
+    String renamedClass = classNameAnnotation.renamedClass();
+    String nonRenamedClass = classNameAnnotation.nonRenamedClass();
+
+    assertTrue(renamedClass.matches(OBFUSCATION_PATTERN +
+        "-com-google-gwt-resources-client-gss-TestResources-ClassNameAnnotation-renamed-class"));
+    assertTrue(nonRenamedClass.matches(OBFUSCATION_PATTERN +
+        "-com-google-gwt-resources-client-gss-TestResources-ClassNameAnnotation-nonRenamedClass"));
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/RenamingClassNameTest.java b/user/test/com/google/gwt/resources/client/gss/RenamingClassNameTest.java
new file mode 100644
index 0000000..a59c03b
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/RenamingClassNameTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.core.shared.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.resources.client.gss.TestResources.ExternalClasses;
+import com.google.gwt.resources.client.gss.TestResources.TestImportCss;
+
+/**
+ * These tests are tested with several configurations.
+ *
+ * @see com.google.gwt.resources.client.gss.StableNoTypeObfuscationStyleTest
+ * @see com.google.gwt.resources.client.gss.StableObfuscationStyleTest
+ * @see com.google.gwt.resources.client.gss.StableShortTypeObfuscationStyleTest
+ * @see com.google.gwt.resources.client.gss.DebugObfuscationStyleTest
+ * @see com.google.gwt.resources.client.gss.GssResourceTest
+ */
+public abstract class RenamingClassNameTest extends GWTTestCase {
+  static final String OBFUSCATION_PATTERN = "[a-zA-Z][a-zA-Z0-9]*_[a-zA-Z][a-zA-Z0-9]*";
+
+  /**
+   * Test that style classes mentioned as external are not obfuscated.
+   */
+  public void testExternalClasses() {
+    ExternalClasses externalClasses = res().externalClasses();
+
+    // external at-rule shouldn't be printed
+    assertFalse(externalClasses.getText().contains("@external"));
+
+    assertNotSame("obfuscatedClass", externalClasses.obfuscatedClass());
+
+    assertEquals("externalClass", externalClasses.externalClass());
+    assertEquals("externalClass2", externalClasses.externalClass2());
+    assertEquals("unobfuscated", externalClasses.unobfuscated());
+    assertEquals("unobfuscated2", externalClasses.unobfuscated2());
+  }
+
+  public void testObfuscationScope() {
+    ScopeResource res = GWT.create(ScopeResource.class);
+
+    assertEquals(res.scopeA().foo(), res.scopeA2().foo());
+    assertNotSame(res.scopeA().foo(), res.scopeB().foo());
+    assertNotSame(res.scopeB().foo(), res.scopeC().foo());
+    assertNotSame(res.scopeA().foo(), res.scopeC().foo());
+  }
+
+  public void testImportAndImportWithPrefix() {
+    TestImportCss css = res().testImportCss();
+    ImportResource importResource = GWT.create(ImportResource.class);
+    ImportResource.ImportCss importCss = importResource.importCss();
+    ImportResource.ImportWithPrefixCss importWithPrefixCss = importResource.importWithPrefixCss();
+
+    String expectedCss = "." + css.other() + "{color:black}." + importCss.className() +
+        " ." + css.other() + "{color:white}." + importWithPrefixCss.className() + " ." +
+        css.other() + "{color:gray}";
+    assertEquals(expectedCss, css.getText());
+  }
+
+  public void testSharedScope() {
+    ScopeResource res = GWT.create(ScopeResource.class);
+    TestResources res2 = res();
+
+    // shareClassName1 is shared
+    assertEquals(res.sharedParent().sharedClassName1(), res.sharedChild1().sharedClassName1());
+    assertEquals(res.sharedParent().sharedClassName1(), res.sharedChild2().sharedClassName1());
+    assertEquals(res.sharedParent().sharedClassName1(), res.sharedGreatChild().sharedClassName1());
+    assertEquals(res.sharedParent().sharedClassName1(), res2.sharedChild3().sharedClassName1());
+
+    // shareClassName2 is shared
+    assertEquals(res.sharedParent().sharedClassName2(), res.sharedChild1().sharedClassName2());
+    assertEquals(res.sharedParent().sharedClassName2(), res.sharedChild2().sharedClassName2());
+    assertEquals(res.sharedParent().sharedClassName2(), res.sharedGreatChild().sharedClassName2());
+    assertEquals(res.sharedParent().sharedClassName2(), res2.sharedChild3().sharedClassName2());
+
+    // nonSharedClassName isn't shared
+    assertNotSame(res.sharedChild1().nonSharedClassName(),
+        res.sharedChild2().nonSharedClassName());
+    assertNotSame(res.sharedChild1().nonSharedClassName(),
+        res.sharedGreatChild().nonSharedClassName());
+    assertNotSame(res.sharedChild1().nonSharedClassName(),
+        res2.sharedChild3().nonSharedClassName());
+    assertNotSame(res.sharedChild2().nonSharedClassName(),
+        res.sharedGreatChild().nonSharedClassName());
+    assertNotSame(res.sharedChild2().nonSharedClassName(),
+        res2.sharedChild3().nonSharedClassName());
+    assertNotSame(res2.sharedChild3().nonSharedClassName(),
+        res.sharedGreatChild().nonSharedClassName());
+  }
+
+  public abstract void testClassesRenaming();
+
+  protected TestResources res() {
+    return GWT.create(TestResources.class);
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/ScopeResource.java b/user/test/com/google/gwt/resources/client/gss/ScopeResource.java
new file mode 100644
index 0000000..09f7605
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/ScopeResource.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.Shared;
+
+/**
+ * ClientBundle that contains several CssResource for testing different scopes.
+ */
+public interface ScopeResource extends ClientBundle {
+  /**
+   * Not shared CssResource.
+   */
+  interface ScopeA extends CssResource {
+    String foo();
+  }
+
+  /**
+   * Not shared CssResource.
+   */
+  interface ScopeB extends ScopeA {
+    String foo();
+  }
+
+  /**
+   * Not shared CssResource.
+   */
+  interface ScopeC extends ScopeA {
+    // Intentionally not defining foo()
+  }
+
+  /**
+   * Shared CssResource.
+   */
+  @Shared
+  interface SharedParent extends CssResource {
+    String sharedClassName1();
+    String sharedClassName2();
+  }
+
+  /**
+   * Shared CssResource.
+   */
+  interface SharedChild1 extends SharedParent {
+    String nonSharedClassName();
+  }
+
+  /**
+   * Shared CssResource.
+   */
+  interface SharedChild2 extends SharedParent {
+    String nonSharedClassName();
+  }
+
+  /**
+   * Shared CssResource.
+   */
+  interface SharedGreatChild extends SharedChild2 {
+    // Intentionally empty
+  }
+
+  SharedChild1 sharedChild1();
+
+  SharedChild2 sharedChild2();
+
+  SharedGreatChild sharedGreatChild();
+
+  SharedParent sharedParent();
+
+  ScopeA scopeA();
+
+  ScopeA scopeA2();
+
+  ScopeB scopeB();
+
+  ScopeC scopeC();
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/StableNoTypeObfuscationStyleTest.java b/user/test/com/google/gwt/resources/client/gss/StableNoTypeObfuscationStyleTest.java
new file mode 100644
index 0000000..1262f6b
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/StableNoTypeObfuscationStyleTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+import com.google.gwt.resources.client.gss.TestResources.ClassNameAnnotation;
+
+/**
+ * Tests style classes renaming with the configuration property <code>CssResource.style</code> set
+ * to stable-notype.
+ */
+public class StableNoTypeObfuscationStyleTest extends GWTTestCase {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.resources.GssStableNoTypeStyle";
+  }
+
+  public void testClassesRenaming() {
+    ClassNameAnnotation classNameAnnotation = GWT.<TestResources>create(TestResources.class)
+        .classNameAnnotation();
+
+    String renamedClass = classNameAnnotation.renamedClass();
+    String nonRenamedClass = classNameAnnotation.nonRenamedClass();
+
+    assertEquals("renamed-class",renamedClass);
+    assertEquals("nonRenamedClass", nonRenamedClass);
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/StableObfuscationStyleTest.java b/user/test/com/google/gwt/resources/client/gss/StableObfuscationStyleTest.java
new file mode 100644
index 0000000..1e6a875
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/StableObfuscationStyleTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.resources.client.gss.TestResources.ClassNameAnnotation;
+
+/**
+ * Tests style classes renaming with the configuration property <code>CssResource.style</code> set
+ * to stable.
+ */
+public class StableObfuscationStyleTest extends RenamingClassNameTest {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.resources.GssStableStyle";
+  }
+
+  public void testClassesRenaming() {
+    ClassNameAnnotation classNameAnnotation = res().classNameAnnotation();
+    String renamedClass = classNameAnnotation.renamedClass();
+    String nonRenamedClass = classNameAnnotation.nonRenamedClass();
+
+    assertEquals("com-google-gwt-resources-client-gss-TestResources-ClassNameAnnotation-renamed" +
+            "-class", renamedClass);
+    assertEquals("com-google-gwt-resources-client-gss-TestResources-ClassNameAnnotation" +
+        "-nonRenamedClass", nonRenamedClass);
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/StableShortTypeObfuscationStyleTest.java b/user/test/com/google/gwt/resources/client/gss/StableShortTypeObfuscationStyleTest.java
new file mode 100644
index 0000000..94747d4
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/StableShortTypeObfuscationStyleTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.resources.client.gss.TestResources.ClassNameAnnotation;
+
+/**
+ * Tests style classes renaming with the configuration property <code>CssResource.style</code> set
+ * to stable-shorttype.
+ */
+public class StableShortTypeObfuscationStyleTest extends RenamingClassNameTest {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.resources.GssStableShortTypeStyle";
+  }
+
+  public void testClassesRenaming() {
+    ClassNameAnnotation classNameAnnotation = res().classNameAnnotation();
+    String renamedClass = classNameAnnotation.renamedClass();
+    String nonRenamedClass = classNameAnnotation.nonRenamedClass();
+
+    assertEquals("TestResources-ClassNameAnnotation-renamed-class", renamedClass);
+    assertEquals("TestResources-ClassNameAnnotation-nonRenamedClass", nonRenamedClass);
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/TestResources.java b/user/test/com/google/gwt/resources/client/gss/TestResources.java
new file mode 100644
index 0000000..ec13ecb
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/TestResources.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.client.gss;
+
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.CssResource;
+import com.google.gwt.resources.client.CssResource.Import;
+import com.google.gwt.resources.client.CssResource.NotStrict;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.gss.ScopeResource.SharedParent;
+
+/**
+ * Contains various CssResource used to test the CssResource system with GSS.
+ */
+public interface TestResources extends ClientBundle {
+  /**
+   * Inner ClientBundle with a reference to an ImageResource.
+   */
+  public interface ImageResources extends ClientBundle {
+        @Source("someImageResource.png")
+        ImageResource someResource();
+  }
+
+  /**
+   * Simple CssResource.
+   */
+  interface SomeGssResource extends CssResource {
+    String someClass();
+  }
+
+  /**
+   * Used to test sprite definition.
+   */
+  interface SpriteGssResource extends CssResource {
+    String someClassWithSprite();
+
+    String embeddedSprite();
+
+    // define a style class having the same name than another resource in the ClientBundle
+    // test possible conflict
+    String someImageResource();
+  }
+
+  /**
+   * Used to test {@code @external} at-rule.
+   */
+  interface ExternalClasses extends CssResource {
+    String obfuscatedClass();
+
+    String externalClass();
+
+    String externalClass2();
+
+    String unobfuscated();
+
+    String unobfuscated2();
+  }
+
+  /**
+   * Used to test that GSS file can contains empty style class definition.
+   */
+  interface EmptyClass extends CssResource {
+    String empty();
+  }
+
+  /**
+   * Used to test constant definition.
+   */
+  interface WithConstant extends CssResource {
+    String constantOne();
+
+    String classOne();
+  }
+
+  /**
+   * Used to test {@link ClassName} annotation.
+   */
+  interface ClassNameAnnotation extends CssResource {
+    @ClassName("renamed-class")
+    String renamedClass();
+
+    String nonRenamedClass();
+  }
+
+  /**
+   * Used to test {@link Import} annotation.
+   */
+  interface TestImportCss extends CssResource {
+    String other();
+  }
+
+  /**
+   * Used to test shared annotation between clientBundle.
+   */
+  interface SharedChild3 extends SharedParent {
+    String nonSharedClassName();
+  }
+
+  /**
+   * Used to test conflict between style class name and constant name.
+   */
+  interface CssWithConstant extends CssResource {
+    String constantOne();
+
+    int constantTwo();
+
+    String CONSTANT_THREE();
+
+    String className1();
+
+    String conflictConstantClass();
+  }
+
+  /**
+   * Used to test conditional nodes containing conditions evaluated at runtime.
+   */
+  interface RuntimeConditional extends CssResource {
+    String foo();
+  }
+
+  /**
+   * Used to test that the generator accepts non standard at-rules if they are defined in the
+   * module file.
+   */
+  interface NonStandardAtRules extends CssResource {
+    String foo();
+  }
+
+  /**
+   * Used to test that the generator accepts non standard css function if they are defined in the
+   * module file.
+   */
+  interface NonStandardFunctions extends CssResource {
+    String foo();
+  }
+
+  ClassNameAnnotation classNameAnnotation();
+
+  SomeGssResource mixin();
+
+  SomeGssResource add();
+
+  SomeGssResource eval();
+
+  SomeGssResource resourceUrl();
+
+  SpriteGssResource sprite();
+
+  ExternalClasses externalClasses();
+
+  EmptyClass emptyClass();
+
+  WithConstant withConstant();
+
+  ImageResource someImageResource();
+
+  @Source("bananaguitar.ani")
+  DataResource someDataResource();
+
+  @Import({ImportResource.ImportWithPrefixCss.class, ImportResource.ImportCss.class})
+  TestImportCss testImportCss();
+
+  SharedChild3 sharedChild3();
+
+  CssWithConstant cssWithConstant();
+
+  @NotStrict
+  SomeGssResource notstrict();
+
+  RuntimeConditional runtimeConditional();
+
+  ImageResources embeddedImageResources();
+
+  NonStandardAtRules nonStandardAtRules();
+
+  NonStandardFunctions nonStandardFunctions();
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/add.gss b/user/test/com/google/gwt/resources/client/gss/add.gss
new file mode 100644
index 0000000..f314591
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/add.gss
@@ -0,0 +1,3 @@
+.someClass {
+    width: add(120px, 100px);
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/bananaguitar.ani b/user/test/com/google/gwt/resources/client/gss/bananaguitar.ani
new file mode 100644
index 0000000..da841ad
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/bananaguitar.ani
Binary files differ
diff --git a/user/test/com/google/gwt/resources/client/gss/classNameAnnotation.gss b/user/test/com/google/gwt/resources/client/gss/classNameAnnotation.gss
new file mode 100644
index 0000000..dd3ac2b
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/classNameAnnotation.gss
@@ -0,0 +1,7 @@
+.renamed-class {
+    color: black;
+}
+
+.nonRenamedClass {
+    color: white;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/constantConditional.gss b/user/test/com/google/gwt/resources/client/gss/constantConditional.gss
new file mode 100644
index 0000000..916cad0
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/constantConditional.gss
@@ -0,0 +1,23 @@
+@def WIDTH 10px;
+@def HEIGHT 10px;
+@def COLOR white;
+
+/* will be considered as outside the conditional */
+/* Compiler fails when lenient mode is disable */
+@if (eval("com.google.gwt.resources.client.gss.BooleanEval.FIRST")) {
+  @def WIDTH 15px;
+}
+
+@if (is("customProperty", "true")) {
+  @def COLOR black;
+}
+
+@if (!is("customProperty", "true")) {
+  @def HEIGHT 15px;
+}
+
+.foo {
+  width: WIDTH;
+  height: HEIGHT;
+  color: COLOR;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/constantRenaming.css b/user/test/com/google/gwt/resources/client/gss/constantRenaming.css
new file mode 100644
index 0000000..bedf379
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/constantRenaming.css
@@ -0,0 +1,8 @@
+@def myConstant 15px;
+@def myConstant 25px;
+@def myConstant 35px;
+@def myConstant 45px;
+
+@def my_constant 38px;
+@def ie6 0;
+@def gecko1_8 0;
diff --git a/user/test/com/google/gwt/resources/client/gss/cssWithConstant.gss b/user/test/com/google/gwt/resources/client/gss/cssWithConstant.gss
new file mode 100644
index 0000000..8ad9216
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/cssWithConstant.gss
@@ -0,0 +1,14 @@
+@def CONSTANT_ONE 15px;
+@def CONSTANT_TWO 5px;
+@def CONSTANT_THREE black;
+@def CONFLICT_CONSTANT_CLASS white;
+
+.className1 {
+    padding-right: CONSTANT_ONE;
+    padding-left: CONSTANT_TWO;
+    color: CONSTANT_THREE;
+}
+
+.conflictConstantClass{
+    color: CONFLICT_CONSTANT_CLASS;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/emptyClass.gss b/user/test/com/google/gwt/resources/client/gss/emptyClass.gss
new file mode 100644
index 0000000..ddf3789
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/emptyClass.gss
@@ -0,0 +1,2 @@
+.empty {
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/eval.gss b/user/test/com/google/gwt/resources/client/gss/eval.gss
new file mode 100644
index 0000000..dd5369c
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/eval.gss
@@ -0,0 +1,7 @@
+@def WHITE eval('com.google.gwt.resources.client.gss.Constants.white()');
+
+.someClass {
+    color: WHITE;
+    background-color: eval('com.google.gwt.resources.client.gss.Constants.red("blop")');
+    width: eval('com.google.gwt.resources.client.gss.Constants.width() - 15 + "px"')
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/externalClasses.gss b/user/test/com/google/gwt/resources/client/gss/externalClasses.gss
new file mode 100644
index 0000000..d7559cf
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/externalClasses.gss
@@ -0,0 +1,32 @@
+@external externalClass, externalWithoutMethod, "unobfuscated*";
+
+@if (is("test.property", "true")) {
+  @external externalClass2;
+}
+@else {
+  @external obfuscatedClass;
+}
+
+.obfuscatedClass {
+    width: 100%;
+}
+
+.externalClass {
+    height: 50px;
+}
+
+.externalClass2 {
+    height: 50px;
+}
+
+.unobfuscated {
+    height: 50px;
+}
+
+.unobfuscated2 {
+    height: 50px;
+}
+
+.externalWithoutMethod {
+  color: white;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/importCss.gss b/user/test/com/google/gwt/resources/client/gss/importCss.gss
new file mode 100644
index 0000000..195cf6c
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/importCss.gss
@@ -0,0 +1,2 @@
+.className{}
+.className2{}
diff --git a/user/test/com/google/gwt/resources/client/gss/importWithPrefixCss.gss b/user/test/com/google/gwt/resources/client/gss/importWithPrefixCss.gss
new file mode 100644
index 0000000..9e6d564
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/importWithPrefixCss.gss
@@ -0,0 +1 @@
+.className{}
diff --git a/user/test/com/google/gwt/resources/client/gss/lenientExternal.gss b/user/test/com/google/gwt/resources/client/gss/lenientExternal.gss
new file mode 100644
index 0000000..86760fd
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/lenientExternal.gss
@@ -0,0 +1,22 @@
+@external nonObfuscated;
+
+/* will be considered as outside the conditional */
+/* Compiler fails when lenient mode is disable */
+@if (eval("com.google.gwt.resources.client.gss.BooleanEval.FIRST")) {
+  @external nonObfuscated2;
+}
+
+@if (is("customProperty", "true")) {
+  @external nonObfuscated3;
+}
+
+@if (!is("customProperty", "true")) {
+  @external obfuscated;
+}
+
+.obfuscated,
+.nonObfuscated,
+.nonObfuscated2,
+.nonObfuscated3{
+  color: white;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/mixin.gss b/user/test/com/google/gwt/resources/client/gss/mixin.gss
new file mode 100644
index 0000000..c8084c3
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/mixin.gss
@@ -0,0 +1,8 @@
+@defmixin size(WIDTH, HEIGHT) {
+  width: WIDTH;
+  height: HEIGHT;
+}
+
+.someClass {
+  @mixin size(120px, 100px);
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/nonStandardAtRules.gss b/user/test/com/google/gwt/resources/client/gss/nonStandardAtRules.gss
new file mode 100644
index 0000000..8e87ec6
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/nonStandardAtRules.gss
@@ -0,0 +1,10 @@
+@extenal;
+@-moz-document;
+
+@supports ( display: flex ) {
+    body{ display: flex; }
+}
+
+.foo {
+    color:black;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/nonStandardFunctions.gss b/user/test/com/google/gwt/resources/client/gss/nonStandardFunctions.gss
new file mode 100644
index 0000000..b8d0823
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/nonStandardFunctions.gss
@@ -0,0 +1,5 @@
+.foo {
+    top: expression(15px);
+    color: green;
+    filter: progid:DXImageTransform.Microsoft.gradient(enabled='false', startColorstr=#550000FF, endColorstr=#55FFFF00)
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/notstrict.gss b/user/test/com/google/gwt/resources/client/gss/notstrict.gss
new file mode 100644
index 0000000..8da974b
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/notstrict.gss
@@ -0,0 +1,7 @@
+.someClass {
+    color: black;
+}
+
+.otherNotStrictClass {
+    color: white;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/resourceUrl.gss b/user/test/com/google/gwt/resources/client/gss/resourceUrl.gss
new file mode 100644
index 0000000..f77123d
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/resourceUrl.gss
@@ -0,0 +1,9 @@
+@def THE_DATA_URL resourceUrl('someDataResource');
+@def THE_IMG_URL resourceUrl('someImageResource');
+
+.someClass {
+    cursor: THE_DATA_URL;
+    background-image: THE_IMG_URL;
+    cursor: resourceUrl('someDataResource');
+    /* @alternate */ background-image: resourceUrl('someImageResource');
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/runtimeConditional.gss b/user/test/com/google/gwt/resources/client/gss/runtimeConditional.gss
new file mode 100644
index 0000000..681d209
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/runtimeConditional.gss
@@ -0,0 +1,65 @@
+.foo {
+    width: 100%;
+}
+
+/* will be evaluated to false) */
+@if (is("custom.one", "foo")) {
+    .foo {
+        color: black;
+    }
+}
+@elseif (eval("com.google.gwt.resources.client.gss.BooleanEval.FIRST")) {
+    /* will be evaluated to false) */
+    @if (is("custom.one", "foo")) {
+        .foo {
+            color: orange;
+        }
+    }
+    @else {
+        .foo {
+            color: purple;
+        }
+    }
+}
+@elseif (eval("com.google.gwt.resources.client.gss.BooleanEval.SECOND")) {
+    @if (eval("com.google.gwt.resources.client.gss.BooleanEval.THIRD")) {
+        .foo {
+            color: black;
+        }
+    } @else {
+        .foo {
+            color: khaki;
+        }
+    }
+}
+@else {
+    .foo {
+        color: gray;
+    }
+}
+
+/* will be evaluated to false */
+@if (is("custom.one", "foo")) {
+    .foo {
+        padding: 5px;
+    }
+}
+/* will be evaluated to true */
+@elseif (is("custom.two", "bar")) {
+    .foo {
+        padding: 10px;
+    }
+}
+/* will be never evaluated */
+@elseif (eval("com.google.gwt.resources.client.gss.BooleanEval.FIRST")) {
+    .foo {
+        padding: 20px;
+    }
+}
+
+/* Test that we can pass String parameters */
+@if (eval('java.lang.Boolean.valueOf("true")')) {
+  .foo {
+    margin: 100px;
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/scopeA.gss b/user/test/com/google/gwt/resources/client/gss/scopeA.gss
new file mode 100644
index 0000000..5becf4f
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/scopeA.gss
@@ -0,0 +1,3 @@
+.foo {
+    width: 100%;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/scopeA2.gss b/user/test/com/google/gwt/resources/client/gss/scopeA2.gss
new file mode 100644
index 0000000..9086c97
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/scopeA2.gss
@@ -0,0 +1,3 @@
+.foo {
+    width: 50%;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/scopeB.gss b/user/test/com/google/gwt/resources/client/gss/scopeB.gss
new file mode 100644
index 0000000..a0dc8fa
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/scopeB.gss
@@ -0,0 +1,3 @@
+.foo {
+    width: 25%;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/scopeC.gss b/user/test/com/google/gwt/resources/client/gss/scopeC.gss
new file mode 100644
index 0000000..7ee7868
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/scopeC.gss
@@ -0,0 +1,3 @@
+.foo {
+    width: 10%;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/sharedChild1.gss b/user/test/com/google/gwt/resources/client/gss/sharedChild1.gss
new file mode 100644
index 0000000..9d2538e
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/sharedChild1.gss
@@ -0,0 +1,9 @@
+.sharedClassName1 {
+    color: white;
+}
+.sharedClassName2 {
+    color: white;
+}
+.nonSharedClassName{
+    color: black;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/sharedChild2.gss b/user/test/com/google/gwt/resources/client/gss/sharedChild2.gss
new file mode 100644
index 0000000..9d2538e
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/sharedChild2.gss
@@ -0,0 +1,9 @@
+.sharedClassName1 {
+    color: white;
+}
+.sharedClassName2 {
+    color: white;
+}
+.nonSharedClassName{
+    color: black;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/sharedChild3.gss b/user/test/com/google/gwt/resources/client/gss/sharedChild3.gss
new file mode 100644
index 0000000..9d2538e
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/sharedChild3.gss
@@ -0,0 +1,9 @@
+.sharedClassName1 {
+    color: white;
+}
+.sharedClassName2 {
+    color: white;
+}
+.nonSharedClassName{
+    color: black;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/sharedGreatChild.gss b/user/test/com/google/gwt/resources/client/gss/sharedGreatChild.gss
new file mode 100644
index 0000000..9d2538e
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/sharedGreatChild.gss
@@ -0,0 +1,9 @@
+.sharedClassName1 {
+    color: white;
+}
+.sharedClassName2 {
+    color: white;
+}
+.nonSharedClassName{
+    color: black;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/sharedParent.gss b/user/test/com/google/gwt/resources/client/gss/sharedParent.gss
new file mode 100644
index 0000000..cfe6ca3
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/sharedParent.gss
@@ -0,0 +1,6 @@
+.sharedClassName1 {
+    color: white;
+}
+.sharedClassName2 {
+    color: white;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/someImageResource.png b/user/test/com/google/gwt/resources/client/gss/someImageResource.png
new file mode 100644
index 0000000..40b8683
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/someImageResource.png
Binary files differ
diff --git a/user/test/com/google/gwt/resources/client/gss/sprite.gss b/user/test/com/google/gwt/resources/client/gss/sprite.gss
new file mode 100644
index 0000000..db62f00
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/sprite.gss
@@ -0,0 +1,11 @@
+.someClassWithSprite {
+    gwt-sprite: 'someImageResource';
+}
+
+.someImageResource {
+    width: 15px;
+}
+
+.embeddedSprite {
+  gwt-sprite: 'embeddedImageResources.someResource';
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/testImportCss.gss b/user/test/com/google/gwt/resources/client/gss/testImportCss.gss
new file mode 100644
index 0000000..2d02c69
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/testImportCss.gss
@@ -0,0 +1,11 @@
+.other {
+    color: black;
+}
+
+.ImportCss-className .other {
+    color: white;
+}
+
+.testPrefix-className .other {
+    color: gray;
+}
diff --git a/user/test/com/google/gwt/resources/client/gss/withConstant.gss b/user/test/com/google/gwt/resources/client/gss/withConstant.gss
new file mode 100644
index 0000000..578b7d6
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/gss/withConstant.gss
@@ -0,0 +1,5 @@
+@def CONSTANT_ONE 15px;
+
+.classOne {
+    padding: CONSTANT_ONE;
+}
diff --git a/user/test/com/google/gwt/resources/gss/BaseGssTest.java b/user/test/com/google/gwt/resources/gss/BaseGssTest.java
new file mode 100644
index 0000000..90c5fd9
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/BaseGssTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.gss;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.google.gwt.thirdparty.common.css.SourceCode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssParser;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssParserException;
+import com.google.gwt.thirdparty.guava.common.base.Joiner;
+
+import junit.framework.TestCase;
+
+/**
+ * Base class for all test that want to build an ast based on css strings.
+ */
+public abstract class BaseGssTest extends TestCase {
+  /**
+   * Parse the css given in parameter and return the corresponding CssTree.
+   */
+  protected CssTree parseAndBuildTree(String source) {
+    CssTree cssTree = parse(source);
+
+    ErrorManager errorManager = mock(ErrorManager.class);
+
+    runPassesOnNewTree(cssTree, errorManager);
+
+    // we don't expect a failure here
+    verify(errorManager, never()).report(any(GssError.class));
+
+    return cssTree;
+  }
+
+  /**
+   * Run the Passes needed to have a well formed ast needed for the test.
+   * <p/>
+   * This method should be overridden by concrete class in order to run visitor they need to get
+   * a expected ast to use in the test.
+   */
+  protected void runPassesOnNewTree(CssTree cssTree, ErrorManager errorManager) {
+  }
+
+  protected String lines(String... lines) {
+    return Joiner.on("\n").join(lines);
+  }
+
+  private CssTree parse(String source) {
+    try {
+      return new GssParser(new SourceCode("test", source)).parse();
+    } catch (GssParserException e) {
+      fail(e.getMessage());
+    }
+    return null;
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/CssPrinterTest.java b/user/test/com/google/gwt/resources/gss/CssPrinterTest.java
new file mode 100644
index 0000000..1b5e676
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/CssPrinterTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.gss;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.resources.ext.ResourceContext;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunction;
+import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateConditionalNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateStandardAtRuleNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.ResolveCustomFunctionNodes;
+
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * Test for {@link CssPrinter}.
+ */
+public class CssPrinterTest extends BaseGssTest {
+  public void testWrapCssInValidJavaString() {
+    assertPrintedResult("(\".foo{width:15px}\")",
+        lines(
+            ".foo {",
+            "    width:15px;",
+            "}"
+        ));
+  }
+
+  public void testDontPrintExternalAtRule() {
+    assertPrintedResult("(\".foo{width:15px}\")",
+        lines(
+            "@external foo;",
+            ".foo {",
+            "    width:15px;",
+            "}"
+        ));
+  }
+
+  public void testRuntimeConditionalNode() {
+    String expectedCss = "((com.foo.BAR) ? (\".foo{color:black}\") + (" +
+        "(com.foo.BAR2) ? (\".foo{color:white}\") : (\".foo{color:gray}\"))" +
+        " : (com.foo.foo()) ? (\".foo{color:blue}\")" +
+        " : (\".foo{color:yellow}\"))";
+
+    assertPrintedResult(expectedCss,
+        lines(
+            "@if (eval('com.foo.BAR')) {",
+            "  .foo {",
+            "    color: black;",
+            "  }",
+            "",
+            "  @if (eval('com.foo.BAR2')) {",
+            "    .foo {",
+            "      color: white;",
+            "    }",
+            "  }",
+            "  @else {",
+            "    .foo {",
+            "      color: gray;",
+            "    }",
+            "  }",
+            "}",
+            "@elseif (eval('com.foo.foo()')) {",
+            "  .foo {",
+            "    color: blue;",
+            "  }",
+            "}",
+            "@else {",
+            "  .foo {",
+            "    color:yellow",
+            "  }",
+            "}"
+        ));
+  }
+
+  public void testCssDotPathNodePrint() {
+    assertPrintedResult("(\".foo{width:\" + (image().getWidth() + \"px\") + \"}\")",
+        lines(
+            ".foo {",
+            "  width: value('image.getWidth', 'px');",
+            "}"
+        ));
+  }
+
+  public void testCssJavaExpressionNodePrint() {
+    assertPrintedResult("(\".foo{width:\" " +
+            "+ (com.foo.bar.WIDTH) " +
+            "+ \";height:\" " +
+            "+ (com.foo.bar.height()) " +
+            "+ \"}\")",
+        lines(
+            ".foo {",
+            "  width: eval('com.foo.bar.WIDTH');",
+            "  height: eval('com.foo.bar.height()');",
+            "}"
+        ));
+  }
+
+  private void assertPrintedResult(String expectedCss, String source) {
+    CssTree cssTree = parseAndBuildTree(source);
+
+    CssPrinter pass = new CssPrinter(cssTree);
+    pass.runPass();
+
+    assertEquals(expectedCss, pass.getCompactPrintedString());
+  }
+
+  @Override
+  protected void runPassesOnNewTree(CssTree cssTree, ErrorManager errorManager) {
+    MutatingVisitController mutatingVisitController = cssTree.getMutatingVisitController();
+
+    new CreateConditionalNodes(mutatingVisitController, errorManager).runPass();
+    new CreateRuntimeConditionalNodes(mutatingVisitController).runPass();
+    new CreateStandardAtRuleNodes(mutatingVisitController, errorManager).runPass();
+
+    ResourceContext context = mockResourceContext();
+    Map<String, GssFunction> gssFunctionMap = new GwtGssFunctionMapProvider(context).get();
+    new ResolveCustomFunctionNodes(mutatingVisitController, errorManager,
+        gssFunctionMap, true, new HashSet<String>()).runPass();
+    new ExternalClassesCollector(mutatingVisitController, errorManager)
+        .runPass();
+  }
+
+  private ResourceContext mockResourceContext() {
+    ResourceContext context = mock(ResourceContext.class);
+    GeneratorContext generatorContext = mock(GeneratorContext.class);
+    TypeOracle oracle = mock(TypeOracle.class);
+    when(generatorContext.getTypeOracle()).thenReturn(oracle);
+    when(context.getGeneratorContext()).thenReturn(generatorContext);
+    return context;
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/ExtendedEliminateConditionalNodesTest.java b/user/test/com/google/gwt/resources/gss/ExtendedEliminateConditionalNodesTest.java
new file mode 100644
index 0000000..420991a
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/ExtendedEliminateConditionalNodesTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalBlockNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateConditionalNodes;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import java.util.Set;
+
+/**
+ * Test class for {@link ExtendedEliminateConditionalNodes}.
+ */
+public class ExtendedEliminateConditionalNodesTest extends BaseGssTest {
+  private Set<CssConditionalBlockNode> cssRuntimeConditionalBlockNodes;
+
+  public void testCompileTimeConditional() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (is('custom.one', 'foo') && is('custom.one', 'bar')) {",
+        "  .foo {",
+        "    padding: 5px;",
+        " }",
+        "}",
+        "",
+        "@elseif (!is('custom.two', 'foo') && is('custom.three', 'foo') " +
+            "|| is('custom.four', 'foo')) {",
+        "  .foo {",
+        "    padding: 15px;",
+        " }",
+        "}"));
+
+    Set<String> trueConditions = Sets.newHashSet("custom.one:foo", "custom.three:foo");
+
+    ExtendedEliminateConditionalNodes visitor =
+        new ExtendedEliminateConditionalNodes(cssTree.getMutatingVisitController(),
+            trueConditions, cssRuntimeConditionalBlockNodes);
+
+    // when
+    visitor.runPass();
+
+    // then
+    String expectedTreeToString = "[[.foo]{[padding:[15px]]}]";
+
+    assertEquals(expectedTreeToString, cssTree.getRoot().getBody().toString());
+  }
+
+  public void testIgnoreRuntimeConditional() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (eval('com.foo.BAR')) {",
+        "  .foo {",
+        "    padding: 5px;",
+        "  }",
+        "  @if (is('ie9')) {",
+        "    .foo {",
+        "      padding: 55px;",
+        "    }",
+        "  }",
+        "}",
+        "@elseif (eval('com.foo.bar()')) {",
+        "  .foo {",
+        "    padding: 15px;",
+        "  }",
+        "}"));
+
+    Set<String> trueConditions = Sets.newHashSet("user.agent:ie6");
+    ExtendedEliminateConditionalNodes visitor =
+        new ExtendedEliminateConditionalNodes(cssTree.getMutatingVisitController(),
+            trueConditions, cssRuntimeConditionalBlockNodes);
+
+    // when
+    visitor.runPass();
+
+    // then
+    // the  "  @if (is('ie9')) {", node is removed because it is evaluated to false
+    String expectedTreeToString =
+        "[[@if[Java expression : com.foo.BAR]{" +
+            "[[.foo]{" +
+            "[padding:[5px]]" +
+            "}]}, " +
+            "@elseif[Java expression : com.foo.bar()]{" +
+            "[[.foo]{" +
+            "[padding:[15px]]" +
+            "}]" +
+            "}]]";
+
+    assertEquals(expectedTreeToString, cssTree.getRoot().getBody().toString());
+  }
+
+  public void testRemoveUnreachableRuntimeConditional() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (is('foo', 'BAR')) {",
+        "  .foo {",
+        "    padding: 5px;",
+        "  }",
+        "}",
+        "@elseif (eval('com.foo.bar()')) {",
+        "  @if (eval('com.foo.FOO')) {",
+        "    .foo {",
+        "      padding: 15px;",
+        "    }",
+        "  }",
+        "  @else{",
+        "    .foo {",
+        "      padding: 15px;",
+        "    }",
+        "  }",
+        "}"));
+
+    Set<String> trueConditions = Sets.newHashSet("foo:BAR");
+    ExtendedEliminateConditionalNodes visitor =
+        new ExtendedEliminateConditionalNodes(cssTree.getMutatingVisitController(),
+            trueConditions, cssRuntimeConditionalBlockNodes);
+
+    // when
+    visitor.runPass();
+
+    // then
+    String expectedTreeToString = "[[.foo]{[padding:[5px]]}]";
+
+    assertEquals(expectedTreeToString, cssTree.getRoot().getBody().toString());
+  }
+
+  @Override
+  protected void runPassesOnNewTree(CssTree cssTree, ErrorManager errorManager) {
+    new CreateConditionalNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
+    new CreateRuntimeConditionalNodes(cssTree.getMutatingVisitController()).runPass();
+    new PermutationsCollector(cssTree.getMutatingVisitController(), errorManager).runPass();
+    RuntimeConditionalBlockCollector runtimeConditionalBlockCollector = new
+        RuntimeConditionalBlockCollector(cssTree.getVisitController());
+    runtimeConditionalBlockCollector.runPass();
+    cssRuntimeConditionalBlockNodes = runtimeConditionalBlockCollector.getRuntimeConditionalBlock();
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/ExternalClassesCollectorTest.java b/user/test/com/google/gwt/resources/gss/ExternalClassesCollectorTest.java
new file mode 100644
index 0000000..fda7ff5
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/ExternalClassesCollectorTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssCompositeValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssLiteralNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssStringNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssUnknownAtRuleNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import junit.framework.TestCase;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test for {@link com.google.gwt.resources.gss.ExternalClassesCollector}.
+ */
+public class ExternalClassesCollectorTest  extends TestCase {
+  private CssUnknownAtRuleNode cssUnknownAtRuleNode;
+  private CssLiteralNode atRuleNameNode;
+  private MutatingVisitController mutatingVisitController;
+  private CssCompositeValueNode atRuleParameters;
+  private ErrorManager errorManager;
+
+  @Override
+  protected void setUp() {
+    cssUnknownAtRuleNode = mock(CssUnknownAtRuleNode.class);
+    atRuleNameNode = mock(CssLiteralNode.class);
+    mutatingVisitController = mock(MutatingVisitController.class);
+    atRuleParameters = mock(CssCompositeValueNode.class);
+    errorManager = mock(ErrorManager.class);
+
+    when(cssUnknownAtRuleNode.getName()).thenReturn(atRuleNameNode);
+    when(cssUnknownAtRuleNode.getParameters()).thenReturn(
+        Lists.<CssValueNode>newArrayList(atRuleParameters));
+  }
+
+  public void testLeaveUnknownAtRule_notAnExternalAtRule_doNothing() {
+    // Given
+    ExternalClassesCollector externalClassesCollector = createAndInitExternalClassesCollector();
+    when(atRuleNameNode.getValue()).thenReturn("dummy");
+
+    // When
+    externalClassesCollector.leaveUnknownAtRule(cssUnknownAtRuleNode);
+
+    // Then
+    verify(cssUnknownAtRuleNode, never()).getParameters();
+    verify(mutatingVisitController, never()).removeCurrentNode();
+  }
+
+  public void testLeaveUnknownAtRule_simpleExternalAtRule_classesReturnByGetExternalClass() {
+    // Given
+    HashSet<String> styleClassSet = Sets.newHashSet();
+    ExternalClassesCollector externalClassesCollector = createAndInitExternalClassesCollector();
+    when(atRuleNameNode.getValue()).thenReturn("external");
+
+    List<CssValueNode> parameters = Lists.newArrayList(literalNode("externalClass"),
+        literalNode("externalClass2"));
+    when(atRuleParameters.getValues()).thenReturn(parameters);
+
+    // When
+    externalClassesCollector.leaveUnknownAtRule(cssUnknownAtRuleNode);
+
+    // Then
+    verify(cssUnknownAtRuleNode).getParameters();
+    verify(atRuleParameters).getValues();
+    verify(mutatingVisitController).removeCurrentNode();
+
+    Set<String> externalClasses = externalClassesCollector.getExternalClassNames(styleClassSet);
+    assertEquals(2, externalClasses.size());
+    assertTrue(externalClasses.contains("externalClass"));
+    assertTrue(externalClasses.contains("externalClass2"));
+  }
+
+  public void testLeaveUnknownAtRule_externalAtRuleWithMatchAllPrefix_allClassesAreExternals() {
+    // Given
+    HashSet<String> styleClassSet = Sets.newHashSet("class1", "class2", "class3");
+    ExternalClassesCollector externalClassesCollector = createAndInitExternalClassesCollector();
+    when(atRuleNameNode.getValue()).thenReturn("external");
+    List<CssValueNode> parameters = Lists.newArrayList(stringNode("*"));
+    when(atRuleParameters.getValues()).thenReturn(parameters);
+
+    // When
+    externalClassesCollector.leaveUnknownAtRule(cssUnknownAtRuleNode);
+
+    // Then
+    verify(cssUnknownAtRuleNode).getParameters();
+    verify(atRuleParameters).getValues();
+    verify(mutatingVisitController).removeCurrentNode();
+
+    Set<String> externalClasses = externalClassesCollector.getExternalClassNames(styleClassSet);
+    assertEquals(3, externalClasses.size());
+    assertTrue(externalClasses.contains("class1"));
+    assertTrue(externalClasses.contains("class2"));
+    assertTrue(externalClasses.contains("class3"));
+  }
+
+  public void testLeaveUnknownAtRule_externalAtRuleWithMatchAllPrefixThenAnotherExternalAtRule_anotherAtRuleNotProcessed() {
+    // Given
+    ExternalClassesCollector externalClassesCollector = createAndInitExternalClassesCollector();
+    when(atRuleNameNode.getValue()).thenReturn("external");
+    List<CssValueNode> parameters = Lists.newArrayList(stringNode("*"));
+    when(atRuleParameters.getValues()).thenReturn(parameters);
+    externalClassesCollector.leaveUnknownAtRule(cssUnknownAtRuleNode);
+    reset(mutatingVisitController);
+    CssUnknownAtRuleNode secondAtRuleNode = mock(CssUnknownAtRuleNode.class);
+    CssLiteralNode secondAtRuleNameNode = mock(CssLiteralNode.class);
+    when(secondAtRuleNameNode.getValue()).thenReturn("external");
+    when(secondAtRuleNode.getName()).thenReturn(secondAtRuleNameNode);
+
+    // When
+    externalClassesCollector.leaveUnknownAtRule(secondAtRuleNode);
+
+    // Then
+    verify(secondAtRuleNode, never()).getParameters();
+    verify(mutatingVisitController).removeCurrentNode();
+  }
+
+  public void testLeaveUnknownAtRule_externalAtRuleWithPrefix_classesMatchingThePrefixAreExternals() {
+    // Given
+    HashSet<String> styleClassSet = Sets.newHashSet("prefix", "prefix-class1",
+        "prefi-notexternal","external");
+    ExternalClassesCollector externalClassesCollector = createAndInitExternalClassesCollector();
+    when(atRuleNameNode.getValue()).thenReturn("external");
+    List<CssValueNode> parameters = Lists.newArrayList(literalNode("external"),
+        stringNode("prefix*"));
+    when(atRuleParameters.getValues()).thenReturn(parameters);
+
+    // When
+    externalClassesCollector.leaveUnknownAtRule(cssUnknownAtRuleNode);
+
+    // Then
+    verify(cssUnknownAtRuleNode).getParameters();
+    verify(atRuleParameters).getValues();
+    verify(mutatingVisitController).removeCurrentNode();
+
+    Set<String> externalClasses = externalClassesCollector.getExternalClassNames(styleClassSet);
+    assertEquals(3, externalClasses.size());
+    assertTrue(externalClasses.contains("prefix"));
+    assertTrue(externalClasses.contains("prefix-class1"));
+    assertTrue(externalClasses.contains("external"));
+  }
+
+  private CssValueNode literalNode(String externalClass) {
+    CssValueNode node = mock(CssLiteralNode.class);
+    when(node.getValue()).thenReturn(externalClass);
+    return node;
+  }
+
+  private CssValueNode stringNode(String selector) {
+    CssStringNode node = mock(CssStringNode.class);
+    when(node.getConcreteValue()).thenReturn(selector);
+    return node;
+  }
+
+  private ExternalClassesCollector createAndInitExternalClassesCollector() {
+    ExternalClassesCollector externalClassesCollector =
+        new ExternalClassesCollector(mutatingVisitController, errorManager);
+
+    // initialise the object but do nothing
+    externalClassesCollector.runPass();
+
+    return externalClassesCollector;
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/ImageSpriteCreatorTest.java b/user/test/com/google/gwt/resources/gss/ImageSpriteCreatorTest.java
new file mode 100644
index 0000000..7971872
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/ImageSpriteCreatorTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.gss;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.client.ImageResource.ImageOptions;
+import com.google.gwt.resources.client.ImageResource.RepeatStyle;
+import com.google.gwt.resources.ext.ResourceContext;
+import com.google.gwt.resources.gss.ImageSpriteCreator.MethodByPathHelper;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+
+/**
+ * Test class for {@link ImageSpriteCreator}.
+ */
+public class ImageSpriteCreatorTest extends BaseGssTest {
+  private JClassType imageResourceType;
+  private ResourceContext resourceContext;
+  private ErrorManager errorManager;
+  private MethodByPathHelper methodByPathHelper;
+  private JMethod imageMethod;
+
+  @Override
+  protected void setUp() {
+    errorManager = mock(ErrorManager.class);
+    imageResourceType = mock(JClassType.class);
+    resourceContext = mockResourceContext();
+    methodByPathHelper = mock(MethodByPathHelper.class);
+    imageMethod = mock(JMethod.class);
+  }
+
+  public void testInvalidSpriteWithSeveralValue() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        ".someClassWithSprite { ",
+        "  gwt-sprite: imageResource otherImageResource;",
+        "}"));
+
+    ImageSpriteCreator visitor = new ImageSpriteCreator(cssTree.getMutatingVisitController(),
+        resourceContext, errorManager, methodByPathHelper);
+
+    // when
+    visitor.runPass();
+
+    // then
+    verify(errorManager).report(any(GssError.class));
+  }
+
+  public void testInvalidSpriteImageMethodNotFound() throws NotFoundException {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        ".someClassWithSprite { ",
+        "  gwt-sprite: imageResource;",
+        "}"));
+    when(resourceContext.getClientBundleType()).thenReturn(mock(JClassType.class));
+    when(methodByPathHelper.getMethodByPath(any(ResourceContext.class), anyList(),
+        any(JClassType.class))).thenThrow(new NotFoundException(""));
+
+    ImageSpriteCreator visitor = new ImageSpriteCreator(cssTree.getMutatingVisitController(),
+        resourceContext, errorManager, methodByPathHelper);
+
+    // when
+    visitor.runPass();
+
+    // then
+    verify(errorManager).report(any(GssError.class));
+  }
+
+  public void testSimpleSpriteCreation() throws NotFoundException {
+    testSpriteCreation("no-repeat", null);
+  }
+
+  public void testSpriteCreationWithHorizontalRepeat() throws NotFoundException {
+    testSpriteCreation("repeat-x", RepeatStyle.Horizontal);
+  }
+
+  public void testSpriteCreationWithVerticalRepeat() throws NotFoundException {
+    testSpriteCreation("repeat-y", RepeatStyle.Vertical);
+  }
+
+  public void testSpriteCreationWithRepeat() throws NotFoundException {
+    testSpriteCreation("repeat", RepeatStyle.Both);
+  }
+
+  public void testSpriteCreationWithNoneRepeat() throws NotFoundException {
+    testSpriteCreation("no-repeat", RepeatStyle.None);
+  }
+
+  private void testSpriteCreation(String repeat, RepeatStyle repeatStyle) throws NotFoundException {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        ".someClassWithSprite { ",
+        "  color: white;",
+        "  gwt-sprite: 'imageResource';",
+        "  background-color: black;",
+        "}"));
+
+    when(methodByPathHelper.getMethodByPath(any(ResourceContext.class), anyList(),
+        any(JClassType.class))).thenReturn(imageMethod);
+
+    if (repeatStyle != null) {
+      // simulate a @ImageOptions(repeatStyle)
+      ImageOptions imageOptions = mock(ImageOptions.class);
+      when(imageOptions.repeatStyle()).thenReturn(repeatStyle);
+      when(imageMethod.getAnnotation(ImageOptions.class)).thenReturn(imageOptions);
+    }
+
+    ImageSpriteCreator visitor = new ImageSpriteCreator(cssTree.getMutatingVisitController(),
+        resourceContext, errorManager, methodByPathHelper);
+
+    // when
+    visitor.runPass();
+
+    // then
+    verify(errorManager, never()).report(any(GssError.class));
+
+    String widthRule = "[/* @alternate */]width:[ImageSpriteCreatorTest.this.imageResource()" +
+        ".getWidth() + \"px\"], ";
+    String heightRule = "[/* @alternate */]height:[ImageSpriteCreatorTest.this.imageResource()" +
+        ".getHeight() + \"px\"], ";
+
+    if (repeatStyle == RepeatStyle.Horizontal || repeatStyle == RepeatStyle.Both) {
+      widthRule = "";
+    }
+
+    if (repeatStyle == RepeatStyle.Vertical || repeatStyle == RepeatStyle.Both) {
+      heightRule = "";
+    }
+
+    String cssTreeStringExpected = "[" +
+        "[.someClassWithSprite]{" +
+        "[color:[white], " +
+        heightRule +
+        widthRule +
+        "[/* @alternate */]overflow:[hidden], " +
+        "[/* @alternate */]background:" +
+        "[url(ImageSpriteCreatorTest.this.imageResource().getSafeUri().asString()), " +
+        "\"-\" + ImageSpriteCreatorTest.this.imageResource().getLeft() + \"px\", " +
+        "\"-\" + ImageSpriteCreatorTest.this.imageResource().getTop() + \"px\",  " +
+        "" + repeat + "], " +
+        "background-color:[black]]" +
+        "}]";
+    assertEquals(cssTreeStringExpected, cssTree.getRoot().getBody().toString());
+  }
+
+  private ResourceContext mockResourceContext() {
+    ResourceContext context = mock(ResourceContext.class);
+    when(context.getImplementationSimpleSourceName()).thenReturn("ImageSpriteCreatorTest");
+    GeneratorContext generatorContext = mock(GeneratorContext.class);
+    TypeOracle oracle = mock(TypeOracle.class);
+
+    when(oracle.findType(ImageResource.class.getCanonicalName())).thenReturn(imageResourceType);
+
+    when(generatorContext.getTypeOracle()).thenReturn(oracle);
+    when(context.getGeneratorContext()).thenReturn(generatorContext);
+    return context;
+  }
+
+  @Override
+  protected void runPassesOnNewTree(CssTree cssTree, ErrorManager errorManager) {
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/PermutationsCollectorTest.java b/user/test/com/google/gwt/resources/gss/PermutationsCollectorTest.java
new file mode 100644
index 0000000..ba3e51e
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/PermutationsCollectorTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.gss;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateConditionalNodes;
+
+import java.util.List;
+
+/**
+ * Test class for {@link PermutationsCollector}.
+ */
+public class PermutationsCollectorTest extends BaseGssTest {
+
+  public void testPermutationCollector() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (is('custom.one', 'foo') && is('custom.one', 'bar')) {",
+        "  .foo {",
+        "    padding: 5px;",
+        " }",
+        "}",
+        "",
+        "@elseif (!is('custom.two', 'foo') && is('custom.three', 'foo') " +
+            "|| is('custom.four', 'foo')) {",
+        "  .foo {",
+        "    padding: 15px;",
+        " }",
+        "}"));
+
+    ErrorManager errorManager = mock(ErrorManager.class);
+    PermutationsCollector visitor = new PermutationsCollector(cssTree.getMutatingVisitController(),
+        errorManager);
+
+    // when
+    visitor.runPass();
+
+    // then
+    verify(errorManager, never()).report(any(GssError.class));
+
+    List<String> permutationAxis = visitor.getPermutationAxes();
+    assertEquals(4, permutationAxis.size());
+    assertTrue(permutationAxis.contains("custom.one"));
+    assertTrue(permutationAxis.contains("custom.two"));
+    assertTrue(permutationAxis.contains("custom.three"));
+    assertTrue(permutationAxis.contains("custom.four"));
+  }
+
+  public void testUserAgentShortcut() {
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (is('custom.one', 'foo') && is('ie6')) {",
+        "  .foo {",
+        "    padding: 5px;",
+        " }",
+        "}"));
+
+    ErrorManager errorManager = mock(ErrorManager.class);
+    PermutationsCollector visitor = new PermutationsCollector(cssTree.getMutatingVisitController(),
+        errorManager);
+
+    // when
+    visitor.runPass();
+
+    // then
+    verify(errorManager, never()).report(any(GssError.class));
+
+    List<String> permutationAxis = visitor.getPermutationAxes();
+    assertEquals(2, permutationAxis.size());
+    assertTrue(permutationAxis.contains("custom.one"));
+    assertTrue(permutationAxis.contains("user.agent"));
+  }
+
+  public void testRuntimeConditionAreIgnored() {
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (eval('com.foo.BAR')) {",
+        "  .foo {",
+        "    padding: 5px;",
+        "  }",
+        "}",
+        "@elseif (eval('com.foo.bar()')) {",
+        "  @if (is('custom.one', 'foo')) {",
+        "    .foo {",
+        "      padding: 15px;",
+        "    }",
+        "  }",
+        "  @else{",
+        "    .foo {",
+        "      padding: 15px;",
+        "    }",
+        "  }",
+        "}"));
+
+    ErrorManager errorManager = mock(ErrorManager.class);
+    PermutationsCollector visitor = new PermutationsCollector(cssTree.getMutatingVisitController(),
+        errorManager);
+
+    // when
+    visitor.runPass();
+
+    // then
+    verify(errorManager, never()).report(any(GssError.class));
+
+    List<String> permutationAxis = visitor.getPermutationAxes();
+    assertEquals(1, permutationAxis.size());
+    assertTrue(permutationAxis.contains("custom.one"));
+  }
+
+  public void testInvalidConditionThrowsAnError() {
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (evaluate('com.foo.BAR')) {",
+        "  .foo {",
+        "    padding: 5px;",
+        "  }",
+        "}"
+    ));
+
+    ErrorManager errorManager = mock(ErrorManager.class);
+    PermutationsCollector visitor = new PermutationsCollector(cssTree.getMutatingVisitController(),
+        errorManager);
+
+    // when
+    visitor.runPass();
+
+    // then
+    verify(errorManager).report(any(GssError.class));
+    assertEquals(0, visitor.getPermutationAxes().size());
+  }
+
+  @Override
+  protected void runPassesOnNewTree(CssTree cssTree, ErrorManager errorManager) {
+    new CreateConditionalNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
+    new CreateRuntimeConditionalNodes(cssTree.getMutatingVisitController()).runPass();
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/RecordingBidiFlipperTest.java b/user/test/com/google/gwt/resources/gss/RecordingBidiFlipperTest.java
new file mode 100644
index 0000000..996bc47
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/RecordingBidiFlipperTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+
+/**
+ * Test class for {@link RecordingBidiFlipper}.
+ */
+public class RecordingBidiFlipperTest extends BaseGssTest {
+
+  public void testFlipWithRuleValueIsRecorded() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        ".foo {",
+        "  background-position: 10% 20%;",
+        "}"));
+
+    RecordingBidiFlipper visitor = new RecordingBidiFlipper(cssTree.getMutatingVisitController(),
+        false, false, true);
+
+    // when
+    visitor.runPass();
+
+    // then
+    assertEquals(true, visitor.nodeFlipped());
+  }
+
+  public void testFlipWithRuleNameIsRecorded() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        ".foo {",
+        "  padding-left: 15px;",
+        "}"));
+
+    RecordingBidiFlipper visitor = new RecordingBidiFlipper(cssTree.getMutatingVisitController(),
+        false, false, true);
+
+    // when
+    visitor.runPass();
+
+    // then
+    assertEquals(true, visitor.nodeFlipped());
+  }
+
+  public void testNoFlipNothingIsRecorder() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        ".foo {",
+        "  background-color: black;",
+        "  color: white;",
+        "  font-size: 12px;",
+        "  padding: 5px;",
+        "}"));
+
+    RecordingBidiFlipper visitor = new RecordingBidiFlipper(cssTree.getMutatingVisitController(),
+        false, false, true);
+
+    // when
+    visitor.runPass();
+
+    // then
+    assertEquals(false, visitor.nodeFlipped());
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/RenamingSubstitutionMapTest.java b/user/test/com/google/gwt/resources/gss/RenamingSubstitutionMapTest.java
new file mode 100644
index 0000000..e2d044c
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/RenamingSubstitutionMapTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
+
+import junit.framework.TestCase;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Test for {@link com.google.gwt.resources.gss.RenamingSubstitutionMap}.
+ */
+public class RenamingSubstitutionMapTest extends TestCase {
+
+  public void testComputeReplacementMapWithPrefixedClasses() {
+    Map<String, Map<String, String>> replacementWithPrefix = new HashMap<String, Map<String,
+        String>>();
+    replacementWithPrefix.put("", ImmutableMap.of("class1", "obfuscated1", "class2",
+        "obfuscated2"));
+    replacementWithPrefix.put("prefix1-", ImmutableMap.of("class3", "obfuscated3", "class4",
+        "obfuscated4"));
+
+    RenamingSubstitutionMap substitutionMap = new RenamingSubstitutionMap(replacementWithPrefix);
+
+    assertEquals("obfuscated1", substitutionMap.get("class1"));
+    assertEquals("obfuscated2", substitutionMap.get("class2"));
+    assertEquals("obfuscated3", substitutionMap.get("prefix1-class3"));
+    assertEquals("obfuscated4", substitutionMap.get("prefix1-class4"));
+    assertTrue(substitutionMap.getExternalClassCandidates().isEmpty());
+  }
+
+  public void testComputeReplacementMapWithMissingCLass() {
+    Map<String, Map<String, String>> replacementWithPrefix = new HashMap<String, Map<String,
+        String>>();
+    replacementWithPrefix.put("", ImmutableMap.of("class1", "obfuscated1"));
+
+    RenamingSubstitutionMap substitutionMap = new RenamingSubstitutionMap(replacementWithPrefix);
+
+    assertEquals("obfuscated1", substitutionMap.get("class1"));
+    assertEquals("otherClass", substitutionMap.get("otherClass"));
+
+    assertFalse(substitutionMap.getExternalClassCandidates().isEmpty());
+    assertTrue(substitutionMap.getExternalClassCandidates().contains("otherClass"));
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/ResourceUrlFunctionTest.java b/user/test/com/google/gwt/resources/gss/ResourceUrlFunctionTest.java
new file mode 100644
index 0000000..0dbe206
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/ResourceUrlFunctionTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.gss;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.google.gwt.core.ext.GeneratorContext;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.ImageResource;
+import com.google.gwt.resources.ext.ResourceContext;
+import com.google.gwt.resources.gss.ResourceUrlFunction.MethodByPathHelper;
+import com.google.gwt.resources.gss.ast.CssJavaExpressionNode;
+import com.google.gwt.thirdparty.common.css.SourceCodeLocation;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssFunctionArgumentsNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssFunctionNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunctionException;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+/**
+ * Test for {@link ResourceUrlFunction}.
+ */
+public class ResourceUrlFunctionTest extends TestCase {
+  private static String JAVA_EXPRESSION_PATTERN = "ResourceUrlFunctionTest.this.%s.getSafeUri()" +
+      ".asString()";
+  private ResourceContext resourceContext;
+  private ErrorManager errorManager;
+  private MethodByPathHelper methodByPathHelper;
+  private ResourceUrlFunction resourceUrlFunction;
+  private JClassType dataResourceType;
+  private JClassType imageResourceType;
+
+  @Override
+  protected void setUp() {
+    errorManager = mock(ErrorManager.class);
+    methodByPathHelper = mock(MethodByPathHelper.class);
+    dataResourceType = mock(JClassType.class);
+    imageResourceType = mock(JClassType.class);
+    resourceContext = mockResourceContext();
+
+    resourceUrlFunction = new ResourceUrlFunction(resourceContext, methodByPathHelper);
+  }
+
+  public void testValidImageResource() throws GssFunctionException {
+    // given
+    List<CssValueNode> input = createInput("image");
+    when(dataResourceType.isAssignableFrom(any(JClassType.class))).thenReturn(false);
+    when(imageResourceType.isAssignableFrom(any(JClassType.class))).thenReturn(true);
+
+    // when
+    List<CssValueNode> result = resourceUrlFunction.getCallResultNodes(input, errorManager);
+
+    // then
+    assertResultIsValid(result, JAVA_EXPRESSION_PATTERN.replace("%s", "image()"));
+  }
+
+  public void testValidDataResource() throws GssFunctionException {
+    // given
+    List<CssValueNode> input = createInput("data");
+    when(dataResourceType.isAssignableFrom(any(JClassType.class))).thenReturn(true);
+    when(imageResourceType.isAssignableFrom(any(JClassType.class))).thenReturn(false);
+
+    // when
+    List<CssValueNode> result = resourceUrlFunction.getCallResultNodes(input, errorManager);
+
+    // then
+    assertResultIsValid(result, JAVA_EXPRESSION_PATTERN.replace("%s", "data()"));
+  }
+
+  public void testMultiplePath() throws GssFunctionException {
+    // given
+    List<CssValueNode> input = createInput("method1.method2.resource");
+    when(dataResourceType.isAssignableFrom(any(JClassType.class))).thenReturn(true);
+
+    // when
+    List<CssValueNode> result = resourceUrlFunction.getCallResultNodes(input, errorManager);
+
+    // then
+    assertResultIsValid(result, JAVA_EXPRESSION_PATTERN.replace("%s",
+        "method1().method2().resource()"));
+  }
+
+  public void testInvalidResource() {
+    // given
+    List<CssValueNode> input = createInput("invalidResource");
+    when(dataResourceType.isAssignableFrom(any(JClassType.class))).thenReturn(false);
+    when(imageResourceType.isAssignableFrom(any(JClassType.class))).thenReturn(false);
+
+    // when
+    try {
+      resourceUrlFunction.getCallResultNodes(input, errorManager);
+    } catch (GssFunctionException expected) {
+      // then
+      verify(errorManager).report(any(GssError.class));
+      return;
+    }
+
+    fail("GssFunctionException expected");
+  }
+
+  public void testInvalidPath() throws NotFoundException {
+    // given
+    List<CssValueNode> input = createInput("invalid.path");
+    when(methodByPathHelper.getReturnType(any(ResourceContext.class),
+        anyList())).thenThrow(NotFoundException.class);
+
+    // when
+    try {
+      resourceUrlFunction.getCallResultNodes(input, errorManager);
+    } catch (GssFunctionException expected) {
+      // then
+      verify(errorManager).report(any(GssError.class));
+      return;
+    }
+
+    fail("GssFunctionException expected");
+  }
+
+  private void assertResultIsValid(List<CssValueNode> result, String expectedJavaExpression) {
+    assertEquals(1, result.size());
+    assertTrue(result.get(0) instanceof CssFunctionNode);
+
+    CssFunctionArgumentsNode arguments = ((CssFunctionNode) result.get(0)).getArguments();
+    assertEquals(1, arguments.numChildren());
+    assertTrue(arguments.getChildAt(0) instanceof CssJavaExpressionNode);
+
+    CssJavaExpressionNode javaExpressionNode = (CssJavaExpressionNode) arguments.getChildAt(0);
+    assertEquals(expectedJavaExpression, javaExpressionNode.getValue());
+
+    verify(errorManager, never()).report(any(GssError.class));
+    verify(errorManager, never()).reportWarning(any(GssError.class));
+  }
+
+  private List<CssValueNode> createInput(String value) {
+    CssValueNode input = mock(CssValueNode.class);
+    when(input.getValue()).thenReturn(value);
+
+    SourceCodeLocation sourceCodeLocation = mock(SourceCodeLocation.class);
+    when(input.getSourceCodeLocation()).thenReturn(sourceCodeLocation);
+
+    return ImmutableList.of(input);
+  }
+
+  private ResourceContext mockResourceContext() {
+    ResourceContext context = mock(ResourceContext.class);
+    when(context.getImplementationSimpleSourceName()).thenReturn("ResourceUrlFunctionTest");
+    GeneratorContext generatorContext = mock(GeneratorContext.class);
+    TypeOracle oracle = mock(TypeOracle.class);
+
+    when(oracle.findType(DataResource.class.getCanonicalName())).thenReturn(dataResourceType);
+    when(oracle.findType(ImageResource.class.getCanonicalName())).thenReturn(imageResourceType);
+
+    when(generatorContext.getTypeOracle()).thenReturn(oracle);
+    when(context.getGeneratorContext()).thenReturn(generatorContext);
+    return context;
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/RuntimeConditionalBlockCollectorTest.java b/user/test/com/google/gwt/resources/gss/RuntimeConditionalBlockCollectorTest.java
new file mode 100644
index 0000000..d3529e5
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/RuntimeConditionalBlockCollectorTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.gss;
+
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssConditionalBlockNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateConditionalNodes;
+
+import java.util.Set;
+
+/**
+ * Test class for {@link RuntimeConditionalBlockCollector}.
+ */
+public class RuntimeConditionalBlockCollectorTest extends BaseGssTest {
+
+  public void testCollectRuntimeConditionalBlock() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (eval('com.foo.BAR')) {",
+        "  .foo {",
+        "    padding: 5px;",
+        "  }",
+        "  @if (is('ie9')) {",
+        "    .foo {",
+        "      padding: 55px;",
+        "    }",
+        "  }",
+        "}",
+        "@elseif (eval('com.foo.bar()')) {",
+        "  @if (eval('com.foo.FOO')) {",
+        "    .foo {",
+        "      padding: 15px;",
+        "    }",
+        "  }",
+        "  @else{",
+        "    .foo {",
+        "      padding: 15px;",
+        "    }",
+        "  }",
+        "}",
+        "@if (is('ie6')) {",
+        "  .foo {",
+        "    padding: 25px;",
+        "  }",
+        "}",
+        "@elseif (eval('com.foo.BAR')) {",
+        "  .foo {",
+        "    padding: 35px;",
+        "  }",
+        "}"));
+
+    RuntimeConditionalBlockCollector visitor =
+        new RuntimeConditionalBlockCollector(cssTree.getMutatingVisitController());
+
+    // when
+    visitor.runPass();
+
+    // then
+    Set<CssConditionalBlockNode> runtimeConditionalNodes = visitor.getRuntimeConditionalBlock();
+
+    // We have 4 conditional blocks (@if, @elsif @ else block), 3 are with runtime condition
+    assertEquals(3, runtimeConditionalNodes.size());
+  }
+
+  @Override
+  protected void runPassesOnNewTree(CssTree cssTree, ErrorManager errorManager) {
+    new CreateConditionalNodes(cssTree.getMutatingVisitController(), errorManager).runPass();
+    new CreateRuntimeConditionalNodes(cssTree.getMutatingVisitController()).runPass();
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/ValidateRuntimeConditionalNodeTest.java b/user/test/com/google/gwt/resources/gss/ValidateRuntimeConditionalNodeTest.java
new file mode 100644
index 0000000..18870f8
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/ValidateRuntimeConditionalNodeTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.gss;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssTree;
+import com.google.gwt.thirdparty.common.css.compiler.ast.ErrorManager;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssError;
+import com.google.gwt.thirdparty.common.css.compiler.ast.MutatingVisitController;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateConditionalNodes;
+import com.google.gwt.thirdparty.common.css.compiler.passes.CreateDefinitionNodes;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import org.mockito.verification.VerificationMode;
+
+/**
+ * Test class for {@link ValidateRuntimeConditionalNode}.
+ */
+public class ValidateRuntimeConditionalNodeTest extends BaseGssTest {
+
+  public void testValidRuntimeConditional() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (eval('com.foo.BAR')) {",
+        "  .foo {",
+        "    padding: 5px;",
+        "  }",
+        "  @if (is('ie9')) {",
+        "    .foo {",
+        "      padding: 55px;",
+        "    }",
+        "  }",
+        "}",
+        "@elseif (eval('com.foo.bar()')) {",
+        "  @if (eval('com.foo.FOO')) {",
+        "    .foo {",
+        "      padding: 15px;",
+        "    }",
+        "  }",
+        "  @else{",
+        "    .foo {",
+        "      padding: 15px;",
+        "    }",
+        "  }",
+        "}",
+        "@if (is('ie6')) {",
+        "  .foo {",
+        "    padding: 25px;",
+        "  }",
+        "}",
+        "@elseif (eval('com.foo.BAR')) {",
+        "  .foo {",
+        "    padding: 35px;",
+        "  }",
+        "}"));
+
+    ErrorManager errorManager = mock(ErrorManager.class);
+    boolean lenient = false;
+    ValidateRuntimeConditionalNode visitor = new ValidateRuntimeConditionalNode(
+        cssTree.getMutatingVisitController(), errorManager, lenient);
+
+    // when
+    visitor.runPass();
+
+    // then
+    verify(errorManager, never()).report(any(GssError.class));
+    verify(errorManager, never()).reportWarning(any(GssError.class));
+  }
+
+  public void testInvalidRuntimeConditionalWithExternalAtRuleLenient() {
+   testInvalidRuntimeConditionalWithExternal(true);
+  }
+
+  public void testInvalidRuntimeConditionalWithExternalAtRuleNotLenient() {
+    testInvalidRuntimeConditionalWithExternal(false);
+  }
+
+  private void testInvalidRuntimeConditionalWithExternal(boolean lenient) {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (eval('com.foo.BAR')) {",
+        "  @external foo;",
+        "}"));
+
+    ErrorManager errorManager = mock(ErrorManager.class);
+    ValidateRuntimeConditionalNode visitor = new ValidateRuntimeConditionalNode(
+        cssTree.getMutatingVisitController(), errorManager, lenient);
+
+    // when
+    visitor.runPass();
+
+    // then
+    VerificationMode error = lenient ? never() : times(1);
+    VerificationMode warning = lenient ? times(1) : never();
+
+    verify(errorManager, error).report(any(GssError.class));
+    verify(errorManager, warning).reportWarning(any(GssError.class));
+  }
+
+  public void testInvalidRuntimeConditionalWithConstantDefLenient() {
+    testInvalidRuntimeConditionalWithConstantDef(true);
+  }
+
+  public void testInvalidRuntimeConditionalWithConstantDefNotLenient() {
+    testInvalidRuntimeConditionalWithConstantDef(false);
+  }
+
+  private void testInvalidRuntimeConditionalWithConstantDef(boolean lenient) {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (eval('com.foo.BAR')) {",
+        "  @def FOO 5px;",
+        "}"));
+
+    ErrorManager errorManager = mock(ErrorManager.class);
+    ValidateRuntimeConditionalNode visitor = new ValidateRuntimeConditionalNode(
+        cssTree.getMutatingVisitController(), errorManager, lenient);
+
+    // when
+    visitor.runPass();
+
+    // then
+    VerificationMode error = lenient ? never() : times(1);
+    VerificationMode warning = lenient ? times(1) : never();
+
+    verify(errorManager, error).report(any(GssError.class));
+    verify(errorManager, warning).reportWarning(any(GssError.class));
+  }
+
+  public void testValidCompileTimeConditionalWithConstantDef() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (is('foo', 'bar')) {",
+        "  @def FOO 5px;",
+        "}"));
+
+    ErrorManager errorManager = mock(ErrorManager.class);
+    ValidateRuntimeConditionalNode visitor = new ValidateRuntimeConditionalNode(
+        cssTree.getMutatingVisitController(), errorManager, false);
+
+    // when
+    visitor.runPass();
+
+    // then
+    verify(errorManager, never()).report(any(GssError.class));
+    verify(errorManager, never()).reportWarning(any(GssError.class));
+  }
+
+  public void testValidCompileTimeConditionalWithExternal() {
+    // given
+    CssTree cssTree = parseAndBuildTree(lines(
+        "@if (is('foo', 'bar')) {",
+        "  @external bar;",
+        "}"));
+
+    ErrorManager errorManager = mock(ErrorManager.class);
+    ValidateRuntimeConditionalNode visitor = new ValidateRuntimeConditionalNode(
+        cssTree.getMutatingVisitController(), errorManager, false);
+
+    // when
+    visitor.runPass();
+
+    // then
+    verify(errorManager, never()).report(any(GssError.class));
+    verify(errorManager, never()).reportWarning(any(GssError.class));
+  }
+
+  @Override
+  protected void runPassesOnNewTree(CssTree cssTree, ErrorManager errorManager) {
+    MutatingVisitController mutatingVisitController = cssTree.getMutatingVisitController();
+    new CreateDefinitionNodes(mutatingVisitController, errorManager).runPass();
+    new CreateConditionalNodes(mutatingVisitController, errorManager).runPass();
+    new CreateRuntimeConditionalNodes(mutatingVisitController).runPass();
+
+    new PermutationsCollector(mutatingVisitController, errorManager).runPass();
+    RuntimeConditionalBlockCollector runtimeConditionalBlockCollector = new
+        RuntimeConditionalBlockCollector(mutatingVisitController);
+    runtimeConditionalBlockCollector.runPass();
+
+    new ExtendedEliminateConditionalNodes(mutatingVisitController, Sets.newHashSet("foo:bar"),
+        runtimeConditionalBlockCollector.getRuntimeConditionalBlock()).runPass();
+  }
+}
diff --git a/user/test/com/google/gwt/resources/gss/ValueFunctionTest.java b/user/test/com/google/gwt/resources/gss/ValueFunctionTest.java
new file mode 100644
index 0000000..7532a0f
--- /dev/null
+++ b/user/test/com/google/gwt/resources/gss/ValueFunctionTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2014 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.gwt.resources.gss;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.gwt.resources.gss.ast.CssDotPathNode;
+import com.google.gwt.thirdparty.common.css.SourceCodeLocation;
+import com.google.gwt.thirdparty.common.css.compiler.ast.CssValueNode;
+import com.google.gwt.thirdparty.common.css.compiler.ast.GssFunctionException;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableList.Builder;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+/**
+ * Test for {@link ValueFunction}.
+ */
+public class ValueFunctionTest extends TestCase {
+
+  public void testValueFunction() throws GssFunctionException {
+    // given
+    List<CssValueNode> input = createInput("image.width");
+
+    // when
+    List<CssValueNode> result = new ValueFunction().getCallResultNodes(input, null);
+
+    // then
+    assertResultIsValid(result, "image().width()");
+  }
+
+  public void testValueFunctionWithPrefix() throws GssFunctionException {
+    // given
+    List<CssValueNode> input = createInput("image.width", "px");
+
+    // when
+    List<CssValueNode> result = new ValueFunction().getCallResultNodes(input, null);
+
+    // then
+    assertResultIsValid(result, "image().width() + \"px\"");
+  }
+
+  public void testValueFunctionWithPrefixAndSuffix() throws GssFunctionException {
+    // given
+    List<CssValueNode> input = createInput("image.width", "px", "-");
+
+    // when
+    List<CssValueNode> result = new ValueFunction().getCallResultNodes(input, null);
+
+    // then
+    assertResultIsValid(result, "\"-\" + image().width() + \"px\"");
+  }
+
+  private void assertResultIsValid(List<CssValueNode> result, String expectedJavaExpression) {
+    assertEquals(1, result.size());
+    assertTrue(result.get(0) instanceof CssDotPathNode);
+
+    CssDotPathNode cssDotPathNode = (CssDotPathNode) result.get(0);
+
+    assertEquals(expectedJavaExpression, cssDotPathNode.getValue());
+  }
+
+  private List<CssValueNode> createInput(String... argumentValue) {
+    Builder<CssValueNode> listBuilder = ImmutableList.builder();
+
+    for (String arg: argumentValue) {
+      CssValueNode input = mock(CssValueNode.class);
+      when(input.getValue()).thenReturn(arg);
+
+      SourceCodeLocation sourceCodeLocation = mock(SourceCodeLocation.class);
+      when(input.getSourceCodeLocation()).thenReturn(sourceCodeLocation);
+
+      listBuilder.add(input);
+    }
+
+    return listBuilder.build();
+  }
+}