Export user.agent and locale more reliably.

Introduces the js.embedded.properties configuration
parameter. This is a list of binding properties to
include in each permutation's initial fragment (in the
$permProps variable).

When using a CrossSiteIframeLinker, export these properties
in __gwt_activeModules so that Super Dev Mode can pick them
up.

Rationale: The Super Dev Mode bookmarklet calls
__gwt_activeModules[(module name)].bindings() to find out
which bindings it should compile with. However, this isn't
reliable when the developer restricts permutations by
setting these variables to a fixed value. This causes Super
Dev Mode to compile more permutations than necessary.

Bug: issue 7458
Change-Id: I2c7bf4635139eef3d938c4bd8aaaa19499c43bc3
Review-Link: https://gwt-review.googlesource.com/#/c/7702/
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 79e9eaa..60b08d5 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
@@ -481,6 +481,8 @@
         + "__gwtModuleFunction.__computePropValue);");
     out.newlineOpt();
     out.print("$sendStats('moduleStartup', 'end');");
+    out.newlineOpt();
+    out.print("__gwtModuleFunction.__moduleStartupDone($permProps);");
 
     writeMagicComments(out, context, 0, strongName);
     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 25b85f6..b5af74c 100644
--- a/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
+++ b/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
@@ -86,6 +86,21 @@
       ($wnd.__gwt_activeModules = ($wnd.__gwt_activeModules || {}));
   activeModules["__MODULE_NAME__"] = {moduleName: "__MODULE_NAME__"};
 
+  __MODULE_FUNC__.__moduleStartupDone = function(permProps) {
+    // Make embedded properties available to Super Dev Mode.
+    // (They override any properties already exported.)
+    var oldBindings = activeModules["__MODULE_NAME__"].bindings;
+    activeModules["__MODULE_NAME__"].bindings = function() {
+      var props = oldBindings ? oldBindings() : {};
+      var embeddedProps = permProps[__MODULE_FUNC__.__softPermutationId];
+      for (var i = 0; i < embeddedProps.length; i++) {
+        var pair = embeddedProps[i];
+        props[pair[0]] = pair[1];
+      }
+      return props;
+    };
+  };
+
   /****************************************************************************
    * Internal Helper functions that have been broken out into their own .js
    * files for readability and for easy sharing between linkers.  The linker
diff --git a/dev/core/src/com/google/gwt/dev/cfg/PermProps.java b/dev/core/src/com/google/gwt/dev/cfg/PermProps.java
index 167a3c0..4ce3f15 100644
--- a/dev/core/src/com/google/gwt/dev/cfg/PermProps.java
+++ b/dev/core/src/com/google/gwt/dev/cfg/PermProps.java
@@ -15,8 +15,17 @@
  */
 package com.google.gwt.dev.cfg;
 
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.thirdparty.guava.common.base.Objects;
 import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
+import com.google.gwt.thirdparty.guava.common.collect.Lists;
+import com.google.gwt.thirdparty.guava.common.collect.Sets;
+
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
 
 /**
  * The properties for one hard permutation.
@@ -44,6 +53,14 @@
   }
 
   /**
+   * Returns the binding properties in dependency order (permutation-independent).
+   */
+  public ImmutableList<BindingProperty> getBindingProperties() {
+    // Just take the first one.
+    return ImmutableList.copyOf(props.get(0).getOrderedProps());
+  }
+
+  /**
    * Returns the properties for each soft permutation, ordered by soft permutation id.
    *
    * <p>If soft permutations aren't turned on, the list will contain one item.
@@ -97,6 +114,46 @@
   }
 
   /**
+   * Returns the binding property values to be embedded into the initial JavaScript fragment
+   * for this permutation. (There will be one map for each soft permutation.)
+   */
+  public ImmutableList<ImmutableMap<String, String>> findEmbeddedProperties(TreeLogger logger) {
+
+    Set<String> propsWanted = Sets.newTreeSet(getConfigProps().getStrings(
+        "js.embedded.properties"));
+
+    // Filter out any binding properties that don't exist.
+    SortedSet<String> propsToSave = Sets.newTreeSet();
+    for (BindingProperty prop : getBindingProperties()) {
+      String name = prop.getName();
+      if (propsWanted.remove(name)) {
+        propsToSave.add(name);
+      }
+    }
+
+    // Warn about binding properties that don't exist.
+    if (!propsWanted.isEmpty()) {
+      TreeLogger branch = logger.branch(Type.WARN,
+          propsWanted.size() + "properties listed in js.embedded.properties are undefined");
+      for (String prop : propsWanted) {
+        branch.log(Type.WARN, "undefined property: '" + prop + "'");
+      }
+    }
+
+    // Find the values.
+    List<ImmutableMap<String, String>> result = Lists.newArrayList();
+    for (BindingProps softProps : getSoftProps()) {
+      ImmutableMap.Builder<String, String> values = ImmutableMap.builder();
+      for (String key : propsToSave) {
+        values.put(key, softProps.getString(key, null));
+      }
+      result.add(values.build());
+    }
+
+    return ImmutableList.copyOf(result);
+  }
+
+  /**
    * Dumps the properties for this hard permuation, for logging and soyc.
    */
   public String prettyPrint() {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index eae6151..cdd6e94 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -110,6 +110,7 @@
 import com.google.gwt.dev.js.JsSymbolResolver;
 import com.google.gwt.dev.js.JsUnusedFunctionRemover;
 import com.google.gwt.dev.js.SizeBreakdown;
+import com.google.gwt.dev.js.ast.JsArrayLiteral;
 import com.google.gwt.dev.js.ast.JsContext;
 import com.google.gwt.dev.js.ast.JsForIn;
 import com.google.gwt.dev.js.ast.JsFunction;
@@ -121,7 +122,9 @@
 import com.google.gwt.dev.js.ast.JsNode;
 import com.google.gwt.dev.js.ast.JsParameter;
 import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsStringLiteral;
 import com.google.gwt.dev.js.ast.JsVars;
+import com.google.gwt.dev.js.ast.JsVars.JsVar;
 import com.google.gwt.dev.js.ast.JsVisitor;
 import com.google.gwt.dev.util.DefaultTextOutput;
 import com.google.gwt.dev.util.Empty;
@@ -137,6 +140,7 @@
 import com.google.gwt.soyc.SoycDashboard;
 import com.google.gwt.soyc.io.ArtifactsOutputDirectory;
 import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting;
+import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
 import com.google.gwt.thirdparty.guava.common.collect.Lists;
 import com.google.gwt.thirdparty.guava.common.collect.Multimap;
 
@@ -153,6 +157,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
@@ -304,6 +309,8 @@
         // TODO(stalcup): move to normalization
         JsBreakUpLargeVarStatements.exec(jsProgram, props.getConfigProps());
 
+        embedBindingProperties(jsProgram, props);
+
         // (8) Generate Js source
         List<JsSourceMap> sourceInfoMaps = new ArrayList<JsSourceMap>();
         boolean isSourceMapsEnabled = props.isTrueInAnyPermutation("compiler.useSourceMaps");
@@ -441,6 +448,38 @@
     }
 
     /**
+     * Embeds properties into $permProps for easy access from JavaScript.
+     */
+    private void embedBindingProperties(JsProgram jsProgram, PermProps props) {
+
+      // Generates a list of lists of pairs: [[["key", "value"], ...], ...]
+      // The outermost list is indexed by soft permutation id. Each item represents
+      // a map from binding properties to their values, but is stored as a list of pairs
+      // for easy iteration.
+      JsArrayLiteral permProps = new JsArrayLiteral(SourceOrigin.UNKNOWN);
+      for (ImmutableMap<String, String> propMap : props.findEmbeddedProperties(logger)) {
+        JsArrayLiteral entryList = new JsArrayLiteral(SourceOrigin.UNKNOWN);
+        for (Entry<String, String> entry : propMap.entrySet()) {
+          JsArrayLiteral pair = new JsArrayLiteral(SourceOrigin.UNKNOWN);
+          pair.getExpressions().add(new JsStringLiteral(SourceOrigin.UNKNOWN, entry.getKey()));
+          pair.getExpressions().add(new JsStringLiteral(SourceOrigin.UNKNOWN, entry.getValue()));
+          entryList.getExpressions().add(pair);
+        }
+        permProps.getExpressions().add(entryList);
+      }
+
+      // Generate: var $permProps = ...;
+      JsVar var = new JsVar(SourceOrigin.UNKNOWN,
+          jsProgram.getScope().findExistingUnobfuscatableName("$permProps"));
+      var.setInitExpr(permProps);
+      JsVars vars = new JsVars(SourceOrigin.UNKNOWN);
+      vars.add(var);
+
+      // Put it at the beginning for easy reference.
+      jsProgram.getGlobalBlock().getStatements().add(0, vars);
+    }
+
+    /**
      * Generate Js code from the given Js ASTs. Also produces information about that transformation.
      */
     private void generateJavaScriptCode(JavaToJavaScriptMap jjsMap, String[] jsFragments,
@@ -746,7 +785,7 @@
     }
 
     private Map<JsName, JsLiteral> renameJsSymbols(PermProps props) {
-      Map<JsName, JsLiteral> internedLiteralByVariableName = null;
+      Map<JsName, JsLiteral> internedLiteralByVariableName;
       switch (options.getOutput()) {
         case OBFUSCATED:
           internedLiteralByVariableName = runObfuscateNamer(props);
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java b/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
index 3850b37..f785d61 100644
--- a/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
+++ b/dev/core/src/com/google/gwt/dev/js/ast/JsRootScope.java
@@ -329,6 +329,7 @@
 
       // GWT-defined identifiers
       "$wnd", "$doc", "$moduleName", "$moduleBase", "$gwt_version", "$sessionId", "gwtOnLoad",
+      "$permProps",
 
       // typeMarker 'tM' field will break JSO detection on window object if nullMethod is called 'tM'
       "tM",
diff --git a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
index 8820488..654a175 100644
--- a/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
+++ b/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
@@ -119,4 +119,13 @@
                                    is-multi-valued="false"/>
   <set-configuration-property name="js.export.closurestyle.fullyqualified"
                                 value="false" />
+
+  <!--
+    Specifies that a binding property should be included in the $permProps variable
+    in the generated JavaScript. This will allow Super Dev Mode to pick it up
+    regardless of how many permutations are generated.
+  -->
+  <define-configuration-property name="js.embedded.properties" is-multi-valued="true"/>
+  <set-configuration-property name="js.embedded.properties" value="locale"/>
+  <set-configuration-property name="js.embedded.properties" value="user.agent"/>
 </module>