Add Support for server side script selection in linker

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

Review by: jgw@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9038 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/PermutationsUtil.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/PermutationsUtil.java
new file mode 100644
index 0000000..c4de9d6
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/PermutationsUtil.java
@@ -0,0 +1,180 @@
+/*
+ * 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.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.LinkerContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.SelectionProperty;
+import com.google.gwt.dev.util.StringKey;
+import com.google.gwt.dev.util.collect.HashSet;
+import com.google.gwt.dev.util.collect.Lists;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * A utility class to help linkers generate a list of permutation mappings and
+ * and then either output them to javascript code which selects the correct
+ * permutation, or to a file which can be parsed by server side code.
+ */
+public class PermutationsUtil {
+  
+  /**
+   * This represents the combination of a unique content hash (i.e. the MD5 of
+   * the bytes to be written into the cache.html file) and a soft permutation
+   * id.
+   */
+  protected static class PermutationId extends StringKey {
+    private final int softPermutationId;
+    private final String strongName;
+
+    public PermutationId(String strongName, int softPermutationId) {
+      super(strongName + ":" + softPermutationId);
+      this.strongName = strongName;
+      this.softPermutationId = softPermutationId;
+    }
+
+    public int getSoftPermutationId() {
+      return softPermutationId;
+    }
+
+    public String getStrongName() {
+      return strongName;
+    }
+  }
+
+  /**
+   * This maps each unique permutation to the property settings for that
+   * compilation. A single compilation can have multiple property settings if
+   * the compiles for those settings yielded the exact same compiled output.
+   */
+  protected SortedMap<PermutationId, List<Map<String, String>>> propMapsByPermutation = null;
+
+  /**
+   * Uses the internal map to insert JS to select a permutation into the
+   * selection script.
+   */
+  public StringBuffer addPermutationsJs(StringBuffer selectionScript,
+      TreeLogger logger, LinkerContext context) {
+    int startPos;
+    
+    PropertiesUtil.addPropertiesJs(selectionScript, logger, context);
+
+    // Possibly add permutations
+    startPos = selectionScript.indexOf("// __PERMUTATIONS_END__");
+    if (startPos != -1) {
+      StringBuffer text = new StringBuffer();
+      if (propMapsByPermutation.size() == 0) {
+        // Hosted mode link.
+        text.append("alert(\"GWT module '" + context.getModuleName()
+            + "' may need to be (re)compiled\");");
+        text.append("return;");
+
+      } else if (propMapsByPermutation.size() == 1) {
+        // Just one distinct compilation; no need to evaluate properties
+        text.append("strongName = '"
+            + propMapsByPermutation.keySet().iterator().next().getStrongName()
+            + "';");
+      } else {
+        Set<String> propertiesUsed = new HashSet<String>();
+        for (PermutationId permutationId : propMapsByPermutation.keySet()) {
+          for (Map<String, String> propertyMap : propMapsByPermutation.get(permutationId)) {
+            // unflatten([v1, v2, v3], 'strongName' + ':softPermId');
+            // The soft perm ID is concatenated to improve string interning
+            text.append("unflattenKeylistIntoAnswers([");
+            boolean needsComma = false;
+            for (SelectionProperty p : context.getProperties()) {
+              if (p.tryGetValue() != null) {
+                continue;
+              } else if (p.isDerived()) {
+                continue;
+              }
+
+              if (needsComma) {
+                text.append(",");
+              } else {
+                needsComma = true;
+              }
+              text.append("'" + propertyMap.get(p.getName()) + "'");
+              propertiesUsed.add(p.getName());
+            }
+
+            text.append("], '").append(permutationId.getStrongName()).append(
+                "'");
+            /*
+             * For compatibility with older linkers, skip the soft permutation
+             * if it's 0
+             */
+            if (permutationId.getSoftPermutationId() != 0) {
+              text.append(" + ':").append(permutationId.getSoftPermutationId()).append(
+                  "'");
+            }
+            text.append(");\n");
+          }
+        }
+
+        // strongName = answers[compute('p1')][compute('p2')];
+        text.append("strongName = answers[");
+        boolean needsIndexMarkers = false;
+        for (SelectionProperty p : context.getProperties()) {
+          if (!propertiesUsed.contains(p.getName())) {
+            continue;
+          }
+          if (needsIndexMarkers) {
+            text.append("][");
+          } else {
+            needsIndexMarkers = true;
+          }
+          text.append("computePropValue('" + p.getName() + "')");
+        }
+        text.append("];");
+      }
+      selectionScript.insert(startPos, text);
+    }
+
+    return selectionScript;
+  }
+  
+  public SortedMap<PermutationId, List<Map<String, String>>> getPermutationsMap() {
+    return propMapsByPermutation;
+  }
+  
+  /**
+   * Find all instances of {@link SelectionInformation} and add them to the
+   * internal map of selection information.
+   */
+  public void setupPermutationsMap(ArtifactSet artifacts) {
+    propMapsByPermutation =
+      new TreeMap<PermutationId, List<Map<String, String>>>();
+    for (SelectionInformation selInfo : artifacts.find(SelectionInformation.class)) {
+      TreeMap<String, String> entries = selInfo.getPropMap();
+      PermutationId permutationId = new PermutationId(selInfo.getStrongName(),
+          selInfo.getSoftPermutationId());
+      if (!propMapsByPermutation.containsKey(permutationId)) {
+        propMapsByPermutation.put(permutationId,
+            Lists.<Map<String, String>> create(entries));
+      } else {
+        propMapsByPermutation.put(permutationId, Lists.add(
+            propMapsByPermutation.get(permutationId), entries));
+      }
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesMappingArtifact.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesMappingArtifact.java
new file mode 100644
index 0000000..a7b4caf
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesMappingArtifact.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2008 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.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.Linker;
+import com.google.gwt.core.ext.linker.Artifact;
+import com.google.gwt.core.ext.linker.Transferable;
+import com.google.gwt.core.ext.linker.impl.PermutationsUtil.PermutationId;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * Artifact that contains a mapping from deferred binding properties to a string (which typically
+ * represents another artifact). These are typically paired with an emitted
+ * artifact containing the results of {@link #getSerialized()}.
+ */
+@Transferable
+public class PropertiesMappingArtifact extends Artifact<PropertiesMappingArtifact> {
+
+  private final Map<PermutationId, List<Map<String, String>>> mappings;
+  private final String serialized;
+
+  public PropertiesMappingArtifact(Class<? extends Linker> linker,
+      Map<PermutationId, List<Map<String, String>>> mappings) {
+    super(linker);
+    this.mappings = mappings;
+    
+  StringBuilder sb = new StringBuilder();
+  for (Entry<PermutationId, List<Map<String, String>>> mapping : mappings.entrySet()) {
+    for (Map<String, String> deferredBindings : mapping.getValue()) {
+      sb.append(mapping.getKey().getStrongName() + ".cache.js");
+      sb.append('\n');
+      for (Entry<String, String> oneBinding : deferredBindings.entrySet()) {
+        sb.append(oneBinding.getKey());
+        sb.append(' ');
+        sb.append(oneBinding.getValue());
+        sb.append('\n');
+      }
+      sb.append('\n');
+    }
+  }
+    
+    serialized = sb.toString();
+  }
+
+  @Override
+  public int compareToComparableArtifact(PropertiesMappingArtifact that) {
+    return serialized.compareTo(that.getSerialized());
+  }
+
+  @Override
+  public Class<PropertiesMappingArtifact> getComparableArtifactType() {
+    return PropertiesMappingArtifact.class;
+  }
+
+  public String getSerialized() {
+    return serialized;
+  }
+
+  @Override
+  public int hashCode() {
+    return serialized.hashCode();
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
new file mode 100644
index 0000000..3ee5355
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/PropertiesUtil.java
@@ -0,0 +1,68 @@
+/*
+ * 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.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.LinkerContext;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.linker.SelectionProperty;
+
+/**
+ * A utility class to fill in the properties javascript in linker templates.
+ */
+public class PropertiesUtil {
+  public static StringBuffer addPropertiesJs(StringBuffer selectionScript,
+      TreeLogger logger, LinkerContext context) {
+    int startPos;
+
+    // Add property providers
+    startPos = selectionScript.indexOf("// __PROPERTIES_END__");
+    if (startPos != -1) {
+      for (SelectionProperty p : context.getProperties()) {
+        String text = generatePropertyProvider(p);
+        selectionScript.insert(startPos, text);
+        startPos += text.length();
+      }
+    }
+    return selectionScript;
+  }
+  
+  private static String generatePropertyProvider(SelectionProperty prop) {
+    StringBuffer toReturn = new StringBuffer();
+
+    if (prop.tryGetValue() == null && !prop.isDerived()) {
+      toReturn.append("providers['" + prop.getName() + "'] = function()");
+      toReturn.append(prop.getPropertyProvider());
+      toReturn.append(";");
+
+      toReturn.append("values['" + prop.getName() + "'] = {");
+      boolean needsComma = false;
+      int counter = 0;
+      for (String value : prop.getPossibleValues()) {
+        if (needsComma) {
+          toReturn.append(",");
+        } else {
+          needsComma = true;
+        }
+        toReturn.append("'" + value + "':");
+        toReturn.append(counter++);
+      }
+      toReturn.append("};");
+    }
+
+    return toReturn.toString();
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/ResourceInjectionUtil.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/ResourceInjectionUtil.java
new file mode 100644
index 0000000..0986ee4
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/ResourceInjectionUtil.java
@@ -0,0 +1,126 @@
+/*
+ * 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.core.ext.linker.impl;
+
+import com.google.gwt.core.ext.linker.ArtifactSet;
+import com.google.gwt.core.ext.linker.ScriptReference;
+import com.google.gwt.core.ext.linker.StylesheetReference;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Utility class to help linkers do resource injection.
+ */
+public class ResourceInjectionUtil {
+  public static StringBuffer injectResources(StringBuffer selectionScript,
+      ArtifactSet artifacts) {
+    // Add external dependencies
+    int startPos = selectionScript.indexOf("// __MODULE_STYLES_END__");
+    if (startPos != -1) {
+      for (StylesheetReference resource : artifacts.find(StylesheetReference.class)) {
+        String text = generateStylesheetInjector(resource.getSrc());
+        selectionScript.insert(startPos, text);
+        startPos += text.length();
+      }
+    }
+
+    startPos = selectionScript.indexOf("// __MODULE_SCRIPTS_END__");
+    if (startPos != -1) {
+      for (ScriptReference resource : artifacts.find(ScriptReference.class)) {
+        String text = generateScriptInjector(resource.getSrc());
+        selectionScript.insert(startPos, text);
+        startPos += text.length();
+      }
+    }
+    return selectionScript;
+  }
+  
+  private static String generateScriptInjector(String scriptUrl) {
+    if (isRelativeURL(scriptUrl)) {
+      return "  if (!__gwt_scriptsLoaded['"
+          + scriptUrl
+          + "']) {\n"
+          + "    __gwt_scriptsLoaded['"
+          + scriptUrl
+          + "'] = true;\n"
+          + "    document.write('<script language=\\\"javascript\\\" src=\\\"'+base+'"
+          + scriptUrl + "\\\"></script>');\n" + "  }\n";
+    } else {
+      return "  if (!__gwt_scriptsLoaded['" + scriptUrl + "']) {\n"
+          + "    __gwt_scriptsLoaded['" + scriptUrl + "'] = true;\n"
+          + "    document.write('<script language=\\\"javascript\\\" src=\\\""
+          + scriptUrl + "\\\"></script>');\n" + "  }\n";
+    }
+  }
+  
+  /**
+   * Generate a Snippet of JavaScript to inject an external stylesheet.
+   * 
+   * <pre>
+   * if (!__gwt_stylesLoaded['URL']) {
+   *   var l = $doc.createElement('link');
+   *   __gwt_styleLoaded['URL'] = l;
+   *   l.setAttribute('rel', 'stylesheet');
+   *   l.setAttribute('href', HREF_EXPR);
+   *   $doc.getElementsByTagName('head')[0].appendChild(l);
+   * }
+   * </pre>
+   */
+  private static String generateStylesheetInjector(String stylesheetUrl) {
+    String hrefExpr = "'" + stylesheetUrl + "'";
+    if (isRelativeURL(stylesheetUrl)) {
+      hrefExpr = "base + " + hrefExpr;
+    }
+
+    return "if (!__gwt_stylesLoaded['" + stylesheetUrl + "']) {\n           "
+        + "  var l = $doc.createElement('link');\n                          "
+        + "  __gwt_stylesLoaded['" + stylesheetUrl + "'] = l;\n             "
+        + "  l.setAttribute('rel', 'stylesheet');\n                         "
+        + "  l.setAttribute('href', " + hrefExpr + ");\n                    "
+        + "  $doc.getElementsByTagName('head')[0].appendChild(l);\n         "
+        + "}\n";
+  }
+  
+  /**
+   * Determines whether or not the URL is relative.
+   * 
+   * @param src the test url
+   * @return <code>true</code> if the URL is relative, <code>false</code> if not
+   */
+  private static boolean isRelativeURL(String src) {
+    // A straight absolute url for the same domain, server, and protocol.
+    if (src.startsWith("/")) {
+      return false;
+    }
+
+    // If it can be parsed as a URL, then it's probably absolute.
+    try {
+      // Just check to see if it can be parsed, no need to store the result.
+      new URL(src);
+
+      // Let's guess that it is absolute (thus, not relative).
+      return false;
+    } catch (MalformedURLException e) {
+      // Do nothing, since it was a speculative parse.
+    }
+
+    // Since none of the above matched, let's guess that it's relative.
+    return true;
+  }
+  
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
index 055c04a..3e9003c 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/SelectionScriptLinker.java
@@ -23,27 +23,22 @@
 import com.google.gwt.core.ext.linker.ArtifactSet;
 import com.google.gwt.core.ext.linker.CompilationResult;
 import com.google.gwt.core.ext.linker.EmittedArtifact;
-import com.google.gwt.core.ext.linker.ScriptReference;
 import com.google.gwt.core.ext.linker.SelectionProperty;
 import com.google.gwt.core.ext.linker.SoftPermutation;
-import com.google.gwt.core.ext.linker.StylesheetReference;
-import com.google.gwt.dev.util.StringKey;
+import com.google.gwt.dev.util.DefaultTextOutput;
+import com.google.gwt.dev.util.TextOutput;
 import com.google.gwt.dev.util.Util;
-import com.google.gwt.dev.util.collect.HashSet;
-import com.google.gwt.dev.util.collect.Lists;
 import com.google.gwt.util.tools.Utility;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLConnection;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
@@ -58,29 +53,6 @@
    * goes away?
    */
 
-  /**
-   * This represents the combination of a unique content hash (i.e. the MD5 of
-   * the bytes to be written into the cache.html file) and a soft permutation
-   * id.
-   */
-  protected static class PermutationId extends StringKey {
-    private final int softPermutationId;
-    private final String strongName;
-
-    public PermutationId(String strongName, int softPermutationId) {
-      super(strongName + ":" + softPermutationId);
-      this.strongName = strongName;
-      this.softPermutationId = softPermutationId;
-    }
-
-    public int getSoftPermutationId() {
-      return softPermutationId;
-    }
-
-    public String getStrongName() {
-      return strongName;
-    }
-  }
 
   /**
    * File name for computeScriptBase.js.
@@ -98,41 +70,15 @@
   protected static final String FRAGMENT_SUBDIR = "deferredjs";
 
   /**
-   * File name for permutations.js.
+   * Utility class to handle insertion of permutations code.
    */
-  protected static final String PERMUTATIONS_JS = "com/google/gwt/core/ext/linker/impl/permutations.js";
-
+  protected static PermutationsUtil permutationsUtil = new PermutationsUtil();
+  
   /**
    * File name for processMetas.js.
    */
-  protected static final String PROCESS_METAS_JS = "com/google/gwt/core/ext/linker/impl/processMetas.js";
+  protected static final String PROCESS_METAS_JS = "com/google/gwt/core/ext/linker/impl/processMetasOld.js";
 
-  /**
-   * Determines whether or not the URL is relative.
-   * 
-   * @param src the test url
-   * @return <code>true</code> if the URL is relative, <code>false</code> if not
-   */
-  protected static boolean isRelativeURL(String src) {
-    // A straight absolute url for the same domain, server, and protocol.
-    if (src.startsWith("/")) {
-      return false;
-    }
-
-    // If it can be parsed as a URL, then it's probably absolute.
-    try {
-      // Just check to see if it can be parsed, no need to store the result.
-      new URL(src);
-
-      // Let's guess that it is absolute (thus, not relative).
-      return false;
-    } catch (MalformedURLException e) {
-      // Do nothing, since it was a speculative parse.
-    }
-
-    // Since none of the above matched, let's guess that it's relative.
-    return true;
-  }
 
   protected static void replaceAll(StringBuffer buf, String search,
       String replace) {
@@ -143,12 +89,7 @@
     }
   }
 
-  /**
-   * This maps each unique permutation to the property settings for that
-   * compilation. A single compilation can have multiple property settings if
-   * the compiles for those settings yielded the exact same compiled output.
-   */
-  private final SortedMap<PermutationId, List<Map<String, String>>> propMapsByPermutation = new TreeMap<PermutationId, List<Map<String, String>>>();
+  private String selectionScriptText = null;
 
   /**
    * This method is left in place for existing subclasses of
@@ -178,11 +119,14 @@
       }
       return toReturn;
     } else {
-      processSelectionInformation(artifacts);
-
+      permutationsUtil.setupPermutationsMap(artifacts);
       ArtifactSet toReturn = new ArtifactSet(artifacts);
-      toReturn.add(emitSelectionScript(logger, context, artifacts));
-      maybeAddHostedModeFile(logger, toReturn);
+      EmittedArtifact art = emitSelectionScript(logger, context, artifacts);
+      if (art != null) {
+        toReturn.add(art);
+      }
+      maybeOutputPropertyMap(logger, context, toReturn);
+      maybeAddHostedModeFile(logger, context, toReturn);
       return toReturn;
     }
   }
@@ -219,9 +163,6 @@
   protected EmittedArtifact emitSelectionScript(TreeLogger logger,
       LinkerContext context, ArtifactSet artifacts)
       throws UnableToCompleteException {
-    String selectionScript = generateSelectionScript(logger, context, artifacts);
-    selectionScript = context.optimizeJavaScript(logger, selectionScript);
-
     /*
      * Last modified is important to keep hosted mode refreses from clobbering
      * web mode compiles. We set the timestamp on the hosted mode selection
@@ -229,16 +170,47 @@
      * mode, we just set it to now.
      */
     long lastModified;
-    if (propMapsByPermutation.isEmpty()) {
+    if (permutationsUtil.getPermutationsMap().isEmpty()) {
       lastModified = context.getModuleLastModified();
     } else {
       lastModified = System.currentTimeMillis();
     }
-
-    return emitString(logger, selectionScript, context.getModuleName()
+    String ss = generateSelectionScript(logger, context, artifacts);
+    return emitString(logger, ss, context.getModuleName()
         + ".nocache.js", lastModified);
   }
+  
+  /**
+   * Generate a selection script. The selection information should previously
+   * have been scanned using {@link #setupPermutationsMap(ArtifactSet)}.
+   */
+  protected String fillSelectionScriptTemplate(StringBuffer selectionScript,
+      TreeLogger logger, LinkerContext context, ArtifactSet artifacts) throws
+      UnableToCompleteException {
+    String computeScriptBase;
+    String processMetas;
+    try {
+      computeScriptBase = Utility.getFileFromClassPath(COMPUTE_SCRIPT_BASE_JS);
+      processMetas = Utility.getFileFromClassPath(PROCESS_METAS_JS);
+    } catch (IOException e) {
+      logger.log(TreeLogger.ERROR, "Unable to read selection script template",
+          e);
+      throw new UnableToCompleteException();
+    }
+    replaceAll(selectionScript, "__COMPUTE_SCRIPT_BASE__", computeScriptBase);
+    replaceAll(selectionScript, "__PROCESS_METAS__", processMetas);
+    
+    selectionScript = ResourceInjectionUtil.injectResources(selectionScript, artifacts);
+    permutationsUtil.addPermutationsJs(selectionScript, logger, context);
+    
+    replaceAll(selectionScript, "__MODULE_FUNC__",
+        context.getModuleFunctionName());
+    replaceAll(selectionScript, "__MODULE_NAME__", context.getModuleName());
+    replaceAll(selectionScript, "__HOSTED_FILENAME__", getHostedFilename());
 
+    return selectionScript.toString();
+  }
+  
   /**
    * @param logger a TreeLogger
    * @param context a LinkerContext
@@ -257,132 +229,34 @@
   protected byte[] generatePrimaryFragment(TreeLogger logger,
       LinkerContext context, CompilationResult result, String[] js)
       throws UnableToCompleteException {
-    StringBuffer b = new StringBuffer();
-    b.append(getModulePrefix(logger, context, result.getStrongName(), js.length));
-    b.append(js[0]);
-    b.append(getModuleSuffix(logger, context));
-    return Util.getBytes(b.toString());
-  }
-
-  protected String generatePropertyProvider(SelectionProperty prop) {
-    StringBuffer toReturn = new StringBuffer();
-
-    if (prop.tryGetValue() == null && !prop.isDerived()) {
-      toReturn.append("providers['" + prop.getName() + "'] = function()");
-      toReturn.append(prop.getPropertyProvider());
-      toReturn.append(";");
-
-      toReturn.append("values['" + prop.getName() + "'] = {");
-      boolean needsComma = false;
-      int counter = 0;
-      for (String value : prop.getPossibleValues()) {
-        if (needsComma) {
-          toReturn.append(",");
-        } else {
-          needsComma = true;
-        }
-        toReturn.append("'" + value + "':");
-        toReturn.append(counter++);
-      }
-      toReturn.append("};");
-    }
-
-    return toReturn.toString();
-  }
-
-  protected String generateScriptInjector(String scriptUrl) {
-    if (isRelativeURL(scriptUrl)) {
-      return "  if (!__gwt_scriptsLoaded['"
-          + scriptUrl
-          + "']) {\n"
-          + "    __gwt_scriptsLoaded['"
-          + scriptUrl
-          + "'] = true;\n"
-          + "    document.write('<script language=\\\"javascript\\\" src=\\\"'+base+'"
-          + scriptUrl + "\\\"></script>');\n" + "  }\n";
-    } else {
-      return "  if (!__gwt_scriptsLoaded['" + scriptUrl + "']) {\n"
-          + "    __gwt_scriptsLoaded['" + scriptUrl + "'] = true;\n"
-          + "    document.write('<script language=\\\"javascript\\\" src=\\\""
-          + scriptUrl + "\\\"></script>');\n" + "  }\n";
-    }
-  }
-
-  /**
-   * Generate a selection script. The selection information should previously
-   * have been scanned using {@link #processSelectionInformation(ArtifactSet)}.
-   */
-  protected String generateSelectionScript(TreeLogger logger,
-      LinkerContext context, ArtifactSet artifacts) throws UnableToCompleteException {
-    StringBuffer selectionScript = getSelectionScriptStringBuffer(logger, context);
-    
-    String computeScriptBase;
-    String processMetas;
-    try {
-      computeScriptBase = Utility.getFileFromClassPath(COMPUTE_SCRIPT_BASE_JS);
-      processMetas = Utility.getFileFromClassPath(PROCESS_METAS_JS);
-    } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Unable to read selection script template",
-          e);
-      throw new UnableToCompleteException();
-    }
-    replaceAll(selectionScript, "__COMPUTE_SCRIPT_BASE__", computeScriptBase);
-    replaceAll(selectionScript, "__PROCESS_METAS__", processMetas);
-
-    // Add external dependencies
-    int startPos = selectionScript.indexOf("// __MODULE_STYLES_END__");
-    if (startPos != -1) {
-      for (StylesheetReference resource : artifacts.find(StylesheetReference.class)) {
-        String text = generateStylesheetInjector(resource.getSrc());
-        selectionScript.insert(startPos, text);
-        startPos += text.length();
-      }
-    }
-
-    startPos = selectionScript.indexOf("// __MODULE_SCRIPTS_END__");
-    if (startPos != -1) {
-      for (ScriptReference resource : artifacts.find(ScriptReference.class)) {
-        String text = generateScriptInjector(resource.getSrc());
-        selectionScript.insert(startPos, text);
-        startPos += text.length();
-      }
-    }
-    
-    // This method needs to be called after all of the .js files have been
-    // swapped into the selectionScript since it will fill in __MODULE_NAME__
-    // and many of the .js files contain that template variable
-    selectionScript =
-      processSelectionScriptCommon(selectionScript, logger, context);
-
-    return selectionScript.toString();
+    TextOutput to = new DefaultTextOutput(context.isOutputCompact());
+    to.print(generatePrimaryFragmentString(
+        logger, context, result.getStrongName(), js[0], js.length));
+    return Util.getBytes(to.toString());
   }
   
-  /**
-   * Generate a Snippet of JavaScript to inject an external stylesheet.
-   * 
-   * <pre>
-   * if (!__gwt_stylesLoaded['URL']) {
-   *   var l = $doc.createElement('link');
-   *   __gwt_styleLoaded['URL'] = l;
-   *   l.setAttribute('rel', 'stylesheet');
-   *   l.setAttribute('href', HREF_EXPR);
-   *   $doc.getElementsByTagName('head')[0].appendChild(l);
-   * }
-   * </pre>
-   */
-  protected String generateStylesheetInjector(String stylesheetUrl) {
-    String hrefExpr = "'" + stylesheetUrl + "'";
-    if (isRelativeURL(stylesheetUrl)) {
-      hrefExpr = "base + " + hrefExpr;
+  protected String generatePrimaryFragmentString(TreeLogger logger,
+      LinkerContext context, String strongName, String js, int length) 
+  throws UnableToCompleteException {
+    StringBuffer b = new StringBuffer();
+    b.append(getModulePrefix(logger, context, strongName, length));
+    b.append(js);
+    b.append(getModuleSuffix(logger, context));
+    return wrapPrimaryFragment(logger, context, b.toString());
+  }
+  
+  protected String generateSelectionScript(TreeLogger logger,
+      LinkerContext context, ArtifactSet artifacts) throws UnableToCompleteException {
+    if (selectionScriptText != null) {
+      return selectionScriptText;
     }
-
-    return "if (!__gwt_stylesLoaded['" + stylesheetUrl + "']) {\n           "
-        + "  var l = $doc.createElement('link');\n                          "
-        + "  __gwt_stylesLoaded['" + stylesheetUrl + "'] = l;\n             "
-        + "  l.setAttribute('rel', 'stylesheet');\n                         "
-        + "  l.setAttribute('href', " + hrefExpr + ");\n                    "
-        + "  $doc.getElementsByTagName('head')[0].appendChild(l);\n         "
-        + "}\n";
+    StringBuffer buffer = readFileToStringBuffer(
+        getSelectionScriptTemplate(logger,context), logger);
+    selectionScriptText = fillSelectionScriptTemplate(
+        buffer, logger, context, artifacts);
+    selectionScriptText =
+      context.optimizeJavaScript(logger, selectionScriptText);
+    return selectionScriptText;
   }
   
   protected abstract String getCompilationExtension(TreeLogger logger,
@@ -391,7 +265,7 @@
   protected String getHostedFilename() {
     return "";
   }
-
+  
   /**
    * Compute the beginning of a JavaScript file that will hold the main module
    * implementation.
@@ -399,7 +273,7 @@
   protected abstract String getModulePrefix(TreeLogger logger,
       LinkerContext context, String strongName)
       throws UnableToCompleteException;
-
+  
   /**
    * Compute the beginning of a JavaScript file that will hold the main module
    * implementation. By default, calls
@@ -416,29 +290,16 @@
 
   protected abstract String getModuleSuffix(TreeLogger logger,
       LinkerContext context) throws UnableToCompleteException;
-
-  protected StringBuffer getSelectionScriptStringBuffer(TreeLogger logger,
-      LinkerContext context) throws UnableToCompleteException {
-    StringBuffer selectionScript;
-    try {
-      selectionScript = new StringBuffer(
-          Utility.getFileFromClassPath(getSelectionScriptTemplate(logger,
-              context)));
-    } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Unable to read selection script template",
-          e);
-      throw new UnableToCompleteException();
-    }
-    return selectionScript;
-  }
-
+  
   protected abstract String getSelectionScriptTemplate(TreeLogger logger,
       LinkerContext context) throws UnableToCompleteException;
 
+  
   /**
    * Add the hosted file to the artifact set.
    */
-  protected void maybeAddHostedModeFile(TreeLogger logger, ArtifactSet artifacts)
+  protected void maybeAddHostedModeFile(TreeLogger logger, 
+      LinkerContext context, ArtifactSet artifacts)
       throws UnableToCompleteException {
     String hostedFilename = getHostedFilename();
     if ("".equals(hostedFilename)) {
@@ -479,119 +340,26 @@
     }
   }
 
-  /**
-   * Find all instances of {@link SelectionInformation} and add them to the
-   * internal map of selection information.
-   */
-  protected void processSelectionInformation(ArtifactSet artifacts) {
-    for (SelectionInformation selInfo : artifacts.find(SelectionInformation.class)) {
-      processSelectionInformation(selInfo);
-    }
+  protected void maybeOutputPropertyMap(TreeLogger logger,
+      LinkerContext context, ArtifactSet toReturn) {
+    return;
   }
 
-  protected StringBuffer processSelectionScriptCommon(StringBuffer selectionScript,
-      TreeLogger logger, LinkerContext context)
-      throws UnableToCompleteException {
-    String permutations;
+  protected StringBuffer readFileToStringBuffer(String filename,
+      TreeLogger logger) throws UnableToCompleteException {
+    StringBuffer buffer;
     try {
-      permutations = Utility.getFileFromClassPath(PERMUTATIONS_JS);
+      buffer = new StringBuffer(Utility.getFileFromClassPath(filename));
     } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Unable to read selection script template",
-          e);
+      logger.log(TreeLogger.ERROR, "Unable to read file: " + filename, e);
       throw new UnableToCompleteException();
     }
+    return buffer;
+  }
 
-    replaceAll(selectionScript, "__PERMUTATIONS__", permutations);
-    replaceAll(selectionScript, "__MODULE_FUNC__",
-        context.getModuleFunctionName());
-    replaceAll(selectionScript, "__MODULE_NAME__", context.getModuleName());
-    replaceAll(selectionScript, "__HOSTED_FILENAME__", getHostedFilename());
-
-    int startPos;
-
-    // Add property providers
-    startPos = selectionScript.indexOf("// __PROPERTIES_END__");
-    if (startPos != -1) {
-      for (SelectionProperty p : context.getProperties()) {
-        String text = generatePropertyProvider(p);
-        selectionScript.insert(startPos, text);
-        startPos += text.length();
-      }
-    }
-
-    // Possibly add permutations
-    startPos = selectionScript.indexOf("// __PERMUTATIONS_END__");
-    if (startPos != -1) {
-      StringBuffer text = new StringBuffer();
-      if (propMapsByPermutation.size() == 0) {
-        // Hosted mode link.
-        text.append("alert(\"GWT module '" + context.getModuleName()
-            + "' may need to be (re)compiled\");");
-        text.append("return;");
-
-      } else if (propMapsByPermutation.size() == 1) {
-        // Just one distinct compilation; no need to evaluate properties
-        text.append("strongName = '"
-            + propMapsByPermutation.keySet().iterator().next().getStrongName()
-            + "';");
-      } else {
-        Set<String> propertiesUsed = new HashSet<String>();
-        for (PermutationId permutationId : propMapsByPermutation.keySet()) {
-          for (Map<String, String> propertyMap : propMapsByPermutation.get(permutationId)) {
-            // unflatten([v1, v2, v3], 'strongName' + ':softPermId');
-            // The soft perm ID is concatenated to improve string interning
-            text.append("unflattenKeylistIntoAnswers([");
-            boolean needsComma = false;
-            for (SelectionProperty p : context.getProperties()) {
-              if (p.tryGetValue() != null) {
-                continue;
-              } else if (p.isDerived()) {
-                continue;
-              }
-
-              if (needsComma) {
-                text.append(",");
-              } else {
-                needsComma = true;
-              }
-              text.append("'" + propertyMap.get(p.getName()) + "'");
-              propertiesUsed.add(p.getName());
-            }
-
-            text.append("], '").append(permutationId.getStrongName()).append(
-                "'");
-            /*
-             * For compatibility with older linkers, skip the soft permutation
-             * if it's 0
-             */
-            if (permutationId.getSoftPermutationId() != 0) {
-              text.append(" + ':").append(permutationId.getSoftPermutationId()).append(
-                  "'");
-            }
-            text.append(");\n");
-          }
-        }
-
-        // strongName = answers[compute('p1')][compute('p2')];
-        text.append("strongName = answers[");
-        boolean needsIndexMarkers = false;
-        for (SelectionProperty p : context.getProperties()) {
-          if (!propertiesUsed.contains(p.getName())) {
-            continue;
-          }
-          if (needsIndexMarkers) {
-            text.append("][");
-          } else {
-            needsIndexMarkers = true;
-          }
-          text.append("computePropValue('" + p.getName() + "')");
-        }
-        text.append("];");
-      }
-      selectionScript.insert(startPos, text);
-    }
-
-    return selectionScript;
+  protected String wrapPrimaryFragment(TreeLogger logger,
+      LinkerContext context, String script) {
+    return script;
   }
 
   private List<Artifact<?>> emitSelectionInformation(String strongName,
@@ -618,19 +386,5 @@
 
     return emitted;
   }
-  
-  private Map<String, String> processSelectionInformation(
-      SelectionInformation selInfo) {
-    TreeMap<String, String> entries = selInfo.getPropMap();
-    PermutationId permutationId = new PermutationId(selInfo.getStrongName(),
-        selInfo.getSoftPermutationId());
-    if (!propMapsByPermutation.containsKey(permutationId)) {
-      propMapsByPermutation.put(permutationId,
-          Lists.<Map<String, String>> create(entries));
-    } else {
-      propMapsByPermutation.put(permutationId, Lists.add(
-          propMapsByPermutation.get(permutationId), entries));
-    }
-    return entries;
-  }
+
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/computeScriptBase.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/computeScriptBase.js
index 016bb4f..066ce99 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/computeScriptBase.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/computeScriptBase.js
@@ -25,10 +25,16 @@
  * wherever COMPUTE_SCRIPT_BASE appears with underlines
  * on each side.
  */
+
 function computeScriptBase() {
-  //First, check if the meta properties give the baseUrl
-  if (metaProps['baseUrl']) {
-    base = metaProps['baseUrl'];
+  // First, check if the meta properties give the baseUrl
+  var metaVal = __gwt_getMetaProperty('baseUrl');
+  // Note: the base variable should not be defined in this function because in
+  // the older templates (like IFrameTemplate.js), base is defined outside this
+  // function, and they rely on the fact that calling computeScriptBase will set
+  // that base variable rather than using the return value.
+  if (metaVal != null) {
+    base = metaVal;
     return base;
   }
 
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/devmode.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/devmode.js
index 6c4a179..7a27bef 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/devmode.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/devmode.js
@@ -4,33 +4,10 @@
 var $entry;
 var $hostedHtmlVersion="2.1";
 
-// We assume that the following variables have been set in the same scope as
-// this code is executing in:
-// $wnd - the main window which the GWT code is affecting. in iframe based linkers
-//        $wnd = window.parent; Usually, in others, $wnd = window;
-// __gwtModuleFunction - the bootstrap function, which exports needed variables.
-//        usually this is set to something like $wnd.hello
-
-// Pull some variables from the bootstrapping code. If this file got the prefix
-// and suffix that the regular md5.js files did, then we wouldn't need this,
-// although we would need to give this file a gwtOnLoad() entry point.  If we
-// did this, we also would not need the __gwtModuleFunction and $moduleName
-// variables to be set.
-var $sendStats = __gwtModuleFunction.__sendStats;
-var $doc = $wnd.document
-var $errFn = __gwtModuleFunction.__errFn;
-var $moduleName = __gwtModuleFunction.__moduleName;
-var $moduleBase = __gwtModuleFunction.__moduleBase;
-var __gwt_getProperty = __gwtModuleFunction.__computePropValue;
-
-// Even though we call the $sendStats function in the code written in this
-// file, some of the compilation code still needs the $stats and $sessionId
-// variables to be available.
-var $stats = $wnd.__gwtStatsEvent ? function(a) {return $wnd.__gwtStatsEvent(a);} : null;
-var $sessionId = $wnd.__gwtStatsSessionId ? $wnd.__gwtStatsSessionId : null;
-
-$sendStats("moduleStartup", "moduleEvalStart");
-
+var $errFn;
+var $moduleName;
+var $moduleBase;
+var __gwt_getProperty;
 
 /******************************************************************************
  * WRITE ME - what does this invokes stuff do??? Probably related to invoking
@@ -340,18 +317,24 @@
   var pluginFinders = [findPluginXPCOM, findPluginObject, findPluginEmbed];
   var codeServer = getCodeServer();
   var plugin;
-    for (var i = 0; i < pluginFinders.length; ++i) {
-      try {
-        plugin = pluginFinders[i]();
-        if (plugin != null && plugin.init(window)) {
-          if (!plugin.connect(url, sessionId, codeServer, $moduleName,
-            $hostedHtmlVersion)) {
-            pluginConnectionError(codeServer);
-            return null;
-          }
-          return plugin;
+  for (var i = 0; i < pluginFinders.length; ++i) {
+    try {
+      plugin = pluginFinders[i]();
+      if (plugin != null) {
+        if (!plugin.init(window)) {
+          pluginConnectionError(codeServer);
+          return null;
         }
-      } catch (e) {
+        if (!plugin.connect(url, sessionId, codeServer, $moduleName,
+          $hostedHtmlVersion)) {
+          pluginConnectionError(codeServer);
+          return null;
+        }
+        return plugin;
+      }
+    } catch (e) {
+      pluginConnectionError(codeServer);
+      return null;
     }
   }
   return null;
@@ -361,27 +344,29 @@
 /******************************************************************************
  * DevMode startup code
  *****************************************************************************/
-window.onunload = function() { };
+function gwtOnLoad(errFn, moduleName, moduleBase, softPermutationId, computePropValue) {
+  $errFn = errFn;
+  $moduleName = moduleName;
+  $moduleBase = moduleBase;
+  __gwt_getProperty = computePropValue;
+  
+  window.onunload = function() { };
+  doBrowserSpecificFixes();
 
-$sendStats("moduleStartup", "moduleEvalEnd");
-doBrowserSpecificFixes();
+  if (!findPluginXPCOM()) {
+    embedPlugin();
+  }
 
-if (!findPluginXPCOM()) {
-  embedPlugin();
+  var topWin = window.top;
+  if (!topWin.__gwt_SessionID) {
+    topWin.__gwt_SessionID = generateSessionId();
+  }
+
+  var plugin = tryConnectingToPlugin(topWin.__gwt_SessionID, topWin.location.href);
+  if (plugin == null) {
+    loadIframe("http://gwt.google.com/missing-plugin/");
+  } else {
+    window.onUnload = disconnectPlugin();
+  }
 }
 
-var topWin = window.top;
-if (!topWin.__gwt_SessionID) {
-  topWin.__gwt_SessionID = generateSessionId();
-}
-
-var plugin = tryConnectingToPlugin(topWin.__gwt_SessionID, topWin.location.href);
-if (plugin == null) {
-  loadIframe("http://gwt.google.com/missing-plugin/");
-} else {
-  window.onUnload = disconnectPlugin();
-}
-
-// It looks like we never send the "moduleStartup", "end" stat in dev mode.
-// Is that on purpose, or should we send it here?
-
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/installLocationIframe.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/installLocationIframe.js
index bae2386..44226d3 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/installLocationIframe.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/installLocationIframe.js
@@ -16,20 +16,17 @@
 var frameDoc;
 
 function getInstallLocationDoc() {
-  if (!frameDoc) {
-    setupInstallLocation();
-  }
+  setupInstallLocation();
   return frameDoc;
 }
   
 function getInstallLocation() {
-  if (!frameDoc) {
-    setupInstallLocation();
-  }
+  setupInstallLocation();
   return frameDoc.getElementsByTagName('body')[0];
 }
 
 function setupInstallLocation() {
+  if (frameDoc) { return; }
   // Create the script frame, making sure it's invisible, but not
   // "display:none", which keeps some browsers from running code in it.
   var scriptFrame = $doc.createElement('iframe');
@@ -56,9 +53,6 @@
   var script = frameDoc.createElement('script');
   script.language='javascript';
   var temp = "var $wnd = window.parent;";
-  if (isHostedMode) {
-    temp += "var __gwtModuleFunction = $wnd.__MODULE_FUNC__;";
-  }
   script.text = temp;
   frameDocbody.appendChild(script);
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptAlreadyIncluded.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptAlreadyIncluded.js
new file mode 100644
index 0000000..43d4b0a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptAlreadyIncluded.js
@@ -0,0 +1,27 @@
+function installScript(filename) {
+  // Provides the getInstallLocation() and getInstallLocationDoc() functions
+  __INSTALL_LOCATION__
+
+  // Provides the setupWaitForBodyLoad() and isBodyLoaded() functions
+  __WAIT_FOR_BODY_LOADED__
+
+  function installCode(code) {
+    var docbody = getInstallLocation();
+    var script = getInstallLocationDoc().createElement('script');
+    script.language='javascript';
+    script.text = code;
+    docbody.appendChild(script);
+
+    // Remove the tags to shrink the DOM a little.
+    // It should have installed its code immediately after being added.
+    docbody.removeChild(script);
+  }
+  
+  // Set up a script tag to start downloading immediately, as well as a
+  // callback to install the code once it is downloaded and the body is loaded.
+  __MODULE_FUNC__.onScriptDownloaded = function(code) {
+    setupWaitForBodyLoad(function() {
+      installCode(code);
+    });
+  };
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptDirect.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptDirect.js
new file mode 100644
index 0000000..16c19aa
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptDirect.js
@@ -0,0 +1,27 @@
+function installScript(filename) {
+  // Provides the getInstallLocation() and getInstallLocationDoc() functions
+  __INSTALL_LOCATION__
+
+  // Provides the setupWaitForBodyLoad() and isBodyLoaded() functions
+  __WAIT_FOR_BODY_LOADED__
+  
+  function installCode(code) {
+    var docbody = getInstallLocation();
+    var script = getInstallLocationDoc().createElement('script');
+    script.language='javascript';
+    script.src = code;
+    sendStats('moduleStartup', 'moduleRequested');
+    docbody.appendChild(script);
+
+    // Remove the tags to shrink the DOM a little.
+    // It should have installed its code immediately after being added.
+    docbody.removeChild(script);
+  }
+
+  // Just pass along the filename so that a script tag can be installed in the
+  // iframe to download it.  Since we will be adding the iframe to the body,
+  // we still need to wait for the body to load before going forward.
+  setupWaitForBodyLoad(function() {
+    installCode(filename);
+  });
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js
new file mode 100644
index 0000000..877e4e2
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js
@@ -0,0 +1,41 @@
+function installScript(filename) {
+  // Provides the getInstallLocation() and getInstallLocationDoc() functions
+  __INSTALL_LOCATION__
+
+  // Provides the setupWaitForBodyLoad() and isBodyLoaded() functions
+  __WAIT_FOR_BODY_LOADED__
+
+  function installCode(code) {
+    var docbody = getInstallLocation();
+    var script = getInstallLocationDoc().createElement('script');
+    script.language='javascript';
+    script.text = code;
+    docbody.appendChild(script);
+
+    // Remove the tags to shrink the DOM a little.
+    // It should have installed its code immediately after being added.
+    docbody.removeChild(script);
+  }
+  
+  // Set up a script tag to start downloading immediately, as well as a
+  // callback to install the code once it is downloaded and the body is loaded.
+  __MODULE_FUNC__.onScriptDownloaded = function(code) {
+    setupWaitForBodyLoad(function() {
+      installCode(code);
+    });
+  };
+  sendStats('moduleStartup', 'moduleRequested');
+  if (isBodyLoaded()) {
+    // if the body is loaded, then the tag to download the script can be added
+    // in a non-destructive manner
+    var script = document.createElement('script');
+    script.src = filename;
+    $doc.getElementsByTagName('head')[0].appendChild(script);
+  } else {
+    // if the doc has not yet loaded, go ahead and do a destructive
+    // document.write since we want to immediately start the download.
+    // Note that we cannot append an element to the doc if it is still loading
+    // since this would cause problems in IE.
+    $doc.write("<script src='" + filename + "'></scr" + "ipt>");
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptNull.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptNull.js
new file mode 100644
index 0000000..e3787b9
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/installScriptNull.js
@@ -0,0 +1,3 @@
+function installScript(filename) {
+  // Does nothing
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/permutations.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/permutations.js
index 2c7372c..a36ce80 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/permutations.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/permutations.js
@@ -14,11 +14,18 @@
  * the License.
  */
 
+function getCompiledCodeFilename() {
+
+  // A multi-tier lookup map that uses actual property values to quickly find
+  // the strong name of the cache.js file to load.
+  var answers = [];
+
+  var softPermutationId;
+
   // Deferred-binding mapper function.  Sets a value into the several-level-deep
   // answers map. The keys are specified by a non-zero-length propValArray,
   // which should be a flat array target property values. Used by the generated
   // PERMUTATIONS code.
-  //
   function unflattenKeylistIntoAnswers(propValArray, value) {
     var answer = answers;
     for (var i = 0, n = propValArray.length - 1; i < n; ++i) {
@@ -29,20 +36,28 @@
     answer[propValArray[n]] = value;
   }
 
-  // Computes the value of a given property.  propName must be a valid property
-  // name. Used by the generated PERMUTATIONS code.
-  //
-  function computePropValue(propName) {
-    var value = providers[propName](), allowedValuesMap = values[propName];
-    if (value in allowedValuesMap) {
-      return value;
-    }
-    var allowedValuesList = [];
-    for (var k in allowedValuesMap) {
-      allowedValuesList[allowedValuesMap[k]] = k;
-    }
-    if (propertyErrorFunc) {
-      propertyErrorFunc(propName, allowedValuesList, value);
-    }
-    throw null;
+  // Provides the computePropvalue() function and sets the 
+  // __gwt_isKnownPropertyValue and MODULE_FUNC__.__computePropValue variables 
+  __PROPERTIES__
+  
+  sendStats('bootstrap', 'selectingPermutation');
+  if (isHostedMode()) {
+    return __MODULE_FUNC__.__moduleBase + "__HOSTED_FILENAME__"; 
   }
+  var strongName;
+  try {
+    // __PERMUTATIONS_BEGIN__
+    // Permutation logic is injected here. this code populates the 
+    // answers variable.
+    // __PERMUTATIONS_END__
+    var idx = strongName.indexOf(':');
+    if (idx != -1) {
+      softPermutationId = +(strongName.substring(idx + 1));
+      strongName = strongName.substring(0, idx);
+    }
+  } catch (e) {
+    // intentionally silent on property failure
+  }
+  __MODULE_FUNC__.__softPermutationId = softPermutationId;
+  return __MODULE_FUNC__.__moduleBase + strongName + '.cache.js';
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/permutationsNull.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/permutationsNull.js
new file mode 100644
index 0000000..5f5cc3b
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/permutationsNull.js
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+function getCompiledCodeFilename() {
+  // Provides the computePropvalue() function and sets the 
+  // __gwt_isKnownPropertyValue and MODULE_FUNC__.__computePropValue variables 
+  __PROPERTIES__
+  
+  __MODULE_FUNC__.__softPermutationId = 0;
+  return null;
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/processMetas.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/processMetas.js
index 5b13dd6..2921189 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/processMetas.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/processMetas.js
@@ -22,6 +22,10 @@
  * on each side.
  */
 function processMetas() {
+  var metaProps = {}
+  var propertyErrorFunc;
+  var onLoadErrorFunc;
+
   var metas = document.getElementsByTagName('meta');
   for (var i = 0, n = metas.length; i < n; ++i) {
     var meta = metas[i]
@@ -52,22 +56,30 @@
         content = meta.getAttribute('content');
         if (content) {
           try {
-        	propertyErrorFunc = eval(content);
+            propertyErrorFunc = eval(content);
           } catch (e) {
-        	alert('Bad handler \"' + content +
-        	    '\" for \"gwt:onPropertyErrorFn\"');
+            alert('Bad handler \"' + content +
+              '\" for \"gwt:onPropertyErrorFn\"');
           }
-    	}
+        }
       } else if (name == 'gwt:onLoadErrorFn') {
         content = meta.getAttribute('content');
         if (content) {
-  	      try {
-  	        onLoadErrorFunc = eval(content);
-  	      } catch (e) {
+          try {
+            onLoadErrorFunc = eval(content);
+          } catch (e) {
             alert('Bad handler \"' + content + '\" for \"gwt:onLoadErrorFn\"');
           }
         }
       }
     }
   }
+
+  // Set some of the variables in the main script
+  __gwt_getMetaProperty = function(name) {
+    var value = metaProps[name];
+    return (value == null) ? null : value;
+  }
+  __propertyErrorFunction = propertyErrorFunc;
+  __MODULE_FUNC__.__errFn = onLoadErrorFunc;
 }
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/processMetasNull.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/processMetasNull.js
new file mode 100644
index 0000000..ca99de4
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/processMetasNull.js
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+/** Called to slurp up all <meta> tags:
+ * gwt:property, gwt:onPropertyErrorFn, gwt:onLoadErrorFn
+ * 
+ * This is included into the selection scripts
+ * wherever PROCESS_METAS appears with underlines
+ * on each side.
+ */
+function processMetas() {
+  // Set some of the variables in the main script
+  __gwt_getMetaProperty = function(name) {
+    return null;
+  }
+  __propertyErrorFunction = null;
+  __MODULE_FUNC__.__errFn = null;
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/processMetasOld.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/processMetasOld.js
new file mode 100644
index 0000000..83223be
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/processMetasOld.js
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+/** Called to slurp up all <meta> tags:
+ * gwt:property, gwt:onPropertyErrorFn, gwt:onLoadErrorFn
+ * 
+ * This is included into the selection scripts
+ * wherever PROCESS_METAS appears with underlines
+ * on each side.
+ */
+function processMetas() {
+  var metas = document.getElementsByTagName('meta');
+  for (var i = 0, n = metas.length; i < n; ++i) {
+    var meta = metas[i]
+    , name = meta.getAttribute('name')
+    , content;
+
+    if (name) {
+      name = name.replace('__MODULE_NAME__::', '');
+      if (name.indexOf('::') >= 0) {
+        // It's for a different module
+        continue;
+      }
+
+      if (name == 'gwt:property') {
+        content = meta.getAttribute('content');
+        if (content) {
+          var value, eq = content.indexOf('=');
+          if (eq >= 0) {
+            name = content.substring(0, eq);
+            value = content.substring(eq + 1);
+          } else {
+            name = content;
+            value = '';
+          }
+          metaProps[name] = value;
+        }
+      } else if (name == 'gwt:onPropertyErrorFn') {
+        content = meta.getAttribute('content');
+        if (content) {
+          try {
+            propertyErrorFunc = eval(content);
+          } catch (e) {
+            alert('Bad handler \"' + content +
+              '\" for \"gwt:onPropertyErrorFn\"');
+          }
+        }
+      } else if (name == 'gwt:onLoadErrorFn') {
+        content = meta.getAttribute('content');
+        if (content) {
+          try {
+            onLoadErrorFunc = eval(content);
+          } catch (e) {
+            alert('Bad handler \"' + content + '\" for \"gwt:onLoadErrorFn\"');
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/properties.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/properties.js
new file mode 100644
index 0000000..7228358
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/properties.js
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+  // Maps property names onto sets of legal values for that property.
+  var values = [];
+
+  // Maps property names onto a function to compute that property.
+  var providers = [];
+
+  // Computes the value of a given property.  propName must be a valid property
+  // name. Used by the generated PERMUTATIONS code.
+  function computePropValue(propName) {
+    var value = providers[propName](), allowedValuesMap = values[propName];
+    if (value in allowedValuesMap) {
+      return value;
+    }
+    var allowedValuesList = [];
+    for (var k in allowedValuesMap) {
+      allowedValuesList[allowedValuesMap[k]] = k;
+    }
+    if (__propertyErrorFunc) {
+      __propertyErrorFunc(propName, allowedValuesList, value);
+    }
+    throw null;
+  }
+
+  // __PROPERTIES_BEGIN__
+  // Properties logic is injected here. This code populates the values and
+  // providers variables
+  // __PROPERTIES_END__
+
+  // Determines whether or not a particular property value is allowed. Called by
+  // property providers.
+  __gwt_isKnownPropertyValue = function(propName, propValue) {
+    return propValue in values[propName];
+  }
+  __MODULE_FUNC__.__computePropValue = computePropValue;
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/propertiesNull.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/propertiesNull.js
new file mode 100644
index 0000000..393ce1a
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/propertiesNull.js
@@ -0,0 +1,4 @@
+  __gwt_isKnownPropertyValue = function(propName, propValue) {
+    return false;
+  }
+  __MODULE_FUNC__.__computePropValue = null;
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js
index 35ed95d..839a68d 100644
--- a/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js
@@ -1,3 +1,8 @@
+// Check whether the body is loaded.
+function isBodyLoaded() {
+  return (/loaded|complete/.test($doc.readyState));
+}
+
 // Setup code which waits for the body to be loaded and then calls the
 // callback function
 function setupWaitForBodyLoad(callback) {
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoadedNull.js b/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoadedNull.js
new file mode 100644
index 0000000..3abdcb7
--- /dev/null
+++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/waitForBodyLoadedNull.js
@@ -0,0 +1,10 @@
+// Check whether the body is loaded.
+function isBodyLoaded() {
+  return true;
+}
+
+// Setup code which waits for the body to be loaded and then calls the
+// callback function
+function setupWaitForBodyLoad(callback) {
+    callback();
+}
diff --git a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
index eeea37e..23d8b9f 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
@@ -20,20 +20,21 @@
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
 import com.google.gwt.core.ext.linker.ArtifactSet;
-import com.google.gwt.core.ext.linker.CompilationResult;
+import com.google.gwt.core.ext.linker.EmittedArtifact;
 import com.google.gwt.core.ext.linker.LinkerOrder;
-import com.google.gwt.core.ext.linker.Shardable;
 import com.google.gwt.core.ext.linker.LinkerOrder.Order;
+import com.google.gwt.core.ext.linker.Shardable;
+import com.google.gwt.core.ext.linker.impl.PropertiesMappingArtifact;
 import com.google.gwt.core.ext.linker.impl.SelectionScriptLinker;
 import com.google.gwt.dev.About;
 import com.google.gwt.dev.js.JsToStringGenerationVisitor;
 import com.google.gwt.dev.util.DefaultTextOutput;
 import com.google.gwt.dev.util.TextOutput;
-import com.google.gwt.dev.util.Util;
 import com.google.gwt.util.tools.Utility;
 
 import java.io.IOException;
 
+
 /**
  * This linker uses an iframe to hold the code and a script tag to download the
  * code. It can download code cross-site, because it uses a script tag to
@@ -43,109 +44,35 @@
 @LinkerOrder(Order.PRIMARY)
 @Shardable
 public class CrossSiteIframeLinker extends SelectionScriptLinker {
-  // TODO(unnurg): For each of the following properties, decide whether to make
-  // it a gwt.xml configuration property, a constant which can be overridden
-  // by subclasses, or not configurable at all.
-  private static final String installLocationJsProperty = 
-    "com/google/gwt/core/ext/linker/impl/installLocationIframe.js";
-  private static final boolean processMetasProperty = true;
-  private static final String scriptBaseProperty = "";
-  private static final boolean startDownloadImmediatelyProperty = true;
   
-  private static final String WAIT_FOR_BODY_LOADED_JS =
-    "com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js";
-  
-  private static final boolean waitForBodyLoadedProperty = true;
-
   @Override
   public String getDescription() {
     return "Cross-Site-Iframe";
   }
-
-  @Override
-  protected byte[] generatePrimaryFragment(TreeLogger logger,
-      LinkerContext context, CompilationResult result, String[] js) {
-    TextOutput script = new DefaultTextOutput(context.isOutputCompact());
-    script.print(getModulePrefix(context, result.getStrongName()));
-    script.print(js[0]);
-    script.print(getModuleSuffix(logger, context));
-    StringBuffer out = new StringBuffer();
-
-    if (startDownloadImmediatelyProperty) {
-      // Rewrite the code so it can be installed with
-      // __MODULE_FUNC__.onScriptDownloaded
-      out.append(context.getModuleFunctionName());
-      out.append(".onScriptDownloaded(");
-      out.append(JsToStringGenerationVisitor.javaScriptString(script.toString()));
-      out.append(")");
-    } else {
-      out.append(script.toString());
-    }
-
-    return Util.getBytes(out.toString());
-  }
   
   @Override
-  protected String generateSelectionScript(TreeLogger logger,
-      LinkerContext context, ArtifactSet artifacts) throws UnableToCompleteException {
-    StringBuffer selectionScript = getSelectionScriptStringBuffer(logger, context);
+  protected String fillSelectionScriptTemplate(StringBuffer ss,
+      TreeLogger logger, LinkerContext context, ArtifactSet artifacts) throws
+      UnableToCompleteException {
     
-    String waitForBodyLoadedJs;
-    String installLocationJs;
-    String computeScriptBase;
-    String processMetas;
-
-    try {
-      waitForBodyLoadedJs = Utility.getFileFromClassPath(WAIT_FOR_BODY_LOADED_JS);
-      processMetas = Utility.getFileFromClassPath(PROCESS_METAS_JS);
-      installLocationJs = Utility.getFileFromClassPath(installLocationJsProperty);
-      computeScriptBase = Utility.getFileFromClassPath(COMPUTE_SCRIPT_BASE_JS);
-    } catch (IOException e) {
-      logger.log(TreeLogger.ERROR, "Unable to read selection script template",
-          e);
-      throw new UnableToCompleteException();
-    }
+    // Must do installScript before installLocation and waitForBodyLoaded
+    includeJs(ss, logger, getJsInstallScript(context), "__INSTALL_SCRIPT__");
+    includeJs(ss, logger, getJsInstallLocation(context), "__INSTALL_LOCATION__");
+    includeJs(ss, logger, getJsWaitForBodyLoaded(context), "__WAIT_FOR_BODY_LOADED__");
     
-    replaceAll(selectionScript, "__INSTALL_LOCATION__", installLocationJs);
-
-    if (waitForBodyLoadedProperty) {
-      replaceAll(selectionScript, "__WAIT_FOR_BODY_LOADED__", waitForBodyLoadedJs);
-    } else {
-      String waitForBodyLoadedNullImpl = 
-      "function setupWaitForBodyLoad(callback) { callback(); }";
-      replaceAll(selectionScript, "__WAIT_FOR_BODY_LOADED__", waitForBodyLoadedNullImpl);
-    }
-
-    if (startDownloadImmediatelyProperty) {
-      replaceAll(selectionScript, "__START_DOWNLOAD_IMMEDIATELY__", "true");
-    } else {
-      replaceAll(selectionScript, "__START_DOWNLOAD_IMMEDIATELY__", "false");
-    }
+    // Must do permutations before providers
+    includeJs(ss, logger, getJsPermutations(context), "__PERMUTATIONS__");
+    includeJs(ss, logger, getJsProperties(context), "__PROPERTIES__");
+    includeJs(ss, logger, getJsProcessMetas(context), "__PROCESS_METAS__");
+    includeJs(ss, logger, getJsComputeScriptBase(context), "__COMPUTE_SCRIPT_BASE__");
     
-    if (processMetasProperty) {
-      replaceAll(selectionScript, "__PROCESS_METAS__", processMetas);
-    } else {
-      String processMetasNullImpl =
-        "function processMetas() { }";
-      replaceAll(selectionScript, "__PROCESS_METAS__", processMetasNullImpl);
-    }
-    
-    String scriptBase = scriptBaseProperty;
-    if ("".equals(scriptBase)) {
-      replaceAll(selectionScript, "__COMPUTE_SCRIPT_BASE__", computeScriptBase);
-    } else {
-      String computeScriptBaseNullImpl =
-        "function computeScriptBase() { return '" + scriptBase + "';}";
-      replaceAll(selectionScript, "__COMPUTE_SCRIPT_BASE__", computeScriptBaseNullImpl);
-    }
+    replaceAll(ss, "__MODULE_FUNC__", context.getModuleFunctionName());
+    replaceAll(ss, "__MODULE_NAME__", context.getModuleName());
+    replaceAll(ss, "__HOSTED_FILENAME__", getHostedFilename());
 
-    // This method needs to be called after all of the .js files have been
-    // swapped into the selectionScript since it will fill in __MODULE_NAME__
-    // and many of the .js files contain that template variable
-    selectionScript =
-      processSelectionScriptCommon(selectionScript, logger, context);
+    permutationsUtil.addPermutationsJs(ss, logger, context);
 
-    return selectionScript.toString();
+    return ss.toString();
   }
 
   @Override
@@ -153,49 +80,53 @@
       LinkerContext context) {
     return ".cache.js";
   }
-
+ 
   @Override
   protected String getHostedFilename() {
     return "devmode.js";
   }
   
+  protected String getJsComputeScriptBase(LinkerContext context) {
+    return "com/google/gwt/core/ext/linker/impl/computeScriptBase.js";
+  }
+  
+  protected String getJsInstallLocation(LinkerContext context) { 
+    return "com/google/gwt/core/ext/linker/impl/installLocationIframe.js";
+  }
+  
+  // If you override this to return installScriptDirect.js, then you should
+  // also override shouldInstallCode() to return false
+  protected String getJsInstallScript(LinkerContext context) {
+    return "com/google/gwt/core/ext/linker/impl/installScriptEarlyDownload.js";
+  }
+  
+  protected String getJsPermutations(LinkerContext context) {
+    return "com/google/gwt/core/ext/linker/impl/permutations.js";
+  }
+  
+  protected String getJsProcessMetas(LinkerContext context) {
+    return "com/google/gwt/core/ext/linker/impl/processMetas.js";
+  }
+  
+  protected String getJsProperties(LinkerContext context) {
+    return "com/google/gwt/core/ext/linker/impl/properties.js";
+  }
+    
+  protected String getJsWaitForBodyLoaded(LinkerContext context) {
+    return "com/google/gwt/core/ext/linker/impl/waitForBodyLoaded.js";
+  }
+
   @Override
   protected String getModulePrefix(TreeLogger logger, LinkerContext context,
       String strongName) {
-    throw new UnsupportedOperationException("Should not be called");
-  }
-
-  @Override
-  protected String getModulePrefix(TreeLogger logger, LinkerContext context,
-      String strongName, int numFragments) {
-    throw new UnsupportedOperationException("Should not be called");
-  }
-
-  @Override
-  protected String getModuleSuffix(TreeLogger logger, LinkerContext context) {
-    DefaultTextOutput out = new DefaultTextOutput(context.isOutputCompact());
-
-    out.print("$sendStats('moduleStartup', 'moduleEvalEnd');");
-    out.newlineOpt();
-    out.print("gwtOnLoad("
-        + "__gwtModuleFunction.__errFn, "
-        + "__gwtModuleFunction.__moduleName, "
-        + "__gwtModuleFunction.__moduleBase, "
-        + "__gwtModuleFunction.__softPermutationId);");
-    out.newlineOpt();
-    out.print("$sendStats('moduleStartup', 'end');");
-
-    return out.toString();
-  }
-
-  @Override
-  protected String getSelectionScriptTemplate(TreeLogger logger,
-      LinkerContext context) {
-    return "com/google/gwt/core/linker/CrossSiteIframeTemplate.js";
-  }
-
-  private String getModulePrefix(LinkerContext context, String strongName) {
     TextOutput out = new DefaultTextOutput(context.isOutputCompact());
+    
+    // We assume that the $wnd has been set in the same scope as this code is
+    // executing in. $wnd is the main window which the GWT code is affecting. It
+    // is also usually the location the bootstrap function was defined in. 
+    // In iframe based  linkers, $wnd = window.parent; 
+    // Usually, in others, $wnd = window;
+
     out.print("var __gwtModuleFunction = $wnd." + context.getModuleFunctionName() + ";");
     out.newlineOpt();
     out.print("var $sendStats = __gwtModuleFunction.__sendStats;");
@@ -219,4 +150,132 @@
     return out.toString();
   }
   
+  @Override
+  protected String getModuleSuffix(TreeLogger logger, LinkerContext context) {
+    DefaultTextOutput out = new DefaultTextOutput(context.isOutputCompact());
+    
+    out.print("$sendStats('moduleStartup', 'moduleEvalEnd');");
+    out.newlineOpt();
+    out.print("gwtOnLoad("
+        + "__gwtModuleFunction.__errFn, "
+        + "__gwtModuleFunction.__moduleName, "
+        + "__gwtModuleFunction.__moduleBase, "
+        + "__gwtModuleFunction.__softPermutationId,"
+        + "__gwtModuleFunction.__computePropValue);");
+    out.newlineOpt();
+    out.print("$sendStats('moduleStartup', 'end');");
+    
+    return out.toString();
+  }
+  
+  @Override
+  protected String getSelectionScriptTemplate(TreeLogger logger,
+      LinkerContext context) {
+    return "com/google/gwt/core/linker/CrossSiteIframeTemplate.js";
+  }
+  
+  protected void includeJs(StringBuffer selectionScript, TreeLogger logger,
+      String jsSource, String templateVar) throws UnableToCompleteException {
+    String js;
+    if (jsSource.endsWith(".js")) {
+      try {
+        js = Utility.getFileFromClassPath(jsSource);
+      } catch (IOException e) {
+        logger.log(TreeLogger.ERROR, "Unable to read file: " + jsSource, e);
+        throw new UnableToCompleteException();
+      }
+    } else {
+      js = jsSource;
+    }
+    replaceAll(selectionScript, templateVar, js);
+  }
+
+  @Override
+  protected void maybeAddHostedModeFile(TreeLogger logger, 
+      LinkerContext context, ArtifactSet artifacts)
+      throws UnableToCompleteException {
+    String filename = getHostedFilename();
+    if ("".equals(filename)) {
+      return;
+    }
+    
+    long lastModified = System.currentTimeMillis();
+    StringBuffer buffer = readFileToStringBuffer(
+        "com/google/gwt/core/ext/linker/impl/" + filename, logger); 
+
+    String script = generatePrimaryFragmentString(
+        logger, context, "", buffer.toString(), 1);
+    
+    EmittedArtifact devArtifact = 
+      emitString(logger, script, filename, lastModified);
+    artifacts.add(devArtifact);
+  }
+
+  // Output compilation-mappings.txt
+  @Override
+  protected void maybeOutputPropertyMap(TreeLogger logger, LinkerContext context,
+      ArtifactSet toReturn) {
+    if (!shouldOutputPropertyMap(context) ||
+        permutationsUtil.getPermutationsMap() == null ||
+        permutationsUtil.getPermutationsMap().isEmpty()) {
+      return;
+    }
+    
+    PropertiesMappingArtifact mappingArtifact =
+      new PropertiesMappingArtifact(CrossSiteIframeLinker.class,
+          permutationsUtil.getPermutationsMap());
+    
+    toReturn.add(mappingArtifact);
+    EmittedArtifact serializedMap;
+    try {
+      serializedMap = emitString(logger, mappingArtifact.getSerialized(),
+          "compilation-mappings.txt");
+      serializedMap.setPrivate(false);
+      toReturn.add(serializedMap);
+    } catch (UnableToCompleteException e) {
+      e.printStackTrace();
+    }
+  }
+
+  // If you set this to return true, you should also override 
+  // getJsPermutations() to return permutationsNull.js and 
+  // getJsInstallScript() to return installScriptAlreadyIncluded.js
+  protected boolean shouldIncludeBootstrapInPrimaryFragment(LinkerContext context) {
+    return false;
+  }
+
+  protected boolean shouldInstallCode(LinkerContext context) {
+    return true;
+  }
+
+  protected boolean shouldOutputPropertyMap(LinkerContext context) {
+    return false;
+  }
+  
+  @Override
+  protected String wrapPrimaryFragment(TreeLogger logger,
+      LinkerContext context, String script) {
+    StringBuffer out = new StringBuffer();
+    if (shouldIncludeBootstrapInPrimaryFragment(context)) {
+      try {
+        out.append(generateSelectionScript(logger, context, null));
+      } catch (UnableToCompleteException e) {
+        logger.log(TreeLogger.ERROR, "Problem setting up selection script", e);
+        e.printStackTrace();
+      }
+    }
+    
+    if (shouldInstallCode(context)) {
+      // Rewrite the code so it can be installed with
+      // __MODULE_FUNC__.onScriptDownloaded
+      out.append(context.getModuleFunctionName());
+      out.append(".onScriptDownloaded(");
+      out.append(JsToStringGenerationVisitor.javaScriptString(script.toString()));
+      out.append(")");
+    } else {
+      out.append(script.toString());
+    }
+    return out.toString();
+  }
+  
 }
diff --git a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
index 7b7e8e0..5b458ca 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
@@ -14,94 +14,14 @@
  * the License.
  */
 function __MODULE_FUNC__() {
-
-  /****************************************************************************
-   * Internal Global Variables
-   ***************************************************************************/
-  // Cache symbols locally for good obfuscation
-  var $wnd = window
-  ,$doc = document
-
-  // If non-empty, an alternate base url for this module
-  ,base = ''
-
-  // A map of properties that were declared in meta tags
-  ,metaProps = {}
-
-  // Maps property names onto sets of legal values for that property.
-  ,values = []
-
-  // Maps property names onto a function to compute that property.
-  ,providers = []
-
-  // A multi-tier lookup map that uses actual property values to quickly find
-  // the strong name of the cache.js file to load.
-  ,answers = []
-
-  // Provides the module with the soft permutation id
-  ,softPermutationId = 0
-
-  // Error functions.  Default unset in compiled mode, may be set by meta props.
-  ,onLoadErrorFunc, propertyErrorFunc
-
-  ;
-
+  var $wnd = window;
+  var $doc = document;
   sendStats('bootstrap', 'begin');
 
   /****************************************************************************
    * Internal Helper Functions
    ***************************************************************************/
 
-  // Get the name of the filename which contains the GWT code (usually something
-  // like 12BA3D5...21E.js) or devmode.js if we are in hosted mode.
-  // Also sets the softPermutationId variable if appropriate.
-  function getCompiledCodeFilename() {
-    if (isHostedMode()) {
-      return base + "__HOSTED_FILENAME__";
-    }
-    var strongName;
-    try {
-      // __PERMUTATIONS_BEGIN__
-      // Permutation logic is injected here. this code populates the 
-      // answers variable.
-      // __PERMUTATIONS_END__
-      var idx = strongName.indexOf(':');
-      if (idx != -1) {
-        softPermutationId = +(strongName.substring(idx + 1));
-        strongName = strongName.substring(0, idx);
-      }
-    } catch (e) {
-      // intentionally silent on property failure
-    }
-    return base + strongName + '.cache.js';
-  }
-
-  // Write a script tag to the element returned by getInstallLocation. We then
-  // either set the content of that script tag to be the code, or set the src
-  // tag if the code is actually a URL.
-  function installCode(code, isUrl) {
-    var docbody = getInstallLocation();
-
-    // Inject the fetched script into the script frame.
-    // The script will call onScriptInstalled.
-    var script = getInstallLocationDoc().createElement('script');
-    script.language='javascript';
-    if (isUrl) {
-      script.src = code;
-    } else {
-      script.text = code;
-    }
-    docbody.appendChild(script);
-
-    // Remove the tags to shrink the DOM a little.
-    // It should have installed its code immediately after being added.
-    docbody.removeChild(script);
-  }
-
-  function isBodyLoaded() {
-    return (/loaded|complete/.test($doc.readyState));
-  }
-
   function isHostedMode() {
     var query = $wnd.location.search;
     return (query.indexOf('gwt.codesvr=') != -1);
@@ -124,122 +44,72 @@
 
 
   /****************************************************************************
-   * Internal Helper functions that have been broken out into their own .js
-   * files for readability and for easy sharing between linkers.  The linker
-   * code will inject these functions in these placeholders.
-   ***************************************************************************/
-  // Provides the getInstallLocation() function
-  __INSTALL_LOCATION__
-
-  // Provides the processMetas() function, and sets the metaProps,
-  // onLoadErrorFunc and propertyErrorFunc variables
-  __PROCESS_METAS__
-
-  // Provides the computeScriptBase() function, which sets the base variable
-  __COMPUTE_SCRIPT_BASE__
-
-  // Provides the setupWaitForBodyLoad() function
-  __WAIT_FOR_BODY_LOADED__
-
-  // Provides functions used by the generated PERMUTATIONS code. 
-  __PERMUTATIONS__
-
-
-  /****************************************************************************
-   * WRITE ME
-   ***************************************************************************/
-  // __PROPERTIES_BEGIN__
-  // Properties logic is injected here. This code populates the values and
-  // providers variables
-  // __PROPERTIES_END__
-
-  // Determines whether or not a particular property value is allowed. Called by
-  // property providers.
-  function __gwt_isKnownPropertyValue(propName, propValue) {
-    return propValue in values[propName];
-  }
-
-  // Returns a meta property value, if any.  Used by DefaultPropertyProvider.
-  function __gwt_getMetaProperty(name) {
-    var value = metaProps[name];
-    return (value == null) ? null : value;
-  }
-
-
-  /****************************************************************************
    * Exposed Functions and Variables
    ***************************************************************************/
+  // These are set by various parts of the bootstrapping code, but they always
+  // need to exist, so give them all default values here.
+  
   // Exposed for the convenience of the devmode.js and md5.js files
   __MODULE_FUNC__.__sendStats = sendStats;
 
   // Exposed for the call made to gwtOnLoad. Some are not figured out yet, so
   // assign them later, once the values are known.
   __MODULE_FUNC__.__moduleName = '__MODULE_NAME__';
-  __MODULE_FUNC__.__errFn;
-  __MODULE_FUNC__.__moduleBase;
-  __MODULE_FUNC__.__softPermutationId;
+  __MODULE_FUNC__.__errFn = null;
+  __MODULE_FUNC__.__moduleBase = 'DUMMY';
+  __MODULE_FUNC__.__softPermutationId = 0;
 
   // Exposed for devmode.js
-  __MODULE_FUNC__.__computePropValue = computePropValue;
+  __MODULE_FUNC__.__computePropValue = null;
+
+  // Exposed for property provider code
+  var __gwt_isKnownPropertyValue = function() { return false; };
+  var __gwt_getMetaProperty = function() { return null; };
+
+  // Exposed for permutations code
+  __propertyErrorFunction = null;
+
+
+  /****************************************************************************
+   * Internal Helper functions that have been broken out into their own .js
+   * files for readability and for easy sharing between linkers.  The linker
+   * code will inject these functions in these placeholders.
+   ***************************************************************************/
+  // Provides the installScript() function.
+  __INSTALL_SCRIPT__
+
+  // Provides the processMetas() function which sets the __gwt_getMetaProperty
+  // __propertyErrorFunction and __MODULE_FUNC__.__errFn variables if needed
+  __PROCESS_METAS__
+
+  // Provides the computeScriptBase() function
+  __COMPUTE_SCRIPT_BASE__
+
+  // Provides the getCompiledCodeFilename() function which sets the 
+  // __gwt_isKnownPropertyValue, MODULE_FUNC__.__computePropValue and
+  // __MODULE_FUNC__.__softPermutationId variables if needed
+  __PERMUTATIONS__
 
 
   /****************************************************************************
    * Bootstrap startup code
    ***************************************************************************/
-
-  var startDownloadImmediately = __START_DOWNLOAD_IMMEDIATELY__;
-  if (isHostedMode()) {
-    // since hosted.js doesn't have the necessary wrappings, we always install
-    // a script tag in the iframe rather than using a giant string literal.
-    // If the devmode.js file went throught the same processing as the
-    // md5.js files, this could go away.
-    startDownloadImmediately = false;
-  }
-
+  // Must be called before computeScriptBase() and getCompiledFilename()
   processMetas();
-  base = computeScriptBase();
-  __MODULE_FUNC__.__errFn = onLoadErrorFunc;
-  __MODULE_FUNC__.__moduleBase = base;
 
-  sendStats('bootstrap', 'selectingPermutation');
+  // Must be set before getCompiledFilename() is called
+  __MODULE_FUNC__.__moduleBase = computeScriptBase();
+
+  // Must be done right before the "bootstrap" "end" stat is sent
   var filename = getCompiledCodeFilename();
-  __MODULE_FUNC__.__softPermutationId = softPermutationId;
-  sendStats('bootstrap', 'end');
 
+  sendStats('bootstrap', 'end');
   // For now, send this dummy statistic since some people are depending on it
   // being present. TODO(unnurg): remove this statistic soon
   sendStats('loadExternalRefs', 'begin');
   sendStats('loadExternalRefs', 'end');
 
-  sendStats('moduleStartup', 'moduleRequested');
-  if (startDownloadImmediately) {
-    // Set up a script tag to start downloading immediately, as well as a
-    // callback to install the code once it is downloaded and the body is loaded.
-    __MODULE_FUNC__.onScriptDownloaded = function(code) {
-      setupWaitForBodyLoad(function() {
-        installCode(code, false);
-      });
-    };
-    if (isBodyLoaded()) {
-      // if the body is loaded, then the tag to download the script can be added
-      // in a non-destructive manner
-      var script = document.createElement('script');
-      script.src = filename;
-      $doc.getElementsByTagName('head')[0].appendChild(script);
-    } else {
-      // if the doc has not yet loaded, go ahead and do a destructive
-      // document.write since we want to immediately start the download.
-      // Note that we cannot append an element to the doc if it is still loading
-      // since this would cause problems in IE.
-      $doc.write("<script src='" + filename + "'></scr" + "ipt>");
-    }
-  } else {
-    // Just pass along the filename so tha a script tag can be installed in the
-    // iframe to download it.  Since we will be adding the iframe to the body,
-    // we still need to wait for the body to load before going forward.
-    setupWaitForBodyLoad(function() {
-      installCode(filename, true);
-    });
-  }
+  installScript(filename);
+
 }
 __MODULE_FUNC__();
diff --git a/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java b/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
index 140d90c..d5516bc 100644
--- a/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/SingleScriptLinker.java
@@ -50,7 +50,7 @@
       ArtifactSet artifacts, boolean onePermutation)
       throws UnableToCompleteException {
     if (onePermutation) {
-      processSelectionInformation(artifacts);
+      permutationsUtil.setupPermutationsMap(artifacts);
       ArtifactSet toReturn = new ArtifactSet(artifacts);
       toReturn.add(emitSelectionScript(logger, context, artifacts));
       return toReturn;
diff --git a/dev/core/test/com/google/gwt/core/ext/linker/impl/SelectionScriptJavaScriptTest.java b/dev/core/test/com/google/gwt/core/ext/linker/impl/SelectionScriptJavaScriptTest.java
index 479d695..f3baebc 100644
--- a/dev/core/test/com/google/gwt/core/ext/linker/impl/SelectionScriptJavaScriptTest.java
+++ b/dev/core/test/com/google/gwt/core/ext/linker/impl/SelectionScriptJavaScriptTest.java
@@ -47,6 +47,9 @@
   private static String loadComputeScriptBase() throws IOException {
     StringBuffer code = new StringBuffer();
     code.append("var base = \"\", $doc=document;\n");
+    code.append("__gwt_getMetaProperty = function(name) { "
+                 + "var value = metaProps[name];"
+                 + "return (value == null) ? null : value; }");
     code.append(Utility.getFileFromClassPath(SelectionScriptLinker.COMPUTE_SCRIPT_BASE_JS));
     code.append("computeScriptBase();\n");
     return code.toString().replaceAll("__MODULE_NAME__", TEST_MODULE_NAME);