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_subframe_navigation_throttle.h"

#include "base/memory/ptr_util.h"
#include "content/browser/preloading/prerender/prerender_features.h"
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/preloading/prerender/prerender_host_registry.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/browser/renderer_host/render_frame_host_delegate.h"
#include "content/public/browser/navigation_handle.h"
#include "url/origin.h"

namespace content {

// static
void PrerenderSubframeNavigationThrottle::MaybeCreateAndAdd(
    NavigationThrottleRegistry& registry) {
  auto* navigation_request =
      NavigationRequest::From(&registry.GetNavigationHandle());
  FrameTreeNode* frame_tree_node = navigation_request->frame_tree_node();
  if (frame_tree_node->IsMainFrame() ||
      !frame_tree_node->frame_tree().is_prerendering()) {
    return;
  }

  registry.AddThrottle(
      base::WrapUnique(new PrerenderSubframeNavigationThrottle(registry)));
}

PrerenderSubframeNavigationThrottle::PrerenderSubframeNavigationThrottle(
    NavigationThrottleRegistry& registry)
    : NavigationThrottle(registry),
      prerender_root_ftn_id_(
          NavigationRequest::From(&registry.GetNavigationHandle())
              ->frame_tree_node()
              ->frame_tree()
              .root()
              ->frame_tree_node_id()) {}

PrerenderSubframeNavigationThrottle::~PrerenderSubframeNavigationThrottle() =
    default;

const char* PrerenderSubframeNavigationThrottle::GetNameForLogging() {
  return "PrerenderSubframeNavigationThrottle";
}

NavigationThrottle::ThrottleCheckResult
PrerenderSubframeNavigationThrottle::WillStartRequest() {
  return WillStartOrRedirectRequest();
}

NavigationThrottle::ThrottleCheckResult
PrerenderSubframeNavigationThrottle::WillRedirectRequest() {
  return WillStartOrRedirectRequest();
}

NavigationThrottle::ThrottleCheckResult
PrerenderSubframeNavigationThrottle::WillProcessResponse() {
  auto* navigation_request = NavigationRequest::From(navigation_handle());
  FrameTreeNode* frame_tree_node = navigation_request->frame_tree_node();
  if (!frame_tree_node->frame_tree().is_prerendering()) {
    return NavigationThrottle::PROCEED;
  }

  // TODO(crbug.com/40222993): Delay until activation instead of cancellation.
  if (navigation_handle()->IsDownload()) {
    // Disallow downloads during prerendering and cancel the prerender.
    PrerenderHostRegistry* prerender_host_registry =
        frame_tree_node->current_frame_host()
            ->delegate()
            ->GetPrerenderHostRegistry();
    prerender_host_registry->CancelHost(
        frame_tree_node->frame_tree().root()->frame_tree_node_id(),
        PrerenderFinalStatus::kDownload);
    return CANCEL;
  }

  // Don't run cross-origin subframe navigation check for non-renderable
  // contents like 204/205 as their GetOriginToCommit() is invalid. In this
  // case, we can safely proceed with navigation without deferring it.
  if (!navigation_request->response_should_be_rendered()) {
    return PROCEED;
  }

  // Defer cross-origin subframe navigation until page activation. The check is
  // added here, because this is the first place that the throttle can properly
  // check for cross-origin using GetOriginToCommit(). See comments in
  // WillStartOrRedirectRequest() for more details.
  RenderFrameHostImpl* rfhi = frame_tree_node->frame_tree().GetMainFrame();
  const url::Origin& main_origin = rfhi->GetLastCommittedOrigin();
  if (!main_origin.IsSameOriginWith(
          navigation_request->GetOriginToCommit().value())) {
    return DecidePolicyForCrossOriginSubframeNavigation(*frame_tree_node);
  }

  return PROCEED;
}

void PrerenderSubframeNavigationThrottle::OnActivated() {
  CHECK(!NavigationRequest::From(navigation_handle())
             ->frame_tree_node()
             ->frame_tree()
             .is_prerendering());
  // OnActivated() is called right before activation navigation commit which is
  // a little early. We want to resume the subframe navigation after the
  // PageBroadcast ActivatePrerenderedPage IPC is sent, to
  // guarantee that the new document starts in the non-prerendered state and
  // does not get a prerenderingchange event.
  //
  // Listen to the WebContents to wait for the activation navigation to finish
  // before resuming the subframe navigation.
  Observe(navigation_handle()->GetWebContents());
}

// Use DidFinishNavigation() rather than PrimaryPageChanged() in order to
// Resume() after the PageBroadcast Activate IPC is sent, which happens a
// little after PrimaryPageChanged() and before DidFinishNavigation(). This
// guarantees the new document starts in non-prerendered state.
void PrerenderSubframeNavigationThrottle::DidFinishNavigation(
    NavigationHandle* nav_handle) {
  // Ignore finished navigations that are not the activation navigation for the
  // prerendering frame tree that this subframe navigation started in.
  auto* finished_navigation = NavigationRequest::From(nav_handle);
  if (finished_navigation->prerender_frame_tree_node_id() !=
      prerender_root_ftn_id_) {
    return;
  }

  // The activation is finished. There is no need to listen to the WebContents
  // anymore.
  Observe(nullptr);

  // If the finished navigation did not commit, do not Resume(). We expect that
  // the prerendered page and therefore the subframe navigation will eventually
  // be cancelled.
  if (!finished_navigation->HasCommitted()) {
    return;
  }

  // Resume the subframe navigation.
  if (!is_deferred_) {
    return;
  }
  is_deferred_ = false;
  Resume();
  // Resume() may have deleted `this`.
}

NavigationThrottle::ThrottleCheckResult
PrerenderSubframeNavigationThrottle::WillCommitWithoutUrlLoader() {
  auto* navigation_request = NavigationRequest::From(navigation_handle());
  if (navigation_request->GetUrlInfo().is_sandboxed) {
    FrameTreeNode* frame_tree_node = navigation_request->frame_tree_node();
    // Although main frames can be in sandboxed SiteInfo's, we don't encounter
    // that here since this throttle check should never occur for a mainframe.
    CHECK(!frame_tree_node->IsMainFrame());
    return DecidePolicyForCrossOriginSubframeNavigation(*frame_tree_node);
  }

  return NavigationThrottle::PROCEED;
}

NavigationThrottle::ThrottleCheckResult PrerenderSubframeNavigationThrottle::
    DecidePolicyForCrossOriginSubframeNavigation(
        const FrameTreeNode& frame_tree_node) {
  CHECK(frame_tree_node.frame_tree().is_prerendering());
  CHECK(!frame_tree_node.IsMainFrame());

  // Look up the PrerenderHost.
  PrerenderHostRegistry* registry = frame_tree_node.current_frame_host()
                                        ->delegate()
                                        ->GetPrerenderHostRegistry();
  PrerenderHost* prerender_host =
      registry->FindNonReservedHostById(prerender_root_ftn_id_);
  if (!prerender_host) {
    // The PrerenderHostRegistry removed the PrerenderHost and scheduled to
    // destroy it asynchronously.
    return NavigationThrottle::CANCEL;
  }

  if (prerender_host->AllowCrossOriginSubframeNavigation()) {
    return NavigationThrottle::PROCEED;
  }

  // Defer cross-origin subframe navigations during prerendering.
  // Will resume the navigation upon activation.
  CHECK(!observation_.IsObserving());
  observation_.Observe(prerender_host);
  CHECK(observation_.IsObservingSource(prerender_host));
  is_deferred_ = true;
  return NavigationThrottle::DEFER;
}

void PrerenderSubframeNavigationThrottle::OnHostDestroyed(
    PrerenderFinalStatus final_status) {
  observation_.Reset();
}

NavigationThrottle::ThrottleCheckResult
PrerenderSubframeNavigationThrottle::WillStartOrRedirectRequest() {
  auto* navigation_request = NavigationRequest::From(navigation_handle());
  FrameTreeNode* frame_tree_node = navigation_request->frame_tree_node();
  CHECK(!frame_tree_node->IsMainFrame());

  // Proceed if the page isn't in the prerendering state.
  if (!frame_tree_node->frame_tree().is_prerendering()) {
    return NavigationThrottle::PROCEED;
  }

  // Decide a loading policy for cross-origin subframe navigation: cancel,
  // defer it until page activation, or proceed if the opt-in header allows.
  //
  // Using url::Origin::Create() to check same-origin might not be
  // completely accurate for cases such as sandboxed iframes, which have a
  // different origin from the main frame even when the URL is same-origin.
  // There is another check in WillProcessResponse to fix this issue.
  // In WillProcessResponse, GetOriginToCommit is used to identify the
  // accurate Origin.
  // Note: about:blank and about:srcdoc also might not result in an appropriate
  // origin if we create the origin from the URL, but those cases won't go
  // through the NavigationThrottle, so it's not a problem here
  RenderFrameHostImpl* rfhi = frame_tree_node->frame_tree().GetMainFrame();
  const url::Origin& main_origin = rfhi->GetLastCommittedOrigin();
  if (!main_origin.IsSameOriginWith(navigation_handle()->GetURL())) {
    return DecidePolicyForCrossOriginSubframeNavigation(*frame_tree_node);
  }

  return NavigationThrottle::PROCEED;
}

}  // namespace content