blob: b2032ff4a8a3f5a3224f950230a2b42cd73a9ad2 [file] [log] [blame]
// 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/display_lock_budget.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_display_lock_callback.h"
#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/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"
namespace blink {
class DisplayLockBudgetTest : public RenderingTest {
void SetUp() override {
RenderingTest::SetUp();
features_backup_.emplace();
RuntimeEnabledFeatures::SetDisplayLockingEnabled(true);
}
void TearDown() override {
if (features_backup_) {
features_backup_->Restore();
features_backup_.reset();
}
}
private:
base::Optional<RuntimeEnabledFeatures::Backup> features_backup_;
};
class BudgetTestEmptyFunction : public ScriptFunction {
public:
static v8::Local<v8::Function> Create(ScriptState* script_state) {
auto* callback =
MakeGarbageCollected<BudgetTestEmptyFunction>(script_state);
return callback->BindToV8Function();
}
BudgetTestEmptyFunction(ScriptState* script_state)
: ScriptFunction(script_state) {}
ScriptValue Call(ScriptValue value) final { return value; }
};
TEST_F(DisplayLockBudgetTest, UnyieldingBudget) {
WeakPersistent<Element> element =
GetDocument().CreateRawElement(html_names::kDivTag);
{
auto* script_state = ToScriptStateForMainWorld(GetDocument().GetFrame());
ScriptState::Scope scope(script_state);
element->acquireDisplayLock(
script_state, V8DisplayLockCallback::Create(
BudgetTestEmptyFunction::Create(script_state)));
}
ASSERT_TRUE(element->GetDisplayLockContext());
UnyieldingDisplayLockBudget budget(element->GetDisplayLockContext());
// Check everything twice since it shouldn't matter how many times we ask the
// unyielding budget, the results should always be the same.
for (int i = 0; i < 2; ++i) {
budget.WillStartLifecycleUpdate();
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
budget.DidPerformPhase(DisplayLockBudget::Phase::kStyle);
budget.DidPerformPhase(DisplayLockBudget::Phase::kLayout);
budget.DidPerformPhase(DisplayLockBudget::Phase::kPrePaint);
budget.DidPerformPhase(DisplayLockBudget::Phase::kPaint);
EXPECT_FALSE(budget.DidFinishLifecycleUpdate());
}
}
TEST_F(DisplayLockBudgetTest, StrictYieldingBudget) {
WeakPersistent<Element> element =
GetDocument().CreateRawElement(html_names::kDivTag);
{
auto* script_state = ToScriptStateForMainWorld(GetDocument().GetFrame());
ScriptState::Scope scope(script_state);
element->acquireDisplayLock(
script_state, V8DisplayLockCallback::Create(
BudgetTestEmptyFunction::Create(script_state)));
}
ASSERT_TRUE(element->GetDisplayLockContext());
StrictYieldingDisplayLockBudget budget(element->GetDisplayLockContext());
{
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));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
// Not doing anything should ensure that we schedule another animation by
// returning true here.
EXPECT_TRUE(budget.DidFinishLifecycleUpdate());
}
{
budget.WillStartLifecycleUpdate();
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
// Once we perform a phase, its check should remain true, but the rest
// will be false for this cycle.
budget.DidPerformPhase(DisplayLockBudget::Phase::kStyle);
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_FALSE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_FALSE(
budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
EXPECT_FALSE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
// We would need at least one more run to finish everything.
EXPECT_TRUE(budget.DidFinishLifecycleUpdate());
}
{
budget.WillStartLifecycleUpdate();
// Run the previous block again, now everything will always return true
// since the phase we complete here (style) has already been completed
// before, and we are open to complete a new phase.
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
// Since we already befored style before, no new phase has been processed
// and all phases are allowed to finish.
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));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
// We would need at least one more run to finish everything.
EXPECT_TRUE(budget.DidFinishLifecycleUpdate());
}
{
budget.WillStartLifecycleUpdate();
// On the next run, the checks for phases completed before should always
// return true, and as before since we haven't completed a new phase, the
// remainder of the phases should return true for now.
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
// This check is the same as in the previous block, but is here to verify
// that going through DidFinishLifecycleUpdate() and then
// WillStartLifecycleUpdate() again doesn't change the fact that we should
// still perform all of the phases at this point.
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));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
// Let's say layout was clean and we jumped and did prepaint instead, now
// every phase before and including prepaint should be true, the rest are
// locked from completing.
budget.DidPerformPhase(DisplayLockBudget::Phase::kPrePaint);
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
EXPECT_FALSE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
// We would need at least one more run to finish everything.
EXPECT_TRUE(budget.DidFinishLifecycleUpdate());
}
{
budget.WillStartLifecycleUpdate();
// On the last run, we'll complete all phases. Since there is only one
// remaining phase we haven't done, all of the checks should always return
// true (it's either an old phase or a first uncompleted phase).
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
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));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
budget.DidPerformPhase(DisplayLockBudget::Phase::kLayout);
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
budget.DidPerformPhase(DisplayLockBudget::Phase::kPrePaint);
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
budget.DidPerformPhase(DisplayLockBudget::Phase::kPaint);
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kStyle));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kLayout));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPrePaint));
EXPECT_TRUE(budget.ShouldPerformPhase(DisplayLockBudget::Phase::kPaint));
// Since we completed everything, we should now be returning false here (no
// more updates needed).
EXPECT_FALSE(budget.DidFinishLifecycleUpdate());
}
}
} // namespace blink