Commit 98226e4a authored by Sami Kyostila's avatar Sami Kyostila Committed by Commit Bot

requestIdleCallback: Schedule idle timeout tasks asynchronously when unpausing

When the execution context is unpaused, schedule any pending idle
callback timeout tasks as asynchronous tasks instead of running them
directly. This is because it's not allowed to execute script directly
from lifecycle callbacks.

Bug: 1110503
Change-Id: I37dc6a6a41bf1f1bf7953073f7824222409cdffa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2335446Reviewed-by: default avatarRoss McIlroy <rmcilroy@chromium.org>
Reviewed-by: default avatarMason Freed <masonfreed@chromium.org>
Commit-Queue: Sami Kyöstilä <skyostil@chromium.org>
Cr-Commit-Position: refs/heads/master@{#794974}
parent 4702a75d
......@@ -241,12 +241,19 @@ void ScriptedIdleTaskController::ContextUnpaused() {
DCHECK(paused_);
paused_ = false;
// Run any pending timeouts.
Vector<CallbackId> pending_timeouts;
pending_timeouts_.swap(pending_timeouts);
for (auto& id : pending_timeouts)
RunCallback(id, base::TimeTicks::Now(),
IdleDeadline::CallbackType::kCalledByTimeout);
// Run any pending timeouts as separate tasks, since it's not allowed to
// execute script from lifecycle callbacks.
for (auto& id : pending_timeouts_) {
scoped_refptr<internal::IdleRequestCallbackWrapper> callback_wrapper =
internal::IdleRequestCallbackWrapper::Create(id, this);
GetExecutionContext()
->GetTaskRunner(TaskType::kIdleTask)
->PostTask(
FROM_HERE,
WTF::Bind(&internal::IdleRequestCallbackWrapper::TimeoutFired,
callback_wrapper));
}
pending_timeouts_.clear();
// Repost idle tasks for any remaining callbacks.
for (auto& idle_task : idle_tasks_) {
......
......@@ -6,10 +6,12 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/frame/lifecycle.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_idle_request_callback.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_idle_request_options.h"
#include "third_party/blink/renderer/core/testing/null_execution_context.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/scheduler/test/fake_task_runner.h"
#include "third_party/blink/renderer/platform/testing/scoped_scheduler_overrider.h"
namespace blink {
......@@ -38,7 +40,7 @@ class MockScriptedIdleTaskControllerScheduler final : public ThreadScheduler {
}
scoped_refptr<base::SingleThreadTaskRunner> DeprecatedDefaultTaskRunner()
override {
return nullptr;
return task_runner_;
}
void Shutdown() override {}
bool ShouldYieldForHighPriorityWork() override { return should_yield_; }
......@@ -83,9 +85,15 @@ class MockScriptedIdleTaskControllerScheduler final : public ThreadScheduler {
void RunIdleTask() { std::move(idle_task_).Run(base::TimeTicks()); }
bool HasIdleTask() const { return !!idle_task_; }
void AdvanceTimeAndRun(base::TimeDelta delta) {
task_runner_->AdvanceTimeAndRun(delta);
}
private:
bool should_yield_;
Thread::IdleTask idle_task_;
scoped_refptr<scheduler::FakeTaskRunner> task_runner_ =
base::MakeRefCounted<scheduler::FakeTaskRunner>();
DISALLOW_COPY_AND_ASSIGN(MockScriptedIdleTaskControllerScheduler);
};
......@@ -146,4 +154,36 @@ TEST_F(ScriptedIdleTaskControllerTest, DontRunCallbackWhenAskedToYield) {
EXPECT_TRUE(scheduler.HasIdleTask());
}
TEST_F(ScriptedIdleTaskControllerTest, RunCallbacksAsyncWhenUnpaused) {
MockScriptedIdleTaskControllerScheduler scheduler(ShouldYield::YIELD);
ScopedSchedulerOverrider scheduler_overrider(&scheduler);
ScriptedIdleTaskController* controller =
ScriptedIdleTaskController::Create(execution_context_);
// Register an idle task with a deadline.
Persistent<MockIdleTask> idle_task(MakeGarbageCollected<MockIdleTask>());
IdleRequestOptions* options = IdleRequestOptions::Create();
options->setTimeout(1);
int id = controller->RegisterCallback(idle_task, options);
EXPECT_NE(0, id);
// Hitting the deadline while the frame is paused shouldn't cause any tasks to
// run.
controller->ContextLifecycleStateChanged(mojom::FrameLifecycleState::kPaused);
EXPECT_CALL(*idle_task, invoke(testing::_)).Times(0);
scheduler.AdvanceTimeAndRun(base::TimeDelta::FromMilliseconds(1));
testing::Mock::VerifyAndClearExpectations(idle_task);
// Even if we unpause, no tasks should run immediately.
EXPECT_CALL(*idle_task, invoke(testing::_)).Times(0);
controller->ContextLifecycleStateChanged(
mojom::FrameLifecycleState::kRunning);
testing::Mock::VerifyAndClearExpectations(idle_task);
// Idle callback should have been scheduled as an asynchronous task.
EXPECT_CALL(*idle_task, invoke(testing::_)).Times(1);
scheduler.AdvanceTimeAndRun(base::TimeDelta::FromMilliseconds(0));
testing::Mock::VerifyAndClearExpectations(idle_task);
}
} // namespace blink
......@@ -18,6 +18,10 @@ class ThreadWithCustomScheduler : public Thread {
ThreadScheduler* Scheduler() override { return scheduler_; }
scoped_refptr<base::SingleThreadTaskRunner> GetTaskRunner() const override {
return scheduler_->DeprecatedDefaultTaskRunner();
}
private:
ThreadScheduler* scheduler_;
};
......
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