910e62b5创建于 1月15日历史提交
// 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/preloading/prerender/prerender_commit_deferring_condition.h"

#include "base/memory/ptr_util.h"
#include "base/task/sequenced_task_runner.h"
#include "content/browser/preloading/prerender/prerender_host.h"
#include "content/browser/preloading/prerender/prerender_metrics.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/public/browser/render_frame_host.h"

namespace content {

namespace {

// Returns the root prerender frame tree node associated with navigation_request
// of ongoing prerender activation.
FrameTreeNode* GetRootPrerenderFrameTreeNode(
    FrameTreeNodeId prerender_frame_tree_node_id) {
  FrameTreeNode* root =
      FrameTreeNode::GloballyFindByID(prerender_frame_tree_node_id);
  if (root) {
    CHECK(root->IsOutermostMainFrame());
  }
  return root;
}

}  // namespace

// static
std::unique_ptr<CommitDeferringCondition>
PrerenderCommitDeferringCondition::MaybeCreate(
    NavigationRequest& navigation_request,
    NavigationType navigation_type,
    std::optional<FrameTreeNodeId> candidate_prerender_frame_tree_node_id) {
  // Don't create if this navigation is not for prerender page activation.
  if (navigation_type != NavigationType::kPrerenderedPageActivation)
    return nullptr;

  return base::WrapUnique(new PrerenderCommitDeferringCondition(
      navigation_request, candidate_prerender_frame_tree_node_id.value()));
}

PrerenderCommitDeferringCondition::~PrerenderCommitDeferringCondition() =
    default;

PrerenderCommitDeferringCondition::PrerenderCommitDeferringCondition(
    NavigationRequest& navigation_request,
    FrameTreeNodeId candidate_prerender_frame_tree_node_id)
    : CommitDeferringCondition(navigation_request),
      WebContentsObserver(navigation_request.GetWebContents()),
      candidate_prerender_frame_tree_node_id_(
          candidate_prerender_frame_tree_node_id) {
  CHECK(candidate_prerender_frame_tree_node_id_);
}

CommitDeferringCondition::Result
PrerenderCommitDeferringCondition::WillCommitNavigation(
    base::OnceClosure resume) {
  FrameTreeNode* prerender_frame_tree_node =
      GetRootPrerenderFrameTreeNode(candidate_prerender_frame_tree_node_id_);

  // If the prerender FrameTreeNode is gone, the prerender activation is allowed
  // to continue here but will fail soon.
  if (!prerender_frame_tree_node) {
    return Result::kProceed;
  }

  PrerenderHost& prerender_host =
      PrerenderHost::GetFromFrameTreeNode(*prerender_frame_tree_node);
  // If there is no ongoing main frame navigation in prerender frame tree, the
  // prerender activation is allowed to continue.
  if (!prerender_frame_tree_node->HasNavigation()) {
    // Record the defer waiting time for PrerenderCommitDeferringCondition as no
    // delay.
    RecordPrerenderActivationCommitDeferTime(
        base::TimeDelta(), prerender_host.trigger_type(),
        prerender_host.embedder_histogram_suffix());
    return Result::kProceed;
  }

  // If we cannot match the navigation URL the prerender activation is allowed
  // to continue here but will fail soon. This can happen when matching
  // by No-Vary-Search hint and the No-Vary-Search header doesn't agree.
  if (!prerender_host.IsUrlMatch(GetNavigationHandle().GetURL())) {
    return Result::kProceed;
  }

  // Defer the prerender activation until the ongoing prerender main frame
  // navigation commits.
  done_closure_ = std::move(resume);
  defer_start_time_ = base::TimeTicks::Now();
  return Result::kDefer;
}

const char* PrerenderCommitDeferringCondition::TraceEventName() const {
  return "PrerenderCommitDeferringCondition";
}

void PrerenderCommitDeferringCondition::DidFinishNavigation(
    NavigationHandle* handle) {
  auto* finished_navigation_request = NavigationRequest::From(handle);

  FrameTreeNode* prerender_frame_tree_node =
      GetRootPrerenderFrameTreeNode(candidate_prerender_frame_tree_node_id_);

  // If the prerender frame tree node is gone, there is nothing to do.
  if (!prerender_frame_tree_node)
    return;

  // If the finished navigation is not for the prerendering main frame,
  // ignore this event.
  if (finished_navigation_request->frame_tree_node() !=
      prerender_frame_tree_node) {
    return;
  }

  // PrerenderNavigationThrottle allows navigations after the initial commit so
  // the callback should be called after all ongoing navigations are completed.
  if (done_closure_ && !prerender_frame_tree_node->HasNavigation()) {
    // It's possible that another navigation happens after posting a task to
    // resume the activation because PrerenderNavigationThrottle allows the main
    // frame navigation in a prerendered page. In that case, prerendering is
    // cancelled and the activation falls back to network.
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(done_closure_));

    // Record the defer waiting time for PrerenderCommitDeferringCondition.
    base::TimeDelta delta = base::TimeTicks::Now() - defer_start_time_;
    PrerenderHost& prerender_host =
        PrerenderHost::GetFromFrameTreeNode(*prerender_frame_tree_node);
    RecordPrerenderActivationCommitDeferTime(
        delta, prerender_host.trigger_type(),
        prerender_host.embedder_histogram_suffix());
  }
}

}  // namespace content