Commit 1266d1fb authored by Vladimir Levin's avatar Vladimir Levin Committed by Commit Bot

[DL]: Add a budget that yields between lifecycles if the deadline is passed.

This patch adds a yielding budget which tries to do as much work as
possible within the budget. If the budget expires, it yields between
lifecycles.

This patch also makes this the default budget, since it's the best
behavior currently implemented.

See YieldingDisplayLockBudget::GetCurrentBudgetMs() for the time budgets.
These were picked arbitrarily, but I think they seem reasonable.

R=chrishtr@chromium.org

Bug: 907619, 882663
Change-Id: If8b3680df6b52690c238f23a319591dd8e181add
Reviewed-on: https://chromium-review.googlesource.com/c/1388159Reviewed-by: default avatarChris Harrelson <chrishtr@chromium.org>
Commit-Queue: vmpstr <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#619453}
parent 4fdfc4c1
......@@ -14,6 +14,8 @@ blink_core_sources("display_lock") {
"strict_yielding_display_lock_budget.h",
"unyielding_display_lock_budget.cc",
"unyielding_display_lock_budget.h",
"yielding_display_lock_budget.cc",
"yielding_display_lock_budget.h",
]
public_deps = [
......
......@@ -8,13 +8,16 @@
#include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
#include "third_party/blink/renderer/core/display_lock/strict_yielding_display_lock_budget.h"
#include "third_party/blink/renderer/core/display_lock/unyielding_display_lock_budget.h"
#include "third_party/blink/renderer/core/display_lock/yielding_display_lock_budget.h"
#include "third_party/blink/renderer/core/html_names.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/testing/wtf/scoped_mock_clock.h"
namespace blink {
class DisplayLockBudgetTest : public RenderingTest {
public:
void SetUp() override {
RenderingTest::SetUp();
features_backup_.emplace();
......@@ -28,6 +31,10 @@ class DisplayLockBudgetTest : public RenderingTest {
}
}
double GetBudgetMs(const YieldingDisplayLockBudget& budget) const {
return budget.GetCurrentBudgetMs();
}
private:
base::Optional<RuntimeEnabledFeatures::Backup> features_backup_;
};
......@@ -257,4 +264,101 @@ TEST_F(DisplayLockBudgetTest,
// completing whenever necessary.
EXPECT_FALSE(budget.NeedsLifecycleUpdates());
}
TEST_F(DisplayLockBudgetTest, YieldingBudget) {
// Note that we're not testing the display lock here, just the budget so we
// can do minimal work to ensure we have a context, ignoring containment and
// other requirements.
SetBodyInnerHTML(R"HTML(
<div id="container"></div>
)HTML");
auto* element = GetDocument().getElementById("container");
{
auto* script_state = ToScriptStateForMainWorld(GetDocument().GetFrame());
ScriptState::Scope scope(script_state);
element->getDisplayLockForBindings()->acquire(script_state, nullptr);
}
ASSERT_TRUE(element->GetDisplayLockContext());
YieldingDisplayLockBudget budget(element->GetDisplayLockContext());
WTF::ScopedMockClock clock;
// Since the lifecycle is clean, we don't actually need any updates.
EXPECT_FALSE(budget.NeedsLifecycleUpdates());
// Dirtying the element will cause us to do updates.
element->GetLayoutObject()->SetNeedsLayout("");
EXPECT_TRUE(budget.NeedsLifecycleUpdates());
budget.WillStartLifecycleUpdate();
// Initially all of the phase checks should return true, since we don't know
// which phase the system wants to process next.
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
// Not doing anything should ensure that we schedule another animation by
// returning true here.
EXPECT_TRUE(budget.NeedsLifecycleUpdates());
// Advancing the clock a bit will make us still want to the phases.
clock.Advance(TimeDelta::FromMillisecondsD(GetBudgetMs(budget) / 2));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
// However, once we're out of budget, we will not do anything.
clock.Advance(TimeDelta::FromMillisecondsD(GetBudgetMs(budget)));
EXPECT_FALSE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_FALSE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_FALSE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
// Starting a new lifecycle will reset the budget.
budget.WillStartLifecycleUpdate();
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
// Performing a phase still keeps the rest of the phases available for work
// since we haven't advanced the clock.
budget.DidPerformPhase(DisplayLockBudget::Phase::kStyle);
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
// Now that we're out of budget, phases performed previously should remain
// true.
clock.Advance(TimeDelta::FromMillisecondsD(GetBudgetMs(budget) * 2));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_FALSE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_FALSE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
// Sanity check here: the element still needs layout.
EXPECT_TRUE(budget.NeedsLifecycleUpdates());
// Resetting the budget, and advnacing again should yield the same results as
// before.
budget.WillStartLifecycleUpdate();
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
clock.Advance(TimeDelta::FromMillisecondsD(GetBudgetMs(budget) * 2));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_FALSE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_FALSE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
// Eventually the budget becomes essentially infinite.
for (int i = 0; i < 60; ++i)
budget.WillStartLifecycleUpdate();
EXPECT_GT(GetBudgetMs(budget), 1e6);
for (int i = 0; i < 60; ++i) {
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
clock.Advance(TimeDelta::FromMillisecondsD(10000));
}
}
} // namespace blink
......@@ -8,6 +8,7 @@
#include "third_party/blink/renderer/core/display_lock/display_lock_options.h"
#include "third_party/blink/renderer/core/display_lock/strict_yielding_display_lock_budget.h"
#include "third_party/blink/renderer/core/display_lock/unyielding_display_lock_budget.h"
#include "third_party/blink/renderer/core/display_lock/yielding_display_lock_budget.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
......@@ -407,8 +408,7 @@ std::unique_ptr<DisplayLockBudget> DisplayLockContext::CreateNewBudget() {
case BudgetType::kStrictYieldBetweenLifecyclePhases:
return base::WrapUnique(new StrictYieldingDisplayLockBudget(this));
case BudgetType::kYieldBetweenLifecyclePhases:
NOTIMPLEMENTED();
return nullptr;
return base::WrapUnique(new YieldingDisplayLockBudget(this));
}
NOTREACHED();
return nullptr;
......
......@@ -46,7 +46,7 @@ class CORE_EXPORT DisplayLockContext final
kDoNotYield,
kStrictYieldBetweenLifecyclePhases,
kYieldBetweenLifecyclePhases,
kDefault = kDoNotYield
kDefault = kYieldBetweenLifecyclePhases
};
// See GetScopedPendingFrameRect() for description.
......
// 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.
#include "third_party/blink/renderer/core/display_lock/yielding_display_lock_budget.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
#include <algorithm>
namespace blink {
YieldingDisplayLockBudget::YieldingDisplayLockBudget(
DisplayLockContext* context)
: DisplayLockBudget(context) {}
bool YieldingDisplayLockBudget::ShouldPerformPhase(Phase phase) const {
// We should perform any phase earlier than the one we already completed.
if (last_completed_phase_ && phase <= *last_completed_phase_)
return true;
// Otherwise, we can still do work while we're not past the deadline.
return CurrentTimeTicks() < deadline_;
}
void YieldingDisplayLockBudget::DidPerformPhase(Phase phase) {
if (!last_completed_phase_ || phase > *last_completed_phase_)
last_completed_phase_ = phase;
}
void YieldingDisplayLockBudget::WillStartLifecycleUpdate() {
++lifecycle_count_;
deadline_ =
CurrentTimeTicks() + TimeDelta::FromMillisecondsD(GetCurrentBudgetMs());
// Figure out the next phase we would run. If we had completed a phase before,
// then we should try to complete the next one, otherwise we'll start with the
// first phase.
Phase next_phase =
last_completed_phase_
? static_cast<Phase>(
std::min(static_cast<unsigned>(*last_completed_phase_) + 1,
static_cast<unsigned>(Phase::kLast)))
: Phase::kFirst;
// Mark the next phase we're scheduled to run.
for (auto phase = static_cast<unsigned>(next_phase);
phase <= static_cast<unsigned>(Phase::kLast); ++phase) {
if (MarkAncestorsDirtyForPhaseIfNeeded(static_cast<Phase>(phase)))
break;
}
}
bool YieldingDisplayLockBudget::NeedsLifecycleUpdates() const {
if (last_completed_phase_ && *last_completed_phase_ == Phase::kLast)
return false;
auto next_phase = last_completed_phase_
? static_cast<Phase>(
static_cast<unsigned>(*last_completed_phase_) + 1)
: Phase::kFirst;
// Check if any future phase needs updates.
for (auto phase = static_cast<unsigned>(next_phase);
phase <= static_cast<unsigned>(Phase::kLast); ++phase) {
if (IsElementDirtyForPhase(static_cast<Phase>(phase)))
return true;
}
return false;
}
double YieldingDisplayLockBudget::GetCurrentBudgetMs() const {
if (TimeTicks::IsHighResolution()) {
if (lifecycle_count_ < 3)
return 4.;
if (lifecycle_count_ < 10)
return 8.;
if (lifecycle_count_ < 60)
return 16.;
} else {
// Without a high resolution clock, the resolution can be as bad as 15ms, so
// increase the budgets accordingly to ensure we don't abort before doing
// any phases.
if (lifecycle_count_ < 60)
return 16.;
}
return 1e9;
}
} // namespace blink
// 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_YIELDING_DISPLAY_LOCK_BUDGET_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_YIELDING_DISPLAY_LOCK_BUDGET_H_
#include "base/optional.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/core/display_lock/display_lock_budget.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
namespace blink {
// This budget yields between lifecycle phases even if that phase is quick. In
// other words, it will only do one new lifecycle phase at a time, and block the
// future ones. Any lifecycle phases that have already been allowed by this
// budget in the past are not blocked.
class CORE_EXPORT YieldingDisplayLockBudget final : public DisplayLockBudget {
public:
YieldingDisplayLockBudget(DisplayLockContext*);
~YieldingDisplayLockBudget() override = default;
bool ShouldPerformPhase(Phase) const override;
void DidPerformPhase(Phase) override;
void WillStartLifecycleUpdate() override;
// Returns true if any of the lifecycles that have been previously blocked by
// this budget need updates. Note that this does not check lifecycle phases
// that have already completed by this budget even if they are now dirty
// again. This is done to prevent starvation (ie, it is possible for the
// budget to always schedule more work if something in rAF keeps dirtying
// layout, for example).
bool NeedsLifecycleUpdates() const override;
protected:
friend class DisplayLockBudgetTest;
double GetCurrentBudgetMs() const;
int lifecycle_count_ = 0;
TimeTicks deadline_;
base::Optional<Phase> last_completed_phase_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_DISPLAY_LOCK_YIELDING_DISPLAY_LOCK_BUDGET_H_
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