| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/components/security_interstitials/ios_blocking_page_tab_helper.h" |
| |
| #include "ios/components/security_interstitials/ios_security_interstitial_page.h" |
| #import "ios/web/public/test/fakes/fake_navigation_context.h" |
| #import "ios/web/public/test/fakes/fake_web_state.h" |
| #include "testing/platform_test.h" |
| #include "url/gurl.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace security_interstitials { |
| |
| class TestInterstitialPage : public IOSSecurityInterstitialPage { |
| public: |
| // |*destroyed_tracker| is set to true in the destructor. |
| TestInterstitialPage(web::WebState* web_state, |
| const GURL& request_url, |
| bool* destroyed_tracker) |
| : IOSSecurityInterstitialPage(web_state, |
| request_url, |
| /*argument_name=*/nullptr), |
| destroyed_tracker_(destroyed_tracker) {} |
| |
| ~TestInterstitialPage() override { |
| if (destroyed_tracker_) |
| *destroyed_tracker_ = true; |
| } |
| |
| private: |
| void HandleScriptCommand(const base::DictionaryValue& message, |
| const GURL& origin_url, |
| bool user_is_interacting, |
| web::WebFrame* sender_frame) override {} |
| bool ShouldCreateNewNavigation() const override { return false; } |
| void PopulateInterstitialStrings( |
| base::DictionaryValue* load_time_data) const override {} |
| |
| bool* destroyed_tracker_ = nullptr; |
| }; |
| |
| class IOSBlockingPageTabHelperTest : public PlatformTest { |
| protected: |
| IOSBlockingPageTabHelperTest() { |
| IOSBlockingPageTabHelper::CreateForWebState(&web_state_); |
| } |
| |
| std::unique_ptr<web::NavigationContext> CreateContext(bool committed, |
| bool is_same_document) { |
| std::unique_ptr<web::FakeNavigationContext> context = |
| std::make_unique<web::FakeNavigationContext>(); |
| context->SetHasCommitted(committed); |
| context->SetIsSameDocument(is_same_document); |
| return context; |
| } |
| |
| IOSBlockingPageTabHelper* helper() { |
| return IOSBlockingPageTabHelper::FromWebState(&web_state_); |
| } |
| |
| // Creates a blocking page and associates it with |context|'s navigation ID |
| // in the tab helper. Returns the created blocking page. |destroyed_tracker| |
| // is an out-parameter that is reset to true when the blocking page is |
| // destroyed. |
| IOSSecurityInterstitialPage* CreateAssociatedBlockingPage( |
| web::NavigationContext* context, |
| bool* destroyed_tracker) { |
| std::unique_ptr<IOSSecurityInterstitialPage> passed_blocking_page = |
| std::make_unique<TestInterstitialPage>(&web_state_, GURL(), |
| destroyed_tracker); |
| IOSSecurityInterstitialPage* blocking_page = passed_blocking_page.get(); |
| helper()->AssociateBlockingPage(context->GetNavigationId(), |
| std::move(passed_blocking_page)); |
| return blocking_page; |
| } |
| |
| web::FakeWebState web_state_; |
| }; |
| |
| // Tests that the helper properly handles the lifetime of a single blocking |
| // page, interleaved with other navigations. |
| TEST_F(IOSBlockingPageTabHelperTest, SingleBlockingPage) { |
| std::unique_ptr<web::NavigationContext> blocking_page_context = |
| CreateContext(/*committed=*/true, /*is_same_document=*/false); |
| bool blocking_page_destroyed = false; |
| CreateAssociatedBlockingPage(blocking_page_context.get(), |
| &blocking_page_destroyed); |
| |
| // Test that a same-document navigation doesn't destroy the blocking page if |
| // its navigation hasn't committed yet. |
| std::unique_ptr<web::NavigationContext> same_document_context = |
| CreateContext(/*committed=*/true, /*is_same_document=*/true); |
| web_state_.OnNavigationFinished(same_document_context.get()); |
| EXPECT_FALSE(blocking_page_destroyed); |
| |
| // Test that a committed (non-same-document) navigation doesn't destroy the |
| // blocking page if its navigation hasn't committed yet. |
| std::unique_ptr<web::NavigationContext> committed_context1 = |
| CreateContext(/*committed=*/true, /*is_same_document=*/false); |
| web_state_.OnNavigationFinished(committed_context1.get()); |
| EXPECT_FALSE(blocking_page_destroyed); |
| |
| // Simulate committing the interstitial. |
| web_state_.OnNavigationFinished(blocking_page_context.get()); |
| EXPECT_FALSE(blocking_page_destroyed); |
| |
| // Test that a subsequent committed navigation releases the blocking page |
| // stored for the currently committed navigation. |
| std::unique_ptr<web::NavigationContext> committed_context2 = |
| CreateContext(/*committed=*/true, /*is_same_document=*/false); |
| web_state_.OnNavigationFinished(committed_context2.get()); |
| EXPECT_TRUE(blocking_page_destroyed); |
| } |
| |
| // Tests that the helper properly handles the lifetime of multiple blocking |
| // pages, committed in a different order than they are created. |
| TEST_F(IOSBlockingPageTabHelperTest, MultipleBlockingPage) { |
| // Simulate associating the first interstitial. |
| std::unique_ptr<web::NavigationContext> context1 = |
| CreateContext(/*committed=*/true, /*is_same_document=*/false); |
| bool blocking_page1_destroyed = false; |
| CreateAssociatedBlockingPage(context1.get(), &blocking_page1_destroyed); |
| |
| // Simulate commiting the first interstitial. |
| web_state_.OnNavigationFinished(context1.get()); |
| EXPECT_FALSE(blocking_page1_destroyed); |
| |
| // Associate the second interstitial. |
| std::unique_ptr<web::NavigationContext> context2 = |
| CreateContext(/*committed=*/true, /*is_same_document=*/false); |
| bool blocking_page2_destroyed = false; |
| CreateAssociatedBlockingPage(context2.get(), &blocking_page2_destroyed); |
| EXPECT_FALSE(blocking_page1_destroyed); |
| EXPECT_FALSE(blocking_page2_destroyed); |
| |
| // Associate the third interstitial. |
| std::unique_ptr<web::NavigationContext> context3 = |
| CreateContext(/*committed=*/true, /*is_same_document=*/false); |
| bool blocking_page3_destroyed = false; |
| CreateAssociatedBlockingPage(context3.get(), &blocking_page3_destroyed); |
| EXPECT_FALSE(blocking_page1_destroyed); |
| EXPECT_FALSE(blocking_page2_destroyed); |
| EXPECT_FALSE(blocking_page3_destroyed); |
| |
| // Simulate committing the third interstitial. |
| web_state_.OnNavigationFinished(context3.get()); |
| EXPECT_TRUE(blocking_page1_destroyed); |
| EXPECT_FALSE(blocking_page2_destroyed); |
| EXPECT_FALSE(blocking_page3_destroyed); |
| |
| // Simulate committing the second interstitial. |
| web_state_.OnNavigationFinished(context2.get()); |
| EXPECT_TRUE(blocking_page1_destroyed); |
| EXPECT_FALSE(blocking_page2_destroyed); |
| EXPECT_TRUE(blocking_page3_destroyed); |
| |
| // Test that a subsequent committed navigation releases the last blocking |
| // page. |
| std::unique_ptr<web::NavigationContext> committed_context4 = |
| CreateContext(/*committed=*/true, /*is_same_document=*/false); |
| web_state_.OnNavigationFinished(committed_context4.get()); |
| EXPECT_TRUE(blocking_page2_destroyed); |
| } |
| |
| // Tests that the helper properly handles a navigation that finishes without |
| // committing. |
| TEST_F(IOSBlockingPageTabHelperTest, NavigationDoesNotCommit) { |
| std::unique_ptr<web::NavigationContext> committed_context = |
| CreateContext(/*committed=*/true, /*is_same_document=*/false); |
| bool committed_blocking_page_destroyed = false; |
| CreateAssociatedBlockingPage(committed_context.get(), |
| &committed_blocking_page_destroyed); |
| web_state_.OnNavigationFinished(committed_context.get()); |
| EXPECT_FALSE(committed_blocking_page_destroyed); |
| |
| // Simulate a navigation that does not commit. |
| std::unique_ptr<web::NavigationContext> non_committed_context = |
| CreateContext(/*committed=*/false, /*is_same_document=*/false); |
| bool non_committed_blocking_page_destroyed = false; |
| CreateAssociatedBlockingPage(non_committed_context.get(), |
| &non_committed_blocking_page_destroyed); |
| web_state_.OnNavigationFinished(non_committed_context.get()); |
| |
| // The blocking page for the non-committed navigation should have been cleaned |
| // up, but the one for the previous committed navigation should still be |
| // around. |
| EXPECT_TRUE(non_committed_blocking_page_destroyed); |
| EXPECT_FALSE(committed_blocking_page_destroyed); |
| |
| // When a navigation does commit, the previous one should be cleaned up. |
| std::unique_ptr<web::NavigationContext> next_committed_context = |
| CreateContext(/*committed=*/true, /*is_same_document=*/false); |
| web_state_.OnNavigationFinished(next_committed_context.get()); |
| EXPECT_TRUE(committed_blocking_page_destroyed); |
| } |
| |
| // Tests that a blocking page that is associated with a navigation ID after the |
| // navigation is committed is correctly used as the current blocking page for |
| // the last commited navigation ID. |
| TEST_F(IOSBlockingPageTabHelperTest, BlockingPageAssociatedAfterCommit) { |
| // Commit the navigation, then associate the blocking page. |
| std::unique_ptr<web::NavigationContext> context = |
| CreateContext(/*committed=*/true, /*is_same_document=*/false); |
| web_state_.OnNavigationFinished(context.get()); |
| IOSSecurityInterstitialPage* page = |
| CreateAssociatedBlockingPage(context.get(), nullptr); |
| |
| // Verify that the blocking page is used as the current page. |
| EXPECT_EQ(page, helper()->GetCurrentBlockingPage()); |
| } |
| |
| } // namespace security_interstitials |