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.

#ifndef CONTENT_BROWSER_RENDERER_HOST_COMMIT_DEFERRING_CONDITION_RUNNER_H_
#define CONTENT_BROWSER_RENDERER_HOST_COMMIT_DEFERRING_CONDITION_RUNNER_H_

#include <map>
#include <memory>
#include <vector>

#include "base/memory/ptr_util.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/preloading/prerender/prerender_commit_deferring_condition.h"
#include "content/common/content_export.h"

namespace content {

class NavigationRequest;

// Helper class used to defer an otherwise fully-prepared navigation (i.e.
// followed all redirects, passed all NavigationThrottle checks) from
// proceeding until all preconditions are met.
//
// Clients subclass the CommitDeferringCondition class to wait on a commit
// blocking condition to be resolved and invoke the callback when it's ready.
// The client should register their subclass class in
// RegisterDeferringConditions().  Each condition is run in order, waiting on
// that condition to call Resume() before starting the next one. Once the final
// condition is completed, the navigation is resumed to commit.
//
// This mechanism is not applied to about:blank or same-document navigations.
//
// CommitDeferringCondition vs. NavigationThrottle: At first glance this
// mechanism may seem redundant to using a NavigationThrottle (and deferring in
// WillProcessResponse). However, the behavior will differ on pages initially
// loaded into a non-primary FrameTree (e.g. prerendering or BFCached page).
// In these cases NavigationThrottles will run only when the page was loading,
// they will not get a chance to intervene when it is being activated to the
// primary FrameTree (i.e. user navigates to a prerendered page). If the
// navigation needs to be deferred during such activations, a
// CommitDeferringCondition must be used.  It runs both when the navigation is
// loading and when a navigation activates into the primary FrameTree.
class CONTENT_EXPORT CommitDeferringConditionRunner {
 public:
  class Delegate {
   public:
    // Called after all conditions run. `candidate_prerender_frame_tree_node_id`
    // is used for querying the PrerenderHost that this navigation will try to
    // activate. See comments on `candidate_prerender_frame_tree_node_id_` for
    // details.
    virtual void OnCommitDeferringConditionChecksComplete(
        CommitDeferringCondition::NavigationType navigation_type,
        std::optional<FrameTreeNodeId>
            candidate_prerender_frame_tree_node_id) = 0;
  };

  // Creates the runner and adds all the conditions in
  // RegisterDeferringConditions. `candidate_prerender_frame_tree_node_id`
  // is used for querying the PrerenderHost that this navigation will try to
  // activate. See comments on `candidate_prerender_frame_tree_node_id_` for
  // details.
  static std::unique_ptr<CommitDeferringConditionRunner> Create(
      NavigationRequest& navigation_request,
      CommitDeferringCondition::NavigationType navigation_type,
      std::optional<FrameTreeNodeId> candidate_prerender_frame_tree_node_id);

  CommitDeferringConditionRunner(const CommitDeferringConditionRunner&) =
      delete;
  CommitDeferringConditionRunner& operator=(
      const CommitDeferringConditionRunner&) = delete;

  ~CommitDeferringConditionRunner();

  // Call to start iterating through registered CommitDeferringConditions. This
  // calls OnCommitDeferringConditionChecksComplete on the |delegate_| when all
  // conditions have been resolved. This may happen either synchronously or
  // asynchronously.
  void ProcessChecks();

  // Call to register all deferring conditions. This should be called when
  // NavigationState < WILL_START_NAVIGATION for prerendered page activation, or
  // NavigationState == WILL_PROCESS_RESPONSE for other navigations.
  void RegisterDeferringConditions(NavigationRequest& navigation_request);

  // Installs a callback to generate a deferring condition. Installed callbacks
  // are called every time RegisterDeferringConditions() is called. Generated
  // conditions are added to `conditions_` and run after all regularly
  // registered conditions. This is typically used for adding a condition before
  // NavigationRequest is created.
  using ConditionGenerator =
      base::RepeatingCallback<std::unique_ptr<CommitDeferringCondition>(
          NavigationHandle&,
          CommitDeferringCondition::NavigationType)>;

  // Specifies whether a ConditionGenerator installs its condition to run
  // before existing conditions or after. Note: generators are run in the order
  // in which they are added.
  enum class InsertOrder { kBefore, kAfter };

  // Returns a generator id that is used for uninstalling the generator.
  static int InstallConditionGeneratorForTesting(ConditionGenerator generator,
                                                 InsertOrder order);

  // `generator_id` should be an identifier returned by
  // InstallConditionGeneratorForTesting().
  static void UninstallConditionGeneratorForTesting(int generator_id);

  // Used in tests to inject mock conditions.
  void AddConditionForTesting(
      std::unique_ptr<CommitDeferringCondition> condition);

  // Returns the condition that's currently causing the navigation commit to be
  // deferred. If no condition is currently deferred, returns nullptr.
  CommitDeferringCondition* GetDeferringConditionForTesting() const;

 private:
  friend class CommitDeferringConditionRunnerTest;

  CommitDeferringConditionRunner(
      Delegate& delegate,
      CommitDeferringCondition::NavigationType navigation_type,
      std::optional<FrameTreeNodeId> candidate_prerender_frame_tree_node_id);

  // Called asynchronously to resume iterating through
  // CommitDeferringConditions after one has been deferred. A callback for this
  // method is passed into each condition when WillCommitNavigation is called.
  void ResumeProcessing();

  void ProcessConditions();
  void AddCondition(std::unique_ptr<CommitDeferringCondition> condition,
                    InsertOrder order = InsertOrder::kAfter);

  std::vector<std::unique_ptr<CommitDeferringCondition>> conditions_;

  // This class is owned by its delegate (the NavigationRequest) so it's safe
  // to keep a reference to it.
  const raw_ref<Delegate> delegate_;

  // Used for distiguishing prerendered page activation from other navigations.
  // This is needed as IsPageActivation() and IsPrerenderedPageActivation() on
  // NavigationRequest are not available yet while CommitDeferringCondition is
  // running.
  const CommitDeferringCondition::NavigationType navigation_type_;

  // Used for querying PrerenderHost this navigation will try to activate.
  // This is valid only when `navigation_type_` is kPrerenderedPageActivation.
  // This is needed as PrerenderHost hasn't been reserved and
  // prerender_frame_tree_node_id() on NavigationRequest is not available yet
  // while CommitDeferringCondition is running.
  const std::optional<FrameTreeNodeId> candidate_prerender_frame_tree_node_id_;

  // True when we're blocked waiting on a call to ResumeProcessing.
  bool is_deferred_ = false;

  base::WeakPtrFactory<CommitDeferringConditionRunner> weak_factory_{this};
};

}  // namespace content

#endif  // CONTENT_BROWSER_RENDERER_HOST_COMMIT_DEFERRING_CONDITION_RUNNER_H_