Revision: 8510
Author: [email protected]
Date: Tue Aug 10 07:06:57 2010
Log: Recommiting the new CrossSiteIframeLinker.

This linker works cross-site, because it uses a script tag to
download code instead of XHR. However, like the iframe linker,
it still uses an iframe to hold all the installed code.

This version adds a head element to the inner iframe if one is not already present.

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

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

Added:
 /trunk/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java
 /trunk/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js
 /trunk/user/src/com/google/gwt/core/CrossSiteIframeLinker.gwt.xml
/trunk/user/src/com/google/gwt/core/client/impl/CrossSiteIframeLoadingStrategy.java
 /trunk/user/test/com/google/gwt/core/ext/CrossSiteIframeLinkerTest.gwt.xml
/trunk/user/test/com/google/gwt/core/ext/test/CrossSiteIframeLinkerTest.java /trunk/user/test/com/google/gwt/dev/jjs/CompilerSuiteCrossSiteIframe.gwt.xml /trunk/user/test/com/google/gwt/dev/jjs/CrossSiteIframeRunAsyncFailure.gwt.xml /trunk/user/test/com/google/gwt/dev/jjs/CrossSiteIframeRunAsyncMetrics.gwt.xml
 /trunk/user/test/com/google/gwt/dev/jjs/CrossSiteIframeRunAsyncSuite.java
/trunk/user/test/com/google/gwt/dev/jjs/test/CrossSiteIframeRunAsyncFailureTest.java /trunk/user/test/com/google/gwt/dev/jjs/test/CrossSiteIframeRunAsyncMetricsTest.java /trunk/user/test/com/google/gwt/dev/jjs/test/CrossSiteIframeRunAsyncTest.java
Modified:
 /trunk/user/src/com/google/gwt/core/Core.gwt.xml
 /trunk/user/test/com/google/gwt/core/ext/LinkerSuite.java

=======================================
--- /dev/null
+++ /trunk/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeLinker.java Tue Aug 10 07:06:57 2010
@@ -0,0 +1,158 @@
+/*
+ * 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.linker;
+
+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;
+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;
+
+/**
+ * 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
+ * download it and because it never uses XHR. The iframe, meanwhile, makes it
+ * trivial to install additional code as the app runs.
+ */
+...@linkerorder(Order.PRIMARY)
+...@shardable
+public class CrossSiteIframeLinker extends SelectionScriptLinker {
+  @Override
+  public String getDescription() {
+    return "Cross-Site-Iframe";
+  }
+
+  @Override
+  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);
+    sb.append("(");
+    sb.append(JsToStringGenerationVisitor.javaScriptString(js));
+    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,
+      LinkerContext context) {
+    return ".cache.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("$stats && $stats({moduleName:'" + context.getModuleName()
+        + "',sessionId:$sessionId"
+        + ",subSystem:'startup',evtGroup:'moduleStartup'"
+        + ",millis:(new Date()).getTime(),type:'moduleEvalEnd'});");
+
+    // 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.newlineOpt();
+
+    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,
+      boolean supportRunAsync) {
+    TextOutput out = new DefaultTextOutput(context.isOutputCompact());
+
+    out.print("var $gwt_version = \"" + About.getGwtVersionNum() + "\";");
+    out.newlineOpt();
+    out.print("var $wnd = window.parent;");
+    out.newlineOpt();
+    out.print("var $doc = $wnd.document;");
+    out.newlineOpt();
+    out.print("var $moduleName, $moduleBase;");
+    out.newlineOpt();
+    out.print("var $strongName = '" + strongName + "';");
+    out.newlineOpt();
+ out.print("var $stats = $wnd.__gwtStatsEvent ? function(a) {return $wnd.__gwtStatsEvent(a);} : null;");
+    out.newlineOpt();
+ out.print("var $sessionId = $wnd.__gwtStatsSessionId ? $wnd.__gwtStatsSessionId : null;");
+    out.newlineOpt();
+
+    out.print("$stats && $stats({moduleName:'" + context.getModuleName()
+        + "',sessionId:$sessionId"
+        + ",subSystem:'startup',evtGroup:'moduleStartup'"
+        + ",millis:(new Date()).getTime(),type:'moduleEvalStart'});");
+    out.newlineOpt();
+
+    if (supportRunAsync) {
+      out.print("var __gwtModuleFunction = $wnd.");
+      out.print(context.getModuleFunctionName());
+      out.print(";");
+      out.newlineOpt();
+    }
+
+    return out.toString();
+  }
+}
=======================================
--- /dev/null
+++ /trunk/dev/core/src/com/google/gwt/core/linker/CrossSiteIframeTemplate.js Tue Aug 10 07:06:57 2010
@@ -0,0 +1,356 @@
+/*
+ * 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 __MODULE_FUNC__() {
+  // ---------------- INTERNAL GLOBALS ----------------
+
+  // Cache symbols locally for good obfuscation
+  var $wnd = window
+  ,$doc = document
+ ,$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
+  ,compiledScript
+
+  // 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 = ''
+
+  // 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
+
+  ; // end of global vars
+
+  $stats && $stats({
+    moduleName: '__MODULE_NAME__',
+    sessionId: $sessionId,
+    subSystem: 'startup',
+    evtGroup: 'bootstrap',
+    millis:(new Date()).getTime(),
+    type: 'begin',
+  });
+
+  // ------------------ TRUE GLOBALS ------------------
+
+ // Maps to synchronize the loading of styles and scripts; resources are loaded + // only once, even when multiple modules depend on them. This API must not
+  // change across GWT versions.
+  if (!$wnd.__gwt_stylesLoaded) { $wnd.__gwt_stylesLoaded = {}; }
+  if (!$wnd.__gwt_scriptsLoaded) { $wnd.__gwt_scriptsLoaded = {}; }
+
+  // --------------- INTERNAL FUNCTIONS ---------------
+
+  function isHostedMode() {
+    var result = false;
+    try {
+      var query = $wnd.location.search;
+      return (query.indexOf('gwt.codesvr=') != -1
+          || query.indexOf('gwt.hosted=') != -1
+          || ($wnd.external && $wnd.external.gwtOnLoad)) &&
+          (query.indexOf('gwt.hybrid') == -1);
+    } catch (e) {
+      // Defensive: some versions of IE7 reportedly can throw an exception
+      // evaluating "external.gwtOnLoad".
+    }
+    isHostedMode = function() { return result; };
+    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.
+  //
+  function maybeCreateFrame() {
+    if (bodyDone && compiledScript && !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; left: -1000px; top: -1000px; !important';
+        scriptFrame.tabIndex = -1;
+        document.body.appendChild(scriptFrame);
+
+        // For some reason, adding this setTimeout makes code installation
+        // more reliable.
+        setTimeout(function() {
+          installCode(compiledScript);
+        })
+    }
+  }
+
+  // Install code into scriptFrame
+  //
+  function installCode(code) {
+    var doc = scriptFrame.contentDocument;
+    if (!doc) {
+      doc = scriptFrame.contentWindow.document;
+    }
+
+    var dochead = doc.getElementsByTagName('head')[0];
+    if (!dochead) {
+       // This has been seen on Safari 3.
+       // Give the iframe a skeletal HTML document and try again.
+       doc.write('<html><head></head><body></body></html>');
+       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);
+  }
+
+
+  __PROCESS_METAS__
+  __COMPUTE_SCRIPT_BASE__
+
+  /**
+ * Determines whether or not a particular property value is allowed. Called by
+   * property providers.
+   *
+   * @param propName the name of the property being checked
+   * @param propValue the property value being tested
+   */
+  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;
+  }
+
+ // 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) {
+      // lazy initialize an empty object for the current key if needed
+      answer = answer[propValArray[i]] || (answer[propValArray[i]] = []);
+    }
+    // set the final one to the value
+    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;
+  }
+
+  // --------------- PROPERTY PROVIDERS ---------------
+
+// __PROPERTIES_BEGIN__
+// __PROPERTIES_END__
+
+  // --------------- EXPOSED FUNCTIONS ----------------
+
+  // Called when the initial script code has been downloaded
+  __MODULE_FUNC__.onScriptDownloaded = function(code) {
+       compiledScript = code;
+       maybeCreateFrame();
+  }
+
+  // Called when the compiled script has been installed
+  //
+  __MODULE_FUNC__.onScriptInstalled = 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;
+
+  // --------------- STRAIGHT-LINE CODE ---------------
+
+  if (isHostedMode()) {
+    alert("Cross-site hosted mode not yet implemented. See issue " +
+      "http://code.google.com/p/google-web-toolkit/issues/detail?id=2079";);
+    return;
+  }
+
+  // do it early for compile/browse rebasing
+  processMetas();
+  computeScriptBase();
+
+  // --------------- WINDOW ONLOAD HOOK ---------------
+
+  $stats && $stats({
+    moduleName:'__MODULE_NAME__',
+    sessionId: $sessionId,
+    subSystem:'startup',
+    evtGroup: 'bootstrap',
+    millis:(new Date()).getTime(),
+    type: 'selectingPermutation'
+  });
+
+  var strongName;
+  try {
+// __PERMUTATIONS_BEGIN__
+    // Permutation logic
+// __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;
+  }
+
+  var onBodyDoneTimerId;
+  function onBodyDone() {
+    if (!bodyDone) {
+      bodyDone = true;
+// __MODULE_STYLES_BEGIN__
+ // Style resources are injected here to prevent operation aborted errors on ie
+// __MODULE_STYLES_END__
+      maybeCreateFrame();
+
+      if ($doc.removeEventListener) {
+        $doc.removeEventListener("DOMContentLoaded", onBodyDone, false);
+      }
+      if (onBodyDoneTimerId) {
+        clearInterval(onBodyDoneTimerId);
+      }
+    }
+  }
+
+  // For everyone that supports DOMContentLoaded.
+  if ($doc.addEventListener) {
+    $doc.addEventListener("DOMContentLoaded", function() {
+      onBodyDone();
+    }, false);
+  }
+
+  // Fallback. If onBodyDone() gets fired twice, it's not a big deal.
+  var onBodyDoneTimerId = setInterval(function() {
+    if (/loaded|complete/.test($doc.readyState)) {
+      onBodyDone();
+    }
+  }, 50);
+
+  $stats && $stats({
+    moduleName:'__MODULE_NAME__',
+    sessionId: $sessionId,
+    subSystem:'startup',
+    evtGroup: 'bootstrap',
+    millis:(new Date()).getTime(),
+    type: 'end'
+  });
+
+  $stats && $stats({
+    moduleName:'__MODULE_NAME__',
+    sessionId: $sessionId,
+    subSystem:'startup',
+    evtGroup: 'loadExternalRefs',
+    millis:(new Date()).getTime(),
+    type: 'begin'
+  });
+
+// __MODULE_SCRIPTS_BEGIN__
+  // Script resources are injected here
+// __MODULE_SCRIPTS_END__
+
+ // This is a bit ugly, but serves a purpose. We need to ensure that the stats + // script runs before the compiled script. If they are both doc.write()n in
+  // sequence, that should be the effect. Except on IE it turns out that a
+ // script injected via doc.write() can execute immediately! Adding 'defer'
+  // attributes to both seemed to fix this, but caused startup problems for
+  // some apps. The final solution was simply to inject the compiled script
+ // from *within* the stats script, guaranteeing order at the expense of near
+  // total inscrutability :(
+ var compiledScriptTag = '"<script src=\\"' + base + strongName + '.cache.js\\"></scr" + "ipt>"';
+
+  $doc.write('<scr' + 'ipt><!-' + '-\n'
+    + 'window.__gwtStatsEvent && window.__gwtStatsEvent({'
+ + 'moduleName:"__MODULE_NAME__", sessionId:window.__gwtStatsSessionId, subSystem:"startup",'
+    + 'evtGroup: "loadExternalRefs", millis:(new Date()).getTime(),'
+    + 'type: "end"});'
+    + 'window.__gwtStatsEvent && window.__gwtStatsEvent({'
+ + 'moduleName:"__MODULE_NAME__", sessionId:window.__gwtStatsSessionId, subSystem:"startup",'
+    + 'evtGroup: "moduleStartup", millis:(new Date()).getTime(),'
+    + 'type: "moduleRequested"});'
+    + 'document.write(' + compiledScriptTag + ');'
+    + '\n-' + '-></scr' + 'ipt>');
+}
+
+__MODULE_FUNC__();
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/core/CrossSiteIframeLinker.gwt.xml Tue Aug 10 07:06:57 2010
@@ -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 --> +<!-- 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. License for the specific language governing permissions and --> +<!-- limitations under the License. -->
+
+<!-- Defines the cross-site iframe linker   -->
+<module>
+ <define-linker name="xsiframe" class="com.google.gwt.core.linker.CrossSiteIframeLinker" />
+
+ <replace-with class="com.google.gwt.core.client.impl.CrossSiteIframeLoadingStrategy">
+    <when-type-is
+ class="com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadingStrategy" />
+    <when-linker-added name="xsiframe" />
+  </replace-with>
+</module>
=======================================
--- /dev/null
+++ /trunk/user/src/com/google/gwt/core/client/impl/CrossSiteIframeLoadingStrategy.java Tue Aug 10 07:06:57 2010
@@ -0,0 +1,183 @@
+/*
+ * 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.client.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadTerminatedHandler;
+import com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadingStrategy;
+
+/**
+ * Load runAsync code using a script tag. Intended for use with the
+ * {...@link com.google.gwt.core.linker.CrossSiteIframeLinker}.
+ *
+ * <p>
+ * The linker wraps its selection script code with a function refered to by
+ * <code>__gwtModuleFunction</code>. On that function is a property
+ * <code>installCode</code> that can be invoked to eval more code in a scope + * nested somewhere within that function. The loaded script for fragment 123 is
+ * expected to invoke <code>__gwtModuleFunction.runAsyncCallback123</code>
+ * as the final thing it does.
+ */
+public class CrossSiteIframeLoadingStrategy implements LoadingStrategy {
+  /**
+   * A trivial JavaScript map from ints to ints.
+   */
+  private static final class IntToIntMap extends JavaScriptObject {
+    public static IntToIntMap create() {
+      return (IntToIntMap) JavaScriptObject.createArray();
+    }
+
+    @SuppressWarnings("unused")
+    protected IntToIntMap() {
+    }
+
+    /**
+     * Get an entry. If there is no such entry, return 0.
+     */
+    public native int get(int x) /*-{
+      return this[x] ? this[x] : 0;
+    }-*/;
+
+    public native void put(int x, int y) /*-{
+      this[x] = y;
+    }-*/;
+  }
+
+  @SuppressWarnings("unused")
+  private static RuntimeException LoadTerminated = new RuntimeException(
+      "Code download terminated");
+
+  /**
+   * Clear callbacks on script objects. This is important on IE 6 and 7 to
+ * prevent a memory leak. If the callbacks aren't cleared, there is a cyclical + * chain of references between the script tag and the function callback, and
+   * IE 6/7 can't garbage collect them.
+   */
+  @SuppressWarnings("unused")
+  private static native void clearCallbacks(JavaScriptObject script) /*-{
+    var nop = new Function('');
+    script.onerror = script.onload = script.onreadystatechange = nop;
+  }-*/;
+
+  /**
+   * Clear the success callback for fragment <code>fragment</code>.
+   */
+  @SuppressWarnings("unused")
+  private static native void clearOnSuccess(int fragment) /*-{
+    delete __gwtModuleFunction['runAsyncCallback'+fragment];
+  }-*/;
+
+  private static native JavaScriptObject createScriptTag(String url) /*-{
+    var head = document.getElementsByTagName('head').item(0);
+    var script = document.createElement('script');
+    script.src = url;
+    return script;
+  }-*/;
+
+  private static native void installScriptTag(JavaScriptObject script) /*-{
+    var head = document.getElementsByTagName('head').item(0);
+    head.appendChild(script);
+  }-*/;
+
+  private static native JavaScriptObject removeTagAndCallErrorHandler(
+      int fragment, JavaScriptObject tag,
+      LoadTerminatedHandler loadFinishedHandler) /*-{
+     return function(exception) {
+       if (tag.parentNode == null) {
+         // onSuccess or onFailure must have already been called.
+         return;
+       }
+       var head = document.getElementsByTagName('head').item(0);
+ @com.google.gwt.core.client.impl.CrossSiteIframeLoadingStrategy::clearOnSuccess(*)(fragment); + @com.google.gwt.core.client.impl.CrossSiteIframeLoadingStrategy::clearCallbacks(*)(tag);
+       head.removeChild(tag);
+       function callLoadTerminated() {
+ loadfinishedhandl...@com.google.gwt.core.client.impl.asyncfragmentloader.loadterminatedhandler::loadTerminated(*)(exception);
+       }
+       $entry(callLoadTerminated)();
+     }
+   }-*/;
+
+  private static native JavaScriptObject removeTagAndEvalCode(int fragment,
+      JavaScriptObject tag) /*-{
+     return function(code) {
+       var head = document.getElementsByTagName('head').item(0);
+ @com.google.gwt.core.client.impl.CrossSiteIframeLoadingStrategy::clearOnSuccess(*)(fragment); + @com.google.gwt.core.client.impl.CrossSiteIframeLoadingStrategy::clearCallbacks(*)(tag);
+       head.removeChild(tag);
+       __gwtModuleFunction.installCode(code);
+     }
+   }-*/;
+
+  private static native void setOnFailure(JavaScriptObject script,
+      JavaScriptObject callback) /*-{
+ var exception = @com.google.gwt.core.client.impl.CrossSiteIframeLoadingStrategy::LoadTerminated;
+    script.onerror = function() {
+      callback(exception);
+    }
+    script.onload = function() {
+      callback(exception);
+    }
+    script.onreadystatechange = function () {
+ if (script.readyState == 'loaded' || script.readyState == 'complete') {
+        script.onreadystatechange = function () { }
+        callback(exception);
+      }
+    }
+  }-*/;
+
+  /**
+   * Set the success callback for fragment <code>fragment</code>
+   * to the supplied JavaScript function.
+   */
+ private static native void setOnSuccess(int fragment, JavaScriptObject callback) /*-{
+    __gwtModuleFunction['runAsyncCallback'+fragment] = callback;
+  }-*/;
+
+  private final IntToIntMap serialNumbers = IntToIntMap.create();
+
+  public void startLoadingFragment(int fragment,
+      LoadTerminatedHandler loadFinishedHandler) {
+    JavaScriptObject tag = createScriptTag(getUrl(fragment));
+    setOnSuccess(fragment, removeTagAndEvalCode(fragment, tag));
+    setOnFailure(tag, removeTagAndCallErrorHandler(fragment, tag,
+        loadFinishedHandler));
+    installScriptTag(tag);
+  }
+
+  protected String getDeferredJavaScriptDirectory() {
+    return "deferredjs/";
+  }
+
+  private int getSerial(int fragment) {
+    int ser = serialNumbers.get(fragment);
+    serialNumbers.put(fragment, ser + 1);
+    return ser;
+  }
+
+  /**
+ * The URL to retrieve a fragment of code from. NOTE: this function is not + * stable. It tweaks the URL with each call so that browsers are not tempted
+   * to cache a download failure.
+   */
+  private String getUrl(int fragment) {
+    return GWT.getModuleBaseURL() + getDeferredJavaScriptDirectory()
+ + GWT.getPermutationStrongName() + "/" + fragment + ".cache.js?serial="
+        + getSerial(fragment);
+  }
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/core/ext/CrossSiteIframeLinkerTest.gwt.xml Tue Aug 10 07:06:57 2010
@@ -0,0 +1,18 @@
+<!-- --> +<!-- 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 --> +<!-- 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. License for the specific language governing permissions and --> +<!-- limitations under the License. -->
+
+<module>
+       <inherits name='com.google.gwt.core.ext.LinkerTest' />
+       <add-linker name='xsiframe' />
+</module>
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/core/ext/test/CrossSiteIframeLinkerTest.java Tue Aug 10 07:06:57 2010
@@ -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.
+ */
+
+package com.google.gwt.core.ext.test;
+
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+
+/**
+ * Integration test of the cross-site iframe linker.
+ */
+...@donotrunwith(Platform.Devel)
+public class CrossSiteIframeLinkerTest extends LinkerTest {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.core.ext.CrossSiteIframeLinkerTest";
+  }
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dev/jjs/CompilerSuiteCrossSiteIframe.gwt.xml Tue Aug 10 07:06:57 2010
@@ -0,0 +1,18 @@
+<!-- --> +<!-- 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 --> +<!-- 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. License for the specific language governing permissions and --> +<!-- limitations under the License. -->
+
+<module>
+  <inherits name="com.google.gwt.dev.jjs.CompilerSuite"/>
+  <add-linker name="xsiframe"/>
+</module>
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dev/jjs/CrossSiteIframeRunAsyncFailure.gwt.xml Tue Aug 10 07:06:57 2010
@@ -0,0 +1,25 @@
+<!-- --> +<!-- 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 --> +<!-- 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. License for the specific language governing permissions and --> +<!-- limitations under the License. -->
+
+<module>
+  <inherits name="com.google.gwt.dev.jjs.RunAsyncFailure" />
+
+  <add-linker name="xsiframe" />
+
+  <replace-with
+ class="com.google.gwt.user.client.runasync.CrossSiteLoadingStrategyForRunAsyncFailureTest">
+    <when-type-is
+ class="com.google.gwt.core.client.impl.AsyncFragmentLoader.LoadingStrategy" />
+  </replace-with>
+</module>
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dev/jjs/CrossSiteIframeRunAsyncMetrics.gwt.xml Tue Aug 10 07:06:57 2010
@@ -0,0 +1,20 @@
+<!-- --> +<!-- 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 --> +<!-- 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. License for the specific language governing permissions and --> +<!-- limitations under the License. -->
+
+<module>
+  <inherits name="com.google.gwt.dev.jjs.RunAsyncMetricsIntegrationTest" />
+  <add-linker name="xsiframe" />
+
+  <public path="public" />
+</module>
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dev/jjs/CrossSiteIframeRunAsyncSuite.java Tue Aug 10 07:06:57 2010
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import com.google.gwt.dev.jjs.test.CrossSiteIframeRunAsyncFailureTest;
+import com.google.gwt.dev.jjs.test.CrossSiteIframeRunAsyncMetricsTest;
+import com.google.gwt.dev.jjs.test.CrossSiteIframeRunAsyncTest;
+import com.google.gwt.junit.tools.GWTTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * Tests that GWT.runAsync works with the cross-site iframe linker.
+ */
+public class CrossSiteIframeRunAsyncSuite {
+  public static Test suite() {
+ GWTTestSuite suite = new GWTTestSuite("Test for runAsync with the cross-site iframe linker");
+
+    suite.addTestSuite(CrossSiteIframeRunAsyncMetricsTest.class);
+    suite.addTestSuite(CrossSiteIframeRunAsyncFailureTest.class);
+    suite.addTestSuite(CrossSiteIframeRunAsyncTest.class);
+
+    return suite;
+  }
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dev/jjs/test/CrossSiteIframeRunAsyncFailureTest.java Tue Aug 10 07:06:57 2010
@@ -0,0 +1,36 @@
+/*
+ * 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.test;
+
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+
+/**
+ * Tests that the cross-site iframe linker handles download failures.
+ */
+...@donotrunwith(Platform.Devel)
+public class CrossSiteIframeRunAsyncFailureTest extends RunAsyncFailureTest {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.jjs.CrossSiteIframeRunAsyncFailure";
+  }
+
+  @Override
+  public void testHttpFailureRetries() {
+    super.testHttpFailureRetries();
+  }
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dev/jjs/test/CrossSiteIframeRunAsyncMetricsTest.java Tue Aug 10 07:06:57 2010
@@ -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.
+ */
+package com.google.gwt.dev.jjs.test;
+
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+
+/**
+ * Tests runAsync lightweight metrics when the cross-site iframe linker is used.
+ */
+...@donotrunwith(Platform.Devel)
+public class CrossSiteIframeRunAsyncMetricsTest extends
+    RunAsyncMetricsIntegrationTest {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.jjs.CrossSiteIframeRunAsyncMetrics";
+  }
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/dev/jjs/test/CrossSiteIframeRunAsyncTest.java Tue Aug 10 07:06:57 2010
@@ -0,0 +1,30 @@
+/*
+ * 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.test;
+
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+
+/**
+ * Tests GWT.runAsync when used with the cross-site linker.
+ */
+...@donotrunwith(Platform.Devel)
+public class CrossSiteIframeRunAsyncTest extends RunAsyncTest {
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.dev.jjs.CompilerSuiteCrossSiteIframe";
+  }
+}
=======================================
--- /trunk/user/src/com/google/gwt/core/Core.gwt.xml Mon Mar 29 05:13:55 2010 +++ /trunk/user/src/com/google/gwt/core/Core.gwt.xml Tue Aug 10 07:06:57 2010
@@ -30,6 +30,7 @@
<define-linker name="sso" class="com.google.gwt.core.linker.SingleScriptLinker" /> <define-linker name="std" class="com.google.gwt.core.linker.IFrameLinker" />
   <inherits name="com.google.gwt.core.XSLinker" />
+  <inherits name="com.google.gwt.core.CrossSiteIframeLinker" />

<define-linker name="soycReport" class="com.google.gwt.core.linker.SoycReportLinker" /> <define-linker name="symbolMaps" class="com.google.gwt.core.linker.SymbolMapsLinker" />
=======================================
--- /trunk/user/test/com/google/gwt/core/ext/LinkerSuite.java Tue Mar 23 09:32:47 2010 +++ /trunk/user/test/com/google/gwt/core/ext/LinkerSuite.java Tue Aug 10 07:06:57 2010
@@ -17,6 +17,7 @@

 import com.google.gwt.core.ext.linker.impl.SelectionScriptJavaScriptTest;
 import com.google.gwt.core.ext.linker.impl.SelectionScriptLinkerUnitTest;
+import com.google.gwt.core.ext.test.CrossSiteIframeLinkerTest;
 import com.google.gwt.core.ext.test.IFrameLinkerTest;
 import com.google.gwt.core.ext.test.XSLinkerTest;
 import com.google.gwt.junit.tools.GWTTestSuite;
@@ -33,7 +34,9 @@
     GWTTestSuite suite = new GWTTestSuite("Smoke test for linkers");

     // $JUnit-BEGIN$
+    suite.addTestSuite(CrossSiteIframeLinkerTest.class);
     suite.addTestSuite(IFrameLinkerTest.class);
+    suite.addTestSuite(LinkerUnitTest.class);
     suite.addTestSuite(SelectionScriptJavaScriptTest.class);
     suite.addTestSuite(SelectionScriptLinkerUnitTest.class);
     suite.addTestSuite(XSLinkerTest.class);

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

Reply via email to