Add MHTML / RFC 2557 support to ClientBundle for ie6 user.agent. Enable data: urls for ie8 user.agent. Update documentation location in CssResource JavaDoc. Patch by: bobv Review by: rjrjr git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5475 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/resources/Resources.gwt.xml b/user/src/com/google/gwt/resources/Resources.gwt.xml index 8e93b39..1db713a 100644 --- a/user/src/com/google/gwt/resources/Resources.gwt.xml +++ b/user/src/com/google/gwt/resources/Resources.gwt.xml
@@ -23,7 +23,7 @@ <define-property name="ClientBundle.enableInlining" values="true,false" /> <set-property name="ClientBundle.enableInlining" value="true" /> - <!-- Specify the default behavior --> + <!-- Specify the default behavior which should work on all browsers --> <generate-with class="com.google.gwt.resources.rebind.context.StaticClientBundleGenerator"> @@ -34,6 +34,24 @@ <!-- Last-matches wins, so this will selectively override the previous rule --> <generate-with + class="com.google.gwt.resources.rebind.context.MhtmlClientBundleGenerator"> + + <!-- We have a number of conditions that must be satisfied --> + <all> + <!-- Is inlining enabled? --> + <when-property-is name="ClientBundle.enableInlining" value="true" /> + + <!-- Again, it's necessary to specify which types the generator runs on --> + <when-type-assignable + class="com.google.gwt.resources.client.ClientBundle" /> + + <!-- Do this only with IE6 browsers --> + <when-property-is name="user.agent" value="ie6" /> + </all> + </generate-with> + + <!-- Last-matches wins, so this will selectively override the previous rule --> + <generate-with class="com.google.gwt.resources.rebind.context.InlineClientBundleGenerator"> <!-- We have a number of conditions that must be satisfied --> @@ -51,6 +69,7 @@ <when-property-is name="user.agent" value="opera" /> <when-property-is name="user.agent" value="gecko" /> <when-property-is name="user.agent" value="gecko1_8" /> + <when-property-is name="user.agent" value="ie8" /> </any> </all> </generate-with>
diff --git a/user/src/com/google/gwt/resources/client/CssResource.java b/user/src/com/google/gwt/resources/client/CssResource.java index ab03971..f56efdc 100644 --- a/user/src/com/google/gwt/resources/client/CssResource.java +++ b/user/src/com/google/gwt/resources/client/CssResource.java
@@ -90,8 +90,7 @@ * </li> * </ul> * - * @see <a - * href="http://code.google.com/p/google-web-toolkit-incubator/wiki/CssResource" + * @see <a href="http://code.google.com/p/google-web-toolkit/wiki/CssResource" * >CssResource design doc</a> */ @ResourceGeneratorType(CssResourceGenerator.class)
diff --git a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java index 86cfa6d..7ff745d 100644 --- a/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java +++ b/user/src/com/google/gwt/resources/rebind/context/AbstractClientBundleGenerator.java
@@ -170,6 +170,7 @@ FieldsImpl fields = new FieldsImpl(); RequirementsImpl requirements = new RequirementsImpl( generatorContext.getPropertyOracle()); + doAddFieldsAndRequirements(logger, generatorContext, fields, requirements); /* * Initialize the ResourceGenerators and prepare them for subsequent code @@ -230,6 +231,7 @@ } finish(logger, resourceContext, generators.keySet()); + doFinish(); // Return the name of the concrete class return createdClassName; @@ -241,7 +243,23 @@ * custom logic in the resource generation pass. */ protected abstract AbstractResourceContext createResourceContext( - TreeLogger logger, GeneratorContext context, JClassType resourceBundleType); + TreeLogger logger, GeneratorContext context, JClassType resourceBundleType) + throws UnableToCompleteException; + + /** + * Provides a hook for subtypes to add additional fields or requirements to + * the bundle. + */ + protected void doAddFieldsAndRequirements(TreeLogger logger, + GeneratorContext context, ClientBundleFields fields, + ClientBundleRequirements requirements) throws UnableToCompleteException { + } + + /** + * Provides a hook for finalizing generated resources. + */ + protected void doFinish() throws UnableToCompleteException { + } /** * Create fields and assignments for a single ResourceGenerator.
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 9e4e549..458ee59 100644 --- a/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java +++ b/user/src/com/google/gwt/resources/rebind/context/AbstractResourceContext.java
@@ -17,13 +17,32 @@ import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.dev.util.Util; import com.google.gwt.resources.ext.ResourceContext; +import com.google.gwt.resources.ext.ResourceGeneratorUtil; + +import java.io.IOException; +import java.net.URL; /** * Defines base methods for ResourceContext implementations. */ public abstract class AbstractResourceContext implements ResourceContext { + /** + * The largest file size that will be inlined. Note that this value is taken + * before any encodings are applied. + */ + protected static final int MAX_INLINE_SIZE = 2 << 15; + + protected static String toBase64(byte[] data) { + // This is bad, but I am lazy and don't want to write _another_ encoder + sun.misc.BASE64Encoder enc = new sun.misc.BASE64Encoder(); + String base64Contents = enc.encode(data).replaceAll("\\s+", ""); + return base64Contents; + } + private final TreeLogger logger; private final GeneratorContext context; private final JClassType resourceBundleType; @@ -36,6 +55,20 @@ this.resourceBundleType = resourceBundleType; } + public String deploy(URL resource, boolean xhrCompatible) + throws UnableToCompleteException { + String fileName = ResourceGeneratorUtil.baseName(resource); + byte[] bytes = Util.readURLAsBytes(resource); + try { + return deploy(fileName, resource.openConnection().getContentType(), + bytes, xhrCompatible); + } catch (IOException e) { + getLogger().log(TreeLogger.ERROR, + "Unable to determine mime type of resource", e); + throw new UnableToCompleteException(); + } + } + public JClassType getClientBundleType() { return resourceBundleType; } @@ -52,6 +85,10 @@ return simpleSourceName; } + protected GeneratorContext getContext() { + return context; + } + protected TreeLogger getLogger() { return logger; }
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 53aef65..f39d190 100644 --- a/user/src/com/google/gwt/resources/rebind/context/InlineResourceContext.java +++ b/user/src/com/google/gwt/resources/rebind/context/InlineResourceContext.java
@@ -21,15 +21,6 @@ import com.google.gwt.core.ext.typeinfo.JClassType; class InlineResourceContext extends StaticResourceContext { - /** - * The largest file size that will be inlined. Note that this value is taken - * before any encodings are applied. - */ - // The JLS specifies a maximum size for any string to be 2^16 characters, so - // we'll leave some padding. Assuming a Base64 encoding, it is true that - // (2 ^ 15) * 4/3 < 2 ^ 16, so we can safely inline files up to 32k. - private static final int MAX_INLINE_SIZE = 2 << 15; - InlineResourceContext(TreeLogger logger, GeneratorContext context, JClassType resourceBundleType) { super(logger, context, resourceBundleType); @@ -44,9 +35,7 @@ if ((!xhrCompatible) && (data.length < MAX_INLINE_SIZE)) { logger.log(TreeLogger.DEBUG, "Inlining", null); - // This is bad, but I am lazy and don't want to write _another_ encoder - sun.misc.BASE64Encoder enc = new sun.misc.BASE64Encoder(); - String base64Contents = enc.encode(data).replaceAll("\\s+", ""); + String base64Contents = toBase64(data); return "\"data:" + mimeType + ";base64," + base64Contents + "\""; } else {
diff --git a/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java b/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java new file mode 100644 index 0000000..4ca979a --- /dev/null +++ b/user/src/com/google/gwt/resources/rebind/context/MhtmlClientBundleGenerator.java
@@ -0,0 +1,85 @@ +/* + * Copyright 2009 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.rebind.context; + +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; +import com.google.gwt.core.ext.typeinfo.JType; +import com.google.gwt.core.ext.typeinfo.TypeOracleException; +import com.google.gwt.dev.util.Util; +import com.google.gwt.resources.ext.ClientBundleFields; +import com.google.gwt.resources.ext.ClientBundleRequirements; + +/** + * Generates Multipart HTML files. + */ +public class MhtmlClientBundleGenerator extends AbstractClientBundleGenerator { + + private static final String BUNDLE_EXTENSION = ".cache.txt"; + + private MhtmlResourceContext resourceContext; + private String partialPath; + + @Override + protected AbstractResourceContext createResourceContext(TreeLogger logger, + GeneratorContext context, JClassType resourceBundleType) { + resourceContext = new MhtmlResourceContext(logger, context, + resourceBundleType); + + /* + * TODO: figure out how to make the filename stable based on actual content. + */ + partialPath = Util.computeStrongName(Util.getBytes(resourceBundleType.getQualifiedSourceName() + + System.currentTimeMillis())) + + BUNDLE_EXTENSION; + resourceContext.setPartialPath(partialPath); + + return resourceContext; + } + + @Override + protected void doAddFieldsAndRequirements(TreeLogger logger, + GeneratorContext generatorContext, ClientBundleFields fields, + ClientBundleRequirements requirements) throws UnableToCompleteException { + JType booleanType; + JType stringType; + try { + booleanType = generatorContext.getTypeOracle().parse("boolean"); + stringType = generatorContext.getTypeOracle().parse("java.lang.String"); + } catch (TypeOracleException e) { + logger.log(TreeLogger.ERROR, "Expected type not in type oracle", e); + throw new UnableToCompleteException(); + } + + // GWT.getModuleBaseURL().startsWith("https") + String isHttpsIdent = fields.define(booleanType, "isHttps", + "GWT.getModuleBaseURL().startsWith(\"https\")", true, true); + resourceContext.setIsHttpsIdent(isHttpsIdent); + + // "mhtml:" + GWT.getModuleBaseURL() + "partialPath!cid:" + String bundleBaseIdent = fields.define(stringType, "bundleBase", + "\"mhtml:\" + GWT.getModuleBaseURL() + \"" + partialPath + "!cid:\"", + true, true); + resourceContext.setBundleBaseIdent(bundleBaseIdent); + } + + @Override + protected void doFinish() throws UnableToCompleteException { + resourceContext.finish(); + } +}
diff --git a/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java b/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java new file mode 100644 index 0000000..2707ab7 --- /dev/null +++ b/user/src/com/google/gwt/resources/rebind/context/MhtmlResourceContext.java
@@ -0,0 +1,133 @@ +/* + * Copyright 2009 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.rebind.context; + +import com.google.gwt.core.ext.GeneratorContext; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.core.ext.typeinfo.JClassType; + +import java.io.OutputStream; +import java.io.PrintWriter; + +/** + * Encodes resources into Multipart HTML files. In order to avoid mixed-content + * warnings using the mhtml: protocol, this context will include a fallback to + * static files when the module has been loaded from an https source. + * + * @see "RFC 2557" + */ +public class MhtmlResourceContext extends StaticResourceContext { + + /** + * The MIME multipart boundary token. This is chosen so that it does not + * overlap with any possible base64 sequences. + */ + private static final String BOUNDARY = "_GWT"; + private static final String ID_PREFIX = "r"; + + private String bundleBaseIdent; + private int id = 0; + private String isHttpsIdent; + + /** + * Output is lazily initialized in the case that all deployed resources are + * large. + */ + private OutputStream out; + private String partialPath; + private PrintWriter pw; + + MhtmlResourceContext(TreeLogger logger, GeneratorContext context, + JClassType resourceBundleType) { + super(logger, context, resourceBundleType); + } + + @Override + public String deploy(String suggestedFileName, String mimeType, byte[] data, + boolean xhrCompatible) throws UnableToCompleteException { + + assert partialPath != null : "partialPath"; + assert isHttpsIdent != null : "isHttpsIdent"; + assert bundleBaseIdent != null : "bundleBaseIdent"; + + /* + * mhtml URLs don't work in HTTPS, so we'll always need the static versions + * as a fallback. + */ + String staticLocation = super.deploy(suggestedFileName, mimeType, data, + xhrCompatible); + + /* + * 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) { + return staticLocation; + } + + if (out == null) { + out = getContext().tryCreateResource(getLogger(), partialPath); + pw = new PrintWriter(out); + pw.println("Content-Type: multipart/related; boundary=\"" + BOUNDARY + + "\""); + pw.println(); + } + + String location = ID_PREFIX + id++; + String base64 = toBase64(data); + + pw.println("--" + BOUNDARY); + pw.println("Content-Id:<" + location + ">"); + pw.println("Content-Type:" + mimeType); + pw.println("Content-Transfer-Encoding:base64"); + pw.println(); + pw.println(base64); + pw.println(); + + /* + * Return a Java expression: + * + * isHttps ? (staticLocation) : (bundleBaseIdent + "location") + */ + return isHttpsIdent + " ? (" + staticLocation + ") : (" + bundleBaseIdent + + " + \"" + location + "\")"; + } + + public void finish() throws UnableToCompleteException { + if (out != null) { + pw.close(); + getContext().commitResource(getLogger(), out); + } + } + + @Override + public boolean supportsDataUrls() { + return true; + } + + void setBundleBaseIdent(String ident) { + bundleBaseIdent = ident; + } + + void setIsHttpsIdent(String ident) { + isHttpsIdent = ident; + } + + void setPartialPath(String partialPath) { + this.partialPath = partialPath; + } +}
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 047e0dd..4f9ce27 100644 --- a/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java +++ b/user/src/com/google/gwt/resources/rebind/context/StaticResourceContext.java
@@ -23,11 +23,9 @@ import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.dev.util.Util; -import com.google.gwt.resources.ext.ResourceGeneratorUtil; import java.io.IOException; import java.io.OutputStream; -import java.net.URL; class StaticResourceContext extends AbstractResourceContext { /** @@ -50,8 +48,7 @@ // See if filename obfuscation should be enabled String enableRenaming = null; try { - ConfigurationProperty prop = propertyOracle.getConfigurationProperty( - ENABLE_RENAMING); + ConfigurationProperty prop = propertyOracle.getConfigurationProperty(ENABLE_RENAMING); enableRenaming = prop.getValues().get(0); } catch (BadPropertyValueException e) { logger.log(TreeLogger.ERROR, "Bad value for " + ENABLE_RENAMING, e); @@ -105,20 +102,6 @@ return "GWT.getModuleBaseURL() + \"" + outputName + "\""; } - public String deploy(URL resource, boolean xhrCompatible) - throws UnableToCompleteException { - String fileName = ResourceGeneratorUtil.baseName(resource); - byte[] bytes = Util.readURLAsBytes(resource); - try { - return deploy(fileName, resource.openConnection().getContentType(), - bytes, xhrCompatible); - } catch (IOException e) { - getLogger().log(TreeLogger.ERROR, - "Unable to determine mime type of resource", e); - throw new UnableToCompleteException(); - } - } - public boolean supportsDataUrls() { return false; }