#include "content/browser/renderer_host/commit_deferring_condition_runner.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_id_helper.h"
#include "content/browser/preloading/prerender/prerender_commit_deferring_condition.h"
#include "content/browser/preloading/prerender/prerender_no_vary_search_commit_deferring_condition.h"
#include "content/browser/preloading/prerender/prerender_no_vary_search_hint_commit_deferring_condition.h"
#include "content/browser/renderer_host/back_forward_cache_commit_deferring_condition.h"
#include "content/browser/renderer_host/concurrent_navigations_commit_deferring_condition.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/navigator_delegate.h"
#include "content/browser/renderer_host/network_restrictions_commit_deferring_condition.h"
#include "content/browser/renderer_host/view_transition_commit_deferring_condition.h"
#include "content/common/content_navigation_policy.h"
#include "content/common/features.h"
#include "content/public/browser/commit_deferring_condition.h"
namespace content {
namespace {
using GeneratorOrderPair =
std::pair<CommitDeferringConditionRunner::ConditionGenerator,
CommitDeferringConditionRunner::InsertOrder>;
std::map<int, GeneratorOrderPair>& GetConditionGenerators() {
static base::NoDestructor<std::map<int, GeneratorOrderPair>> generators;
return *generators;
}
}
std::unique_ptr<CommitDeferringConditionRunner>
CommitDeferringConditionRunner::Create(
NavigationRequest& navigation_request,
CommitDeferringCondition::NavigationType navigation_type,
std::optional<FrameTreeNodeId> candidate_prerender_frame_tree_node_id) {
CHECK(!navigation_request.IsInitialWebUISyncNavigation());
auto runner = base::WrapUnique(new CommitDeferringConditionRunner(
navigation_request, navigation_type,
candidate_prerender_frame_tree_node_id));
return runner;
}
CommitDeferringConditionRunner::CommitDeferringConditionRunner(
Delegate& delegate,
CommitDeferringCondition::NavigationType navigation_type,
std::optional<FrameTreeNodeId> candidate_prerender_frame_tree_node_id)
: delegate_(delegate),
navigation_type_(navigation_type),
candidate_prerender_frame_tree_node_id_(
candidate_prerender_frame_tree_node_id) {}
CommitDeferringConditionRunner::~CommitDeferringConditionRunner() {
if (is_deferred_) {
TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this));
TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this));
}
}
void CommitDeferringConditionRunner::ProcessChecks() {
ProcessConditions();
}
void CommitDeferringConditionRunner::AddConditionForTesting(
std::unique_ptr<CommitDeferringCondition> condition) {
AddCondition(std::move(condition));
}
CommitDeferringCondition*
CommitDeferringConditionRunner::GetDeferringConditionForTesting() const {
if (!is_deferred_) {
return nullptr;
}
DCHECK(!conditions_.empty());
return (*conditions_.begin()).get();
}
void CommitDeferringConditionRunner::ResumeProcessing() {
DCHECK(is_deferred_);
is_deferred_ = false;
TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this));
TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this));
DCHECK(!conditions_.empty());
conditions_.erase(conditions_.begin());
ProcessConditions();
}
void CommitDeferringConditionRunner::RegisterDeferringConditions(
NavigationRequest& navigation_request) {
CHECK(!navigation_request.IsInitialWebUINavigation() ||
!base::FeatureList::IsEnabled(
features::kInitialWebUISyncNavStartToCommit));
switch (navigation_type_) {
case CommitDeferringCondition::NavigationType::kPrerenderedPageActivation:
DCHECK_LT(navigation_request.state(),
NavigationRequest::WILL_START_NAVIGATION);
break;
case CommitDeferringCondition::NavigationType::kOther:
DCHECK(navigation_request.state() ==
NavigationRequest::WILL_PROCESS_RESPONSE ||
navigation_request.state() ==
NavigationRequest::WILL_FAIL_REQUEST ||
navigation_request.state() == NavigationRequest::CANCELING);
break;
}
std::vector<std::unique_ptr<CommitDeferringCondition>> delegate_conditions =
navigation_request.GetDelegate()
->CreateDeferringConditionsForNavigationCommit(navigation_request,
navigation_type_);
for (auto& condition : delegate_conditions) {
DCHECK(condition);
AddCondition(std::move(condition));
}
AddCondition(PrerenderNoVarySearchHintCommitDeferringCondition::MaybeCreate(
navigation_request, navigation_type_,
candidate_prerender_frame_tree_node_id_));
AddCondition(PrerenderCommitDeferringCondition::MaybeCreate(
navigation_request, navigation_type_,
candidate_prerender_frame_tree_node_id_));
AddCondition(PrerenderNoVarySearchCommitDeferringCondition::MaybeCreate(
navigation_request, navigation_type_,
candidate_prerender_frame_tree_node_id_));
AddCondition(
ViewTransitionCommitDeferringCondition::MaybeCreate(navigation_request));
if (ShouldAvoidRedundantNavigationCancellations()) {
AddCondition(ConcurrentNavigationsCommitDeferringCondition::MaybeCreate(
navigation_request, navigation_type_));
}
AddCondition(NetworkRestrictionsCommitDeferringCondition::MaybeCreate(
navigation_request));
AddCondition(BackForwardCacheCommitDeferringCondition::MaybeCreate(
navigation_request));
for (auto& iter : GetConditionGenerators()) {
GeneratorOrderPair& generator_order_pair = iter.second;
AddCondition(
generator_order_pair.first.Run(navigation_request, navigation_type_),
generator_order_pair.second);
}
}
int CommitDeferringConditionRunner::InstallConditionGeneratorForTesting(
ConditionGenerator generator,
InsertOrder order) {
static int generator_id = 0;
GetConditionGenerators().emplace(generator_id,
std::make_pair(std::move(generator), order));
return generator_id++;
}
void CommitDeferringConditionRunner::UninstallConditionGeneratorForTesting(
int generator_id) {
GetConditionGenerators().erase(generator_id);
}
void CommitDeferringConditionRunner::ProcessConditions() {
while (!conditions_.empty()) {
auto resume_closure =
base::BindOnce(&CommitDeferringConditionRunner::ResumeProcessing,
weak_factory_.GetWeakPtr());
CommitDeferringCondition* condition = (*conditions_.begin()).get();
is_deferred_ = false;
switch (condition->WillCommitNavigation(std::move(resume_closure))) {
case CommitDeferringCondition::Result::kDefer:
is_deferred_ = true;
TRACE_EVENT_BEGIN("navigation", "CommitDeferringConditionRunning",
perfetto::Track::FromPointer(this));
TRACE_EVENT_BEGIN("navigation",
perfetto::DynamicString(condition->TraceEventName()),
perfetto::Track::FromPointer(this));
return;
case CommitDeferringCondition::Result::kCancelled:
return;
case CommitDeferringCondition::Result::kProceed:
break;
}
conditions_.erase(conditions_.begin());
}
delegate_->OnCommitDeferringConditionChecksComplete(
navigation_type_, candidate_prerender_frame_tree_node_id_);
}
void CommitDeferringConditionRunner::AddCondition(
std::unique_ptr<CommitDeferringCondition> condition,
InsertOrder order) {
if (!condition)
return;
if (order == InsertOrder::kAfter)
conditions_.push_back(std::move(condition));
else
conditions_.insert(conditions_.begin(), std::move(condition));
}
}