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
-~----------~----~----~----~------~----~------~--~---

Reply via email to