| // 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 "content/public/browser/commit_deferring_condition.h" | 
 | #include "content/public/test/mock_navigation_handle.h" | 
 | #include "content/public/test/test_renderer_host.h" | 
 | #include "content/test/mock_commit_deferring_condition.h" | 
 |  | 
 | namespace content { | 
 |  | 
 | class CommitDeferringConditionRunnerTest | 
 |     : public RenderViewHostTestHarness, | 
 |       public CommitDeferringConditionRunner::Delegate { | 
 |  public: | 
 |   CommitDeferringConditionRunnerTest() = default; | 
 |  | 
 |   void SetUp() override { | 
 |     RenderViewHostTestHarness::SetUp(); | 
 |     runner_ = base::WrapUnique(new CommitDeferringConditionRunner( | 
 |         *this, CommitDeferringCondition::NavigationType::kOther, | 
 |         /*candidate_prerender_frame_tree_node_id=*/std::nullopt)); | 
 |   } | 
 |  | 
 |   // Whether the callback was called. | 
 |   bool was_delegate_notified() const { return was_delegate_notified_; } | 
 |  | 
 |   bool is_deferring() { return runner_->is_deferred_; } | 
 |  | 
 |   CommitDeferringConditionRunner* runner() { return runner_.get(); } | 
 |  | 
 |  private: | 
 |   // CommitDeferringConditionRunner::Delegate: | 
 |   void OnCommitDeferringConditionChecksComplete( | 
 |       CommitDeferringCondition::NavigationType navigation_type, | 
 |       std::optional<FrameTreeNodeId> candidate_prerender_frame_tree_node_id) | 
 |       override { | 
 |     EXPECT_EQ(navigation_type, | 
 |               CommitDeferringCondition::NavigationType::kOther); | 
 |     EXPECT_FALSE(candidate_prerender_frame_tree_node_id.has_value()); | 
 |     was_delegate_notified_ = true; | 
 |   } | 
 |  | 
 |   std::unique_ptr<CommitDeferringConditionRunner> runner_; | 
 |   bool was_delegate_notified_ = false; | 
 | }; | 
 |  | 
 | // CommitDeferringCondition always need a NavigationHandle. Since we don't have | 
 | // a navigation here, this class is just used to provide it with a | 
 | // MockNavigationHandle. | 
 | class MockHandleConditionWrapper : public MockNavigationHandle, | 
 |                                    public MockCommitDeferringConditionWrapper { | 
 |  public: | 
 |   explicit MockHandleConditionWrapper(CommitDeferringCondition::Result result) | 
 |       : MockCommitDeferringConditionWrapper(*this, result) {} | 
 | }; | 
 |  | 
 | // Check that the runner notifies the delegate synchronously when there are no | 
 | // conditions registered. | 
 | TEST_F(CommitDeferringConditionRunnerTest, NoRegisteredConditions) { | 
 |   EXPECT_FALSE(was_delegate_notified()); | 
 |   runner()->ProcessChecks(); | 
 |   EXPECT_TRUE(was_delegate_notified()); | 
 | } | 
 |  | 
 | // Test that when a condition defers asynchronously, the delegate isn't | 
 | // notified until the condition signals completion. | 
 | TEST_F(CommitDeferringConditionRunnerTest, BasicAsync) { | 
 |   MockHandleConditionWrapper condition( | 
 |       CommitDeferringCondition::Result::kDefer); | 
 |   runner()->AddConditionForTesting(condition.PassToDelegate()); | 
 |   runner()->ProcessChecks(); | 
 |   EXPECT_FALSE(was_delegate_notified()); | 
 |   EXPECT_TRUE(condition.WasInvoked()); | 
 |   EXPECT_TRUE(is_deferring()); | 
 |   condition.CallResumeClosure(); | 
 |   EXPECT_TRUE(was_delegate_notified()); | 
 | } | 
 |  | 
 | // Test that if a condition is already satisfied when ProcessChecks is | 
 | // called, the delegate is notified synchronously. | 
 | TEST_F(CommitDeferringConditionRunnerTest, BasicSync) { | 
 |   MockHandleConditionWrapper condition( | 
 |       CommitDeferringCondition::Result::kProceed); | 
 |   runner()->AddConditionForTesting(condition.PassToDelegate()); | 
 |   runner()->ProcessChecks(); | 
 |   EXPECT_TRUE(was_delegate_notified()); | 
 |   EXPECT_TRUE(condition.WasInvoked()); | 
 | } | 
 |  | 
 | // Test that if a condition indicating the cancellation of the commit, | 
 | // the delegate is not notified. | 
 | TEST_F(CommitDeferringConditionRunnerTest, BasicCancelled) { | 
 |   MockHandleConditionWrapper condition( | 
 |       CommitDeferringCondition::Result::kCancelled); | 
 |   runner()->AddConditionForTesting(condition.PassToDelegate()); | 
 |   runner()->ProcessChecks(); | 
 |   EXPECT_FALSE(was_delegate_notified()); | 
 |   EXPECT_TRUE(condition.WasInvoked()); | 
 | } | 
 |  | 
 | // Test with multiple conditions, some of which are completed synchronously and | 
 | // some asynchronously. The final condition is asynchronous and should notify | 
 | // the delegate on resumption. | 
 | TEST_F(CommitDeferringConditionRunnerTest, MultipleConditionsLastAsync) { | 
 |   // Add conditions, alternating between those that are already satisfied at | 
 |   // ProcessChecks time and those that complete asynchronously. | 
 |   // Complete -> Async -> Complete -> Async | 
 |   MockHandleConditionWrapper condition1( | 
 |       CommitDeferringCondition::Result::kProceed); | 
 |   runner()->AddConditionForTesting(condition1.PassToDelegate()); | 
 |  | 
 |   MockHandleConditionWrapper condition2( | 
 |       CommitDeferringCondition::Result::kDefer); | 
 |   runner()->AddConditionForTesting(condition2.PassToDelegate()); | 
 |  | 
 |   MockHandleConditionWrapper condition3( | 
 |       CommitDeferringCondition::Result::kProceed); | 
 |   runner()->AddConditionForTesting(condition3.PassToDelegate()); | 
 |  | 
 |   MockHandleConditionWrapper condition4( | 
 |       CommitDeferringCondition::Result::kDefer); | 
 |   runner()->AddConditionForTesting(condition4.PassToDelegate()); | 
 |  | 
 |   runner()->ProcessChecks(); | 
 |  | 
 |   // The first should have been completed synchronously so we should have | 
 |   // invoked the second condition and are waiting on it now. | 
 |   EXPECT_FALSE(was_delegate_notified()); | 
 |   EXPECT_TRUE(condition1.WasInvoked()); | 
 |   EXPECT_TRUE(condition2.WasInvoked()); | 
 |   EXPECT_FALSE(condition3.WasInvoked()); | 
 |   EXPECT_FALSE(condition4.WasInvoked()); | 
 |   EXPECT_TRUE(is_deferring()); | 
 |  | 
 |   // Complete the second condition. The third is already completed so we should | 
 |   // synchronously skip to the fourth. | 
 |   condition2.CallResumeClosure(); | 
 |   EXPECT_FALSE(was_delegate_notified()); | 
 |   EXPECT_TRUE(condition3.WasInvoked()); | 
 |   EXPECT_TRUE(condition4.WasInvoked()); | 
 |   EXPECT_TRUE(is_deferring()); | 
 |  | 
 |   // Completing the final condition should notify the delegate. | 
 |   condition4.CallResumeClosure(); | 
 |   EXPECT_TRUE(was_delegate_notified()); | 
 |   EXPECT_FALSE(is_deferring()); | 
 | } | 
 |  | 
 | // Test with multiple conditions, some of which are completed synchronously and | 
 | // some asynchronously. The final condition is synchronous and should notify | 
 | // the delegate synchronously from resuming the last asynchronous condition. | 
 | TEST_F(CommitDeferringConditionRunnerTest, MultipleConditionsLastSync) { | 
 |   // Add conditions, alternating between those that are already satisfied at | 
 |   // ProcessChecks time and those that complete asynchronously. | 
 |   // Async -> Complete -> Async -> Complete | 
 |   MockHandleConditionWrapper condition1( | 
 |       CommitDeferringCondition::Result::kDefer); | 
 |   runner()->AddConditionForTesting(condition1.PassToDelegate()); | 
 |  | 
 |   MockHandleConditionWrapper condition2( | 
 |       CommitDeferringCondition::Result::kProceed); | 
 |   runner()->AddConditionForTesting(condition2.PassToDelegate()); | 
 |  | 
 |   MockHandleConditionWrapper condition3( | 
 |       CommitDeferringCondition::Result::kDefer); | 
 |   runner()->AddConditionForTesting(condition3.PassToDelegate()); | 
 |  | 
 |   MockHandleConditionWrapper condition4( | 
 |       CommitDeferringCondition::Result::kProceed); | 
 |   runner()->AddConditionForTesting(condition4.PassToDelegate()); | 
 |  | 
 |   runner()->ProcessChecks(); | 
 |  | 
 |   // The first condition is deferring asynchronously. | 
 |   EXPECT_FALSE(was_delegate_notified()); | 
 |   EXPECT_TRUE(condition1.WasInvoked()); | 
 |   EXPECT_FALSE(condition2.WasInvoked()); | 
 |   EXPECT_TRUE(is_deferring()); | 
 |  | 
 |   // Complete the first condition. The second is a synchronous condition so we | 
 |   // should now be awaiting completion of the third which is async. | 
 |   condition1.CallResumeClosure(); | 
 |   EXPECT_FALSE(was_delegate_notified()); | 
 |   EXPECT_TRUE(condition2.WasInvoked()); | 
 |   EXPECT_TRUE(condition3.WasInvoked()); | 
 |   EXPECT_FALSE(condition4.WasInvoked()); | 
 |   EXPECT_TRUE(is_deferring()); | 
 |  | 
 |   // Resuming from the third condition should synchronously complete the fourth | 
 |   // and then notify the delegate. | 
 |   condition3.CallResumeClosure(); | 
 |   EXPECT_TRUE(condition4.WasInvoked()); | 
 |   EXPECT_TRUE(was_delegate_notified()); | 
 |   EXPECT_FALSE(is_deferring()); | 
 | } | 
 |  | 
 | // Test with multiple conditions, with one indicating that the commit is | 
 | // cancelled invoked in the middle. | 
 | TEST_F(CommitDeferringConditionRunnerTest, MultipleConditionsWithCancelled) { | 
 |   MockHandleConditionWrapper condition1( | 
 |       CommitDeferringCondition::Result::kProceed); | 
 |   runner()->AddConditionForTesting(condition1.PassToDelegate()); | 
 |  | 
 |   MockHandleConditionWrapper condition2( | 
 |       CommitDeferringCondition::Result::kCancelled); | 
 |   runner()->AddConditionForTesting(condition2.PassToDelegate()); | 
 |  | 
 |   MockHandleConditionWrapper condition3( | 
 |       CommitDeferringCondition::Result::kProceed); | 
 |   runner()->AddConditionForTesting(condition3.PassToDelegate()); | 
 |  | 
 |   runner()->ProcessChecks(); | 
 |  | 
 |   // Only the first two conditions are invoked, as the commit is cancelled with | 
 |   // the second condition. | 
 |   EXPECT_FALSE(was_delegate_notified()); | 
 |   EXPECT_TRUE(condition1.WasInvoked()); | 
 |   EXPECT_TRUE(condition2.WasInvoked()); | 
 |   EXPECT_FALSE(condition3.WasInvoked()); | 
 |   EXPECT_FALSE(is_deferring()); | 
 | } | 
 |  | 
 | }  // namespace content |