Makes GWT.runAsync to always run async.

Fixes issue 5560

Change-Id: Ia55b41c6978a7d2824ceeb3289dc48fe05bf117b
Review-Link: https://gwt-review.googlesource.com/#/c/2770/
diff --git a/user/src/com/google/gwt/core/client/GWT.java b/user/src/com/google/gwt/core/client/GWT.java
index f9b68b2..3d3440d 100644
--- a/user/src/com/google/gwt/core/client/GWT.java
+++ b/user/src/com/google/gwt/core/client/GWT.java
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.core.client;
 
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 import com.google.gwt.core.client.impl.Impl;
 
 /**
@@ -258,14 +259,18 @@
    */
   @SuppressWarnings("unused") // parameter will be used following replacement
   public static void runAsync(Class<?> name, RunAsyncCallback callback) {
-    callback.onSuccess();
+    runAsync(callback);
   }
 
   /**
    * Run the specified callback once the necessary code for it has been loaded.
    */
-  public static void runAsync(RunAsyncCallback callback) {
-    callback.onSuccess();
+  public static void runAsync(final RunAsyncCallback callback) {
+    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+      @Override public void execute() {
+        callback.onSuccess();
+      }
+    });
   }
 
   /**
diff --git a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
index 528a734..77ab67f 100644
--- a/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
+++ b/user/src/com/google/gwt/core/client/impl/AsyncFragmentLoader.java
@@ -18,6 +18,8 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.RunAsyncCallback;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 
 /**
  * <p>
@@ -587,7 +589,7 @@
   private void runAsyncImpl(final int fragment, RunAsyncCallback callback) {
     if (isLoaded[fragment]) {
       assert allCallbacks[fragment] == null;
-      callback.onSuccess();
+      executeOnSuccessAsynchronously(callback);
       return;
     }
 
@@ -615,6 +617,28 @@
     }
   }
 
+  /**
+   * Executes onSuccess asynchronously.
+   */
+  private void executeOnSuccessAsynchronously(final RunAsyncCallback callback) {
+    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+      @Override public void execute() {
+        executeOnSuccess(callback);
+      }
+    });
+  }
+
+  private void executeOnSuccess(RunAsyncCallback callback) {
+    /*
+     * Calls on {@link RunAsyncCallback#onSuccess} from {@link AsyncFragmentLoader} is special
+     * treated (See RescueVisitor in ControlFlowAnalyzer) so that code splitter will not follow them
+     * on fragment analysis. That is, if don't call onSuccess from here and instead call it directly
+     * from the scheduled command, then it will make the code splitter put the split point code in
+     * the initial fragment.
+     */
+    callback.onSuccess();
+  }
+
   private void startLoadingFragment(int fragment) {
     assert (fragmentLoading < 0);
     fragmentLoading = fragment;
diff --git a/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java b/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java
index 65d6fae..8b771cf 100644
--- a/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/RunAsyncFailureTest.java
@@ -17,6 +17,8 @@
 
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.RunAsyncCallback;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
 import com.google.gwt.core.client.impl.LoadingStrategyBase;
 import com.google.gwt.junit.DoNotRunWith;
 import com.google.gwt.junit.Platform;
@@ -82,6 +84,8 @@
 
   private static final int RUNASYNC_TIMEOUT = 30000;
   
+  private static int staticWrittenByAsync;
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.dev.jjs.RunAsyncFailure";
@@ -165,6 +169,19 @@
       }
     });
   }
+
+  private void runAsync5() {
+    GWT.runAsync(new RunAsyncCallback() {
+      public void onFailure(Throwable caught) {
+        staticWrittenByAsync++;
+      }
+      public void onSuccess() {
+        // Use the string "INSTALL_FAILURE_TEST" so we can identify this
+        // fragment on the server.  In the fail message is good enough.
+        fail("INSTALL_FAILURE_TEST_2 - Code should have failed to install!");
+      }
+    });
+  }
   
   /**
    * Test the basic functionality of retrying runAsync until is succeeds.
@@ -201,4 +218,33 @@
     LoadingStrategyBase.MAX_AUTO_RETRY_COUNT = 3;
     runAsync4();
   }
+
+  public void testDownloadSuccessButInstallFailureStillRunsAsync() {
+    delayTestFinish(RUNASYNC_TIMEOUT);
+    LoadingStrategyBase.MAX_AUTO_RETRY_COUNT = 3;
+    staticWrittenByAsync = 0;
+
+    assertRunAsyncIsAsync();
+
+    // Give it little bit more time to loaded and try runAsync again
+    Scheduler.get().scheduleFixedPeriod(new RepeatingCommand() {
+      @Override public boolean execute() {
+        if (staticWrittenByAsync == 0) {
+          return true;
+        }
+
+        // Code is loaded, let's assert it still runs async
+        assertRunAsyncIsAsync();
+
+        finishTest();
+        return false;
+      }
+    }, 100);
+  }
+
+  private void assertRunAsyncIsAsync() {
+    final int lastValue = staticWrittenByAsync;
+    runAsync5();
+    assertEquals(lastValue, staticWrittenByAsync);
+  }
 }
diff --git a/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java b/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java
index e9e7c94..38a8e6c 100644
--- a/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java
+++ b/user/test/com/google/gwt/dev/jjs/test/RunAsyncTest.java
@@ -18,6 +18,8 @@
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
 import com.google.gwt.core.client.RunAsyncCallback;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.RepeatingCommand;
 import com.google.gwt.junit.client.GWTTestCase;
 
 /**
@@ -30,6 +32,8 @@
 
   private static String staticWrittenInBaseButReadLater;
 
+  private static int staticWrittenByAsync;
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.dev.jjs.CompilerSuite";
@@ -73,6 +77,44 @@
   }
 
   /**
+   * Test runAsync always runs async.
+   */
+  public void testAsyncIsAlwaysAsync() {
+    delayTestFinish(RUNASYNC_TIMEOUT);
+    staticWrittenByAsync = 0;
+
+    assertRunAsyncIsAsync();
+
+    // Give it little bit more time to loaded and try runAsync again
+    Scheduler.get().scheduleFixedPeriod(new RepeatingCommand() {
+      @Override public boolean execute() {
+        if (staticWrittenByAsync == 0) {
+          return true;
+        }
+
+        // Code is loaded, let's assert it still runs async
+        assertRunAsyncIsAsync();
+
+        finishTest();
+        return false;
+      }
+    }, 100);
+  }
+
+  private void assertRunAsyncIsAsync() {
+    final int lastValue = staticWrittenByAsync;
+    GWT.runAsync(RunAsyncTest.class, new RunAsyncCallback() {
+      public void onFailure(Throwable caught) {
+        throw new RuntimeException(caught);
+      }
+      public void onSuccess() {
+        staticWrittenByAsync++;
+      }
+    });
+    assertEquals(lastValue, staticWrittenByAsync);
+  }
+
+  /**
    * Test that callbacks are called in the order they are posted.
    */
   public void testOrder() {
@@ -130,17 +172,13 @@
     });
     delayTestFinish(RUNASYNC_TIMEOUT);
 
-    try {
-      GWT.runAsync(new RunAsyncCallback() {
-        public void onFailure(Throwable caught) {
-        }
+    GWT.runAsync(new RunAsyncCallback() {
+      public void onFailure(Throwable caught) {
+      }
 
-        public void onSuccess() {
-          throw toThrow;
-        }
-      });
-    } catch (Throwable e) {
-      GWT.maybeReportUncaughtException(e);
-    }
+      public void onSuccess() {
+        throw toThrow;
+      }
+    });
   }
 }