Commit 368911e4 authored by alexclarke's avatar alexclarke Committed by Commit bot

Don't throttle web views until they've been in the background for 10s

Previously background web views had timer alignment throttling applied immediately
and budget based throttling after 10s.  It looks like even the timer alignment throttling
on it's own is causing problems for new tabs where the visibility isn't initially known.
To fix this we're changing the policy so we don't do any throttling at all until the web
view has been in the background for 10s and then we turn budget based throttling &
alignment on.

Note this patch removes fast/dom/timer-throttling-hidden-page.html
because the 10s grace period means this test would be too slow.

Virtual time doesn't help because Date.now() (currently) isn't
overridden. There's no support for using a mock time source in
layout tests.  In any event there's plenty of C++ unit test coverage
for this so it's not too bad to remove the layout test.

BUG=649942

Review-Url: https://codereview.chromium.org/2620743002
Cr-Commit-Position: refs/heads/master@{#442989}
parent b61b2646
......@@ -663,7 +663,6 @@ crbug.com/521858 [ Win7 ] http/tests/security/media-element-audio-source-node-sa
crbug.com/521858 [ Win7 ] virtual/mojo-loading/http/tests/security/media-element-audio-source-node-same-origin.html [ Failure Pass ]
crbug.com/521853 [ Win ] http/tests/inspector/search/sources-search-scope.html [ Failure Pass ]
crbug.com/521853 [ Win ] virtual/mojo-loading/http/tests/inspector/search/sources-search-scope.html [ Failure Pass ]
crbug.com/520170 [ Win ] fast/dom/timer-throttling-hidden-page.html [ Failure Pass ]
crbug.com/520174 fast/events/message-port-start-and-close-different-microtask.html [ Failure Pass ]
crbug.com/652536 fast/events/mouse-cursor-change-after-image-load.html [ Failure Pass ]
crbug.com/520188 [ Win ] http/tests/local/fileapi/file-last-modified-after-delete.html [ Failure Pass ]
......
Bug 98474: Throttle DOM timers on hidden pages and bug 400343 prerender pages.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS timerIntervalWhilePageVisible is within 20 of 100
PASS firstTimerIntervalWhilePageNotVisible is >= 80
PASS firstTimerIntervalWhilePageNotVisible <= 1120 is true
PASS timerIntervalWhilePageNotVisible is within 20 of 1000
PASS timerIntervalWhilePageVisible is within 20 of 100
PASS timerIntervalWhilePageVisible is within 20 of 100
PASS firstTimerIntervalWhilePageNotVisible is >= 80
PASS firstTimerIntervalWhilePageNotVisible <= 1120 is true
PASS timerIntervalWhilePageNotVisible is within 20 of 1000
PASS timerIntervalWhilePageVisible is within 20 of 100
PASS successfullyParsed is true
TEST COMPLETE
This test measures the time taken to fire a 100ms DOM Timer when the page visibility is set to "visible", "hidden", "visible", "prerender" and then back to "visible". Due to page timer throttling, the timer should fire close to 1s when page is hidden or prerender. And it should fire close to 100ms, when the page is visible.
<html>
<head>
<script src="../../resources/js-test.js"></script>
<script>
description('<a href="https://bugs.webkit.org/show_bug.cgi?id=98474">Bug 98474</a>: Throttle DOM timers on hidden pages and <a href="http://crbug.com/400343">bug 400343</a> prerender pages.');
var jsTestIsAsync = true;
var previousTime = 0;
var timerCount = 0;
var firstTimerWhileNotVisible = true;
var isPageVisible = true;
var timeoutInterval = 100;
var tolerance = 20;
var timerAlignmentInterval = 1000;
function testTimer()
{
var time = Date.now();
if (!isPageVisible) {
if (firstTimerWhileNotVisible) {
firstTimerIntervalWhilePageNotVisible = time - previousTime;
var minValue = timeoutInterval - tolerance;
shouldBeGreaterThanOrEqual("firstTimerIntervalWhilePageNotVisible", minValue.toString());
var maxValue = timeoutInterval + timerAlignmentInterval + tolerance;
shouldBeTrue("firstTimerIntervalWhilePageNotVisible <= " + maxValue);
firstTimerWhileNotVisible = false;
} else {
timerIntervalWhilePageNotVisible = time - previousTime;
shouldBeCloseTo("timerIntervalWhilePageNotVisible", timerAlignmentInterval, tolerance);
}
} else {
timerIntervalWhilePageVisible = time - previousTime;
shouldBeCloseTo("timerIntervalWhilePageVisible", timeoutInterval, tolerance);
}
timerCount++;
previousTime = time;
if (timerCount == 1) {
testRunner.setPageVisibility("hidden");
isPageVisible = false;
} else if (timerCount == 3) {
testRunner.setPageVisibility("visible");
isPageVisible = true;
} else if (timerCount == 5) {
testRunner.setPageVisibility("prerender");
isPageVisible = false;
firstTimerWhileNotVisible = true;
} else if (timerCount == 7) {
testRunner.setPageVisibility("visible");
isPageVisible = true;
} else if (timerCount >= 8){
finishJSTest();
return;
}
previousTime = Date.now();
setTimeout(testTimer, timeoutInterval);
}
function runTest()
{
if (!window.testRunner) {
debug('This test requires testRunner');
return;
}
var timeoutIntervalSpans = document.getElementsByClassName('timeoutInterval');
for (var i = 0; i < timeoutIntervalSpans.length; i++)
timeoutIntervalSpans[i].innerText = timeoutInterval;
document.getElementById('alignmentInterval').innerText = timerAlignmentInterval / 1000;
testRunner.dumpAsText();
previousTime = Date.now();
setTimeout(testTimer, timeoutInterval);
}
</script>
</head>
<body onload="runTest()">
<p>
This test measures the time taken to fire a <span class="timeoutInterval"></span>ms DOM Timer when the page visibility is set to "visible", "hidden", "visible", "prerender" and then back to "visible". Due to page timer throttling, the timer should fire close to <span id="alignmentInterval"></span>s when page is hidden or prerender. And it should fire close to <span class="timeoutInterval"></span>ms, when the page is visible.
</p>
</body>
</html>
......@@ -19,10 +19,9 @@ class WebFrameScheduler {
// The scheduler may throttle tasks associated with offscreen frames.
virtual void setFrameVisible(bool) {}
// Tells the scheduler that the page this frame belongs to is not visible.
// The scheduler may throttle tasks associated with pages that are not
// visible.
virtual void setPageVisible(bool) {}
// Tells the scheduler that the page this frame belongs to supposed to be
// throttled (because it's not been visible for a few seconds).
virtual void setPageThrottled(bool) {}
// Set whether this frame is suspended. Only unthrottledTaskRunner tasks are
// allowed to run on a suspended frame.
......
......@@ -36,7 +36,7 @@ WebFrameSchedulerImpl::WebFrameSchedulerImpl(
parent_web_view_scheduler_(parent_web_view_scheduler),
blame_context_(blame_context),
frame_visible_(true),
page_visible_(true),
page_throttled_(true),
frame_suspended_(false),
cross_origin_(false) {}
......@@ -178,7 +178,7 @@ void WebFrameSchedulerImpl::setDocumentParsingInBackground(
void WebFrameSchedulerImpl::AsValueInto(
base::trace_event::TracedValue* state) const {
state->SetBoolean("frame_visible", frame_visible_);
state->SetBoolean("page_visible", page_visible_);
state->SetBoolean("page_throttled", page_throttled_);
state->SetBoolean("cross_origin", cross_origin_);
if (loading_task_queue_) {
state->SetString("loading_task_queue",
......@@ -199,12 +199,12 @@ void WebFrameSchedulerImpl::AsValueInto(
}
}
void WebFrameSchedulerImpl::setPageVisible(bool page_visible) {
void WebFrameSchedulerImpl::setPageThrottled(bool page_throttled) {
DCHECK(parent_web_view_scheduler_);
if (page_visible_ == page_visible)
if (page_throttled_ == page_throttled)
return;
bool was_throttled = ShouldThrottleTimers();
page_visible_ = page_visible;
page_throttled_ = page_throttled;
UpdateTimerThrottling(was_throttled);
}
......@@ -225,7 +225,7 @@ void WebFrameSchedulerImpl::onFirstMeaningfulPaint() {
}
bool WebFrameSchedulerImpl::ShouldThrottleTimers() const {
if (!page_visible_)
if (page_throttled_)
return true;
return RuntimeEnabledFeatures::timerThrottlingForHiddenFramesEnabled() &&
!frame_visible_ && cross_origin_;
......
......@@ -39,7 +39,7 @@ class WebFrameSchedulerImpl : public WebFrameScheduler {
// WebFrameScheduler implementation:
void setFrameVisible(bool frame_visible) override;
void setPageVisible(bool page_visible) override;
void setPageThrottled(bool page_throttled) override;
void setSuspended(bool frame_suspended) override;
void setCrossOrigin(bool cross_origin) override;
RefPtr<WebTaskRunner> loadingTaskRunner() override;
......@@ -74,7 +74,7 @@ class WebFrameSchedulerImpl : public WebFrameScheduler {
WebViewSchedulerImpl* parent_web_view_scheduler_; // NOT OWNED
base::trace_event::BlameContext* blame_context_; // NOT OWNED
bool frame_visible_;
bool page_visible_;
bool page_throttled_;
bool frame_suspended_;
bool cross_origin_;
......
......@@ -105,6 +105,14 @@ TEST_F(WebFrameSchedulerImplTest, RepeatingTimer_PageInBackground) {
makeRepeatingTask(web_frame_scheduler_->timerTaskRunner(), &run_count),
1.0);
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1000, run_count);
// The task queue isn't throttled at all until it's been in the background for
// a 10 second grace period.
clock_->Advance(base::TimeDelta::FromSeconds(10));
run_count = 0;
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1, run_count);
}
......@@ -163,11 +171,20 @@ TEST_F(WebFrameSchedulerImplTest, PageInBackground_ThrottlingDisabled) {
makeRepeatingTask(web_frame_scheduler_->timerTaskRunner(), &run_count),
1.0);
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1000, run_count);
// The task queue isn't throttled at all until it's been in the background for
// a 10 second grace period.
clock_->Advance(base::TimeDelta::FromSeconds(10));
run_count = 0;
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1, run_count);
}
TEST_F(WebFrameSchedulerImplTest, RepeatingTimer_FrameHidden_CrossOrigin_ThrottlingDisabled) {
TEST_F(WebFrameSchedulerImplTest,
RepeatingTimer_FrameHidden_CrossOrigin_ThrottlingDisabled) {
RuntimeEnabledFeatures::setTimerThrottlingForHiddenFramesEnabled(false);
web_frame_scheduler_->setFrameVisible(false);
web_frame_scheduler_->setCrossOrigin(true);
......
......@@ -30,7 +30,7 @@ constexpr base::TimeDelta kDefaultMaxBackgroundThrottlingDelay =
base::TimeDelta::FromMinutes(1);
constexpr base::TimeDelta kDefaultInitialBackgroundBudget =
base::TimeDelta::FromSeconds(1);
constexpr base::TimeDelta kBackgroundBudgetThrottlingGracePeriod =
constexpr base::TimeDelta kBackgroundThrottlingGracePeriod =
base::TimeDelta::FromSeconds(10);
// Values coming from WebViewSchedulerSettings are interpreted as follows:
......@@ -101,6 +101,7 @@ WebViewSchedulerImpl::WebViewSchedulerImpl(
virtual_time_policy_(VirtualTimePolicy::ADVANCE),
background_parser_count_(0),
page_visible_(true),
should_throttle_frames_(false),
disable_background_timer_throttling_(disable_background_timer_throttling),
allow_virtual_time_to_advance_(true),
have_seen_loading_task_(false),
......@@ -111,8 +112,8 @@ WebViewSchedulerImpl::WebViewSchedulerImpl(
settings_(settings) {
renderer_scheduler->AddWebViewScheduler(this);
delayed_background_budget_throttling_enabler_.Reset(
base::Bind(&WebViewSchedulerImpl::EnableBackgroundBudgetThrottling,
delayed_background_throttling_enabler_.Reset(
base::Bind(&WebViewSchedulerImpl::EnableBackgroundThrottling,
base::Unretained(this)));
}
......@@ -134,11 +135,7 @@ void WebViewSchedulerImpl::setPageVisible(bool page_visible) {
page_visible_ = page_visible;
for (WebFrameSchedulerImpl* frame_scheduler : frame_schedulers_) {
frame_scheduler->setPageVisible(page_visible_);
}
UpdateBackgroundBudgetThrottlingState();
UpdateBackgroundThrottlingState();
}
std::unique_ptr<WebFrameSchedulerImpl>
......@@ -147,7 +144,7 @@ WebViewSchedulerImpl::createWebFrameSchedulerImpl(
MaybeInitializeBackgroundTimeBudgetPool();
std::unique_ptr<WebFrameSchedulerImpl> frame_scheduler(
new WebFrameSchedulerImpl(renderer_scheduler_, this, blame_context));
frame_scheduler->setPageVisible(page_visible_);
frame_scheduler->setPageThrottled(should_throttle_frames_);
frame_schedulers_.insert(frame_scheduler.get());
return frame_scheduler;
}
......@@ -302,7 +299,7 @@ void WebViewSchedulerImpl::MaybeInitializeBackgroundTimeBudgetPool() {
"background", GetMaxBudgetLevel(settings_),
GetMaxThrottlingDelay(settings_));
UpdateBackgroundBudgetThrottlingState();
UpdateBackgroundThrottlingState();
LazyNow lazy_now(renderer_scheduler_->tick_clock());
......@@ -333,30 +330,34 @@ void WebViewSchedulerImpl::OnThrottlingReported(
intervention_reporter_->ReportIntervention(WebString::fromUTF8(message));
}
void WebViewSchedulerImpl::EnableBackgroundBudgetThrottling() {
if (!background_time_budget_pool_)
return;
void WebViewSchedulerImpl::EnableBackgroundThrottling() {
should_throttle_frames_ = true;
for (WebFrameSchedulerImpl* frame_scheduler : frame_schedulers_) {
frame_scheduler->setPageThrottled(true);
}
if (background_time_budget_pool_) {
LazyNow lazy_now(renderer_scheduler_->tick_clock());
background_time_budget_pool_->EnableThrottling(&lazy_now);
}
}
void WebViewSchedulerImpl::UpdateBackgroundBudgetThrottlingState() {
if (!background_time_budget_pool_)
return;
delayed_background_budget_throttling_enabler_.Cancel();
LazyNow lazy_now(renderer_scheduler_->tick_clock());
void WebViewSchedulerImpl::UpdateBackgroundThrottlingState() {
delayed_background_throttling_enabler_.Cancel();
if (page_visible_) {
should_throttle_frames_ = false;
for (WebFrameSchedulerImpl* frame_scheduler : frame_schedulers_) {
frame_scheduler->setPageThrottled(false);
}
if (background_time_budget_pool_) {
LazyNow lazy_now(renderer_scheduler_->tick_clock());
background_time_budget_pool_->DisableThrottling(&lazy_now);
}
} else {
// TODO(altimin): Consider moving this logic into PumpThrottledTasks.
renderer_scheduler_->ControlTaskRunner()->PostDelayedTask(
FROM_HERE, delayed_background_budget_throttling_enabler_.callback(),
kBackgroundBudgetThrottlingGracePeriod);
FROM_HERE, delayed_background_throttling_enabler_.callback(),
kBackgroundThrottlingGracePeriod);
}
}
......
......@@ -79,11 +79,11 @@ class BLINK_PLATFORM_EXPORT WebViewSchedulerImpl : public WebViewScheduler {
static const char* VirtualTimePolicyToString(
VirtualTimePolicy virtual_time_policy);
// Depending on page visibility, either turns budget throttling off, or
// schedules a call to enable it after a grace period.
void UpdateBackgroundBudgetThrottlingState();
// Depending on page visibility, either turns throttling off, or schedules a
// call to enable it after a grace period.
void UpdateBackgroundThrottlingState();
void EnableBackgroundBudgetThrottling();
void EnableBackgroundThrottling();
std::set<WebFrameSchedulerImpl*> frame_schedulers_;
std::set<unsigned long> pending_loads_;
......@@ -92,6 +92,7 @@ class BLINK_PLATFORM_EXPORT WebViewSchedulerImpl : public WebViewScheduler {
VirtualTimePolicy virtual_time_policy_;
int background_parser_count_;
bool page_visible_;
bool should_throttle_frames_;
bool disable_background_timer_throttling_;
bool allow_virtual_time_to_advance_;
bool have_seen_loading_task_;
......@@ -100,7 +101,7 @@ class BLINK_PLATFORM_EXPORT WebViewSchedulerImpl : public WebViewScheduler {
bool reported_background_throttling_since_navigation_;
TaskQueueThrottler::TimeBudgetPool*
background_time_budget_pool_; // Not owned.
CancelableClosureHolder delayed_background_budget_throttling_enabler_;
CancelableClosureHolder delayed_background_throttling_enabler_;
WebViewScheduler::WebViewSchedulerSettings* settings_; // Not owned.
DISALLOW_COPY_AND_ASSIGN(WebViewSchedulerImpl);
......
......@@ -113,7 +113,8 @@ TEST_F(WebViewSchedulerImplTest, RepeatingTimer_PageInForeground) {
EXPECT_EQ(1000, run_count);
}
TEST_F(WebViewSchedulerImplTest, RepeatingTimer_PageInBackground) {
TEST_F(WebViewSchedulerImplTest,
RepeatingTimer_PageInBackgroundThenForeground) {
web_view_scheduler_->setPageVisible(false);
int run_count = 0;
......@@ -122,8 +123,62 @@ TEST_F(WebViewSchedulerImplTest, RepeatingTimer_PageInBackground) {
makeRepeatingTask(web_frame_scheduler_->timerTaskRunner(), &run_count),
1.0);
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1000, run_count);
// The task queue isn't throttled at all until it's been in the background for
// a 10 second grace period.
clock_->Advance(base::TimeDelta::FromSeconds(10));
run_count = 0;
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1, run_count);
// Make sure there's no delay in throttling being removed for pages that have
// become visible.
web_view_scheduler_->setPageVisible(true);
run_count = 0;
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1001, run_count); // Note we end up running 1001 here because the
// task was posted while throttled with a delay of 1ms so the first task was
// due to run before the 1s period started.
}
TEST_F(WebViewSchedulerImplTest, GracePeriodAppliesToNewBackgroundFrames) {
web_view_scheduler_->setPageVisible(false);
std::unique_ptr<WebFrameSchedulerImpl> web_frame_scheduler =
web_view_scheduler_->createWebFrameSchedulerImpl(nullptr);
blink::WebTaskRunner* timer_task_runner =
web_frame_scheduler->timerTaskRunner().get();
int run_count = 0;
timer_task_runner->postDelayedTask(
BLINK_FROM_HERE,
makeRepeatingTask(web_frame_scheduler->timerTaskRunner(), &run_count),
1.0);
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1000, run_count);
// The task queue isn't throttled at all until it's been in the background for
// a 10 second grace period.
clock_->Advance(base::TimeDelta::FromSeconds(10));
run_count = 0;
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1, run_count);
// Make sure there's no delay in throttling being removed for pages that have
// become visible.
web_view_scheduler_->setPageVisible(true);
run_count = 0;
mock_task_runner_->RunForPeriod(base::TimeDelta::FromSeconds(1));
EXPECT_EQ(1001, run_count); // Note we end up running 1001 here because the
// task was posted while throttled with a delay of 1ms so the first task was
// due to run before the 1s period started.
}
TEST_F(WebViewSchedulerImplTest, RepeatingLoadingTask_PageInBackground) {
......@@ -148,6 +203,9 @@ TEST_F(WebViewSchedulerImplTest, RepeatingTimers_OneBackgroundOneForeground) {
web_view_scheduler_->setPageVisible(true);
web_view_scheduler2->setPageVisible(false);
// Advance past the no-throttling grace period.
clock_->Advance(base::TimeDelta::FromSeconds(10));
int run_count1 = 0;
int run_count2 = 0;
web_frame_scheduler_->timerTaskRunner()->postDelayedTask(
......@@ -438,6 +496,10 @@ TEST_F(WebViewSchedulerImplTest, DeleteWebViewScheduler_InTask) {
TEST_F(WebViewSchedulerImplTest, DeleteThrottledQueue_InTask) {
web_view_scheduler_->setPageVisible(false);
// The task queue isn't throttled at all until it's been in the background for
// a 10 second grace period.
clock_->Advance(base::TimeDelta::FromSeconds(10));
WebFrameSchedulerImpl* web_frame_scheduler =
web_view_scheduler_->createWebFrameSchedulerImpl(nullptr).release();
RefPtr<blink::WebTaskRunner> timer_task_runner =
......@@ -588,20 +650,19 @@ TEST_F(WebViewSchedulerImplTest, BackgroundThrottlingGracePeriod) {
web_frame_scheduler_->timerTaskRunner()->postDelayedTask(
BLINK_FROM_HERE, base::Bind(&ExpensiveTestTask, clock_.get(), &run_times),
0.1);
1);
web_frame_scheduler_->timerTaskRunner()->postDelayedTask(
BLINK_FROM_HERE, base::Bind(&ExpensiveTestTask, clock_.get(), &run_times),
0.1);
1);
mock_task_runner_->RunUntilTime(base::TimeTicks() +
base::TimeDelta::FromMilliseconds(3500));
// Check that these tasks are aligned, but are not subject to
// budget-based throttling.
// Check that these tasks are initially unthrottled.
EXPECT_THAT(
run_times,
ElementsAre(base::TimeTicks() + base::TimeDelta::FromMilliseconds(3000),
base::TimeTicks() + base::TimeDelta::FromMilliseconds(3250)));
ElementsAre(base::TimeTicks() + base::TimeDelta::FromMilliseconds(2501),
base::TimeTicks() + base::TimeDelta::FromMilliseconds(2751)));
run_times.clear();
mock_task_runner_->RunUntilTime(base::TimeTicks() +
......@@ -609,18 +670,19 @@ TEST_F(WebViewSchedulerImplTest, BackgroundThrottlingGracePeriod) {
web_frame_scheduler_->timerTaskRunner()->postDelayedTask(
BLINK_FROM_HERE, base::Bind(&ExpensiveTestTask, clock_.get(), &run_times),
0.1);
1);
web_frame_scheduler_->timerTaskRunner()->postDelayedTask(
BLINK_FROM_HERE, base::Bind(&ExpensiveTestTask, clock_.get(), &run_times),
0.1);
1);
mock_task_runner_->RunUntilIdle();
// Check that tasks are aligned and throttled.
// After the grace period has passed, tasks should be aligned and have budget
// based throttling.
EXPECT_THAT(
run_times,
ElementsAre(base::TimeTicks() + base::TimeDelta::FromSeconds(12),
base::TimeTicks() + base::TimeDelta::FromSeconds(29)));
base::TimeTicks() + base::TimeDelta::FromSeconds(26)));
}
} // namespace 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