Add two new ClientBundle DataResource annotations:

1. @DoNotEmbed which allows a DataResource to be designated as not embeddable
Although in pre IE8, where there is no data URL support, the following test is a no-op
DataResourceDoNotEmbedTest#testDoNotEmbedAnnotationMissingShouldEmbed

2. @MimeType(String) which allows a MIME Type to be specified for use in embedded resources


Related changes:

3. Allow MIME Types with double quotes as per RFC 4281 e.g. video/3gpp2; codecs="sevc, s263"

4. While in the neighborhood, adding @Document to ClientBundle's @Source annotation

Review at http://gwt-code-reviews.appspot.com/714801

Review by: robertvawter@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8479 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/resources/client/ClientBundle.java b/user/src/com/google/gwt/resources/client/ClientBundle.java
index 7bbd76c..bf4b34e 100644
--- a/user/src/com/google/gwt/resources/client/ClientBundle.java
+++ b/user/src/com/google/gwt/resources/client/ClientBundle.java
@@ -18,6 +18,7 @@
 import com.google.gwt.resources.ext.ResourceGeneratorType;
 import com.google.gwt.resources.rg.BundleResourceGenerator;
 
+import java.lang.annotation.Documented;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -37,6 +38,7 @@
    * Specifies the classpath location of the resource or resources associated
    * with the {@link ResourcePrototype}.
    */
+  @Documented
   @Retention(RetentionPolicy.RUNTIME)
   @Target(ElementType.METHOD)
   public @interface Source {
diff --git a/user/src/com/google/gwt/resources/client/DataResource.java b/user/src/com/google/gwt/resources/client/DataResource.java
index fd3d2cb..e87d0c6 100644
--- a/user/src/com/google/gwt/resources/client/DataResource.java
+++ b/user/src/com/google/gwt/resources/client/DataResource.java
@@ -18,12 +18,43 @@
 import com.google.gwt.resources.ext.ResourceGeneratorType;
 import com.google.gwt.resources.rg.DataResourceGenerator;
 
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
 /**
- * A non-text resource.
+ * A non-text resource. Use {@link MimeType} to provide MIME Types for embedded
+ * resources which may not be determined automatically at compile time. Use
+ * {@link DoNotEmbed} to prevent a resource from being embedded.
  */
 @ResourceGeneratorType(DataResourceGenerator.class)
 public interface DataResource extends ResourcePrototype {
   /**
+   * Specifies that the resource or resources associated with the
+   * {@link ResourcePrototype} should not be embedded into the compiled output.
+   * This may be useful, for exmaple, when it a particular browser or plugin is
+   * unable to handle RFC 2397 data URLs.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  public @interface DoNotEmbed {
+  }
+
+  /**
+   * Specifies the MIME Type of the resource or resources associated with the
+   * {@link ResourcePrototype}.
+   */
+  @Documented
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.METHOD)
+  public @interface MimeType {
+    String value();
+  }
+
+  /**
    * Retrieves a URL by which the contents of the resource can be obtained. This
    * will be an absolute URL.
    */
diff --git a/user/src/com/google/gwt/resources/ext/ResourceContext.java b/user/src/com/google/gwt/resources/ext/ResourceContext.java
index f327eee..7c5f2ff 100644
--- a/user/src/com/google/gwt/resources/ext/ResourceContext.java
+++ b/user/src/com/google/gwt/resources/ext/ResourceContext.java
@@ -31,9 +31,10 @@
  * Depending on the optimizations made by the implementation of {@link #deploy},
  * the resulting URL may or may not be compatible with standard
  * {@link com.google.gwt.http.client.RequestBuilder} / XMLHttpRequest security
- * semantics. If the resource is intended to be used with XHR, the
- * <code>xhrCompatible</code> paramater should be set to <code>true</code> when
- * invoking {@link #deploy}.
+ * semantics. If the resource is intended to be used with XHR, or if there are
+ * other reasons why embedding the resource is undesirable such as known
+ * incompatibilities, the <code>forceExternal</code> parameter should be set to
+ * <code>true</code> when invoking {@link #deploy}.
  * </p>
  */
 public interface ResourceContext {
@@ -48,13 +49,34 @@
    *          resource
    * @param mimeType the MIME type of the data being provided
    * @param data the bytes to add to the output
-   * @param xhrCompatible enforces compatibility with security restrictions if
-   *          the resource is intended to be accessed via an XMLHttpRequest.
+   * @param forceExternal prevents embedding of the resource, e.g. in case of
+   *          known incompatibilities or for example to enforce compatibility
+   *          with security restrictions if the resource is intended to be
+   *          accessed via an XMLHttpRequest
    * @return a Java expression which will evaluate to the location of the
-   *         provided resource at runtime.
+   *         provided resource at runtime
    */
   String deploy(String suggestedFileName, String mimeType, byte[] data,
-      boolean xhrCompatible) throws UnableToCompleteException;
+      boolean forceExternal) throws UnableToCompleteException;
+
+  /**
+   * Cause a specific collection of bytes to be available in the program's
+   * compiled output. The return value of this method is a Java expression which
+   * will evaluate to the location of the resource at runtime. The exact format
+   * should not be depended upon.
+   *
+   * @param resource the resource to add to the compiled output
+   * @param forceExternal prevents embedding of the resource, e.g. in case of
+   *          known incompatibilities or for example to enforce compatibility
+   *          with security restrictions if the resource is intended to be
+   *          accessed via an XMLHttpRequest
+   * @return a Java expression which will evaluate to the location of the
+   *         provided resource at runtime
+   * @deprecated use {@link #deploy(URL, String, boolean)} instead
+   */
+  @Deprecated
+  String deploy(URL resource, boolean forceExternal)
+      throws UnableToCompleteException;
 
   /**
    * Cause a specific collection of bytes to be available in the program's
@@ -63,12 +85,15 @@
    * should not be depended upon.
    * 
    * @param resource the resource to add to the compiled output
-   * @param xhrCompatible enforces compatibility with security restrictions if
-   *          the resource is intended to be accessed via an XMLHttpRequest.
+   * @param mimeType optional MIME Type to be used for an embedded resource
+   * @param forceExternal prevents embedding of the resource, e.g. in case of
+   *          known incompatibilities or for example to enforce compatibility
+   *          with security restrictions if the resource is intended to be
+   *          accessed via an XMLHttpRequest
    * @return a Java expression which will evaluate to the location of the
-   *         provided resource at runtime.
+   *         provided resource at runtime
    */
-  String deploy(URL resource, boolean xhrCompatible)
+  String deploy(URL resource, String mimeType, boolean forceExternal)
       throws UnableToCompleteException;
 
   /**
diff --git a/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java b/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java
index 5058147..1fd7bd7 100644
--- a/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java
+++ b/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java
@@ -89,13 +89,20 @@
     this.cache = getCache(context.getTypeOracle());
   }
 
-  public String deploy(URL resource, boolean xhrCompatible)
+  @Deprecated
+  public String deploy(URL resource, boolean forceExternal)
+      throws UnableToCompleteException {
+    return deploy(resource, null, forceExternal);
+  }
+  
+  public String deploy(URL resource, String mimeType, boolean forceExternal)
       throws UnableToCompleteException {
     String fileName = ResourceGeneratorUtil.baseName(resource);
     byte[] bytes = Util.readURLAsBytes(resource);
     try {
-      return deploy(fileName, resource.openConnection().getContentType(),
-          bytes, xhrCompatible);
+      String finalMimeType = (mimeType != null)
+          ? mimeType : resource.openConnection().getContentType();
+      return deploy(fileName, finalMimeType, bytes, forceExternal);
     } catch (IOException e) {
       getLogger().log(TreeLogger.ERROR,
           "Unable to determine mime type of resource", e);
diff --git a/user/src/com/google/gwt/resources/rebind/context/InlineResourceContext.java b/user/src/com/google/gwt/resources/rebind/context/InlineResourceContext.java
index dfb2e29..e473b7d 100644
--- a/user/src/com/google/gwt/resources/rebind/context/InlineResourceContext.java
+++ b/user/src/com/google/gwt/resources/rebind/context/InlineResourceContext.java
@@ -33,18 +33,18 @@
 
   @Override
   public String deploy(String suggestedFileName, String mimeType, byte[] data,
-      boolean xhrCompatible) throws UnableToCompleteException {
+      boolean forceExternal) throws UnableToCompleteException {
     TreeLogger logger = getLogger();
 
     // data: URLs are not compatible with XHRs on FF and Safari browsers
-    if ((!xhrCompatible) && (data.length < MAX_INLINE_SIZE)) {
+    if ((!forceExternal) && (data.length < MAX_INLINE_SIZE)) {
       logger.log(TreeLogger.DEBUG, "Inlining", null);
 
       String base64Contents = toBase64(data);
 
       // CHECKSTYLE_OFF
-      String encoded = "\"data:" + mimeType + ";base64," + base64Contents
-          + "\"";
+      String encoded = "\"data:" + mimeType.replaceAll("\"", "\\\\\"")
+          + ";base64," + base64Contents + "\"";
       // CHECKSTYLE_ON
 
       /*
diff --git a/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java b/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java
index 95cd917..fa4e73c 100644
--- a/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java
+++ b/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java
@@ -64,7 +64,7 @@
 
   @Override
   public String deploy(String suggestedFileName, String mimeType, byte[] data,
-      boolean xhrCompatible) throws UnableToCompleteException {
+      boolean forceExternal) throws UnableToCompleteException {
 
     String strongName = Util.computeStrongName(data);
     String toReturn = strongNameToExpressions.get(strongName);
@@ -80,13 +80,13 @@
      * as a fallback.
      */
     String staticLocation = super.deploy(suggestedFileName, mimeType, data,
-        xhrCompatible);
+        forceExternal);
 
     /*
      * ie6 doesn't treat XHRs to mhtml as cross-site, but ie8 does, so we'll
      * play it safe here.
      */
-    if (xhrCompatible || data.length > MAX_INLINE_SIZE) {
+    if (forceExternal || data.length > MAX_INLINE_SIZE) {
       return staticLocation;
     }
 
diff --git a/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java b/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java
index 4f9ce27..e4a93dd 100644
--- a/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java
+++ b/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java
@@ -40,7 +40,7 @@
   }
 
   public String deploy(String suggestedFileName, String mimeType, byte[] data,
-      boolean xhrCompatible) throws UnableToCompleteException {
+      boolean forceExternal) throws UnableToCompleteException {
     TreeLogger logger = getLogger();
     GeneratorContext context = getGeneratorContext();
     PropertyOracle propertyOracle = context.getPropertyOracle();
diff --git a/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java b/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java
index dea8314..8fda74f 100644
--- a/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/DataResourceGenerator.java
@@ -18,7 +18,9 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.resources.client.DataResource.MimeType;
 import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.DataResource.DoNotEmbed;
 import com.google.gwt.resources.ext.AbstractResourceGenerator;
 import com.google.gwt.resources.ext.ResourceContext;
 import com.google.gwt.resources.ext.ResourceGeneratorUtil;
@@ -44,8 +46,18 @@
       throw new UnableToCompleteException();
     }
 
+    // Determine if a MIME Type has been specified
+    MimeType mimeTypeAnnotation = method.getAnnotation(MimeType.class);
+    String mimeType = mimeTypeAnnotation != null
+        ? mimeTypeAnnotation.value() : null;
+
+    // Determine if resource should not be embedded
+    DoNotEmbed doNotEmbed = method.getAnnotation(DoNotEmbed.class);
+    boolean forceExternal = (doNotEmbed != null);
+
     URL resource = resources[0];
-    String outputUrlExpression = context.deploy(resource, false);
+    String outputUrlExpression = context.deploy(
+        resource, mimeType, forceExternal);
 
     SourceWriter sw = new StringSourceWriter();
     // Write the expression to create the subtype.
diff --git a/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java b/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
index c78dab5..5f926dc 100644
--- a/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
+++ b/user/src/com/google/gwt/resources/rg/ImageResourceGenerator.java
@@ -53,6 +53,7 @@
    * Represents a file that contains multiple image regions.
    */
   static class BundledImage extends DisplayedImage {
+    private static final String MIME_TYPE_IMAGE_PNG = "image/png";
     private final ImageBundleBuilder builder;
     private boolean dirty = false;
     private Map<LocalizedImage, ImageRect> images;
@@ -108,6 +109,7 @@
       return images.get(localizedByImageResource.get(image));
     }
 
+    @Override
     public void render(TreeLogger logger, ResourceContext context,
         ClientBundleFields fields, RepeatStyle repeatStyle)
         throws UnableToCompleteException {
@@ -135,8 +137,9 @@
             logger.log(TreeLogger.ERROR, "Unknown RepeatStyle " + repeatStyle);
             throw new UnableToCompleteException();
         }
-        URL normalContents = renderToTempFile(logger, builder, arranger);
-        normalContentsUrlExpression = context.deploy(normalContents, false);
+        URL normalContents = renderToTempPngFile(logger, builder, arranger);
+        normalContentsUrlExpression = context.deploy(
+            normalContents, MIME_TYPE_IMAGE_PNG, false);
 
         if (!rtlImages.isEmpty()) {
           for (LocalizedImage rtlImage : rtlImages) {
@@ -146,10 +149,11 @@
             tx.setTransform(-1, 0, 0, 1, imageRect.getWidth(), 0);
             imageRect.setTransform(tx);
           }
-          URL rtlContents = renderToTempFile(logger, builder,
+          URL rtlContents = renderToTempPngFile(logger, builder,
               new ImageBundleBuilder.IdentityArranger());
           assert rtlContents != null;
-          rtlContentsUrlExpression = context.deploy(rtlContents, false);
+          rtlContentsUrlExpression = context.deploy(
+              rtlContents, MIME_TYPE_IMAGE_PNG, false);
         }
 
         dirty = false;
@@ -293,17 +297,20 @@
       this.rect = rect;
     }
 
+    @Override
     public ImageRect getImageRect(ImageResourceDeclaration image) {
       return this.image.equals(image) ? rect : null;
     }
 
+    @Override
     public void render(TreeLogger logger, ResourceContext context,
         ClientBundleFields fields, RepeatStyle repeatStyle)
         throws UnableToCompleteException {
       JClassType stringType = context.getGeneratorContext().getTypeOracle().findType(
           String.class.getCanonicalName());
 
-      String contentsExpression = context.deploy(localized.getUrl(), false);
+      String contentsExpression = context.deploy(
+          localized.getUrl(), null, false);
       normalContentsFieldName = fields.define(stringType, "externalImage",
           contentsExpression, true, true);
 
@@ -321,6 +328,7 @@
       }
     }
 
+    @Override
     public void setRtlImage(LocalizedImage localized) {
       if (this.localized.equals(localized)) {
         isRtl = true;
@@ -425,7 +433,7 @@
   /**
    * Re-encode an image as a PNG to strip random header data.
    */
-  private static URL renderToTempFile(TreeLogger logger,
+  private static URL renderToTempPngFile(TreeLogger logger,
       ImageBundleBuilder builder, Arranger arranger)
       throws UnableToCompleteException {
     try {
diff --git a/user/test/com/google/gwt/resources/ResourcesSuite.java b/user/test/com/google/gwt/resources/ResourcesSuite.java
index 679ed4e..1c1085c 100644
--- a/user/test/com/google/gwt/resources/ResourcesSuite.java
+++ b/user/test/com/google/gwt/resources/ResourcesSuite.java
@@ -20,6 +20,8 @@
 import com.google.gwt.resources.client.ImageResourceNoInliningTest;
 import com.google.gwt.resources.client.ImageResourceTest;
 import com.google.gwt.resources.client.NestedBundleTest;
+import com.google.gwt.resources.client.DataResourceDoNotEmbedTest;
+import com.google.gwt.resources.client.DataResourceMimeTypeTest;
 import com.google.gwt.resources.client.TextResourceTest;
 import com.google.gwt.resources.css.CssExternalTest;
 import com.google.gwt.resources.css.CssNodeClonerTest;
@@ -49,7 +51,9 @@
     suite.addTestSuite(ImageResourceTest.class);
     suite.addTestSuite(ImageResourceNoInliningTest.class);
     suite.addTestSuite(NestedBundleTest.class);
+    suite.addTestSuite(DataResourceDoNotEmbedTest.class);
     suite.addTestSuite(ResourceGeneratorUtilTest.class);
+    suite.addTestSuite(DataResourceMimeTypeTest.class);
     suite.addTestSuite(TextResourceTest.class);
     suite.addTestSuite(UnknownAtRuleTest.class);
     return suite;
diff --git a/user/test/com/google/gwt/resources/client/DataResourceDoNotEmbedTest.java b/user/test/com/google/gwt/resources/client/DataResourceDoNotEmbedTest.java
new file mode 100644
index 0000000..cb96a26
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/DataResourceDoNotEmbedTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2010 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;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests for {@link DataResource.DoNotEmbed @DoNotEmbed} resource annotations.
+ */
+public class DataResourceDoNotEmbedTest extends GWTTestCase {
+
+  interface Resources extends ClientBundle {
+
+    /**
+     * This is a binary file containing four 0x00 bytes, which is small enough
+     * to be embeddable, and contains insufficient information for a
+     * determination of a recognizable MIME Type.
+     */
+    String FOUR_ZEROS_SOURCE = "fourZeros.dat";
+
+    // Purposely missing a @DoNotEmbed annotation
+    @Source(FOUR_ZEROS_SOURCE)
+    DataResource resourceDoNotEmbedAnnotationMissing();
+
+    @DataResource.DoNotEmbed
+    @Source(FOUR_ZEROS_SOURCE)
+    DataResource resourceDoNotEmbedAnnotationPresent();
+  }
+
+  /**
+   * RFC 2397 data URL scheme
+   */
+  private static final String DATA_URL_SCHEME = "data:";
+
+  /**
+   * HACK: Older versions of IE do not support RFC 2397 data URLs. See
+   * com/google/gwt/resources/Resources.gwt.xml
+   */
+  private static native boolean isPreIe8() /*-{
+    var ua = navigator.userAgent.toLowerCase();
+    return ua.indexOf("msie") != -1
+        && !(document.documentMode >= 8);
+  }-*/;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.resources.Resources";
+  }
+
+  public void testDoNotEmbedAnnotationMissingShouldEmbed() {
+    if (isPreIe8()) {
+      // Skip this test for browsers which do not support data URLs
+      return;
+    }
+    Resources r = GWT.create(Resources.class);
+    String url = r.resourceDoNotEmbedAnnotationMissing().getUrl();
+    assertTrue("url '" + url + "' doesn't start with'" + DATA_URL_SCHEME + "'",
+        url.startsWith(DATA_URL_SCHEME));
+  }
+
+  public void testDoNotEmbedAnnotationPresentShouldNotEmbed() {
+    Resources r = GWT.create(Resources.class);
+    String url = r.resourceDoNotEmbedAnnotationPresent().getUrl();
+    assertFalse(
+        "url '" + url + "' mustn't start with'" + DATA_URL_SCHEME + "'",
+        url.startsWith(DATA_URL_SCHEME));
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/DataResourceMimeTypeTest.java b/user/test/com/google/gwt/resources/client/DataResourceMimeTypeTest.java
new file mode 100644
index 0000000..0c0dce8
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/DataResourceMimeTypeTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2010 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;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests for {@link DataResource.MimeType @MimeType} resource annotations.
+ */
+public class DataResourceMimeTypeTest extends GWTTestCase {
+
+  interface Resources extends ClientBundle {
+
+    /**
+     * This is a binary file containing four 0x00 bytes, which is small enough
+     * to be embeddable, and contains insufficient information for a
+     * determination of a recognizable MIME Type.
+     */
+    String FOUR_ZEROS_SOURCE = "fourZeros.dat";
+
+    /**
+     * A simple MIME Type as per RFC 1521.
+     */
+    String MIME_TYPE_AUDIO_OGG = "audio/ogg";
+
+    /**
+     * MIME Type with a single codecs specification as per RFC 4281.
+     */
+    String MIME_TYPE_WITH_CODECS = "audio/3gpp; codecs=samr";
+
+    /**
+     * MIME Type with a multiple codecs specification as per RFC 4281.
+     */
+    String MIME_TYPE_WITH_QUOTED_CODECS_LIST =
+        "video/3gpp; codecs=\"s263, samr\"";
+
+    // Purposely missing a @MimeType annotation
+    @Source(FOUR_ZEROS_SOURCE)
+    DataResource resourceMimeTypeNoAnnotation();
+
+    @DataResource.MimeType(MIME_TYPE_AUDIO_OGG)
+    @Source(FOUR_ZEROS_SOURCE)
+    DataResource resourceMimeTypeAnnotationAudioOgg();
+
+    @DataResource.MimeType(MIME_TYPE_WITH_CODECS)
+    @Source(FOUR_ZEROS_SOURCE)
+    DataResource resourceMimeTypeAnnotationWithCodecs();
+
+    @DataResource.MimeType(MIME_TYPE_WITH_QUOTED_CODECS_LIST)
+    @Source(FOUR_ZEROS_SOURCE)
+    DataResource resourceMimeTypeAnnotationWithQuotedCodecsList();
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.resources.Resources";
+  }
+
+  public void testMimeTypeAnnotationMissingDefaultsToContentUnknown() {
+    Resources r = GWT.create(Resources.class);
+    String url = r.resourceMimeTypeNoAnnotation().getUrl();
+    if (url.startsWith("http")) {
+      // Skip test, MIME Type will be provided by the HTTP server
+      return;
+    }
+    assertEquals("data:content/unknown;base64,AAAAAA==", url);
+  }
+
+  public void testMimeTypeAnnotationOverridesDefaultMimeType() {
+    Resources r = GWT.create(Resources.class);
+    String url = r.resourceMimeTypeAnnotationAudioOgg().getUrl();
+    if (url.startsWith("http")) {
+      // Skip test, MIME Type will be provided by the HTTP server
+      return;
+    }
+    assertEquals("data:audio/ogg;base64,AAAAAA==", url);
+  }
+
+  public void testMimeTypeAnnotationWithCodecs() {
+    Resources r = GWT.create(Resources.class);
+    String url = r.resourceMimeTypeAnnotationWithCodecs().getUrl();
+    if (url.startsWith("http")) {
+      // Skip test, MIME Type will be provided by the HTTP server
+      return;
+    }
+    assertEquals("data:audio/3gpp; codecs=samr;base64,AAAAAA==", url);
+  }
+
+  public void testMimeTypeAnnotationWithQuotedCodecsList() {
+    Resources r = GWT.create(Resources.class);
+    String url = r.resourceMimeTypeAnnotationWithQuotedCodecsList().getUrl();
+    if (url.startsWith("http")) {
+      // Skip test, MIME Type will be provided by the HTTP server
+      return;
+    }
+    assertEquals("data:video/3gpp; codecs=\"s263, samr\";base64,AAAAAA==", url);
+  }
+}
diff --git a/user/test/com/google/gwt/resources/client/fourZeros.dat b/user/test/com/google/gwt/resources/client/fourZeros.dat
new file mode 100644
index 0000000..593f470
--- /dev/null
+++ b/user/test/com/google/gwt/resources/client/fourZeros.dat
Binary files differ
diff --git a/user/test/com/google/gwt/resources/rg/CssTestCase.java b/user/test/com/google/gwt/resources/rg/CssTestCase.java
index 2974160..a4c07ae 100644
--- a/user/test/com/google/gwt/resources/rg/CssTestCase.java
+++ b/user/test/com/google/gwt/resources/rg/CssTestCase.java
@@ -39,7 +39,7 @@
  */
 public class CssTestCase extends TestCase {
   /*
-   * NB: This class is in the resources.rg package so that it can acess
+   * NB: This class is in the resources.rg package so that it can access
    * package-protected methods in CssResourceGenerator.
    */
 
@@ -79,11 +79,17 @@
    */
   private static class FakeContext implements ResourceContext {
     public String deploy(String suggestedFileName, String mimeType,
-        byte[] data, boolean xhrCompatible) throws UnableToCompleteException {
+        byte[] data, boolean forceExternal) throws UnableToCompleteException {
       return null;
     }
 
-    public String deploy(URL resource, boolean xhrCompatible)
+    @Deprecated
+    public String deploy(URL resource, boolean forceExternal)
+        throws UnableToCompleteException {
+      return null;
+    }
+
+    public String deploy(URL resource, String mimeType, boolean forceExternal)
         throws UnableToCompleteException {
       return null;
     }
@@ -170,10 +176,10 @@
         + "/";
     URL testUrl = getClass().getClassLoader().getResource(
         packagePath + testName + "_test.css");
-    assertNotNull("Cauld not find testUrl", testUrl);
+    assertNotNull("Could not find testUrl", testUrl);
     URL expectedUrl = getClass().getClassLoader().getResource(
         packagePath + testName + "_expected.css");
-    assertNotNull("Cauld not find testUrl", expectedUrl);
+    assertNotNull("Could not find testUrl", expectedUrl);
 
     test(logger, testUrl, expectedUrl, visitors);