Update Scheduler API.
  - Add EntryCommands
  - Add repeating FinallyCommand
  - Always empty the FinallyCommand queue before returning to the browser
Add state-getting method to Impl and add a fix for the finally block potentially not firing on IE.

Patch by: bobv
Review by: jat

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7315 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/core/client/Scheduler.java b/user/src/com/google/gwt/core/client/Scheduler.java
index e7d71de..ef6a090 100644
--- a/user/src/com/google/gwt/core/client/Scheduler.java
+++ b/user/src/com/google/gwt/core/client/Scheduler.java
@@ -57,11 +57,48 @@
   public abstract void scheduleDeferred(ScheduledCommand cmd);
 
   /**
+   * An "entry" command will be executed before GWT-generated code is invoked by
+   * the browser's event loop. The {@link RepeatingCommand} will be called once
+   * per entry from the event loop until <code>false</code> is returned. This
+   * type of command is appropriate for instrumentation or code that needs to
+   * know when "something happens."
+   * <p>
+   * If an entry command schedules another entry command, the second command
+   * will be executed before control flow continues to the GWT-generated code.
+   */
+  public abstract void scheduleEntry(RepeatingCommand cmd);
+
+  /**
+   * An "entry" command will be executed before GWT-generated code is invoked by
+   * the browser's event loop. This type of command is appropriate for code that
+   * needs to know when "something happens."
+   * <p>
+   * If an entry command schedules another entry command, the second command
+   * will be executed before control flow continues to the GWT-generated code.
+   */
+  public abstract void scheduleEntry(ScheduledCommand cmd);
+
+  /**
+   * A "finally" command will be executed before GWT-generated code returns
+   * control to the browser's event loop. The {@link RepeatingCommand#execute()}
+   * method will be called once per exit to the event loop until
+   * <code>false</code> is returned. This type of command is appropriate for
+   * instrumentation or cleanup code.
+   * <p>
+   * If a finally command schedules another finally command, the second command
+   * will be executed before control flow returns to the browser.
+   */
+  public abstract void scheduleFinally(RepeatingCommand cmd);
+
+  /**
    * A "finally" command will be executed before GWT-generated code returns
    * control to the browser's event loop. This type of command is used to
    * aggregate small amounts of work before performing a non-recurring,
    * heavyweight operation.
    * <p>
+   * If a finally command schedules another finally command, the second command
+   * will be executed before control flow returns to the browser.
+   * <p>
    * Consider the following:
    * 
    * <pre>
diff --git a/user/src/com/google/gwt/core/client/impl/Impl.java b/user/src/com/google/gwt/core/client/impl/Impl.java
index 4fd7774..3dd0f85 100644
--- a/user/src/com/google/gwt/core/client/impl/Impl.java
+++ b/user/src/com/google/gwt/core/client/impl/Impl.java
@@ -53,7 +53,15 @@
    */
   public static native JavaScriptObject entry(JavaScriptObject jsFunction) /*-{
     return function() {
-      return @com.google.gwt.core.client.impl.Impl::entry0(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)(jsFunction, this, arguments);
+      try {
+        return @com.google.gwt.core.client.impl.Impl::entry0(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)(jsFunction, this, arguments);
+      } catch (e) {
+        // This catch block is here to ensure that the finally block in entry0
+        // will be executed correctly on IE6/7.  We can't put a catch Throwable
+        // in entry0 because this would always cause the unhandled exception to
+        // be wrapped in a JavaScriptException type.
+        throw e;
+      }
     };
   }-*/;
 
@@ -128,6 +136,20 @@
   }-*/;
 
   /**
+   * Indicates if <code>$entry</code> has been called.
+   */
+  public static boolean isEntryOnStack() {
+    return entryDepth > 0;
+  }
+
+  /**
+   * Indicates if <code>$entry</code> is present on the stack more than once.
+   */
+  public static boolean isNestedEntry() {
+    return entryDepth > 1;
+  }
+
+  /**
    * Implicitly called by JavaToJavaScriptCompiler.findEntryPoints().
    */
   public static native JavaScriptObject registerEntry() /*-{
@@ -161,7 +183,11 @@
     assert entryDepth >= 0 : "Negative entryDepth value at entry " + entryDepth;
 
     // We want to disable some actions in the reentrant case
-    return entryDepth++ == 0;
+    if (entryDepth++ == 0) {
+      SchedulerImpl.INSTANCE.flushEntryCommands();
+      return true;
+    }
+    return false;
   }
 
   /**
@@ -194,6 +220,11 @@
         // Can't handle any exceptions, let them percolate normally
         return apply(jsFunction, thisObj, arguments);
       }
+
+      /*
+       * DO NOT ADD catch(Throwable t) here, it would always wrap the thrown
+       * value. Instead, entry() has a general catch-all block.
+       */
     } finally {
       exit(initialEntry);
     }
diff --git a/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java b/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java
index eccabe7..ea8b598 100644
--- a/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java
+++ b/user/src/com/google/gwt/core/client/impl/SchedulerImpl.java
@@ -70,6 +70,39 @@
   }
 
   /**
+   * Calls {@link SchedulerImpl#flushPostEventPumpCommands()}.
+   */
+  private final class Flusher implements RepeatingCommand {
+    public boolean execute() {
+      flushRunning = true;
+      flushPostEventPumpCommands();
+      /*
+       * No finally here, we want this to be clear only on a normal exit. An
+       * abnormal exit would indicate that an exception isn't being caught
+       * correctly or that a slow script warning canceled the timer.
+       */
+      flushRunning = false;
+      return shouldBeRunning = isWorkQueued();
+    }
+  }
+
+  /**
+   * Keeps {@link Flusher} running.
+   */
+  private final class Rescuer implements RepeatingCommand {
+    public boolean execute() {
+      if (flushRunning) {
+        /*
+         * Since JS is single-threaded, if we're here, then than means that
+         * FLUSHER.execute() started, but did not finish. Reschedule FLUSHER.
+         */
+        scheduleFixedDelay(flusher, FLUSHER_DELAY);
+      }
+      return shouldBeRunning;
+    }
+  }
+
+  /**
    * Use a GWT.create() here to make it simple to hijack the default
    * implementation.
    */
@@ -92,6 +125,13 @@
   private static final double TIME_SLICE = 100;
 
   /**
+   * Extract boilerplate code.
+   */
+  private static JsArray<Task> createQueue() {
+    return JavaScriptObject.createArray().cast();
+  }
+
+  /**
    * Called from scheduledFixedInterval to give $entry a static function.
    */
   @SuppressWarnings("unused")
@@ -100,12 +140,25 @@
   }
 
   /**
+   * Provides lazy-init pattern for the task queues.
+   */
+  private static JsArray<Task> push(JsArray<Task> queue, Task task) {
+    if (queue == null) {
+      queue = createQueue();
+    }
+    queue.push(task);
+    return queue;
+  }
+
+  /**
    * Execute a list of Tasks that hold RepeatingCommands.
    * 
    * @return A replacement array that is possibly a shorter copy of
    *         <code>tasks</code>
    */
   private static JsArray<Task> runRepeatingTasks(JsArray<Task> tasks) {
+    assert tasks != null : "tasks";
+
     boolean canceledSomeTasks = false;
     int length = tasks.length();
     double start = Duration.currentTimeMillis();
@@ -129,16 +182,15 @@
     }
 
     if (canceledSomeTasks) {
-      JsArray<Task> newTasks = JavaScriptObject.createArray().cast();
+      JsArray<Task> newTasks = createQueue();
       // Remove tombstones
       for (int i = 0; i < length; i++) {
-        if (tasks.get(i) == null) {
-          continue;
+        if (tasks.get(i) != null) {
+          newTasks.push(tasks.get(i));
         }
-        newTasks.push(tasks.get(i));
       }
       assert newTasks.length() < length;
-      return newTasks;
+      return newTasks.length() == 0 ? null : newTasks;
     } else {
       return tasks;
     }
@@ -150,9 +202,14 @@
    * that want to repeat will be pushed onto the <code>rescheduled</code> queue.
    * The contents of <code>tasks</code> may not be altered while this method is
    * executing.
+   * 
+   * @return <code>rescheduled</code> or a newly-allocated array if
+   *         <code>rescheduled</code> is null.
    */
-  private static void runScheduledTasks(JsArray<Task> tasks,
+  private static JsArray<Task> runScheduledTasks(JsArray<Task> tasks,
       JsArray<Task> rescheduled) {
+    assert tasks != null : "tasks";
+
     for (int i = 0, j = tasks.length(); i < j; i++) {
       assert tasks.length() == j : "Working array length changed "
           + tasks.length() + " != " + j;
@@ -162,7 +219,7 @@
         // Move repeating commands to incremental commands queue
         if (t.isRepeating()) {
           if (t.executeRepeating()) {
-            rescheduled.push(t);
+            rescheduled = push(rescheduled, t);
           }
         } else {
           t.executeScheduled();
@@ -173,6 +230,7 @@
         }
       }
     }
+    return rescheduled;
   }
 
   private static native void scheduleFixedDelayImpl(RepeatingCommand cmd,
@@ -211,36 +269,13 @@
    * A RepeatingCommand that calls flushPostEventPumpCommands(). It repeats if
    * there are any outstanding deferred or incremental commands.
    */
-  final RepeatingCommand flusher = new RepeatingCommand() {
-    public boolean execute() {
-      flushRunning = true;
-      flushPostEventPumpCommands();
-      /*
-       * No finally here, we want this to be clear only on a normal exit. An
-       * abnormal exit would indicate that an exception isn't being caught
-       * correctly or that a slow script warning canceled the timer.
-       */
-      flushRunning = false;
-      return shouldBeRunning = isWorkQueued();
-    }
-  };
+  Flusher flusher;
 
   /**
    * This provides some backup for the main flusher task in case it gets shut
    * down by a slow-script warning.
    */
-  final RepeatingCommand rescue = new RepeatingCommand() {
-    public boolean execute() {
-      if (flushRunning) {
-        /*
-         * Since JS is single-threaded, if we're here, then than means that
-         * FLUSHER.execute() started, but did not finish. Reschedule FLUSHER.
-         */
-        scheduleFixedDelay(flusher, FLUSHER_DELAY);
-      }
-      return shouldBeRunning;
-    }
-  };
+  Rescuer rescue;
 
   /*
    * Work queues. Timers store their state on the function, so we don't need to
@@ -248,35 +283,74 @@
    * Processing the values in the queues is a one-shot, and then the array is
    * discarded.
    */
-  JsArray<Task> deferredCommands = JavaScriptObject.createArray().cast();
-  JsArray<Task> incrementalCommands = JavaScriptObject.createArray().cast();
-  JsArray<Task> finallyCommands = JavaScriptObject.createArray().cast();
+  JsArray<Task> deferredCommands;
+  JsArray<Task> entryCommands;
+  JsArray<Task> finallyCommands;
+  JsArray<Task> incrementalCommands;
 
   /*
    * These two flags are used to control the state of the flusher and rescuer
    * commands.
    */
-  private boolean shouldBeRunning = false;
   private boolean flushRunning = false;
+  private boolean shouldBeRunning = false;
+
+  /**
+   * Called by {@link Impl#entry(JavaScriptObject)}.
+   */
+  public void flushEntryCommands() {
+    if (entryCommands != null) {
+      JsArray<Task> rescheduled = null;
+      // This do-while loop handles commands scheduling commands
+      do {
+        JsArray<Task> oldQueue = entryCommands;
+        entryCommands = null;
+        rescheduled = runScheduledTasks(oldQueue, rescheduled);
+      } while (entryCommands != null);
+      entryCommands = rescheduled;
+    }
+  }
 
   /**
    * Called by {@link Impl#entry(JavaScriptObject)}.
    */
   public void flushFinallyCommands() {
-    JsArray<Task> oldFinally = finallyCommands;
-    finallyCommands = JavaScriptObject.createArray().cast();
-    runScheduledTasks(oldFinally, finallyCommands);
+    if (finallyCommands != null) {
+      JsArray<Task> rescheduled = null;
+      // This do-while loop handles commands scheduling commands
+      do {
+        JsArray<Task> oldQueue = finallyCommands;
+        finallyCommands = null;
+        rescheduled = runScheduledTasks(oldQueue, rescheduled);
+      } while (finallyCommands != null);
+      finallyCommands = rescheduled;
+    }
   }
 
   @Override
   public void scheduleDeferred(ScheduledCommand cmd) {
-    deferredCommands.push(Task.create(cmd));
+    deferredCommands = push(deferredCommands, Task.create(cmd));
     maybeSchedulePostEventPumpCommands();
   }
 
   @Override
+  public void scheduleEntry(RepeatingCommand cmd) {
+    entryCommands = push(entryCommands, Task.create(cmd));
+  }
+
+  @Override
+  public void scheduleEntry(ScheduledCommand cmd) {
+    entryCommands = push(entryCommands, Task.create(cmd));
+  }
+
+  @Override
+  public void scheduleFinally(RepeatingCommand cmd) {
+    finallyCommands = push(finallyCommands, Task.create(cmd));
+  }
+
+  @Override
   public void scheduleFinally(ScheduledCommand cmd) {
-    finallyCommands.push(Task.create(cmd));
+    finallyCommands = push(finallyCommands, Task.create(cmd));
   }
 
   @Override
@@ -292,7 +366,7 @@
   @Override
   public void scheduleIncremental(RepeatingCommand cmd) {
     // Push repeating commands onto the same initial queue for relative order
-    deferredCommands.push(Task.create(cmd));
+    deferredCommands = push(deferredCommands, Task.create(cmd));
     maybeSchedulePostEventPumpCommands();
   }
 
@@ -300,21 +374,38 @@
    * Called by Flusher.
    */
   void flushPostEventPumpCommands() {
-    JsArray<Task> oldDeferred = deferredCommands;
-    deferredCommands = JavaScriptObject.createArray().cast();
+    if (deferredCommands != null) {
+      JsArray<Task> oldDeferred = deferredCommands;
+      deferredCommands = null;
 
-    runScheduledTasks(oldDeferred, incrementalCommands);
-    incrementalCommands = runRepeatingTasks(incrementalCommands);
+      /* We might not have any incremental commands queued. */
+      if (incrementalCommands == null) {
+        incrementalCommands = createQueue();
+      }
+      runScheduledTasks(oldDeferred, incrementalCommands);
+    }
+
+    if (incrementalCommands != null) {
+      incrementalCommands = runRepeatingTasks(incrementalCommands);
+    }
   }
 
   boolean isWorkQueued() {
-    return deferredCommands.length() > 0 || incrementalCommands.length() > 0;
+    return deferredCommands != null || incrementalCommands != null;
   }
 
   private void maybeSchedulePostEventPumpCommands() {
     if (!shouldBeRunning) {
       shouldBeRunning = true;
+
+      if (flusher == null) {
+        flusher = new Flusher();
+      }
       scheduleFixedDelayImpl(flusher, FLUSHER_DELAY);
+
+      if (rescue == null) {
+        rescue = new Rescuer();
+      }
       scheduleFixedDelayImpl(rescue, RESCUE_DELAY);
     }
   }
diff --git a/user/test/com/google/gwt/core/CoreSuite.java b/user/test/com/google/gwt/core/CoreSuite.java
index df1cedf..8e37156 100644
--- a/user/test/com/google/gwt/core/CoreSuite.java
+++ b/user/test/com/google/gwt/core/CoreSuite.java
@@ -19,6 +19,7 @@
 import com.google.gwt.core.client.HttpThrowableReporterTest;
 import com.google.gwt.core.client.JavaScriptExceptionTest;
 import com.google.gwt.core.client.JsArrayTest;
+import com.google.gwt.core.client.SchedulerTest;
 import com.google.gwt.core.client.impl.AsyncFragmentLoaderTest;
 import com.google.gwt.core.client.impl.EmulatedStackTraceTest;
 import com.google.gwt.core.client.impl.SchedulerImplTest;
@@ -36,15 +37,16 @@
     GWTTestSuite suite = new GWTTestSuite("All core tests");
 
     // $JUnit-BEGIN$
+    suite.addTestSuite(AsyncFragmentLoaderTest.class);
+    suite.addTestSuite(EmulatedStackTraceTest.class);
+    suite.addTestSuite(GWTTest.class);
     suite.addTestSuite(HttpThrowableReporterTest.class);
     suite.addTestSuite(JavaScriptExceptionTest.class);
     suite.addTestSuite(JsArrayTest.class);
-    suite.addTestSuite(GWTTest.class);
-    suite.addTestSuite(StackTraceCreatorTest.class);
-    suite.addTestSuite(EmulatedStackTraceTest.class);
-    suite.addTestSuite(AsyncFragmentLoaderTest.class);
-    suite.addTestSuite(XhrLoadingStrategyTest.class);
     suite.addTestSuite(SchedulerImplTest.class);
+    suite.addTestSuite(SchedulerTest.class);
+    suite.addTestSuite(StackTraceCreatorTest.class);
+    suite.addTestSuite(XhrLoadingStrategyTest.class);
     // $JUnit-END$
 
     return suite;
diff --git a/user/test/com/google/gwt/core/client/SchedulerTest.java b/user/test/com/google/gwt/core/client/SchedulerTest.java
new file mode 100644
index 0000000..78ac9dd
--- /dev/null
+++ b/user/test/com/google/gwt/core/client/SchedulerTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2009 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;
+
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * This is a black-box test of the Scheduler API.
+ */
+@DoNotRunWith(Platform.HtmlUnit)
+// TODO(amitmanjhi) This tickles a devmode HtmlUnit deadlock
+public class SchedulerTest extends GWTTestCase {
+
+  private static final int TEST_DELAY = 500000;
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.core.Core";
+  }
+
+  /**
+   * Tests that an entry command can schedule a finally command where the whole
+   * thing is kicked off by a deferred command.
+   */
+  public void testEndToEnd() {
+    final boolean[] ranEntry = {false};
+
+    final ScheduledCommand finallyCommand = new ScheduledCommand() {
+      public void execute() {
+        assertTrue(ranEntry[0]);
+        finishTest();
+      }
+    };
+
+    Scheduler.get().scheduleEntry(new ScheduledCommand() {
+      public void execute() {
+        ranEntry[0] = true;
+        Scheduler.get().scheduleFinally(finallyCommand);
+      }
+    });
+
+    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
+      public void execute() {
+        assertTrue(ranEntry[0]);
+      }
+    });
+
+    delayTestFinish(TEST_DELAY);
+  }
+}
diff --git a/user/test/com/google/gwt/core/client/impl/SchedulerImplTest.java b/user/test/com/google/gwt/core/client/impl/SchedulerImplTest.java
index 46b019f..00fb773 100644
--- a/user/test/com/google/gwt/core/client/impl/SchedulerImplTest.java
+++ b/user/test/com/google/gwt/core/client/impl/SchedulerImplTest.java
@@ -15,12 +15,16 @@
  */
 package com.google.gwt.core.client.impl;
 
+import com.google.gwt.core.client.JsArray;
 import com.google.gwt.core.client.Scheduler.RepeatingCommand;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
+import com.google.gwt.core.client.impl.SchedulerImpl.Task;
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
 import com.google.gwt.junit.client.GWTTestCase;
 
 /**
- * Tests that poke at the internal state of SchedulerImpl.
+ * This is a white-box test of the Scheduler API implementation.
  */
 public class SchedulerImplTest extends GWTTestCase {
 
@@ -59,6 +63,20 @@
     }
   }
 
+  /**
+   * The EntryCommand and FinallyCommand queues should have the same behavior,
+   * so we use this interface to reuse the same test logic.
+   */
+  interface QueueTester {
+    void flush();
+
+    JsArray<Task> queue();
+
+    void schedule(RepeatingCommand cmd);
+
+    void schedule(ScheduledCommand cmd);
+  }
+
   private static final int TEST_DELAY = 5000;
 
   @Override
@@ -66,6 +84,8 @@
     return "com.google.gwt.core.Core";
   }
 
+  @DoNotRunWith(Platform.HtmlUnit)
+  // TODO(amitmanjhi) This tickles a devmode HtmlUnit deadlock
   public void testDeferredCommands() {
     final SchedulerImpl impl = new SchedulerImpl();
 
@@ -82,7 +102,7 @@
     impl.scheduleDeferred(new ScheduledCommand() {
       public void execute() {
         assertTrue(values[0]);
-        assertEquals(0, impl.deferredCommands.length());
+        assertNull(impl.deferredCommands);
         finishTest();
       }
     });
@@ -90,25 +110,51 @@
     delayTestFinish(TEST_DELAY);
   }
 
-  public void testFinallyCommands() {
-    SchedulerImpl impl = new SchedulerImpl();
+  public void testEntryCommands() {
+    final SchedulerImpl impl = new SchedulerImpl();
 
-    final boolean[] values = {false};
-    impl.scheduleFinally(new ArraySetterCommand(values));
+    testQueue(new QueueTester() {
+      public void flush() {
+        impl.flushEntryCommands();
+      }
 
-    assertEquals(1, impl.finallyCommands.length());
+      public JsArray<Task> queue() {
+        return impl.entryCommands;
+      }
 
-    ScheduledCommand nullCommand = new NullCommand();
-    impl.scheduleFinally(nullCommand);
-    assertEquals(2, impl.finallyCommands.length());
-    assertSame(nullCommand, impl.finallyCommands.get(1).getScheduled());
+      public void schedule(RepeatingCommand cmd) {
+        impl.scheduleEntry(cmd);
+      }
 
-    impl.flushFinallyCommands();
-
-    assertTrue(values[0]);
-    assertEquals(0, impl.finallyCommands.length());
+      public void schedule(ScheduledCommand cmd) {
+        impl.scheduleEntry(cmd);
+      }
+    });
   }
 
+  public void testFinallyCommands() {
+    final SchedulerImpl impl = new SchedulerImpl();
+
+    testQueue(new QueueTester() {
+      public void flush() {
+        impl.flushFinallyCommands();
+      }
+
+      public JsArray<Task> queue() {
+        return impl.finallyCommands;
+      }
+
+      public void schedule(RepeatingCommand cmd) {
+        impl.scheduleFinally(cmd);
+      }
+
+      public void schedule(ScheduledCommand cmd) {
+        impl.scheduleFinally(cmd);
+      }
+    });
+  }
+
+  @DoNotRunWith(Platform.HtmlUnit)
   public void testFixedDelayCommands() {
     final SchedulerImpl impl = new SchedulerImpl();
     final int[] values = {0, 4};
@@ -131,6 +177,7 @@
     delayTestFinish(TEST_DELAY);
   }
 
+  @DoNotRunWith(Platform.HtmlUnit)
   public void testFixedPeriodCommands() {
     final SchedulerImpl impl = new SchedulerImpl();
     final int[] values = {0, 4};
@@ -153,6 +200,7 @@
     delayTestFinish(TEST_DELAY);
   }
 
+  @DoNotRunWith(Platform.HtmlUnit)
   public void testIncrementalCommands() {
     final SchedulerImpl impl = new SchedulerImpl();
 
@@ -166,13 +214,16 @@
     impl.scheduleDeferred(new ScheduledCommand() {
       public void execute() {
         // After the incremental command has fired, it's moved to a new queue
-        assertEquals(0, impl.deferredCommands.length());
+        assertNull(impl.deferredCommands);
         assertTrue(String.valueOf(values[0]), values[0] <= values[1]);
 
         if (values[0] == values[1]) {
+          // Haven't yet cleared the queue, still in flushPostEventPumpCommands
+          assertNotNull(impl.incrementalCommands);
           assertEquals(0, impl.incrementalCommands.length());
           finishTest();
         } else {
+          assertNotNull(impl.incrementalCommands);
           assertEquals(1, impl.incrementalCommands.length());
           assertSame(counter, impl.incrementalCommands.get(0).getRepeating());
           impl.scheduleDeferred(this);
@@ -184,4 +235,41 @@
 
     delayTestFinish(TEST_DELAY);
   }
+
+  private void testQueue(final QueueTester impl) {
+    boolean[] oneShotValues = {false};
+    final boolean[] chainedValues = {false};
+    int[] counterValues = {0, 2};
+
+    impl.schedule(new ArraySetterCommand(oneShotValues));
+    impl.schedule(new CountingCommand(counterValues));
+    impl.schedule(new ScheduledCommand() {
+      public void execute() {
+        // Schedule another entry
+        impl.schedule(new ArraySetterCommand(chainedValues));
+      }
+    });
+
+    assertEquals(3, impl.queue().length());
+
+    ScheduledCommand nullCommand = new NullCommand();
+    impl.schedule(nullCommand);
+    assertEquals(4, impl.queue().length());
+    assertSame(nullCommand, impl.queue().get(3).getScheduled());
+
+    impl.flush();
+
+    // Ensure the command-schedules-command case has been executed
+    assertTrue(chainedValues[0]);
+
+    // Test that the RepeatingCommand is still scheduled
+    assertEquals(1, counterValues[0]);
+    assertEquals(1, impl.queue().length());
+    impl.flush();
+
+    // Everything should be finished now
+    assertEquals(2, counterValues[0]);
+    assertTrue(oneShotValues[0]);
+    assertNull(impl.queue());
+  }
 }