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();