java: Add a choreographer frame task trait

This is useful if you need to guarantee something like a layout pass has
occurred before your task runs. Tasks posted with TaskTraits.CHOREOGRAPHER_FRAME
will run next time there's a vsync and after all other ready tasks on the thread
have run.

I put the trait under base because from what I understand there is some ui/ code
that needs it but ui/ can't depend on content/.

Bug: 863341
Change-Id: Ibcb7a843866c2b2c4f4394ebd18c8ef7aa33b5bc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1549114
Commit-Queue: Alex Clarke <alexclarke@chromium.org>
Reviewed-by: Yaron Friedman <yfriedman@chromium.org>
Reviewed-by: Sami Kyöstilä <skyostil@chromium.org>
Reviewed-by: Karolina Soltys <ksolt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#652215}
diff --git a/base/BUILD.gn b/base/BUILD.gn
index f6fb0361..9aed800 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -3153,6 +3153,7 @@
       "android/java/src/org/chromium/base/task/AsyncTask.java",
       "android/java/src/org/chromium/base/task/BackgroundOnlyAsyncTask.java",
       "android/java/src/org/chromium/base/task/DefaultTaskExecutor.java",
+      "android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java",
       "android/java/src/org/chromium/base/task/ChromeThreadPoolExecutor.java",
       "android/java/src/org/chromium/base/task/PostTask.java",
       "android/java/src/org/chromium/base/task/SequencedTaskRunner.java",
diff --git a/base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java b/base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java
new file mode 100644
index 0000000..d44ef4e
--- /dev/null
+++ b/base/android/java/src/org/chromium/base/task/ChoreographerTaskRunner.java
@@ -0,0 +1,63 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.base.task;
+
+import android.view.Choreographer;
+
+/**
+ * An adapter that allows PostTask to submit Choreographer frame callbacks which
+ * run after the next vsync.
+ */
+final class ChoreographerTaskRunner implements SingleThreadTaskRunner {
+    private final Choreographer mChoreographer;
+
+    ChoreographerTaskRunner(Choreographer choreographer) {
+        mChoreographer = choreographer;
+    }
+
+    @Override
+    public boolean belongsToCurrentThread() {
+        try {
+            return mChoreographer == Choreographer.getInstance();
+        } catch (IllegalStateException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public void postTask(Runnable task) {
+        mChoreographer.postFrameCallback(new Choreographer.FrameCallback() {
+            @Override
+            public void doFrame(long frameTimeNanos) {
+                task.run();
+            }
+        });
+    }
+
+    @Override
+    public void destroy() {
+        // NOP
+    }
+
+    @Override
+    public void disableLifetimeCheck() {
+        // NOP
+    }
+
+    @Override
+    public void postDelayedTask(Runnable task, long delayMillis) {
+        mChoreographer.postFrameCallbackDelayed(new Choreographer.FrameCallback() {
+            @Override
+            public void doFrame(long frameTimeNanos) {
+                task.run();
+            }
+        }, delayMillis);
+    }
+
+    @Override
+    public void initNativeTaskRunner() {
+        // NOP
+    }
+}
diff --git a/base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java b/base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java
index 0613e38..9952c42 100644
--- a/base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java
+++ b/base/android/java/src/org/chromium/base/task/DefaultTaskExecutor.java
@@ -4,6 +4,10 @@
 
 package org.chromium.base.task;
 
+import android.view.Choreographer;
+
+import org.chromium.base.ThreadUtils;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -11,15 +15,18 @@
  * The default {@link TaskExecutor} which maps directly to base::ThreadPool.
  */
 class DefaultTaskExecutor implements TaskExecutor {
-    Map<TaskTraits, TaskRunner> mTraitsToRunnerMap = new HashMap<>();
+    private final Map<TaskTraits, TaskRunner> mTraitsToRunnerMap = new HashMap<>();
+    private ChoreographerTaskRunner mChoreographerTaskRunner;
 
     @Override
     public TaskRunner createTaskRunner(TaskTraits taskTraits) {
+        if (taskTraits.mIsChoreographerFrame) return getChoreographerTaskRunner();
         return new TaskRunnerImpl(taskTraits);
     }
 
     @Override
     public SequencedTaskRunner createSequencedTaskRunner(TaskTraits taskTraits) {
+        if (taskTraits.mIsChoreographerFrame) return getChoreographerTaskRunner();
         return new SequencedTaskRunnerImpl(taskTraits);
     }
 
@@ -29,12 +36,13 @@
      */
     @Override
     public SingleThreadTaskRunner createSingleThreadTaskRunner(TaskTraits taskTraits) {
+        if (taskTraits.mIsChoreographerFrame) return getChoreographerTaskRunner();
         // Tasks posted via this API will not execute until after native has started.
         return new SingleThreadTaskRunnerImpl(null, taskTraits);
     }
 
     @Override
-    public void postDelayedTask(TaskTraits taskTraits, Runnable task, long delay) {
+    public synchronized void postDelayedTask(TaskTraits taskTraits, Runnable task, long delay) {
         if (taskTraits.hasExtension()) {
             TaskRunner runner = createTaskRunner(taskTraits);
             runner.postDelayedTask(task, delay);
@@ -57,4 +65,14 @@
     public boolean canRunTaskImmediately(TaskTraits traits) {
         return false;
     }
+
+    private synchronized ChoreographerTaskRunner getChoreographerTaskRunner() {
+        // TODO(alexclarke): Migrate to the new Android UI thread trait when available.
+        ChoreographerTaskRunner choreographerTaskRunner =
+                ThreadUtils.runOnUiThreadBlockingNoException(
+                        () -> { return new ChoreographerTaskRunner(Choreographer.getInstance()); });
+
+        mTraitsToRunnerMap.put(TaskTraits.CHOREOGRAPHER_FRAME, choreographerTaskRunner);
+        return choreographerTaskRunner;
+    }
 }
diff --git a/base/android/java/src/org/chromium/base/task/TaskTraits.java b/base/android/java/src/org/chromium/base/task/TaskTraits.java
index 4b91a32..0633fc78 100644
--- a/base/android/java/src/org/chromium/base/task/TaskTraits.java
+++ b/base/android/java/src/org/chromium/base/task/TaskTraits.java
@@ -54,6 +54,11 @@
     public static final TaskTraits USER_BLOCKING =
             new TaskTraits().taskPriority(TaskPriority.USER_BLOCKING);
 
+    // A bit like requestAnimationFrame, this task will be posted onto the Choreographer
+    // and will be run on the android main thread after the next vsync.
+    public static final TaskTraits CHOREOGRAPHER_FRAME =
+            new TaskTraits().setIsChoreographerFrame(true);
+
     public TaskTraits() {}
 
     private TaskTraits(TaskTraits other) {
@@ -89,6 +94,11 @@
         return taskTraits;
     }
 
+    private TaskTraits setIsChoreographerFrame(boolean isChoreographerFrame) {
+        mIsChoreographerFrame = isChoreographerFrame;
+        return this;
+    }
+
     // For convenience of the JNI code, we use primitive types only.
     // Note shutdown behavior is not supported on android.
     boolean mPrioritySetExplicitly;
@@ -96,6 +106,7 @@
     boolean mMayBlock;
     byte mExtensionId = INVALID_EXTENSION_ID;
     byte mExtensionData[];
+    boolean mIsChoreographerFrame;
 
     /**
      * @return true if this task is using some TaskTraits extension.
@@ -152,6 +163,7 @@
         hash = 37 * hash + (mMayBlock ? 0 : 1);
         hash = 37 * hash + (int) mExtensionId;
         hash = 37 * hash + Arrays.hashCode(mExtensionData);
+        hash = 37 * hash + (mIsChoreographerFrame ? 0 : 1);
         return hash;
     }
 }
diff --git a/base/android/javatests/src/org/chromium/base/task/PostTaskTest.java b/base/android/javatests/src/org/chromium/base/task/PostTaskTest.java
index 022013b..c7ad98c 100644
--- a/base/android/javatests/src/org/chromium/base/task/PostTaskTest.java
+++ b/base/android/javatests/src/org/chromium/base/task/PostTaskTest.java
@@ -18,6 +18,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -95,4 +96,34 @@
             taskQueue.destroy();
         }
     }
+
+    @Test
+    @SmallTest
+    public void testChoreographerFrameTrait() throws Exception {
+        List<Integer> orderList = new ArrayList<>();
+        CountDownLatch latch = new CountDownLatch(2);
+        PostTask.postTask(TaskTraits.CHOREOGRAPHER_FRAME, new Runnable() {
+            @Override
+            public void run() {
+                synchronized (orderList) {
+                    orderList.add(1);
+                    latch.countDown();
+                }
+            }
+        });
+
+        PostTask.postTask(TaskTraits.CHOREOGRAPHER_FRAME, new Runnable() {
+            @Override
+            public void run() {
+                synchronized (orderList) {
+                    orderList.add(2);
+                    latch.countDown();
+                }
+            }
+        });
+
+        latch.await();
+
+        assertThat(orderList, contains(1, 2));
+    }
 }
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/scheduler/UiThreadSchedulerTest.java b/content/public/android/javatests/src/org/chromium/content/browser/scheduler/UiThreadSchedulerTest.java
index 07ff7c6..caeb843 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/scheduler/UiThreadSchedulerTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/scheduler/UiThreadSchedulerTest.java
@@ -32,6 +32,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -226,6 +227,52 @@
         Assert.assertTrue(taskExecuted.get());
     }
 
+    @Test
+    @MediumTest
+    public void testChoreographerFrameTrait() throws Exception {
+        List<Integer> orderList = new ArrayList<>();
+        CountDownLatch latch = new CountDownLatch(3);
+        // Post a task so we can run on the UI thread.
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
+            @Override
+            public void run() {
+                PostTask.postTask(TaskTraits.CHOREOGRAPHER_FRAME, new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (orderList) {
+                            orderList.add(1);
+                            latch.countDown();
+                        }
+                    }
+                });
+
+                PostTask.postTask(TaskTraits.CHOREOGRAPHER_FRAME, new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (orderList) {
+                            orderList.add(2);
+                            latch.countDown();
+                        }
+                    }
+                });
+
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
+                    @Override
+                    public void run() {
+                        synchronized (orderList) {
+                            orderList.add(3);
+                            latch.countDown();
+                        }
+                    }
+                });
+            }
+        });
+        latch.await();
+
+        // The UiThreadTaskTraits.DEFAULT task should run before the two choreographer tasks.
+        assertThat(orderList, contains(3, 1, 2));
+    }
+
     private void startContentMainOnUiThread() {
         final Object lock = new Object();
         final AtomicBoolean uiThreadInitalized = new AtomicBoolean();