#include "chrome/browser/actor/tools/observation_delay_controller.h"
#include <string>
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/test/with_feature_override.h"
#include "base/time/time.h"
#include "chrome/browser/actor/actor_features.h"
#include "chrome/browser/actor/tools/observation_delay_test_util.h"
#include "chrome/common/actor/task_id.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/page_load_metrics/browser/page_load_metrics_test_waiter.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/strings/str_format.h"
#include "url/gurl.h"
namespace actor {
namespace {
using ::base::test::ScopedFeatureList;
using ::base::test::TestFuture;
using ::content::BeginNavigateToURLFromRenderer;
using ::content::RenderFrameHost;
using ::content::TestNavigationManager;
using ::content::WebContents;
using ::tabs::TabInterface;
using State = ::actor::ObservationDelayController::State;
class ObservationDelayControllerTest : public ObservationDelayTest {
public:
ObservationDelayControllerTest() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kGlicActor,
{
{features::kGlicActorPageStabilityTimeout.name, "30000ms"},
{features::kActorObservationDelayLcp.name, "100ms"}});
}
~ObservationDelayControllerTest() override = default;
private:
ScopedFeatureList scoped_feature_list_;
};
class ObservationDelayControllerNavigateTest
: public ObservationDelayControllerTest,
public base::test::WithFeatureOverride {
public:
ObservationDelayControllerNavigateTest()
: base::test::WithFeatureOverride(
kActorRestartObservationDelayControllerOnNavigate) {}
};
IN_PROC_BROWSER_TEST_P(ObservationDelayControllerNavigateTest,
NavigateDuringPageStabilization) {
ASSERT_TRUE(
content::NavigateToURL(web_contents(), GetPageStabilityTestURL()));
TestObservationDelayController controller(*main_frame(), actor::TaskId(),
journal(), PageStabilityConfig());
ASSERT_TRUE(InitiateFetchRequest());
TestFuture<ObservationDelayController::Result> result;
controller.Wait(*active_tab(), result.GetCallback());
ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
const GURL url = embedded_test_server()->GetURL("/actor/blank.html");
TestNavigationManager manager(web_contents(), url);
ASSERT_TRUE(BeginNavigateToURLFromRenderer(web_contents(), url));
if (IsParamFeatureEnabled()) {
ASSERT_TRUE(controller.WaitForState(State::kDone));
ASSERT_EQ(result.Get(), ObservationDelayController::Result::kPageNavigated);
} else {
ASSERT_TRUE(manager.WaitForResponse());
ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
ASSERT_TRUE(manager.WaitForNavigationFinished());
ASSERT_TRUE(controller.WaitForState(State::kWaitForLoadCompletion));
ASSERT_TRUE(controller.WaitForState(State::kWaitForVisualStateUpdate));
ASSERT_TRUE(controller.WaitForState(State::kMaybeDelayForLcp));
ASSERT_TRUE(controller.WaitForState(State::kDone));
ASSERT_EQ(result.Get(), ObservationDelayController::Result::kOk);
}
}
IN_PROC_BROWSER_TEST_P(ObservationDelayControllerNavigateTest,
NavigateWithTooManyRestarts) {
ASSERT_TRUE(
content::NavigateToURL(web_contents(), GetPageStabilityTestURL()));
TestObservationDelayController controller(*main_frame(), actor::TaskId(),
journal(), PageStabilityConfig());
controller.SetNavigationCount(1000);
ASSERT_TRUE(InitiateFetchRequest());
TestFuture<ObservationDelayController::Result> result;
controller.Wait(*active_tab(), result.GetCallback());
ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
const GURL url = embedded_test_server()->GetURL("/actor/blank.html");
TestNavigationManager manager(web_contents(), url);
ASSERT_TRUE(BeginNavigateToURLFromRenderer(web_contents(), url));
ASSERT_TRUE(manager.WaitForResponse());
ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
ASSERT_TRUE(manager.WaitForNavigationFinished());
ASSERT_TRUE(controller.WaitForState(State::kWaitForLoadCompletion));
ASSERT_TRUE(controller.WaitForState(State::kWaitForVisualStateUpdate));
ASSERT_TRUE(controller.WaitForState(State::kMaybeDelayForLcp));
ASSERT_TRUE(controller.WaitForState(State::kDone));
ASSERT_EQ(result.Get(), ObservationDelayController::Result::kOk);
}
IN_PROC_BROWSER_TEST_F(ObservationDelayControllerTest,
UsePageStabilityForSameDocumentNavigation) {
ASSERT_TRUE(
content::NavigateToURL(web_contents(), GetPageStabilityTestURL()));
TestObservationDelayController controller(*main_frame(), actor::TaskId(),
journal(), PageStabilityConfig());
ASSERT_TRUE(InitiateFetchRequest());
TestFuture<ObservationDelayController::Result> result;
controller.Wait(*active_tab(), result.GetCallback());
ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
EXPECT_FALSE(result.IsReady());
Respond("TEST COMPLETE");
ASSERT_TRUE(controller.WaitForState(State::kWaitForLoadCompletion));
ASSERT_TRUE(controller.WaitForState(State::kWaitForVisualStateUpdate));
ASSERT_TRUE(controller.WaitForState(State::kMaybeDelayForLcp));
ASSERT_TRUE(result.Wait());
ASSERT_EQ(GetOutputText(), "TEST COMPLETE");
}
IN_PROC_BROWSER_TEST_P(ObservationDelayControllerNavigateTest,
LoadAfterStability) {
ASSERT_TRUE(
content::NavigateToURL(web_contents(), GetPageStabilityTestURL()));
TestObservationDelayController controller(*main_frame(), actor::TaskId(),
journal(), PageStabilityConfig());
ASSERT_TRUE(InitiateFetchRequest());
TestFuture<ObservationDelayController::Result> result;
controller.Wait(*active_tab(), result.GetCallback());
ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
EXPECT_FALSE(result.IsReady());
NavigateToLoadDeferredPage deferred_navigation(web_contents(),
embedded_test_server());
ASSERT_TRUE(deferred_navigation.RunToDOMContentLoadedEvent());
if (IsParamFeatureEnabled()) {
ASSERT_TRUE(controller.WaitForState(State::kDone));
ASSERT_EQ(result.Get(), ObservationDelayController::Result::kPageNavigated);
} else {
ASSERT_TRUE(
DoesReachSteadyState(controller, State::kWaitForLoadCompletion));
EXPECT_FALSE(result.IsReady());
ASSERT_TRUE(deferred_navigation.RunToLoadEvent());
ASSERT_TRUE(controller.WaitForState(State::kWaitForVisualStateUpdate));
ASSERT_TRUE(controller.WaitForState(State::kMaybeDelayForLcp));
ASSERT_TRUE(controller.WaitForState(State::kDone));
ASSERT_EQ(result.Get(), ObservationDelayController::Result::kOk);
}
}
INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(ObservationDelayControllerNavigateTest);
IN_PROC_BROWSER_TEST_F(ObservationDelayControllerTest,
BackgroundTabWhileWaitingForStability) {
ASSERT_TRUE(
content::NavigateToURL(web_contents(), GetPageStabilityTestURL()));
TestObservationDelayController controller(*main_frame(), actor::TaskId(),
journal(), PageStabilityConfig());
ASSERT_TRUE(InitiateFetchRequest());
TestFuture<ObservationDelayController::Result> result;
controller.Wait(*active_tab(), result.GetCallback());
ASSERT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
EXPECT_FALSE(result.IsReady());
auto scoped_decrement_closure =
web_contents()->IncrementCapturerCount(gfx::Size(),
false,
true,
true);
TabInterface* observed_tab = active_tab();
ASSERT_TRUE(observed_tab->IsActivated());
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("about:blank"), WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
ASSERT_FALSE(observed_tab->IsActivated());
ASSERT_NE(active_tab(), observed_tab);
EXPECT_TRUE(DoesReachSteadyState(controller, State::kWaitForPageStability));
}
class ObservationDelayControllerLcpTest : public ObservationDelayTest {
public:
static constexpr int kLcpDelayInMs = 3000;
ObservationDelayControllerLcpTest() {
std::string lcp_delay = absl::StrFormat("%dms", kLcpDelayInMs);
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kGlicActor,
{
{features::kGlicActorPageStabilityTimeout.name, "30000ms"},
{features::kGlicActorPageStabilityMinWait.name, "0ms"},
{features::kActorObservationDelayLcp.name, lcp_delay}});
}
~ObservationDelayControllerLcpTest() override = default;
private:
ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(ObservationDelayControllerLcpTest, NoDelayWhenLcpReady) {
const GURL url = embedded_test_server()->GetURL("/title1.html");
auto waiter = std::make_unique<page_load_metrics::PageLoadMetricsTestWaiter>(
web_contents());
waiter->AddPageExpectation(page_load_metrics::PageLoadMetricsTestWaiter::
TimingField::kLargestContentfulPaint);
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
waiter->Wait();
TestObservationDelayController controller(*main_frame(), actor::TaskId(),
journal(), PageStabilityConfig());
base::ElapsedTimer timer;
TestFuture<ObservationDelayController::Result> result;
controller.Wait(*active_tab(), result.GetCallback());
ASSERT_TRUE(controller.WaitForState(State::kMaybeDelayForLcp));
ASSERT_TRUE(result.Wait());
EXPECT_LT(timer.Elapsed(), base::Milliseconds(kLcpDelayInMs));
}
IN_PROC_BROWSER_TEST_F(ObservationDelayControllerLcpTest,
DelayIsAppliedForPageWithNoContent) {
const GURL url = embedded_test_server()->GetURL("/actor/blank.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
TestObservationDelayController controller(*main_frame(), actor::TaskId(),
journal(), PageStabilityConfig());
base::ElapsedTimer timer;
TestFuture<ObservationDelayController::Result> result;
controller.Wait(*active_tab(), result.GetCallback());
ASSERT_TRUE(controller.WaitForState(State::kMaybeDelayForLcp));
ASSERT_TRUE(controller.WaitForState(State::kDelayForLcp));
ASSERT_TRUE(result.Wait());
EXPECT_GE(timer.Elapsed(), base::Milliseconds(kLcpDelayInMs));
}
class ObservationDelayControllerExcludeAdRequestsTest
: public ObservationDelayControllerTest,
public base::test::WithFeatureOverride {
public:
ObservationDelayControllerExcludeAdRequestsTest()
: base::test::WithFeatureOverride(
features::kGlicActorObservationDelayExcludeAdFrameLoading) {}
~ObservationDelayControllerExcludeAdRequestsTest() override = default;
};
IN_PROC_BROWSER_TEST_P(ObservationDelayControllerExcludeAdRequestsTest,
ExcludeAdIframeLoad) {
const GURL url = embedded_test_server()->GetURL("/actor/blank.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
EXPECT_TRUE(content::ExecJs(main_frame(), R"(
const frame = document.createElement('iframe');
frame.id = 'child'
document.body.appendChild(frame);
)"));
RenderFrameHost* iframe_rfh = content::ChildFrameAt(main_frame(), 0);
ASSERT_TRUE(iframe_rfh);
iframe_rfh->UpdateIsAdFrame(true);
const GURL iframe_url = embedded_test_server()->GetURL("/actor/simple.html");
TestNavigationManager iframe_manager(web_contents(), iframe_url);
TestObservationDelayController controller(*main_frame(), actor::TaskId(),
journal(), PageStabilityConfig());
ASSERT_TRUE(BeginNavigateIframeToURL(web_contents(), "child",
iframe_url));
ASSERT_TRUE(iframe_manager.WaitForRequestStart());
ASSERT_TRUE(web_contents()->IsLoading());
ASSERT_FALSE(web_contents()->IsLoadingExcludingAdSubframes());
TestFuture<ObservationDelayController::Result> result;
controller.Wait(*active_tab(), result.GetCallback());
ASSERT_TRUE(controller.WaitForState(State::kWaitForLoadCompletion));
if (IsParamFeatureEnabled()) {
ASSERT_TRUE(controller.WaitForState(State::kWaitForVisualStateUpdate));
ASSERT_TRUE(controller.WaitForState(State::kMaybeDelayForLcp));
ASSERT_TRUE(controller.WaitForState(State::kDone));
} else {
ASSERT_TRUE(
DoesReachSteadyState(controller, State::kWaitForLoadCompletion));
ASSERT_TRUE(iframe_manager.WaitForNavigationFinished());
ASSERT_TRUE(controller.WaitForState(State::kWaitForVisualStateUpdate));
ASSERT_TRUE(controller.WaitForState(State::kMaybeDelayForLcp));
ASSERT_TRUE(controller.WaitForState(State::kDone));
}
ASSERT_TRUE(result.Wait());
}
INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(
ObservationDelayControllerExcludeAdRequestsTest);
}
}