Revision: 8410
Author: [email protected]
Date: Fri Jul 23 09:44:56 2010
Log: Modifies the cross-site linker to put its code in an iframe
rather than a wrapper function.

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

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

Deleted:
/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

=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/jjs/impl/HandleCrossFragmentReferences.java Mon Mar 29 05:13:55 2010
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * 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 Mon Mar 29 05:13:55 2010 +++ /trunk/dev/core/src/com/google/gwt/core/linker/XSLinker.java Fri Jul 23 09:44:56 2010
@@ -17,6 +17,7 @@

 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;
@@ -24,6 +25,8 @@
 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.
@@ -40,6 +43,7 @@
   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);
@@ -48,6 +52,27 @@
     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,
@@ -58,13 +83,13 @@
   @Override
protected String getModulePrefix(TreeLogger logger, LinkerContext context,
       String strongName) {
-    return getModulePrefix(context, strongName, true);
+    throw new UnsupportedOperationException("Should not be called");
   }

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

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

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

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

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

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

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

-  // These variables gate calling gwtOnLoad; all must be true to start
-  ,gwtOnLoad, bodyDone
+  // 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

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

-  // Called by onScriptLoad() and onload(). It causes
-  // the specified module to be cranked up.
+  // 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.
   //
-  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',
-      });
+  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);
+        })
     }
   }

+  // 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__

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

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

- // Called when the compiled script identified by moduleName is done loading.
+  // 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
   //
-  __MODULE_FUNC__.onScriptLoad = function(gwtOnLoadFunc) {
+  __MODULE_FUNC__.onScriptInstalled = function(gwtOnLoadFunc) {
     // remove the callback to prevent it being called twice
-    __MODULE_FUNC__.onScriptLoad = null;
-    gwtOnLoad = gwtOnLoadFunc;
-    maybeStartModule();
-  }
+    __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;

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

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

       if ($doc.removeEventListener) {
         $doc.removeEventListener("DOMContentLoaded", onBodyDone, false);
=======================================
--- /trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java Thu Jul 22 13:05:55 2010 +++ /trunk/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java Fri Jul 23 09:44:56 2010
@@ -69,7 +69,6 @@
 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;
@@ -356,11 +355,6 @@
         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 Mon Mar 29 05:13:55 2010 +++ /trunk/user/src/com/google/gwt/core/CompilerParameters.gwt.xml Fri Jul 23 09:44:56 2010
@@ -22,17 +22,6 @@
<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 Mon Mar 29 05:13:55 2010 +++ /trunk/user/src/com/google/gwt/core/XSLinker.gwt.xml Fri Jul 23 09:44:56 2010
@@ -21,8 +21,4 @@
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