Update to r4917, which has GWT.runAsync download
code via XHR instead of script tags. This version
deals with two browser quirks that r4917 did
not: it avoids no-argument xhr.send(), and it
does not assume window.eval will run in the
scope of "window".
Review by: bobv
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@4983 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
index a02d487..b63b22f 100644
--- a/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
+++ b/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java
@@ -155,13 +155,34 @@
out.newlineOpt();
if (supportRunAsync) {
out.print("function __gwtStartLoadingFragment(frag) {");
- out.newlineOpt();
out.indentIn();
- out.print(" var script = document.createElement('script');");
out.newlineOpt();
- out.print(" script.src = '" + FRAGMENT_SUBDIR + "/" + strongName
- + "/' + frag + '" + FRAGMENT_EXTENSION + "';");
- out.print(" document.getElementsByTagName('head').item(0).appendChild(script);");
+ out.print(" return $moduleBase + '" + FRAGMENT_SUBDIR
+ + "/' + $strongName + '/' + frag + '" + FRAGMENT_EXTENSION + "';");
+ out.indentOut();
+ out.newlineOpt();
+ out.print("};");
+ out.newlineOpt();
+ out.print("function __gwtInstallCode(code) {");
+ /*
+ * Use a script tag on all platforms, for simplicity. It would be cleaner
+ * to use window.eval, but at the time of writing that only reliably works
+ * on Firefox. It would also be cleaner to use window.execScript on
+ * platforms that support it (IE and Chrome). However, trying this causes
+ * IE 6 (and possibly others) to emit "error 80020101", apparently due to
+ * something objectionable in the compiler's output JavaScript.
+ */
+ out.indentIn();
+ out.newlineOpt();
+ out.print("var head = document.getElementsByTagName('head').item(0);");
+ out.newlineOpt();
+ out.print("var script = document.createElement('script');");
+ out.newlineOpt();
+ out.print("script.type = 'text/javascript';");
+ out.newlineOpt();
+ out.print("script.text = code;");
+ out.newlineOpt();
+ out.print("head.appendChild(script);");
out.indentOut();
out.newlineOpt();
out.print("};");
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
index ea097c9..c5344bd 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java
@@ -180,7 +180,13 @@
srcWriter.println("}");
srcWriter.println("if (!loading) {");
srcWriter.println("loading = true;");
- srcWriter.println("AsyncFragmentLoader.inject(" + entryNumber + ");");
+ srcWriter.println("AsyncFragmentLoader.inject(" + entryNumber + ",");
+ srcWriter.println(" new AsyncFragmentLoader.LoadErrorHandler() {");
+ srcWriter.println(" public void loadFailed(Throwable reason) {");
+ srcWriter.println(" loading = false;");
+ srcWriter.println(" runCallbackOnFailures(reason);");
+ srcWriter.println(" }");
+ srcWriter.println(" });");
srcWriter.println("}");
srcWriter.println("}");
}
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java
index ee1f6a2..bc80c03 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java
@@ -120,6 +120,21 @@
private HTML styleWidget = null;
/**
+ * Whether the demo widget has been initialized.
+ */
+ private boolean widgetInitialized;
+
+ /**
+ * Whether the demo widget is (asynchronously) initializing.
+ */
+ private boolean widgetInitializing;
+
+ /**
+ * A vertical panel that holds the demo widget once it is initialized.
+ */
+ private VerticalPanel widgetVpanel;
+
+ /**
* Constructor.
*
* @param constants the constants
@@ -141,6 +156,12 @@
deckPanel.add(w);
}
+ @Override
+ public void ensureWidget() {
+ super.ensureWidget();
+ ensureWidgetInitialized(widgetVpanel);
+ }
+
/**
* Get the description of this example.
*
@@ -255,6 +276,7 @@
* Initialize this widget by creating the elements that should be added to the
* page.
*/
+ @Override
protected final Widget createWidget() {
deckPanel = new DeckPanel();
@@ -264,18 +286,18 @@
tabBar.addSelectionHandler(this);
// Create a container for the main example
- final VerticalPanel vPanel = new VerticalPanel();
- add(vPanel, constants.contentWidgetExample());
+ widgetVpanel = new VerticalPanel();
+ add(widgetVpanel, constants.contentWidgetExample());
// Add the name
HTML nameWidget = new HTML(getName());
nameWidget.setStyleName(DEFAULT_STYLE_NAME + "-name");
- vPanel.add(nameWidget);
+ widgetVpanel.add(nameWidget);
// Add the description
HTML descWidget = new HTML(getDescription());
descWidget.setStyleName(DEFAULT_STYLE_NAME + "-description");
- vPanel.add(descWidget);
+ widgetVpanel.add(descWidget);
// Add source code tab
if (hasSource()) {
@@ -292,22 +314,6 @@
add(styleWidget, constants.contentWidgetStyle());
}
- asyncOnInitialize(new AsyncCallback<Widget>() {
-
- public void onFailure(Throwable caught) {
- Window.alert("exception: " + caught);
- }
-
- public void onSuccess(Widget result) {
- // Initialize the showcase widget (if any) and add it to the page
- Widget widget = result;
- if (widget != null) {
- vPanel.add(widget);
- }
- onInitializeComplete();
- }
- });
-
return deckPanel;
}
@@ -367,4 +373,34 @@
realCallback.onError(request, e);
}
}
+
+ /**
+ * Ensure that the demo widget has been initialized. Note that initialization
+ * can fail if there is a network failure.
+ */
+ private void ensureWidgetInitialized(final VerticalPanel vPanel) {
+ if (widgetInitializing || widgetInitialized) {
+ return;
+ }
+
+ widgetInitializing = true;
+
+ asyncOnInitialize(new AsyncCallback<Widget>() {
+ public void onFailure(Throwable reason) {
+ widgetInitializing = false;
+ Window.alert("Failed to download code for this widget (" + reason + ")");
+ }
+
+ public void onSuccess(Widget result) {
+ widgetInitializing = false;
+ widgetInitialized = true;
+
+ Widget widget = result;
+ if (widget != null) {
+ vPanel.add(widget);
+ }
+ onInitializeComplete();
+ }
+ });
+ }
}
diff --git a/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java b/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java
index 829cfc6..21b4d08 100644
--- a/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java
+++ b/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java
@@ -15,7 +15,12 @@
*/
package com.google.gwt.core.client;
+import com.google.gwt.xhr.client.ReadyStateChangeHandler;
+import com.google.gwt.xhr.client.XMLHttpRequest;
+
+import java.util.ArrayList;
import java.util.LinkedList;
+import java.util.List;
import java.util.Queue;
/**
@@ -41,14 +46,32 @@
* </ul>
*
* <p>
- * Different linkers have different requirements about how the code is
- * downloaded and installed. Thus, when it is time to actually download the
- * code, this class defers to a JavaScript function named
- * <code>__gwtStartLoadingFragment</code>. Linkers must arrange that a suitable
- * <code>__gwtStartLoadingFragment</code> function is in scope.
+ * Since the precise way to load code depends on the linker, each linker should
+ * provide functions for fragment loading for any compilation that includes more
+ * than one fragment. Linkers should always provide a function
+ * <code>__gwtStartLoadingFragment</code>. This function is called by
+ * AsyncFragmentLoader with an integer fragment number that needs to be
+ * downloaded. If the mechanism for loading the contents of fragments is
+ * provided by the linker, this function should return <code>null</code> or
+ * <code>undefined</code>.
+ * </p>
+ * <p>
+ * Alternatively, the function can return a URL designating from where the code
+ * for the requested fragment can be downloaded. In that case, the linker should
+ * also provide a function <code>__gwtInstallCode</code> for actually installing
+ * the code once it is downloaded. That function will be passed the loaded code
+ * once it has been downloaded.
+ * </p>
*/
public class AsyncFragmentLoader {
/**
+ * An interface for handlers of load errors.
+ */
+ public static interface LoadErrorHandler {
+ void loadFailed(Throwable reason);
+ }
+
+ /**
* Labels used for runAsync lightweight metrics.
*/
public static class LwmLabels {
@@ -68,6 +91,73 @@
}
/**
+ * Handles a failure to download a base fragment.
+ */
+ private static class BaseDownloadFailed implements LoadErrorHandler {
+ private final LoadErrorHandler chainedHandler;
+
+ public BaseDownloadFailed(LoadErrorHandler chainedHandler) {
+ this.chainedHandler = chainedHandler;
+ }
+
+ public void loadFailed(Throwable reason) {
+ baseLoading = false;
+ chainedHandler.loadFailed(reason);
+ }
+ }
+
+ /**
+ * An exception indicating than at HTTP download failed.
+ */
+ private static class HttpDownloadFailure extends RuntimeException {
+ private final int statusCode;
+
+ public HttpDownloadFailure(int statusCode) {
+ super("HTTP download failed with status " + statusCode);
+ this.statusCode = statusCode;
+ }
+
+ public int getStatusCode() {
+ return statusCode;
+ }
+ }
+
+ /**
+ * Handles a failure to download a leftovers fragment.
+ */
+ private static class LeftoversDownloadFailed implements LoadErrorHandler {
+ public void loadFailed(Throwable reason) {
+ leftoversLoading = false;
+
+ /*
+ * Cancel all other pending downloads. If any exception is thrown while
+ * cancelling any of them, throw only the last one.
+ */
+ RuntimeException lastException = null;
+
+ assert waitingForLeftovers.size() == waitingForLeftoversErrorHandlers.size();
+
+ // Copy the list in case a handler makes another runAsync call
+ List<LoadErrorHandler> handlersToRun = new ArrayList<LoadErrorHandler>(
+ waitingForLeftoversErrorHandlers);
+ waitingForLeftoversErrorHandlers.clear();
+ waitingForLeftovers.clear();
+
+ for (LoadErrorHandler handler : handlersToRun) {
+ try {
+ handler.loadFailed(reason);
+ } catch (RuntimeException e) {
+ lastException = e;
+ }
+ }
+
+ if (lastException != null) {
+ throw lastException;
+ }
+ }
+ }
+
+ /**
* The first entry point reached after the program started.
*/
private static int base = -1;
@@ -77,6 +167,10 @@
*/
private static boolean baseLoading = false;
+ private static final String HTTP_GET = "GET";
+
+ private static final int HTTP_STATUS_OK = 200;
+
/**
* Whether the leftovers fragment has loaded yet.
*/
@@ -101,6 +195,11 @@
private static Queue<Integer> waitingForLeftovers = new LinkedList<Integer>();
/**
+ * Error handlers for the above queue.
+ */
+ private static Queue<LoadErrorHandler> waitingForLeftoversErrorHandlers = new LinkedList<LoadErrorHandler>();
+
+ /**
* Inform the loader that the code for an entry point has now finished
* loading.
*
@@ -117,10 +216,7 @@
baseLoading = false;
// Go ahead and download the appropriate leftovers fragment
- leftoversLoading = true;
- logEventProgress(LwmLabels.LEFTOVERS_DOWNLOAD, LwmLabels.BEGIN,
- leftoversFragmentNumber(), null);
- startLoadingFragment(leftoversFragmentNumber());
+ startLoadingLeftovers();
}
}
@@ -129,7 +225,7 @@
*
* @param splitPoint the fragment to load
*/
- public static void inject(int splitPoint) {
+ public static void inject(int splitPoint, LoadErrorHandler loadErrorHandler) {
if (leftoversLoaded) {
/*
* A base and a leftovers fragment have loaded. Load an exclusively live
@@ -137,7 +233,7 @@
*/
logEventProgress(LwmLabels.downloadGroup(splitPoint), LwmLabels.BEGIN,
splitPoint, null);
- startLoadingFragment(splitPoint);
+ startLoadingFragment(splitPoint, loadErrorHandler);
return;
}
@@ -145,7 +241,17 @@
/*
* Wait until the leftovers fragment has loaded before loading this one.
*/
+ assert (waitingForLeftovers.size() == waitingForLeftoversErrorHandlers.size());
waitingForLeftovers.add(splitPoint);
+ waitingForLeftoversErrorHandlers.add(loadErrorHandler);
+
+ /*
+ * Also, restart the leftovers download if it previously failed.
+ */
+ if (!leftoversLoading) {
+ startLoadingLeftovers();
+ }
+
return;
}
@@ -153,7 +259,8 @@
baseLoading = true;
logEventProgress(LwmLabels.downloadGroup(splitPoint), LwmLabels.BEGIN,
baseFragmentNumber(splitPoint), null);
- startLoadingFragment(baseFragmentNumber(splitPoint));
+ startLoadingFragment(baseFragmentNumber(splitPoint),
+ new BaseDownloadFailed(loadErrorHandler));
}
/**
@@ -165,8 +272,10 @@
logEventProgress(LwmLabels.LEFTOVERS_DOWNLOAD, LwmLabels.END,
leftoversFragmentNumber(), null);
+ assert (waitingForLeftovers.size() == waitingForLeftoversErrorHandlers.size());
while (!waitingForLeftovers.isEmpty()) {
- inject(waitingForLeftovers.remove());
+ inject(waitingForLeftovers.remove(),
+ waitingForLeftoversErrorHandlers.remove());
}
}
@@ -203,8 +312,18 @@
return evt;
}-*/;
- private static native void gwtStartLoadingFragment(int fragment) /*-{
- __gwtStartLoadingFragment(fragment);
+ /**
+ * Use the linker-supplied __gwtStartLoadingFragment function. It should
+ * either start the download and return null or undefined, or it should return
+ * a URL that should be downloaded to get the code. If it starts the download
+ * itself, it can synchronously load it, e.g. from cache, if that makes sense.
+ */
+ private static native String gwtStartLoadingFragment(int fragment) /*-{
+ return __gwtStartLoadingFragment(fragment);
+ }-*/;
+
+ private static native void installCode(String text) /*-{
+ __gwtInstallCode(text);
}-*/;
private static native boolean isStatsAvailable() /*-{
@@ -232,8 +351,43 @@
&& stats(createStatsEvent(eventGroup, type, fragment, size));
}
- private static void startLoadingFragment(int fragment) {
- gwtStartLoadingFragment(fragment);
+ private static void startLoadingFragment(int fragment,
+ final LoadErrorHandler loadErrorHandler) {
+ String fragmentUrl = gwtStartLoadingFragment(fragment);
+
+ if (fragmentUrl != null) {
+ // use XHR
+ final XMLHttpRequest xhr = XMLHttpRequest.create();
+
+ xhr.open(HTTP_GET, fragmentUrl);
+
+ xhr.setOnReadyStateChange(new ReadyStateChangeHandler() {
+ public void onReadyStateChange(XMLHttpRequest xhr) {
+ if (xhr.getReadyState() == XMLHttpRequest.DONE) {
+ xhr.clearOnReadyStateChange();
+ if (xhr.getStatus() == HTTP_STATUS_OK) {
+ try {
+ installCode(xhr.getResponseText());
+ } catch (RuntimeException e) {
+ loadErrorHandler.loadFailed(e);
+ }
+ } else {
+ loadErrorHandler.loadFailed(new HttpDownloadFailure(xhr.getStatus()));
+ }
+ }
+ }
+ });
+
+ xhr.send();
+ }
+ }
+
+ private static void startLoadingLeftovers() {
+ leftoversLoading = true;
+ logEventProgress(LwmLabels.LEFTOVERS_DOWNLOAD, LwmLabels.BEGIN,
+ leftoversFragmentNumber(), null);
+ startLoadingFragment(leftoversFragmentNumber(),
+ new LeftoversDownloadFailed());
}
/**
diff --git a/user/src/com/google/gwt/core/client/RunAsyncCallback.java b/user/src/com/google/gwt/core/client/RunAsyncCallback.java
index 4c76817..0ec253a 100644
--- a/user/src/com/google/gwt/core/client/RunAsyncCallback.java
+++ b/user/src/com/google/gwt/core/client/RunAsyncCallback.java
@@ -22,9 +22,9 @@
public interface RunAsyncCallback {
/**
* Called when, for some reason, the necessary code cannot be loaded. For
- * example, the user might no longer be on the network.
+ * example, the web browser might no longer have network access.
*/
- void onFailure(Throwable caught);
+ void onFailure(Throwable reason);
/**
* Called once the necessary code for it has been loaded.
diff --git a/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java b/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java
index ae63a6d..c7ca653 100644
--- a/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java
+++ b/user/src/com/google/gwt/xhr/client/XMLHttpRequest.java
@@ -215,16 +215,16 @@
}-*/;
/**
- * Initiates a request.
- *
- * @see http://www.w3.org/TR/XMLHttpRequest/#send
+ * Initiates a request with no request data. This simply calls
+ * {@link #send(String)} with <code>null</code> as an argument, because the
+ * no-argument <code>send()</code> method is unavailable on Firefox.
*/
- public final native void send() /*-{
- this.send();
- }-*/;
+ public final void send() {
+ send(null);
+ }
/**
- * Initiates a request with data.
+ * Initiates a request with data. If there is no data, specify null.
*
* @param requestData the data to be sent with the request
* @see http://www.w3.org/TR/XMLHttpRequest/#send