Commit f4ba6c5b authored by Alex Clarke's avatar Alex Clarke Committed by Commit Bot

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: default avatarYaron Friedman <yfriedman@chromium.org>
Reviewed-by: default avatarSami Kyöstilä <skyostil@chromium.org>
Reviewed-by: default avatarKarolina Soltys <ksolt@chromium.org>
Cr-Commit-Position: refs/heads/master@{#652215}
parent 952b66d4
......@@ -3153,6 +3153,7 @@ if (is_android) {
"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",
......
// 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
}
}
......@@ -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 @@ import java.util.Map;
* 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 @@ class DefaultTaskExecutor implements TaskExecutor {
*/
@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 @@ class DefaultTaskExecutor implements TaskExecutor {
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;
}
}
......@@ -54,6 +54,11 @@ public class TaskTraits {
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 @@ public class TaskTraits {
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 @@ public class TaskTraits {
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 @@ public class TaskTraits {
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;
}
}
......@@ -18,6 +18,7 @@ import org.chromium.base.test.task.SchedulerTestHelpers;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
/**
......@@ -95,4 +96,34 @@ public class PostTaskTest {
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));
}
}
......@@ -32,6 +32,7 @@ import org.chromium.content_public.browser.test.util.UiThreadSchedulerTestUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
/**
......@@ -226,6 +227,52 @@ public class UiThreadSchedulerTest {
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();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment