1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162

content / browser / renderer_host / commit_deferring_condition_runner.h [blame]

// 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_