#include "chrome/renderer/actor/paint_stability_monitor.h"
#include <memory>
#include <optional>
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/common/actor.mojom.h"
#include "chrome/common/actor/action_result.h"
#include "chrome/common/actor/task_id.h"
#include "chrome/common/chrome_features.h"
#include "chrome/renderer/actor/journal.h"
#include "chrome/renderer/actor/tool_utils.h"
#include "chrome/test/base/chrome_render_view_test.h"
#include "content/public/renderer/render_frame.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/web_mouse_event.h"
#include "third_party/blink/public/common/metrics/document_update_reason.h"
#include "third_party/blink/public/web/web_frame_widget.h"
#include "third_party/blink/public/web/web_local_frame.h"
namespace actor {
class PaintStabilityMonitorTest : public ChromeRenderViewTest {
public:
PaintStabilityMonitorTest() : task_id_(100) {
feature_list_.InitAndEnableFeatureWithParameters(
::features::kGlicActor,
{{::features::kActorPaintStabilityMode.name, "enabled"},
{::features::kActorPaintStabilityIntialPaintTimeout.name, "1000ms"},
{::features::kActorPaintStabilitySubsequentPaintTimeout.name,
"500ms"}});
}
protected:
static base::TimeDelta GetInitialPaintTimeout() {
return features::kActorPaintStabilityIntialPaintTimeout.Get();
}
static base::TimeDelta GetSubsequentPaintTimeout() {
return features::kActorPaintStabilitySubsequentPaintTimeout.Get();
}
void Render() {
GetWebFrameWidget()->UpdateAllLifecyclePhases(
blink::DocumentUpdateReason::kTest);
}
Journal journal_;
TaskId task_id_;
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(PaintStabilityMonitorTest, NoContentfulPaint) {
LoadHTML(R"HTML(
<button id="target" onclick="handleClick()">Press Me</button>
<div id="content"></div>
<script>
var clickCount = 0;
function handleClick() { ++clickCount; }
</script>
)HTML");
bool did_reach_paint_stability = false;
{
std::unique_ptr<PaintStabilityMonitor> monitor =
PaintStabilityMonitor::MaybeCreate(*GetMainRenderFrame(), task_id_,
journal_);
bool result = SimulateElementClick("target");
ASSERT_TRUE(result);
int value;
EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(u"clickCount", &value));
EXPECT_EQ(value, 1);
monitor->Start();
monitor->WaitForStable(base::BindLambdaForTesting(
[&]() { did_reach_paint_stability = true; }));
EXPECT_FALSE(did_reach_paint_stability);
Render();
task_environment_.FastForwardBy(GetInitialPaintTimeout() -
base::Milliseconds(1));
EXPECT_FALSE(did_reach_paint_stability);
task_environment_.FastForwardBy(base::Milliseconds(1));
EXPECT_TRUE(did_reach_paint_stability);
}
}
TEST_F(PaintStabilityMonitorTest, SinglePaint) {
LoadHTML(R"HTML(
<button id="target" onclick="handleClick()">Press Me</button>
<div id="content"></div>
<script>
var clickCount = 0;
function handleClick() {
++clickCount;
content.innerHTML = '<div>This is some content</div>';
}
</script>
)HTML");
bool did_reach_paint_stability = false;
{
std::unique_ptr<PaintStabilityMonitor> monitor =
PaintStabilityMonitor::MaybeCreate(*GetMainRenderFrame(), task_id_,
journal_);
bool result = SimulateElementClick("target");
ASSERT_TRUE(result);
int value;
EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(u"clickCount", &value));
EXPECT_EQ(value, 1);
monitor->Start();
monitor->WaitForStable(base::BindLambdaForTesting(
[&]() { did_reach_paint_stability = true; }));
EXPECT_FALSE(did_reach_paint_stability);
Render();
task_environment_.FastForwardBy(GetSubsequentPaintTimeout() -
base::Milliseconds(1));
EXPECT_FALSE(did_reach_paint_stability);
task_environment_.FastForwardBy(base::Milliseconds(1));
EXPECT_TRUE(did_reach_paint_stability);
}
}
TEST_F(PaintStabilityMonitorTest, PaintStabilityReached_DelayedPaint) {
LoadHTML(R"HTML(
<button id="target" onclick="handleClick()">Press Me</button>
<div id="content"></div>
<script>
var clickCount = 0;
var timerCount = 0;
function handleClick() {
++clickCount;
setTimeout(() => {
++timerCount;
content.innerHTML = '<div>This is some content</div>';
}, 700);
}
</script>
)HTML");
bool did_reach_paint_stability = false;
{
std::unique_ptr<PaintStabilityMonitor> monitor =
PaintStabilityMonitor::MaybeCreate(*GetMainRenderFrame(), task_id_,
journal_);
bool result = SimulateElementClick("target");
ASSERT_TRUE(result);
int value;
EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(u"clickCount", &value));
EXPECT_EQ(value, 1);
monitor->Start();
monitor->WaitForStable(base::BindLambdaForTesting(
[&]() { did_reach_paint_stability = true; }));
EXPECT_FALSE(did_reach_paint_stability);
Render();
static constexpr base::TimeDelta kDelayedPaintTimeout =
base::Milliseconds(700);
ASSERT_LT(kDelayedPaintTimeout, GetInitialPaintTimeout());
ASSERT_GT(kDelayedPaintTimeout, GetSubsequentPaintTimeout());
task_environment_.FastForwardBy(kDelayedPaintTimeout);
EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(u"timerCount", &value));
EXPECT_EQ(value, 1);
Render();
task_environment_.FastForwardBy(GetSubsequentPaintTimeout() -
base::Milliseconds(1));
EXPECT_FALSE(did_reach_paint_stability);
task_environment_.FastForwardBy(base::Milliseconds(1));
EXPECT_TRUE(did_reach_paint_stability);
}
}
TEST_F(PaintStabilityMonitorTest, PaintStabilityReached_MultiplePaints) {
LoadHTML(R"HTML(
<button id="target" onclick="handleClick()">Press Me</button>
<div id="content"></div>
<script>
var clickCount = 0;
var timerCount = 0;
function handleClick() {
++clickCount;
const task = () => {
++timerCount;
content.innerHTML =
`<div>This is some content: ${timerCount}</div>`;
if (timerCount < 5) {
setTimeout(task, 100);
}
};
setTimeout(task, 100);
}
</script>
)HTML");
bool did_reach_paint_stability = false;
{
std::unique_ptr<PaintStabilityMonitor> monitor =
PaintStabilityMonitor::MaybeCreate(*GetMainRenderFrame(), task_id_,
journal_);
bool result = SimulateElementClick("target");
ASSERT_TRUE(result);
int value;
EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(u"clickCount", &value));
EXPECT_EQ(value, 1);
monitor->Start();
monitor->WaitForStable(base::BindLambdaForTesting(
[&]() { did_reach_paint_stability = true; }));
EXPECT_FALSE(did_reach_paint_stability);
Render();
static constexpr base::TimeDelta kDelayedPaintTimeout =
base::Milliseconds(100);
ASSERT_LT(kDelayedPaintTimeout, GetInitialPaintTimeout());
ASSERT_LT(kDelayedPaintTimeout, GetSubsequentPaintTimeout());
for (int i = 1; i <= 5; i++) {
task_environment_.FastForwardBy(kDelayedPaintTimeout);
EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(u"timerCount", &value));
EXPECT_EQ(value, i);
Render();
}
EXPECT_FALSE(did_reach_paint_stability);
task_environment_.FastForwardBy(GetSubsequentPaintTimeout() -
base::Milliseconds(1));
EXPECT_FALSE(did_reach_paint_stability);
task_environment_.FastForwardBy(base::Milliseconds(1));
EXPECT_TRUE(did_reach_paint_stability);
}
}
TEST_F(PaintStabilityMonitorTest, DelayedStabilityCallback) {
LoadHTML(R"HTML(
<button id="target" onclick="handleClick()">Press Me</button>
<div id="content"></div>
<script>
var clickCount = 0;
function handleClick() {
++clickCount;
content.innerHTML = `<div>This is some content</div>`;
}
</script>
)HTML");
bool did_reach_paint_stability = false;
{
std::unique_ptr<PaintStabilityMonitor> monitor =
PaintStabilityMonitor::MaybeCreate(*GetMainRenderFrame(), task_id_,
journal_);
bool result = SimulateElementClick("target");
ASSERT_TRUE(result);
int value;
EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(u"clickCount", &value));
EXPECT_EQ(value, 1);
monitor->Start();
Render();
task_environment_.FastForwardBy(GetSubsequentPaintTimeout());
monitor->WaitForStable(base::BindLambdaForTesting(
[&]() { did_reach_paint_stability = true; }));
task_environment_.FastForwardBy(base::TimeDelta());
EXPECT_TRUE(did_reach_paint_stability);
}
}
TEST_F(PaintStabilityMonitorTest, DelayedStabilityCallback_ResetTimer) {
LoadHTML(R"HTML(
<button id="target" onclick="handleClick()">Press Me</button>
<div id="content"></div>
<script>
var clickCount = 0;
var timerCount = 0;
function handleClick() {
++clickCount;
content.innerHTML = `<div>This is some content</div>`;
setTimeout(() => {
++timerCount;
content.innerHTML = `<div>This is different content</div>`;
}, 2000);
}
</script>
)HTML");
bool did_reach_paint_stability = false;
{
std::unique_ptr<PaintStabilityMonitor> monitor =
PaintStabilityMonitor::MaybeCreate(*GetMainRenderFrame(), task_id_,
journal_);
bool result = SimulateElementClick("target");
ASSERT_TRUE(result);
int value;
EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(u"clickCount", &value));
EXPECT_EQ(value, 1);
monitor->Start();
Render();
task_environment_.FastForwardBy(base::Seconds(2));
EXPECT_TRUE(ExecuteJavaScriptAndReturnIntValue(u"timerCount", &value));
EXPECT_EQ(value, 1);
Render();
monitor->WaitForStable(base::BindLambdaForTesting(
[&]() { did_reach_paint_stability = true; }));
EXPECT_FALSE(did_reach_paint_stability);
task_environment_.FastForwardBy(GetSubsequentPaintTimeout() -
base::Milliseconds(1));
EXPECT_FALSE(did_reach_paint_stability);
task_environment_.FastForwardBy(base::Milliseconds(1));
EXPECT_TRUE(did_reach_paint_stability);
}
}
}