Revision: 8413
Author: [email protected]
Date: Fri Jul 23 11:50:52 2010
Log: Rolling back the cross-site linker using an iframe. It's not clear this is a safe change for all apps.

Review by: [email protected]
http://code.google.com/p/google-web-toolkit/source/detail?r=8413

Added:
/trunk/dev/core/src/com/google/gwt/dev/jjs/impl/HandleCrossFragmentReferences.java
Modified:
 /trunk/dev/core/src/com/google/gwt/core/linker/XSLinker.java
 /trunk/dev/core/src/com/google/gwt/core/linker/XSTemplate.js
 /trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
 /trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml
 /trunk/user/src/com/google/gwt/core/XSLinker.gwt.xml

=======================================
--- /dev/null
+++ /trunk/dev/core/src/com/google/gwt/dev/jjs/impl/HandleCrossFragmentReferences.java Fri Jul 23 11:50:52 2010
@@ -0,0 +1,306 @@
+/*
+ * 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.dev.jjs.impl;
+
+import com.google.gwt.core.ext.BadPropertyValueException;
+import com.google.gwt.core.ext.PropertyOracle;
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.dev.jjs.SourceInfo;
+import com.google.gwt.dev.js.ast.JsBinaryOperation;
+import com.google.gwt.dev.js.ast.JsBinaryOperator;
+import com.google.gwt.dev.js.ast.JsContext;
+import com.google.gwt.dev.js.ast.JsExpression;
+import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.js.ast.JsModVisitor;
+import com.google.gwt.dev.js.ast.JsName;
+import com.google.gwt.dev.js.ast.JsNameRef;
+import com.google.gwt.dev.js.ast.JsObjectLiteral;
+import com.google.gwt.dev.js.ast.JsProgram;
+import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsVars;
+import com.google.gwt.dev.js.ast.JsVisitor;
+import com.google.gwt.dev.js.ast.JsVars.JsVar;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.Map.Entry;
+
+/**
+ * Rewrite JavaScript to better handle references from one code fragment to
+ * another. For any function defined off the initial download and accessed from + * a different island than the one it's defined on, predefine a variable in the
+ * initial download to hold its definition.
+ */
+public class HandleCrossFragmentReferences {
+  /**
+   * Find out which islands define and use each named function or variable.
+   * This visitor is not smart about which definitions and uses matter.  It
+   * blindly records all of them.
+   */
+  private class FindNameReferences extends JsVisitor {
+ Map<JsName, Set<Integer>> islandsDefining = new LinkedHashMap<JsName, Set<Integer>>(); + Map<JsName, Set<Integer>> islandsUsing = new LinkedHashMap<JsName, Set<Integer>>();
+    private int currentIsland;
+
+    @Override
+    public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
+      JsName name = x.getName();
+      if (name != null) {
+        definitionSeen(name);
+      }
+    }
+
+    @Override
+    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+      if (x.getQualifier() == null) {
+        JsName name = x.getName();
+        if (name != null) {
+          referenceSeen(name);
+        }
+      }
+    }
+
+    @Override
+    public void endVisit(JsVars x, JsContext<JsStatement> ctx) {
+      for (JsVar var : x) {
+        JsName name = var.getName();
+        if (name != null) {
+          definitionSeen(name);
+        }
+      }
+    }
+
+    @Override
+    public boolean visit(JsProgram x, JsContext<JsProgram> ctx) {
+      for (int i = 0; i < x.getFragmentCount(); i++) {
+        currentIsland = i;
+        accept(x.getFragmentBlock(i));
+      }
+
+      return false;
+    }
+
+    private void definitionSeen(JsName name) {
+      /*
+ * Support multiple definitions, because local variables can reuse the
+       * same name.
+       */
+      Set<Integer> defs = islandsDefining.get(name);
+      if (defs == null) {
+        defs = new LinkedHashSet<Integer>();
+        islandsDefining.put(name, defs);
+      }
+      defs.add(currentIsland);
+    }
+
+    private void referenceSeen(JsName name) {
+      Set<Integer> refs = islandsUsing.get(name);
+      if (refs == null) {
+        refs = new HashSet<Integer>();
+        islandsUsing.put(name, refs);
+      }
+      refs.add(currentIsland);
+    }
+  }
+
+  /**
+   * Rewrite var and function declarations as assignments, if their name is
+   * accessed cross-island. Rewrite refs to such names correspondingly.
+   */
+  private class RewriteDeclsAndRefs extends JsModVisitor {
+    @Override
+    public void endVisit(JsFunction x, JsContext<JsExpression> ctx) {
+      if (namesToPredefine.contains(x.getName())) {
+        JsBinaryOperation asg = new JsBinaryOperation(x.getSourceInfo(),
+            JsBinaryOperator.ASG, makeRefViaJslink(x.getName(),
+                x.getSourceInfo()), x);
+        x.setName(null);
+        ctx.replaceMe(asg);
+      }
+    }
+
+    @Override
+    public void endVisit(JsNameRef x, JsContext<JsExpression> ctx) {
+      if (namesToPredefine.contains(x.getName())) {
+        ctx.replaceMe(makeRefViaJslink(x.getName(), x.getSourceInfo()));
+      }
+    }
+
+    @Override
+    public void endVisit(JsVars x, JsContext<JsStatement> ctx) {
+      if (!ctx.canInsert()) {
+        return;
+      }
+
+      /*
+ * Loop through each var and see if it was predefined. If so, then remove + * the var. If the var has an initializer, then add back an assignment + * statement to initialize it. If there is no initializer, then don't add + * anything back; the var will still have undefined as its initial value,
+       * just like before.
+       *
+       * A complication is that the variables that are predefined might be
+ * interspersed with variables that are not. That means the general result
+       * of this transformation has alternating var lists and assignment
+ * statements. The currentVar variable holds the most recently inserted + * statement, if that statement was a JsVars; otherwise it holds null.
+       */
+
+      JsVars currentVar = null;
+      Iterator<JsVar> varsIterator = x.iterator();
+      while (varsIterator.hasNext()) {
+        JsVar var = varsIterator.next();
+        if (namesToPredefine.contains(var.getName())) {
+          // The var was predefined
+          if (var.getInitExpr() != null) {
+            // If it has an initializer, add an assignment statement
+ JsBinaryOperation asg = new JsBinaryOperation(var.getSourceInfo(),
+                JsBinaryOperator.ASG, makeRefViaJslink(var.getName(),
+                    var.getSourceInfo()), var.getInitExpr());
+            ctx.insertBefore(asg.makeStmt());
+            currentVar = null;
+          }
+        } else {
+          // The var was not predefined; add it to a var list
+          if (currentVar == null) {
+            currentVar = new JsVars(x.getSourceInfo());
+            ctx.insertBefore(currentVar);
+          }
+          currentVar.add(var);
+        }
+      }
+
+      ctx.removeMe();
+    }
+
+ private JsNameRef makeRefViaJslink(JsName name, SourceInfo sourceInfo) {
+      JsNameRef ref = name.makeRef(sourceInfo);
+      ref.setQualifier(jslink.makeRef(sourceInfo));
+      return ref;
+    }
+  }
+
+ public static String PROP_PREDECLARE_VARS = "compiler.predeclare.cross.fragment.references";
+
+  public static void exec(TreeLogger logger, JsProgram jsProgram,
+      PropertyOracle[] propertyOracles) {
+ new HandleCrossFragmentReferences(logger, jsProgram, propertyOracles).execImpl();
+  }
+
+  private static boolean containsOtherThan(Set<Integer> set, int allowed) {
+    for (int elem : set) {
+      if (elem != allowed) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  private JsName jslink;
+  private final JsProgram jsProgram;
+  private final Set<JsName> namesToPredefine = new LinkedHashSet<JsName>();
+  private final PropertyOracle[] propertyOracles;
+  private final TreeLogger logger;
+
+ private HandleCrossFragmentReferences(TreeLogger logger, JsProgram jsProgram,
+      PropertyOracle[] propertyOracles) {
+    this.logger = logger;
+    this.jsProgram = jsProgram;
+    this.propertyOracles = propertyOracles;
+  }
+
+  private void chooseNamesToPredefine(Map<JsName, Set<Integer>> map,
+      Map<JsName, Set<Integer>> islandsUsing) {
+    for (Entry<JsName, Set<Integer>> entry : map.entrySet()) {
+      JsName name = entry.getKey();
+      Set<Integer> defIslands = entry.getValue();
+      if (defIslands.size() != 1) {
+        // Only rewrite global variables, which should have exactly one
+        // definition
+        continue;
+      }
+      int defIsland = defIslands.iterator().next();
+      if (defIsland == 0) {
+ // Variables defined on the base island can be accessed directly from
+        // other islands
+        continue;
+      }
+      Set<Integer> useIslands = islandsUsing.get(name);
+      if (useIslands == null) {
+        // The variable is never used. Leave it alone.
+        continue;
+      }
+
+      if (containsOtherThan(islandsUsing.get(name), defIsland)) {
+        namesToPredefine.add(name);
+      }
+    }
+  }
+
+  /**
+   * Define the jslink object that will be used to fix up cross-island
+   * references.
+   */
+  private void defineJsLink() {
+    SourceInfo info = jsProgram.createSourceInfoSynthetic(
+        HandleCrossFragmentReferences.class, "defining jslink");
+    jslink = jsProgram.getScope().declareName("jslink");
+    JsVars vars = new JsVars(info);
+    JsVar var = new JsVar(info, jslink);
+    var.setInitExpr(new JsObjectLiteral(info));
+    vars.add(var);
+    jsProgram.getFragmentBlock(0).getStatements().add(0, vars);
+  }
+
+  private void execImpl() {
+    if (jsProgram.getFragmentCount() == 1) {
+      return;
+    }
+    if (!shouldPredeclareReferences()) {
+      return;
+    }
+    defineJsLink();
+    FindNameReferences findNameReferences = new FindNameReferences();
+    findNameReferences.accept(jsProgram);
+    chooseNamesToPredefine(findNameReferences.islandsDefining,
+        findNameReferences.islandsUsing);
+    new RewriteDeclsAndRefs().accept(jsProgram);
+  }
+
+  /**
+ * Check the property oracles for whether references should be predeclared or
+   * not. If any of them say yes, then do the rewrite.
+   */
+  private boolean shouldPredeclareReferences() {
+    for (PropertyOracle props : propertyOracles) {
+      try {
+        String propValue = props.getSelectionProperty(logger,
+            PROP_PREDECLARE_VARS).getCurrentValue();
+        if (Boolean.parseBoolean(propValue)) {
+          return true;
+        }
+      } catch (BadPropertyValueException e) {
+        // Property not defined; don't rewrite
+      }
+    }
+
+    return false;
+  }
+}
=======================================
--- /trunk/dev/core/src/com/google/gwt/core/linker/XSLinker.java Fri Jul 23 09:44:56 2010 +++ /trunk/dev/core/src/com/google/gwt/core/linker/XSLinker.java Fri Jul 23 11:50:52 2010
@@ -17,7 +17,6 @@

 import com.google.gwt.core.ext.LinkerContext;
 import com.google.gwt.core.ext.TreeLogger;
-import com.google.gwt.core.ext.linker.CompilationResult;
 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;
@@ -25,8 +24,6 @@
 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;

 /**
  * Generates a cross-site compatible bootstrap sequence.
@@ -43,7 +40,6 @@
   protected String generateDeferredFragment(TreeLogger logger,
       LinkerContext context, int fragment, String js) {
     StringBuilder sb = new StringBuilder();
-    sb.append("$wnd.");
     sb.append(context.getModuleFunctionName());
     sb.append(".runAsyncCallback");
     sb.append(fragment);
@@ -52,27 +48,6 @@
     sb.append(");\n");
     return sb.toString();
   }
-
-  @Override
-  protected byte[] generatePrimaryFragment(TreeLogger logger,
-      LinkerContext context, CompilationResult result, String[] js) {
-    // Wrap the script code with its prefix and suffix
-    TextOutput script = new DefaultTextOutput(context.isOutputCompact());
- script.print(getModulePrefix(context, result.getStrongName(), js.length > 1));
-    script.print(js[0]);
-    script.print(getModuleSuffix(logger, context));
-
-    // Rewrite the code so it can be installed with
-    // __MODULE_FUNC__.onScriptDownloaded
-
-    StringBuffer out = new StringBuffer();
-    out.append(context.getModuleFunctionName());
-    out.append(".onScriptDownloaded(");
- out.append(JsToStringGenerationVisitor.javaScriptString(script.toString()));
-    out.append(")");
-
-    return Util.getBytes(out.toString());
-  }

   @Override
   protected String getCompilationExtension(TreeLogger logger,
@@ -83,13 +58,13 @@
   @Override
protected String getModulePrefix(TreeLogger logger, LinkerContext context,
       String strongName) {
-    throw new UnsupportedOperationException("Should not be called");
+    return getModulePrefix(context, strongName, true);
   }

   @Override
protected String getModulePrefix(TreeLogger logger, LinkerContext context,
       String strongName, int numFragments) {
-    throw new UnsupportedOperationException("Should not be called");
+    return getModulePrefix(context, strongName, numFragments > 1);
   }

   @Override
@@ -103,10 +78,12 @@

     // Generate the call to tell the bootstrap code that we're ready to go.
     out.newlineOpt();
-    out.print("if ($wnd." + context.getModuleFunctionName() + " && $wnd."
-        + context.getModuleFunctionName() + ".onScriptInstalled) $wnd."
- + context.getModuleFunctionName() + ".onScriptInstalled(gwtOnLoad);");
+    out.print("if (" + context.getModuleFunctionName() + " && "
+        + context.getModuleFunctionName() + ".onScriptLoad)"
+        + context.getModuleFunctionName() + ".onScriptLoad(gwtOnLoad);");
     out.newlineOpt();
+    out.print("})();");
+    out.newlineOpt();

     return out.toString();
   }
@@ -119,11 +96,16 @@

   private String getModulePrefix(LinkerContext context, String strongName,
       boolean supportRunAsync) {
-    TextOutput out = new DefaultTextOutput(context.isOutputCompact());
-
+ DefaultTextOutput out = new DefaultTextOutput(context.isOutputCompact());
+
+    out.print("(function(){");
+    out.newlineOpt();
+
+    // Setup the well-known variables.
+    //
     out.print("var $gwt_version = \"" + About.getGwtVersionNum() + "\";");
     out.newlineOpt();
-    out.print("var $wnd = window.parent;");
+    out.print("var $wnd = window;");
     out.newlineOpt();
     out.print("var $doc = $wnd.document;");
     out.newlineOpt();
@@ -143,10 +125,13 @@
     out.newlineOpt();

     if (supportRunAsync) {
-      out.print("var __gwtModuleFunction = $wnd.");
       out.print(context.getModuleFunctionName());
-      out.print(";");
+      out.print(".installCode = function(code) { eval(code) };");
       out.newlineOpt();
+      out.print("var __gwtModuleFunction = ");
+      out.print(context.getModuleFunctionName());
+      out.print(";");
+      out.newline();
     }

     return out.toString();
=======================================
--- /trunk/dev/core/src/com/google/gwt/core/linker/XSTemplate.js Fri Jul 23 09:44:56 2010 +++ /trunk/dev/core/src/com/google/gwt/core/linker/XSTemplate.js Fri Jul 23 11:50:52 2010
@@ -23,17 +23,8 @@
,$stats = $wnd.__gwtStatsEvent ? function(a) {return $wnd.__gwtStatsEvent(a);} : null
   ,$sessionId = $wnd.__gwtStatsSessionId ? $wnd.__gwtStatsSessionId : null

-  // Whether the document body element has finished loading
-  ,bodyDone
-
-  // The downloaded script code, once it arrives
-  ,downloadedScript
-
-  // Whether the main script has been injected yet
-  ,scriptInjected
-
-  // The iframe holding the app's code
-  ,scriptFrame
+  // These variables gate calling gwtOnLoad; all must be true to start
+  ,gwtOnLoad, bodyDone

   // If non-empty, an alternate base url for this module
   ,base = ''
@@ -94,55 +85,25 @@
     return result;
   }

-  // Called when the body has been finished and when the script
-  // to install is available. These can happen in either order.
-  // When both have happened, create the code-holding iframe.
+  // Called by onScriptLoad() and onload(). It causes
+  // the specified module to be cranked up.
   //
-  function maybeCreateFrame() {
-    if (bodyDone && downloadedScript && !scriptInjected) {
-       scriptInjected = true;
-
-       // Create the script frame, making sure it's invisible, but not
-       // "display:none", which keeps some browsers from running code in it.
-       scriptFrame = $doc.createElement('iframe');
-        scriptFrame.src = 'javascript:""';
-        scriptFrame.id = '__MODULE_NAME__';
- scriptFrame.style.cssText = 'position:absolute; width:0; height:0; border:none';
-        scriptFrame.tabIndex = -1;
-        document.body.appendChild(scriptFrame);
-
-        // For some reason, adding this setTimeout makes code installation
-        // more reliable.
-        setTimeout(function() {
-          installCode(downloadedScript);
-        })
+  function maybeStartModule() {
+    // TODO: it may not be necessary to check gwtOnLoad here.
+    if (gwtOnLoad && bodyDone) {
+ gwtOnLoad(onLoadErrorFunc, '__MODULE_NAME__', base, softPermutationId);
+      // Record when the module EntryPoints return.
+      $stats && $stats({
+        moduleName: '__MODULE_NAME__',
+        sessionId: $sessionId,
+        subSystem: 'startup',
+        evtGroup: 'moduleStartup',
+        millis:(new Date()).getTime(),
+        type: 'end',
+      });
     }
   }

-  // Install code into scriptFrame
-  //
-  function installCode(code) {
-    var doc = scriptFrame.contentDocument;
-    if (!doc) {
-      doc = scriptFrame.contentWindow.document;
-    }
-
-    var dochead = doc.getElementsByTagName('head')[0];
-
-    // Inject the fetched script into the script frame.
-    // The script will call onScriptLoad.
-    var script = doc.createElement('script');
-    script.language='javascript';
-    script.text = code;
-
-    dochead.appendChild(script);
-
-    // Remove the tag to shrink the DOM a little.
-    // It should have installed its code immediately after being added.
-    dochead.removeChild(script);
-  }
-
-
   __COMPUTE_SCRIPT_BASE__
   __PROCESS_METAS__

@@ -205,32 +166,14 @@

   // --------------- EXPOSED FUNCTIONS ----------------

-  // Called when the initial script code has been downloaded
-  __MODULE_FUNC__.onScriptDownloaded = function(code) {
-       downloadedScript = code;
-       maybeCreateFrame();
-  }
-
-  // Called when the compiled script has been installed
+ // Called when the compiled script identified by moduleName is done loading.
   //
-  __MODULE_FUNC__.onScriptInstalled = function(gwtOnLoadFunc) {
+  __MODULE_FUNC__.onScriptLoad = function(gwtOnLoadFunc) {
     // remove the callback to prevent it being called twice
-    __MODULE_FUNC__.onScriptInstalled = null;
- gwtOnLoadFunc(onLoadErrorFunc, '__MODULE_NAME__', base, softPermutationId);
-    // Record when the module EntryPoints return.
-    $stats && $stats({
-      moduleName: '__MODULE_NAME__',
-      sessionId: $sessionId,
-      subSystem: 'startup',
-      evtGroup: 'moduleStartup',
-      millis:(new Date()).getTime(),
-      type: 'end',
-    });
-  }
-
-  // Install code pulled in via runAsync
-  //
-  __MODULE_FUNC__.installCode = installCode;
+    __MODULE_FUNC__.onScriptLoad = null;
+    gwtOnLoad = gwtOnLoadFunc;
+    maybeStartModule();
+  }

   // --------------- STRAIGHT-LINE CODE ---------------

@@ -277,7 +220,7 @@
 // __MODULE_STYLES_BEGIN__
// Style resources are injected here to prevent operation aborted errors on ie
 // __MODULE_STYLES_END__
-      maybeCreateFrame();
+      maybeStartModule();

       if ($doc.removeEventListener) {
         $doc.removeEventListener("DOMContentLoaded", onBodyDone, false);
=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java Fri Jul 23 09:44:56 2010 +++ /trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java Fri Jul 23 11:50:52 2010
@@ -69,6 +69,7 @@
 import com.google.gwt.dev.jjs.impl.FragmentLoaderCreator;
 import com.google.gwt.dev.jjs.impl.GenerateJavaAST;
 import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST;
+import com.google.gwt.dev.jjs.impl.HandleCrossFragmentReferences;
 import com.google.gwt.dev.jjs.impl.JavaScriptObjectNormalizer;
 import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap;
 import com.google.gwt.dev.jjs.impl.JsFunctionClusterer;
@@ -355,6 +356,11 @@
         default:
           throw new InternalCompilerException("Unknown output mode");
       }
+
+      // (10.8) Handle cross-island references.
+ // No new JsNames or references to JSNames can be introduced after this
+      // point.
+ HandleCrossFragmentReferences.exec(logger, jsProgram, propertyOracles);

       // (11) Perform any post-obfuscation normalizations.

=======================================
--- /trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml Fri Jul 23 09:44:56 2010 +++ /trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml Fri Jul 23 11:50:52 2010
@@ -22,6 +22,17 @@
<define-configuration-property name='compiler.splitpoint.initial.sequence'
     is-multi-valued='true' />

+  <!--
+ Whether or not the compiler should predeclare variables that are defined
+    outside the initial download and are referenced from a different code
+ fragment than the one defining them. This is usually determined by which
+    linker is used and is not directly meaningful to users.
+  -->
+  <define-property name="compiler.predeclare.cross.fragment.references"
+    values="true,false" />
+  <set-property name="compiler.predeclare.cross.fragment.references"
+    value="false" />
+
<!-- From here down, the properties are unsupported and are only available for test cases -->

   <!--
=======================================
--- /trunk/user/src/com/google/gwt/core/XSLinker.gwt.xml Fri Jul 23 09:44:56 2010 +++ /trunk/user/src/com/google/gwt/core/XSLinker.gwt.xml Fri Jul 23 11:50:52 2010
@@ -21,4 +21,8 @@
class="com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadingStrategy" />
     <when-linker-added name="xs" />
   </replace-with>
+
+ <set-property name="compiler.predeclare.cross.fragment.references" value="true">
+    <when-linker-added name="xs" />
+  </set-property>
 </module>

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to