| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #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/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; |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<CommitDeferringConditionRunner> |
| CommitDeferringConditionRunner::Create( |
| NavigationRequest& navigation_request, |
| CommitDeferringCondition::NavigationType navigation_type, |
| std::optional<FrameTreeNodeId> candidate_prerender_frame_tree_node_id) { |
| 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_) { |
| // End `condition->TraceEventName()` trace event. |
| TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this)); |
| // End "CommitDeferringConditionRunning" trace event. |
| 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; |
| // End `condition->TraceEventName()` trace event. |
| TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this)); |
| // End "CommitDeferringConditionRunning" trace event. |
| TRACE_EVENT_END("navigation", perfetto::Track::FromPointer(this)); |
| // This is resuming from a check that resolved asynchronously. The current |
| // check is always at the front of the vector so pop it and then proceed with |
| // the next one. |
| DCHECK(!conditions_.empty()); |
| conditions_.erase(conditions_.begin()); |
| ProcessConditions(); |
| } |
| |
| void CommitDeferringConditionRunner::RegisterDeferringConditions( |
| NavigationRequest& navigation_request) { |
| switch (navigation_type_) { |
| case CommitDeferringCondition::NavigationType::kPrerenderedPageActivation: |
| // For prerendered page activation, conditions should run before start |
| // navigation. |
| DCHECK_LT(navigation_request.state(), |
| NavigationRequest::WILL_START_NAVIGATION); |
| break; |
| case CommitDeferringCondition::NavigationType::kOther: |
| // For other navigations, conditions should run before navigation commit. |
| DCHECK_EQ(navigation_request.state(), |
| NavigationRequest::WILL_PROCESS_RESPONSE); |
| break; |
| } |
| |
| // Let WebContents add deferring conditions. |
| 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)); |
| } |
| |
| // PrerenderNoVarySearchHintCommitDeferringCondition should run before |
| // PrerenderCommitDeferringCondition as it needs to defer until headers |
| // are received. Headers are a required prerequisite for the correctness of |
| // PrerenderCommitDeferringCondition and |
| // PrerenderNoVarySearchCommitDeferringCondition in the presence of |
| // No-Vary-Search hint/header. |
| 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_)); |
| |
| // PrerenderNoVarySearchCommitDeferringCondition should run after we've |
| // made the decision to activate the prerender as it changes the |
| // prerender renderer's URL. |
| 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_)); |
| } |
| |
| // The BFCache deferring condition should run after all other conditions |
| // since it'll disable eviction on a cached renderer. |
| AddCondition(BackForwardCacheCommitDeferringCondition::MaybeCreate( |
| navigation_request)); |
| |
| // Run condition generators for testing. |
| for (auto& iter : GetConditionGenerators()) { |
| GeneratorOrderPair& generator_order_pair = iter.second; |
| AddCondition( |
| generator_order_pair.first.Run(navigation_request, navigation_type_), |
| generator_order_pair.second); |
| } |
| } |
| |
| // static |
| 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++; |
| } |
| |
| // static |
| void CommitDeferringConditionRunner::UninstallConditionGeneratorForTesting( |
| int generator_id) { |
| GetConditionGenerators().erase(generator_id); |
| } |
| |
| void CommitDeferringConditionRunner::ProcessConditions() { |
| while (!conditions_.empty()) { |
| // If the condition isn't yet ready to commit, it'll be resolved |
| // asynchronously. The loop will continue from ResumeProcessing(); |
| |
| 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; |
| // TODO(crbug.com/40270812): Also add instant tracing for the condition |
| // that is being resolved synchronously. |
| case CommitDeferringCondition::Result::kCancelled: |
| // DO NOT ADD CODE after this. The previous call to |
| // `WillCommitNavigation()` may have caused the destruction of the |
| // `NavigationRequest` that owns this `CommitDeferringConditionRunner`. |
| return; |
| case CommitDeferringCondition::Result::kProceed: |
| break; |
| } |
| |
| // Otherwise, the condition is resolved synchronously so remove it and move |
| // on to the next one. |
| conditions_.erase(conditions_.begin()); |
| } |
| |
| // All checks are completed, proceed with the commit in the |
| // NavigationRequest. |
| 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)); |
| } |
| |
| } // namespace content |