Author: sp...@google.com Date: Wed Mar 4 08:03:43 2009 New Revision: 4917
Modified: trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java trunk/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java trunk/user/src/com/google/gwt/core/client/RunAsyncCallback.java Log: Allow runAsync to download code using XHR instead of script tags. When the XHR downloads are used, download errors are detected and downloads can be retried later. Review by: bobv Modified: trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java ============================================================================== --- trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java (original) +++ trunk/dev/core/src/com/google/gwt/core/linker/IFrameLinker.java Wed Mar 4 08:03:43 2009 @@ -155,13 +155,18 @@ out.newlineOpt(); if (supportRunAsync) { out.print("function __gwtStartLoadingFragment(frag) {"); + out.indentIn(); + out.newlineOpt(); + out.print(" return $moduleBase + '" + FRAGMENT_SUBDIR + + "/' + $strongName + '/' + frag + '" + FRAGMENT_EXTENSION + "';"); + out.indentOut(); + out.newlineOpt(); + out.print("};"); out.newlineOpt(); + out.print("function __gwtInstallCode(code) {"); 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("$wnd.eval(code)"); out.indentOut(); out.newlineOpt(); out.print("};"); Modified: trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java ============================================================================== --- trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java (original) +++ trunk/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentLoaderCreator.java Wed Mar 4 08:03:43 2009 @@ -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("}"); } Modified: trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java ============================================================================== --- trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java (original) +++ trunk/samples/showcase/src/com/google/gwt/sample/showcase/client/ContentWidget.java Wed Mar 4 08:03:43 2009 @@ -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; } @@ -366,5 +372,35 @@ } catch (RequestException e) { 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(); + } + }); } } Modified: trunk/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java ============================================================================== --- trunk/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java (original) +++ trunk/user/src/com/google/gwt/core/client/AsyncFragmentLoader.java Wed Mar 4 08:03:43 2009 @@ -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; } @@ -146,6 +242,15 @@ * Wait until the leftovers fragment has loaded before loading this one. */ waitingForLeftovers.add(splitPoint); + waitingForLeftoversErrorHandlers.add(loadErrorHandler); + + /* + * Also, restart the leftovers download if it previously failed. + */ + if (!leftoversLoading) { + startLoadingLeftovers(); + } + return; } @@ -153,7 +258,8 @@ baseLoading = true; logEventProgress(LwmLabels.downloadGroup(splitPoint), LwmLabels.BEGIN, baseFragmentNumber(splitPoint), null); - startLoadingFragment(baseFragmentNumber(splitPoint)); + startLoadingFragment(baseFragmentNumber(splitPoint), + new BaseDownloadFailed(loadErrorHandler)); } /** @@ -165,8 +271,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 +311,17 @@ 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. + * */ + 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 +349,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()); } /** Modified: trunk/user/src/com/google/gwt/core/client/RunAsyncCallback.java ============================================================================== --- trunk/user/src/com/google/gwt/core/client/RunAsyncCallback.java (original) +++ trunk/user/src/com/google/gwt/core/client/RunAsyncCallback.java Wed Mar 4 08:03:43 2009 @@ -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. --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---