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