| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/web/web_state/web_state_impl.h" |
| |
| #import <stddef.h> |
| |
| #import <memory> |
| |
| #import "base/apple/foundation_util.h" |
| #import "base/base64.h" |
| #import "base/functional/bind.h" |
| #import "base/functional/callback_helpers.h" |
| #import "base/logging.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/task/sequenced_task_runner.h" |
| #import "base/test/gmock_callback_support.h" |
| #import "base/test/ios/wait_util.h" |
| #import "components/sessions/core/session_id.h" |
| #import "ios/web/common/uikit_ui_util.h" |
| #import "ios/web/js_messaging/web_frames_manager_impl.h" |
| #import "ios/web/navigation/navigation_context_impl.h" |
| #import "ios/web/navigation/navigation_item_impl.h" |
| #import "ios/web/navigation/serializable_user_data_manager_impl.h" |
| #import "ios/web/navigation/wk_navigation_util.h" |
| #import "ios/web/public/deprecated/global_web_state_observer.h" |
| #import "ios/web/public/navigation/navigation_item.h" |
| #import "ios/web/public/navigation/navigation_util.h" |
| #import "ios/web/public/navigation/web_state_policy_decider.h" |
| #import "ios/web/public/session/crw_navigation_item_storage.h" |
| #import "ios/web/public/session/crw_session_storage.h" |
| #import "ios/web/public/session/proto/proto_util.h" |
| #import "ios/web/public/session/proto/storage.pb.h" |
| #import "ios/web/public/session/serializable_user_data_manager.h" |
| #import "ios/web/public/test/fakes/async_web_state_policy_decider.h" |
| #import "ios/web/public/test/fakes/fake_java_script_dialog_presenter.h" |
| #import "ios/web/public/test/fakes/fake_navigation_context.h" |
| #import "ios/web/public/test/fakes/fake_web_frame.h" |
| #import "ios/web/public/test/fakes/fake_web_state_delegate.h" |
| #import "ios/web/public/test/fakes/fake_web_state_observer.h" |
| #import "ios/web/public/test/web_test.h" |
| #import "ios/web/public/test/web_view_content_test_util.h" |
| #import "ios/web/public/ui/context_menu_params.h" |
| #import "ios/web/public/ui/java_script_dialog_presenter.h" |
| #import "ios/web/public/web_state_delegate.h" |
| #import "ios/web/public/web_state_observer.h" |
| #import "ios/web/web_state/global_web_state_event_tracker.h" |
| #import "ios/web/web_state/ui/crw_web_controller.h" |
| #import "ios/web/web_state/web_state_policy_decider_test_util.h" |
| #import "net/http/http_response_headers.h" |
| #import "net/http/http_util.h" |
| #import "testing/gmock/include/gmock/gmock.h" |
| #import "testing/gtest/include/gtest/gtest.h" |
| #import "testing/gtest_mac.h" |
| #import "url/gurl.h" |
| |
| using testing::_; |
| using testing::Assign; |
| using testing::AtMost; |
| using testing::DoAll; |
| using testing::Return; |
| using base::test::RunOnceCallback; |
| using base::test::ios::WaitUntilConditionOrTimeout; |
| using base::test::ios::kWaitForPageLoadTimeout; |
| |
| namespace web { |
| namespace { |
| |
| // Test observer to check that the GlobalWebStateObserver methods are called as |
| // expected. |
| class TestGlobalWebStateObserver : public GlobalWebStateObserver { |
| public: |
| TestGlobalWebStateObserver() |
| : GlobalWebStateObserver(), |
| did_start_loading_called_(false), |
| did_stop_loading_called_(false), |
| did_start_navigation_called_(false), |
| web_state_destroyed_called_(false) {} |
| |
| // Methods returning true if the corresponding GlobalWebStateObserver method |
| // has been called. |
| bool did_start_loading_called() const { return did_start_loading_called_; } |
| bool did_stop_loading_called() const { return did_stop_loading_called_; } |
| bool did_start_navigation_called() const { |
| return did_start_navigation_called_; |
| } |
| bool web_state_destroyed_called() const { |
| return web_state_destroyed_called_; |
| } |
| |
| private: |
| // GlobalWebStateObserver implementation: |
| void WebStateDidStartLoading(WebState* web_state) override { |
| did_start_loading_called_ = true; |
| } |
| void WebStateDidStopLoading(WebState* web_state) override { |
| did_stop_loading_called_ = true; |
| } |
| void WebStateDidStartNavigation( |
| WebState* web_state, |
| NavigationContext* navigation_context) override { |
| did_start_navigation_called_ = true; |
| } |
| void WebStateDestroyed(WebState* web_state) override { |
| web_state_destroyed_called_ = true; |
| } |
| |
| bool did_start_loading_called_; |
| bool did_stop_loading_called_; |
| bool did_start_navigation_called_; |
| bool web_state_destroyed_called_; |
| }; |
| |
| // Test decider to check that the WebStatePolicyDecider methods are called as |
| // expected. |
| class MockWebStatePolicyDecider : public WebStatePolicyDecider { |
| public: |
| explicit MockWebStatePolicyDecider(WebState* web_state) |
| : WebStatePolicyDecider(web_state) {} |
| ~MockWebStatePolicyDecider() override {} |
| |
| MOCK_METHOD3(ShouldAllowRequest, |
| void(NSURLRequest* request, |
| WebStatePolicyDecider::RequestInfo request_info, |
| WebStatePolicyDecider::PolicyDecisionCallback callback)); |
| MOCK_METHOD3(ShouldAllowResponse, |
| void(NSURLResponse* response, |
| WebStatePolicyDecider::ResponseInfo response_info, |
| WebStatePolicyDecider::PolicyDecisionCallback callback)); |
| MOCK_METHOD0(WebStateDestroyed, void()); |
| }; |
| |
| } // namespace |
| |
| // Test fixture for web::WebStateImpl class. |
| class WebStateImplTest : public web::WebTest { |
| public: |
| void SetUp() override { |
| WebTest::SetUp(); |
| |
| IgnoreOverRealizationCheck(); |
| } |
| }; |
| |
| // Tests WebState::GetWeakPtr. |
| TEST_F(WebStateImplTest, GetWeakPtr) { |
| // Create a WebState as a unique pointer to allow destruction. |
| std::unique_ptr<WebStateImpl> web_state = |
| std::make_unique<WebStateImpl>(WebState::CreateParams(GetBrowserState())); |
| |
| // Verify that `weak_ptr` points to `web_state`. |
| base::WeakPtr<WebState> weak_ptr = web_state->GetWeakPtr(); |
| EXPECT_EQ(weak_ptr.get(), web_state.get()); |
| |
| // Verify that `weak_ptr` is null after `web_state` destruction. |
| web_state.reset(); |
| EXPECT_EQ(weak_ptr.get(), nullptr); |
| } |
| |
| TEST_F(WebStateImplTest, WebUsageEnabled) { |
| // Default is false. |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| ASSERT_TRUE(web_state.IsWebUsageEnabled()); |
| |
| web_state.SetWebUsageEnabled(false); |
| EXPECT_FALSE(web_state.IsWebUsageEnabled()); |
| EXPECT_FALSE(web_state.GetWebController().webUsageEnabled); |
| |
| web_state.SetWebUsageEnabled(true); |
| EXPECT_TRUE(web_state.IsWebUsageEnabled()); |
| EXPECT_TRUE(web_state.GetWebController().webUsageEnabled); |
| } |
| |
| // Tests forwarding to WebStateObserver callbacks. |
| TEST_F(WebStateImplTest, ObserverTest) { |
| // Create a WebState as a unique pointer to allow destruction. |
| std::unique_ptr<WebStateImpl> web_state = |
| std::make_unique<WebStateImpl>(WebState::CreateParams(GetBrowserState())); |
| |
| auto observer = std::make_unique<FakeWebStateObserver>(web_state.get()); |
| EXPECT_EQ(web_state.get(), observer->web_state()); |
| |
| // Test that WasShown() is called. |
| ASSERT_FALSE(web_state->IsVisible()); |
| ASSERT_FALSE(observer->was_shown_info()); |
| web_state->WasShown(); |
| ASSERT_TRUE(observer->was_shown_info()); |
| EXPECT_EQ(web_state.get(), observer->was_shown_info()->web_state); |
| EXPECT_TRUE(web_state->IsVisible()); |
| |
| // Test that WasShown() callback is not called for the second time. |
| observer = std::make_unique<FakeWebStateObserver>(web_state.get()); |
| web_state->WasShown(); |
| EXPECT_FALSE(observer->was_shown_info()); |
| |
| // Test that WasHidden() is called. |
| ASSERT_TRUE(web_state->IsVisible()); |
| ASSERT_FALSE(observer->was_hidden_info()); |
| web_state->WasHidden(); |
| ASSERT_TRUE(observer->was_hidden_info()); |
| EXPECT_EQ(web_state.get(), observer->was_hidden_info()->web_state); |
| EXPECT_FALSE(web_state->IsVisible()); |
| |
| // Test that WasHidden() callback is not called for the second time. |
| observer = std::make_unique<FakeWebStateObserver>(web_state.get()); |
| web_state->WasHidden(); |
| EXPECT_FALSE(observer->was_hidden_info()); |
| |
| // Test that LoadProgressChanged() is called. |
| ASSERT_FALSE(observer->change_loading_progress_info()); |
| const double kTestLoadProgress = 0.75; |
| web_state->SendChangeLoadProgress(kTestLoadProgress); |
| ASSERT_TRUE(observer->change_loading_progress_info()); |
| EXPECT_EQ(web_state.get(), |
| observer->change_loading_progress_info()->web_state); |
| EXPECT_EQ(kTestLoadProgress, |
| observer->change_loading_progress_info()->progress); |
| |
| // Test that TitleWasSet() is called. |
| ASSERT_FALSE(observer->title_was_set_info()); |
| web_state->OnTitleChanged(); |
| ASSERT_TRUE(observer->title_was_set_info()); |
| EXPECT_EQ(web_state.get(), observer->title_was_set_info()->web_state); |
| |
| // Test that DidChangeVisibleSecurityState() is called. |
| ASSERT_FALSE(observer->did_change_visible_security_state_info()); |
| web_state->DidChangeVisibleSecurityState(); |
| ASSERT_TRUE(observer->did_change_visible_security_state_info()); |
| EXPECT_EQ(web_state.get(), |
| observer->did_change_visible_security_state_info()->web_state); |
| |
| // Test that FaviconUrlUpdated() is called. |
| ASSERT_FALSE(observer->update_favicon_url_candidates_info()); |
| web::FaviconURL favicon_url(GURL("https://chromium.test/"), |
| web::FaviconURL::IconType::kTouchIcon, |
| {gfx::Size(5, 6)}); |
| web_state->OnFaviconUrlUpdated({favicon_url}); |
| ASSERT_TRUE(observer->update_favicon_url_candidates_info()); |
| EXPECT_EQ(web_state.get(), |
| observer->update_favicon_url_candidates_info()->web_state); |
| ASSERT_EQ(1U, |
| observer->update_favicon_url_candidates_info()->candidates.size()); |
| const web::FaviconURL& actual_favicon_url = |
| observer->update_favicon_url_candidates_info()->candidates[0]; |
| EXPECT_EQ(favicon_url.icon_url, actual_favicon_url.icon_url); |
| EXPECT_EQ(favicon_url.icon_type, actual_favicon_url.icon_type); |
| ASSERT_EQ(favicon_url.icon_sizes.size(), |
| actual_favicon_url.icon_sizes.size()); |
| EXPECT_EQ(favicon_url.icon_sizes[0].width(), |
| actual_favicon_url.icon_sizes[0].width()); |
| EXPECT_EQ(favicon_url.icon_sizes[0].height(), |
| actual_favicon_url.icon_sizes[0].height()); |
| |
| // Test that RenderProcessGone() is called. |
| SetIgnoreRenderProcessCrashesDuringTesting(true); |
| ASSERT_FALSE(observer->render_process_gone_info()); |
| web_state->OnRenderProcessGone(); |
| ASSERT_TRUE(observer->render_process_gone_info()); |
| EXPECT_EQ(web_state.get(), observer->render_process_gone_info()->web_state); |
| |
| // Test that DidFinishNavigation() is called. |
| ASSERT_FALSE(observer->did_finish_navigation_info()); |
| const GURL url("http://test"); |
| std::unique_ptr<NavigationContextImpl> context = |
| NavigationContextImpl::CreateNavigationContext( |
| web_state.get(), url, /*has_user_gesture=*/true, |
| ui::PageTransition::PAGE_TRANSITION_AUTO_BOOKMARK, |
| /*is_renderer_initiated=*/true); |
| web_state->OnNavigationFinished(context.get()); |
| ASSERT_TRUE(observer->did_finish_navigation_info()); |
| EXPECT_EQ(web_state.get(), observer->did_finish_navigation_info()->web_state); |
| NavigationContext* actual_context = |
| observer->did_finish_navigation_info()->context.get(); |
| EXPECT_EQ(context->GetUrl(), actual_context->GetUrl()); |
| EXPECT_TRUE(PageTransitionTypeIncludingQualifiersIs( |
| context->GetPageTransition(), actual_context->GetPageTransition())); |
| EXPECT_FALSE(actual_context->IsSameDocument()); |
| EXPECT_FALSE(actual_context->IsPost()); |
| EXPECT_FALSE(actual_context->GetError()); |
| EXPECT_FALSE(actual_context->GetResponseHeaders()); |
| |
| // Test that DidStartNavigation() is called. |
| ASSERT_FALSE(observer->did_start_navigation_info()); |
| web_state->OnNavigationStarted(context.get()); |
| ASSERT_TRUE(observer->did_start_navigation_info()); |
| EXPECT_EQ(web_state.get(), observer->did_start_navigation_info()->web_state); |
| actual_context = observer->did_start_navigation_info()->context.get(); |
| EXPECT_EQ(context->GetUrl(), actual_context->GetUrl()); |
| EXPECT_TRUE(PageTransitionTypeIncludingQualifiersIs( |
| context->GetPageTransition(), actual_context->GetPageTransition())); |
| EXPECT_FALSE(actual_context->IsSameDocument()); |
| EXPECT_FALSE(actual_context->IsPost()); |
| EXPECT_FALSE(actual_context->GetError()); |
| EXPECT_FALSE(actual_context->GetResponseHeaders()); |
| |
| // Test that OnPageLoaded() is called with success when there is no error. |
| ASSERT_FALSE(observer->load_page_info()); |
| web_state->OnPageLoaded(url, false); |
| ASSERT_TRUE(observer->load_page_info()); |
| EXPECT_EQ(web_state.get(), observer->load_page_info()->web_state); |
| EXPECT_FALSE(observer->load_page_info()->success); |
| web_state->OnPageLoaded(url, true); |
| ASSERT_TRUE(observer->load_page_info()); |
| EXPECT_EQ(web_state.get(), observer->load_page_info()->web_state); |
| EXPECT_TRUE(observer->load_page_info()->success); |
| |
| // Test that OnTitleChanged() is called. |
| observer = std::make_unique<FakeWebStateObserver>(web_state.get()); |
| ASSERT_FALSE(observer->title_was_set_info()); |
| web_state->OnTitleChanged(); |
| ASSERT_TRUE(observer->title_was_set_info()); |
| EXPECT_EQ(web_state.get(), observer->title_was_set_info()->web_state); |
| |
| // Test that WebStateDestroyed() is called. |
| EXPECT_FALSE(observer->web_state_destroyed_info()); |
| web_state.reset(); |
| EXPECT_TRUE(observer->web_state_destroyed_info()); |
| |
| EXPECT_EQ(nullptr, observer->web_state()); |
| } |
| |
| // Tests that WebStateDelegate methods appropriately called. |
| TEST_F(WebStateImplTest, DelegateTest) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| FakeWebStateDelegate delegate; |
| web_state.SetDelegate(&delegate); |
| |
| // Test that CreateNewWebState() is called. |
| GURL child_url("https://child.test/"); |
| GURL opener_url("https://opener.test/"); |
| EXPECT_FALSE(delegate.last_create_new_web_state_request()); |
| web_state.CreateNewWebState(child_url, opener_url, true); |
| FakeCreateNewWebStateRequest* create_new_web_state_request = |
| delegate.last_create_new_web_state_request(); |
| ASSERT_TRUE(create_new_web_state_request); |
| EXPECT_EQ(&web_state, create_new_web_state_request->web_state); |
| EXPECT_EQ(child_url, create_new_web_state_request->url); |
| EXPECT_EQ(opener_url, create_new_web_state_request->opener_url); |
| EXPECT_TRUE(create_new_web_state_request->initiated_by_user); |
| |
| // Test that CloseWebState() is called. |
| EXPECT_FALSE(delegate.last_close_web_state_request()); |
| web_state.CloseWebState(); |
| ASSERT_TRUE(delegate.last_close_web_state_request()); |
| EXPECT_EQ(&web_state, delegate.last_close_web_state_request()->web_state); |
| |
| // Test that OpenURLFromWebState() is called without a virtual URL. |
| WebState::OpenURLParams params(GURL("https://chromium.test/"), Referrer(), |
| WindowOpenDisposition::CURRENT_TAB, |
| ui::PAGE_TRANSITION_LINK, true); |
| EXPECT_FALSE(delegate.last_open_url_request()); |
| web_state.OpenURL(params); |
| FakeOpenURLRequest* open_url_request = delegate.last_open_url_request(); |
| ASSERT_TRUE(open_url_request); |
| EXPECT_EQ(&web_state, open_url_request->web_state); |
| WebState::OpenURLParams actual_params = open_url_request->params; |
| EXPECT_EQ(params.url, actual_params.url); |
| EXPECT_EQ(GURL(), params.virtual_url); |
| EXPECT_EQ(GURL(), actual_params.virtual_url); |
| EXPECT_EQ(params.referrer.url, actual_params.referrer.url); |
| EXPECT_EQ(params.referrer.policy, actual_params.referrer.policy); |
| EXPECT_EQ(params.disposition, actual_params.disposition); |
| EXPECT_TRUE( |
| PageTransitionCoreTypeIs(params.transition, actual_params.transition)); |
| EXPECT_EQ(params.is_renderer_initiated, actual_params.is_renderer_initiated); |
| |
| // Test that OpenURLFromWebState() is called with a virtual URL. |
| params = WebState::OpenURLParams( |
| GURL("https://chromium.test/"), GURL("https://virtual.chromium.test/"), |
| Referrer(), WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_LINK, |
| true); |
| web_state.OpenURL(params); |
| open_url_request = delegate.last_open_url_request(); |
| ASSERT_TRUE(open_url_request); |
| EXPECT_EQ(&web_state, open_url_request->web_state); |
| actual_params = open_url_request->params; |
| EXPECT_EQ(params.url, actual_params.url); |
| EXPECT_EQ(params.virtual_url, actual_params.virtual_url); |
| EXPECT_EQ(params.referrer.url, actual_params.referrer.url); |
| EXPECT_EQ(params.referrer.policy, actual_params.referrer.policy); |
| EXPECT_EQ(params.disposition, actual_params.disposition); |
| EXPECT_TRUE( |
| PageTransitionCoreTypeIs(params.transition, actual_params.transition)); |
| EXPECT_EQ(params.is_renderer_initiated, actual_params.is_renderer_initiated); |
| |
| // Test that ShowRepostFormWarningDialog() is called. |
| EXPECT_FALSE(delegate.last_repost_form_request()); |
| base::OnceCallback<void(bool)> repost_callback; |
| web_state.ShowRepostFormWarningDialog(web::FormWarningType::kRepost, |
| std::move(repost_callback)); |
| ASSERT_TRUE(delegate.last_repost_form_request()); |
| EXPECT_EQ(delegate.last_repost_form_request()->web_state, &web_state); |
| |
| // TODO(crbug.com/1501150): Check web::FormWarningType::kInsecureForm as well. |
| |
| // Test that GetJavaScriptDialogPresenter() is called. |
| FakeJavaScriptDialogPresenter* presenter = |
| delegate.GetFakeJavaScriptDialogPresenter(); |
| EXPECT_FALSE(delegate.get_java_script_dialog_presenter_called()); |
| EXPECT_TRUE(presenter->requested_alert_dialogs().empty()); |
| EXPECT_FALSE(presenter->cancel_dialogs_called()); |
| |
| __block bool callback_called = false; |
| web_state.RunJavaScriptAlertDialog(GURL(), @"", base::BindOnce(^() { |
| callback_called = true; |
| })); |
| |
| EXPECT_TRUE(delegate.get_java_script_dialog_presenter_called()); |
| EXPECT_EQ(1U, presenter->requested_alert_dialogs().size()); |
| EXPECT_TRUE(callback_called); |
| |
| EXPECT_FALSE(presenter->cancel_dialogs_called()); |
| web_state.CancelDialogs(); |
| EXPECT_TRUE(presenter->cancel_dialogs_called()); |
| |
| // Test that OnAuthRequired() is called. |
| EXPECT_FALSE(delegate.last_authentication_request()); |
| NSURLProtectionSpace* protection_space = [[NSURLProtectionSpace alloc] init]; |
| NSURLCredential* credential = [[NSURLCredential alloc] init]; |
| WebStateDelegate::AuthCallback callback; |
| web_state.OnAuthRequired(protection_space, credential, std::move(callback)); |
| ASSERT_TRUE(delegate.last_authentication_request()); |
| EXPECT_EQ(delegate.last_authentication_request()->web_state, &web_state); |
| EXPECT_EQ(delegate.last_authentication_request()->protection_space, |
| protection_space); |
| EXPECT_EQ(delegate.last_authentication_request()->credential, credential); |
| } |
| |
| // Verifies that GlobalWebStateObservers are called when expected. |
| TEST_F(WebStateImplTest, GlobalObserverTest) { |
| // Create a WebState as a unique pointer to allow destruction. |
| std::unique_ptr<WebStateImpl> web_state = |
| std::make_unique<WebStateImpl>(WebState::CreateParams(GetBrowserState())); |
| |
| std::unique_ptr<TestGlobalWebStateObserver> observer = |
| std::make_unique<TestGlobalWebStateObserver>(); |
| |
| // Test that DidStartNavigation() is called. |
| EXPECT_FALSE(observer->did_start_navigation_called()); |
| std::unique_ptr<NavigationContextImpl> context = |
| NavigationContextImpl::CreateNavigationContext( |
| web_state.get(), GURL(), /*has_user_gesture=*/true, |
| ui::PageTransition::PAGE_TRANSITION_AUTO_BOOKMARK, |
| /*is_renderer_initiated=*/true); |
| web_state->OnNavigationStarted(context.get()); |
| EXPECT_TRUE(observer->did_start_navigation_called()); |
| |
| // Test that WebStateDidStartLoading() is called. |
| EXPECT_FALSE(observer->did_start_loading_called()); |
| web_state->SetIsLoading(true); |
| EXPECT_TRUE(observer->did_start_loading_called()); |
| |
| // Test that WebStateDidStopLoading() is called. |
| EXPECT_FALSE(observer->did_stop_loading_called()); |
| web_state->SetIsLoading(false); |
| EXPECT_TRUE(observer->did_stop_loading_called()); |
| |
| // Test that WebStateDestroyed() is called. |
| EXPECT_FALSE(observer->web_state_destroyed_called()); |
| web_state.reset(); |
| EXPECT_TRUE(observer->web_state_destroyed_called()); |
| } |
| |
| // A Google Mock matcher which matches WebStatePolicyDecider::RequestInfo. |
| // This is needed because WebStatePolicyDecider::RequestInfo doesn't support |
| // operator==. |
| MATCHER_P(RequestInfoMatch, expected_request_info, /* argument_name = */ "") { |
| return ::web::RequestInfoMatch(expected_request_info, arg); |
| } |
| |
| // A Google Mock matcher which matches WebStatePolicyDecider::ResponseInfo. |
| // This is needed because WebStatePolicyDecider::ResponseInfo doesn't support |
| // operator==. |
| MATCHER_P(ResponseInfoMatch, expected_response_info, /* argument_name = */ "") { |
| return ::web::ResponseInfoMatch(expected_response_info, arg); |
| } |
| |
| // Verifies that policy deciders are correctly called by the web state. |
| TEST_F(WebStateImplTest, PolicyDeciderTest) { |
| // Create a WebState as a unique pointer to allow destruction. |
| std::unique_ptr<WebStateImpl> web_state = |
| std::make_unique<WebStateImpl>(WebState::CreateParams(GetBrowserState())); |
| |
| MockWebStatePolicyDecider decider(web_state.get()); |
| MockWebStatePolicyDecider decider2(web_state.get()); |
| EXPECT_EQ(web_state.get(), decider.web_state()); |
| |
| NSURL* url = [NSURL URLWithString:@"http://example.com"]; |
| NSURLRequest* request = [NSURLRequest requestWithURL:url]; |
| NSURLResponse* response = [[NSURLResponse alloc] initWithURL:url |
| MIMEType:@"text/html" |
| expectedContentLength:0 |
| textEncodingName:nil]; |
| |
| // Test that ShouldAllowRequest() is called for the same parameters. |
| const WebStatePolicyDecider::RequestInfo request_info_main_frame( |
| ui::PageTransition::PAGE_TRANSITION_LINK, |
| /*target_main_frame=*/true, |
| /*target_frame_is_cross_origin=*/false, |
| /*target_window_is_cross_origin=*/false, |
| /*is_user_initiated=*/false, /*user_tapped_recently=*/false); |
| EXPECT_CALL( |
| decider, |
| ShouldAllowRequest(request, RequestInfoMatch(request_info_main_frame), _)) |
| .Times(1) |
| .WillOnce( |
| RunOnceCallback<2>(WebStatePolicyDecider::PolicyDecision::Allow())); |
| EXPECT_CALL( |
| decider2, |
| ShouldAllowRequest(request, RequestInfoMatch(request_info_main_frame), _)) |
| .Times(1) |
| .WillOnce( |
| RunOnceCallback<2>(WebStatePolicyDecider::PolicyDecision::Allow())); |
| |
| WebStatePolicyDecider::PolicyDecision policy_decision = |
| WebStatePolicyDecider::PolicyDecision::Cancel(); |
| auto callback = base::BindRepeating( |
| [](WebStatePolicyDecider::PolicyDecision* policy_decision, |
| WebStatePolicyDecider::PolicyDecision result) { |
| *policy_decision = result; |
| }, |
| base::Unretained(&policy_decision)); |
| web_state->ShouldAllowRequest(request, request_info_main_frame, callback); |
| EXPECT_TRUE(policy_decision.ShouldAllowNavigation()); |
| EXPECT_FALSE(policy_decision.ShouldCancelNavigation()); |
| |
| const WebStatePolicyDecider::RequestInfo request_info_iframe( |
| ui::PageTransition::PAGE_TRANSITION_LINK, |
| /*target_main_frame=*/false, |
| /*target_frame_is_cross_origin=*/false, |
| /*target_window_is_cross_origin=*/false, |
| /*is_user_initiated=*/false, /*user_tapped_recently=*/false); |
| EXPECT_CALL(decider, ShouldAllowRequest( |
| request, RequestInfoMatch(request_info_iframe), _)) |
| .Times(1) |
| .WillOnce( |
| RunOnceCallback<2>(WebStatePolicyDecider::PolicyDecision::Allow())); |
| EXPECT_CALL(decider2, ShouldAllowRequest( |
| request, RequestInfoMatch(request_info_iframe), _)) |
| .Times(1) |
| .WillOnce( |
| RunOnceCallback<2>(WebStatePolicyDecider::PolicyDecision::Allow())); |
| |
| web_state->ShouldAllowRequest(request, request_info_iframe, callback); |
| EXPECT_TRUE(policy_decision.ShouldAllowNavigation()); |
| EXPECT_FALSE(policy_decision.ShouldCancelNavigation()); |
| |
| // Test that ShouldAllowRequest() is stopping on negative answer. Only one |
| // of the deciders should be called. |
| { |
| bool decider_called = false; |
| bool decider2_called = false; |
| EXPECT_CALL(decider, |
| ShouldAllowRequest( |
| request, RequestInfoMatch(request_info_main_frame), _)) |
| .Times(AtMost(1)) |
| .WillOnce(DoAll(Assign(&decider_called, true), |
| RunOnceCallback<2>( |
| WebStatePolicyDecider::PolicyDecision::Cancel()))); |
| EXPECT_CALL(decider2, |
| ShouldAllowRequest( |
| request, RequestInfoMatch(request_info_main_frame), _)) |
| .Times(AtMost(1)) |
| .WillOnce(DoAll(Assign(&decider2_called, true), |
| RunOnceCallback<2>( |
| WebStatePolicyDecider::PolicyDecision::Cancel()))); |
| |
| web_state->ShouldAllowRequest(request, request_info_main_frame, callback); |
| EXPECT_FALSE(policy_decision.ShouldAllowNavigation()); |
| EXPECT_TRUE(policy_decision.ShouldCancelNavigation()); |
| EXPECT_FALSE(decider_called && decider2_called); |
| } |
| |
| const WebStatePolicyDecider::ResponseInfo response_info_main_frame( |
| /* for_main_frame = */ true); |
| |
| // Test that ShouldAllowResponse() is called. |
| EXPECT_CALL(decider, |
| ShouldAllowResponse( |
| response, ResponseInfoMatch(response_info_main_frame), _)) |
| .Times(1) |
| .WillOnce( |
| RunOnceCallback<2>(WebStatePolicyDecider::PolicyDecision::Allow())); |
| EXPECT_CALL(decider2, |
| ShouldAllowResponse( |
| response, ResponseInfoMatch(response_info_main_frame), _)) |
| .Times(1) |
| .WillOnce( |
| RunOnceCallback<2>(WebStatePolicyDecider::PolicyDecision::Allow())); |
| |
| web_state->ShouldAllowResponse(response, response_info_main_frame, callback); |
| EXPECT_TRUE(policy_decision.ShouldAllowNavigation()); |
| EXPECT_FALSE(policy_decision.ShouldCancelNavigation()); |
| |
| // Test that ShouldAllowResponse() is stopping on negative answer. Only the |
| // first decider should be called. |
| { |
| const WebStatePolicyDecider::ResponseInfo response_info_iframe( |
| /* for_main_frame = */ false); |
| |
| EXPECT_CALL(decider, |
| ShouldAllowResponse(response, |
| ResponseInfoMatch(response_info_iframe), _)) |
| .Times(1) |
| .WillOnce(RunOnceCallback<2>( |
| WebStatePolicyDecider::PolicyDecision::Cancel())); |
| |
| web_state->ShouldAllowResponse(response, response_info_iframe, |
| std::move(callback)); |
| EXPECT_FALSE(policy_decision.ShouldAllowNavigation()); |
| EXPECT_TRUE(policy_decision.ShouldCancelNavigation()); |
| } |
| |
| EXPECT_CALL(decider, WebStateDestroyed()).Times(1); |
| EXPECT_CALL(decider2, WebStateDestroyed()).Times(1); |
| web_state.reset(); |
| EXPECT_EQ(nullptr, decider.web_state()); |
| } |
| |
| // Verifies that asynchronous decisions for |
| // WebStatePolicyDecider::ShouldAllowResponse are correctly handled by |
| // WebStateImpl::ShouldAllowResponse. |
| TEST_F(WebStateImplTest, AsyncShouldAllowResponseTest) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| MockWebStatePolicyDecider sync_decider(&web_state); |
| AsyncWebStatePolicyDecider async_decider1(&web_state); |
| AsyncWebStatePolicyDecider async_decider2(&web_state); |
| |
| NSURL* url = [NSURL URLWithString:@"http://example.com"]; |
| NSURLResponse* response = [[NSURLResponse alloc] initWithURL:url |
| MIMEType:@"text/html" |
| expectedContentLength:0 |
| textEncodingName:nil]; |
| |
| __block WebStatePolicyDecider::PolicyDecision policy_decision = |
| WebStatePolicyDecider::PolicyDecision::Allow(); |
| __block bool callback_called = false; |
| |
| const WebStatePolicyDecider::ResponseInfo expected_response_info( |
| /*for_main_frame=*/true); |
| |
| base::RepeatingCallback<void(WebStatePolicyDecider::PolicyDecision)> |
| callback = |
| base::BindRepeating(^(WebStatePolicyDecider::PolicyDecision result) { |
| policy_decision = result; |
| callback_called = true; |
| }); |
| |
| // Case 1: All deciders allow the navigation. |
| EXPECT_CALL(sync_decider, |
| ShouldAllowResponse(response, |
| ResponseInfoMatch(expected_response_info), _)) |
| .Times(1) |
| .WillOnce( |
| RunOnceCallback<2>(WebStatePolicyDecider::PolicyDecision::Allow())); |
| web_state.ShouldAllowResponse(response, expected_response_info, callback); |
| EXPECT_FALSE(callback_called); |
| async_decider1.InvokeCallback(WebStatePolicyDecider::PolicyDecision::Allow()); |
| EXPECT_FALSE(callback_called); |
| async_decider2.InvokeCallback(WebStatePolicyDecider::PolicyDecision::Allow()); |
| EXPECT_TRUE(callback_called); |
| EXPECT_TRUE(policy_decision.ShouldAllowNavigation()); |
| |
| // Case 2: One decider allows the navigation, one decider wants to show an |
| // error, and another decider wants to cancel the navigation. In this case, |
| // the navigation should be cancelled, with no error shown. |
| EXPECT_CALL(sync_decider, |
| ShouldAllowResponse(response, |
| ResponseInfoMatch(expected_response_info), _)) |
| .Times(1) |
| .WillOnce( |
| RunOnceCallback<2>(WebStatePolicyDecider::PolicyDecision::Allow())); |
| callback_called = false; |
| web_state.ShouldAllowResponse(response, expected_response_info, callback); |
| EXPECT_FALSE(callback_called); |
| NSError* error1 = [NSError errorWithDomain:@"ErrorDomain" |
| code:1 |
| userInfo:nil]; |
| async_decider2.InvokeCallback( |
| WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(error1)); |
| EXPECT_FALSE(callback_called); |
| async_decider1.InvokeCallback( |
| WebStatePolicyDecider::PolicyDecision::Cancel()); |
| EXPECT_TRUE(callback_called); |
| EXPECT_TRUE(policy_decision.ShouldCancelNavigation()); |
| EXPECT_FALSE(policy_decision.ShouldDisplayError()); |
| |
| // Case 3: Two deciders want to show an error. In this case, the error to be |
| // shown should be from the decider that responded first. |
| EXPECT_CALL(sync_decider, |
| ShouldAllowResponse(response, |
| ResponseInfoMatch(expected_response_info), _)) |
| .Times(1) |
| .WillOnce( |
| RunOnceCallback<2>(WebStatePolicyDecider::PolicyDecision::Allow())); |
| callback_called = false; |
| web_state.ShouldAllowResponse(response, expected_response_info, callback); |
| EXPECT_FALSE(callback_called); |
| NSError* error2 = [NSError errorWithDomain:@"ErrorDomain" |
| code:2 |
| userInfo:nil]; |
| async_decider1.InvokeCallback( |
| WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(error2)); |
| EXPECT_FALSE(callback_called); |
| async_decider2.InvokeCallback( |
| WebStatePolicyDecider::PolicyDecision::CancelAndDisplayError(error1)); |
| EXPECT_TRUE(callback_called); |
| EXPECT_TRUE(policy_decision.ShouldCancelNavigation()); |
| EXPECT_TRUE(policy_decision.ShouldDisplayError()); |
| EXPECT_EQ(policy_decision.GetDisplayError().code, error2.code); |
| } |
| |
| // Tests that WebState::CreateParams::created_with_opener is translated to |
| // WebState::HasOpener() return values. |
| TEST_F(WebStateImplTest, CreatedWithOpener) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| // Verify that the HasOpener() returns false if not specified in the create |
| // params. |
| EXPECT_FALSE(web_state.HasOpener()); |
| // Set `created_with_opener` to true and verify that HasOpener() returns true. |
| WebState::CreateParams params_with_opener = |
| WebState::CreateParams(GetBrowserState()); |
| params_with_opener.created_with_opener = true; |
| std::unique_ptr<WebState> web_state_with_opener = |
| WebState::Create(params_with_opener); |
| EXPECT_TRUE(web_state_with_opener->HasOpener()); |
| } |
| |
| // Tests that WebStateObserver::FaviconUrlUpdated is called for same-document |
| // navigations. |
| TEST_F(WebStateImplTest, FaviconUpdateForSameDocumentNavigations) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| auto observer = std::make_unique<FakeWebStateObserver>(&web_state); |
| |
| // No callback if icons has not been fetched yet. |
| std::unique_ptr<NavigationContextImpl> context = |
| NavigationContextImpl::CreateNavigationContext( |
| &web_state, GURL(), |
| /*has_user_gesture=*/false, ui::PageTransition::PAGE_TRANSITION_LINK, |
| /*is_renderer_initiated=*/false); |
| context->SetIsSameDocument(true); |
| web_state.OnNavigationFinished(context.get()); |
| EXPECT_FALSE(observer->update_favicon_url_candidates_info()); |
| |
| // Callback is called when icons were fetched. |
| observer = std::make_unique<FakeWebStateObserver>(&web_state); |
| web::FaviconURL favicon_url(GURL("https://chromium.test/"), |
| web::FaviconURL::IconType::kTouchIcon, |
| {gfx::Size(5, 6)}); |
| web_state.OnFaviconUrlUpdated({favicon_url}); |
| EXPECT_TRUE(observer->update_favicon_url_candidates_info()); |
| |
| // Callback is now called after same-document navigation. |
| observer = std::make_unique<FakeWebStateObserver>(&web_state); |
| web_state.OnNavigationFinished(context.get()); |
| ASSERT_TRUE(observer->update_favicon_url_candidates_info()); |
| ASSERT_EQ(1U, |
| observer->update_favicon_url_candidates_info()->candidates.size()); |
| const web::FaviconURL& actual_favicon_url = |
| observer->update_favicon_url_candidates_info()->candidates[0]; |
| EXPECT_EQ(favicon_url.icon_url, actual_favicon_url.icon_url); |
| EXPECT_EQ(favicon_url.icon_type, actual_favicon_url.icon_type); |
| ASSERT_EQ(favicon_url.icon_sizes.size(), |
| actual_favicon_url.icon_sizes.size()); |
| EXPECT_EQ(favicon_url.icon_sizes[0].width(), |
| actual_favicon_url.icon_sizes[0].width()); |
| EXPECT_EQ(favicon_url.icon_sizes[0].height(), |
| actual_favicon_url.icon_sizes[0].height()); |
| |
| // Document change navigation does not call callback. |
| observer = std::make_unique<FakeWebStateObserver>(&web_state); |
| context->SetIsSameDocument(false); |
| web_state.OnNavigationFinished(context.get()); |
| EXPECT_FALSE(observer->update_favicon_url_candidates_info()); |
| |
| // Previous candidates were invalidated by the document change. No callback |
| // if icons has not been fetched yet. |
| context->SetIsSameDocument(true); |
| web_state.OnNavigationFinished(context.get()); |
| EXPECT_FALSE(observer->update_favicon_url_candidates_info()); |
| } |
| |
| // Tests that BuildSessionStorage() and GetTitle() return information about the |
| // most recently restored session if no navigation item has been committed. Also |
| // tests that re-restoring that session includes updated userData. |
| TEST_F(WebStateImplTest, UncommittedRestoreSession) { |
| GURL url("http://test.com"); |
| CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init]; |
| session_storage.stableIdentifier = [[NSUUID UUID] UUIDString]; |
| session_storage.uniqueIdentifier = web::WebStateID::NewUnique(); |
| session_storage.lastCommittedItemIndex = 0; |
| CRWNavigationItemStorage* item_storage = |
| [[CRWNavigationItemStorage alloc] init]; |
| item_storage.title = base::SysNSStringToUTF16(@"Title"); |
| item_storage.virtualURL = url; |
| session_storage.itemStorages = @[ item_storage ]; |
| |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState()), session_storage); |
| |
| // After restoring `web_state` change the uncommitted state's user data. |
| web::SerializableUserDataManager* user_data_manager = |
| web::SerializableUserDataManager::FromWebState(&web_state); |
| user_data_manager->AddSerializableData(@(1), @"user_data_key"); |
| |
| CRWSessionStorage* extracted_session_storage = |
| web_state.BuildSessionStorage(); |
| EXPECT_EQ(0, extracted_session_storage.lastCommittedItemIndex); |
| EXPECT_EQ(1U, extracted_session_storage.itemStorages.count); |
| ASSERT_FALSE(web_state.IsRealized()); |
| EXPECT_EQ(u"Title", web_state.GetTitle()); |
| EXPECT_EQ(url, web_state.GetVisibleURL()); |
| |
| // Check that even if the WebState becomes realized, then GetTitle() and |
| // GetVisibleURL() are correct during the navigation history restoration. |
| web_state.SetWebUsageEnabled(false); |
| web_state.ForceRealized(); |
| |
| ASSERT_TRUE(web_state.IsRealized()); |
| EXPECT_EQ(u"Title", web_state.GetTitle()); |
| EXPECT_EQ(url, web_state.GetVisibleURL()); |
| |
| WebStateImpl restored_web_state(WebState::CreateParams(GetBrowserState()), |
| extracted_session_storage); |
| web::SerializableUserDataManager* restored_user_data_manager = |
| web::SerializableUserDataManager::FromWebState(&restored_web_state); |
| NSNumber* user_data_value = base::apple::ObjCCast<NSNumber>( |
| restored_user_data_manager->GetValueForSerializationKey( |
| @"user_data_key")); |
| EXPECT_EQ(@(1), user_data_value); |
| } |
| |
| // Tests that SerializeToProto() and GetTitle() return information about the |
| // most recently restored session if no navigation item has been committed. Also |
| // tests that re-restoring that session includes updated userData. |
| TEST_F(WebStateImplTest, UncommittedRestoreSessionOptimisedStorage) { |
| GURL url("http://test.com"); |
| proto::WebStateStorage storage; |
| proto::NavigationStorage* navigation_storage = storage.mutable_navigation(); |
| navigation_storage->set_last_committed_item_index(0); |
| proto::NavigationItemStorage* navigation_item_storage = |
| navigation_storage->add_items(); |
| navigation_item_storage->set_title("Title"); |
| navigation_item_storage->set_virtual_url(url.spec()); |
| |
| proto::WebStateMetadataStorage metadata; |
| metadata.set_navigation_item_count(navigation_storage->items_size()); |
| proto::PageMetadataStorage* active_page = metadata.mutable_active_page(); |
| active_page->set_page_title("Title"); |
| active_page->set_page_url(url.spec()); |
| |
| WebStateImpl web_state = |
| WebStateImpl(GetBrowserState(), web::WebStateID::NewUnique(), metadata, |
| base::ReturnValueOnce(std::move(storage)), |
| base::ReturnValueOnce<NSData*>(nil)); |
| |
| // Check that the title and url are correct. |
| ASSERT_FALSE(web_state.IsRealized()); |
| EXPECT_EQ(u"Title", web_state.GetTitle()); |
| EXPECT_EQ(url, web_state.GetVisibleURL()); |
| |
| // Check that even if the WebState becomes realized, then GetTitle() and |
| // GetVisibleURL() are correct during the navigation history restoration. |
| web_state.SetWebUsageEnabled(false); |
| web_state.ForceRealized(); |
| |
| ASSERT_TRUE(web_state.IsRealized()); |
| EXPECT_EQ(u"Title", web_state.GetTitle()); |
| EXPECT_EQ(url, web_state.GetVisibleURL()); |
| } |
| |
| // Test that lastCommittedItemIndex is end-of-list when there's no defined |
| // index, such as during a restore. |
| TEST_F(WebStateImplTest, NoUncommittedRestoreSession) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| CRWSessionStorage* session_storage = web_state.BuildSessionStorage(); |
| EXPECT_EQ(-1, session_storage.lastCommittedItemIndex); |
| EXPECT_NSEQ(@[], session_storage.itemStorages); |
| EXPECT_TRUE(web_state.GetTitle().empty()); |
| EXPECT_EQ(GURL(), web_state.GetVisibleURL()); |
| } |
| |
| // Test that lastCommittedItemIndex is end-of-list when there's no defined |
| // index, such as during a restore. |
| TEST_F(WebStateImplTest, NoUncommittedRestoreSessionOptimisedStorage) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| proto::WebStateStorage storage; |
| web_state.SerializeToProto(storage); |
| EXPECT_EQ(-1, storage.navigation().last_committed_item_index()); |
| EXPECT_EQ(0, storage.navigation().items_size()); |
| |
| EXPECT_TRUE(web_state.GetTitle().empty()); |
| EXPECT_EQ(GURL(), web_state.GetVisibleURL()); |
| } |
| |
| // Tests that CanTakeSnapshot() is false when a JavaScript dialog is being |
| // presented. |
| TEST_F(WebStateImplTest, DisallowSnapshotsDuringDialogPresentation) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| FakeWebStateDelegate delegate; |
| web_state.SetDelegate(&delegate); |
| |
| EXPECT_TRUE(web_state.CanTakeSnapshot()); |
| |
| // Pause the callback execution to allow testing while the dialog is |
| // presented. |
| delegate.GetFakeJavaScriptDialogPresenter()->set_callback_execution_paused( |
| true); |
| web_state.RunJavaScriptAlertDialog(GURL(), @"message", base::DoNothing()); |
| |
| // Verify that CanTakeSnapshot() returns no while the dialog is presented. |
| EXPECT_FALSE(web_state.CanTakeSnapshot()); |
| |
| // Unpause the presenter and verify that snapshots are enabled again. |
| delegate.GetFakeJavaScriptDialogPresenter()->set_callback_execution_paused( |
| false); |
| EXPECT_TRUE(web_state.CanTakeSnapshot()); |
| } |
| |
| // Tests that IsJavaScriptDialogRunning() is true when a JavaScript dialog is |
| // being presented. |
| TEST_F(WebStateImplTest, VerifyDialogRunningBoolean) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| FakeWebStateDelegate delegate; |
| web_state.SetDelegate(&delegate); |
| |
| EXPECT_FALSE(web_state.IsJavaScriptDialogRunning()); |
| |
| // Pause the callback execution to allow testing while the dialog is |
| // presented. |
| delegate.GetFakeJavaScriptDialogPresenter()->set_callback_execution_paused( |
| true); |
| web_state.RunJavaScriptAlertDialog(GURL(), @"message", base::DoNothing()); |
| |
| // Verify that IsJavaScriptDialogRunning() returns true while the dialog is |
| // presented. |
| EXPECT_TRUE(web_state.IsJavaScriptDialogRunning()); |
| |
| // Unpause the presenter and verify that IsJavaScriptDialogRunning() returns |
| // false when the dialog is no longer presented |
| delegate.GetFakeJavaScriptDialogPresenter()->set_callback_execution_paused( |
| false); |
| EXPECT_FALSE(web_state.IsJavaScriptDialogRunning()); |
| } |
| |
| // Tests that CreateFullPagePdf invokes completion callback nil when a |
| // javascript dialog is running |
| TEST_F(WebStateImplTest, CreateFullPagePdfJavaScriptDialog) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| FakeWebStateDelegate delegate; |
| web_state.SetDelegate(&delegate); |
| |
| // Load the HTML content. |
| CRWWebController* web_controller = web_state.GetWebController(); |
| NSString* html_content = |
| @"<html><body><div style='background-color:#FF0000; width:50%; " |
| "height:100%;'></div>Hello world</body></html>"; |
| [web_controller loadHTML:html_content forURL:GURL("http://example.org")]; |
| |
| ASSERT_TRUE(test::WaitForWebViewContainingText(&web_state, "Hello world")); |
| |
| // Pause the callback execution to allow testing while the dialog is |
| // presented. |
| delegate.GetFakeJavaScriptDialogPresenter()->set_callback_execution_paused( |
| true); |
| web_state.RunJavaScriptAlertDialog(GURL(), @"message", base::DoNothing()); |
| |
| // Attempt to create a PDF for this page and validate that it return nil. |
| __block NSData* callback_data_when_dialog = nil; |
| __block BOOL callback_called_when_dialog = NO; |
| web_state.CreateFullPagePdf(base::BindOnce(^(NSData* pdf_document_data) { |
| callback_data_when_dialog = [pdf_document_data copy]; |
| callback_called_when_dialog = YES; |
| })); |
| |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool { |
| return callback_called_when_dialog; |
| })); |
| |
| EXPECT_FALSE(callback_data_when_dialog); |
| |
| // Unpause the presenter and verify that it return data instead of nil when |
| // the dialog is no longer on the screen |
| delegate.GetFakeJavaScriptDialogPresenter()->set_callback_execution_paused( |
| false); |
| |
| __block NSData* callback_data_no_dialog = nil; |
| __block BOOL callback_called_no_dialog = NO; |
| web_state.CreateFullPagePdf(base::BindOnce(^(NSData* pdf_document_data) { |
| callback_data_no_dialog = [pdf_document_data copy]; |
| callback_called_no_dialog = YES; |
| })); |
| |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^bool { |
| return callback_called_no_dialog; |
| })); |
| |
| EXPECT_TRUE(callback_data_no_dialog); |
| } |
| |
| // Tests that the WebView is removed from the view hierarchy and the |
| // visibilitychange JavaScript event is fired when covering/revealing the |
| // WebContent. |
| TEST_F(WebStateImplTest, VisibilitychangeEventFired) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| // Mark the WebState as visibile before adding the observer. |
| web_state.WasShown(); |
| |
| auto observer = std::make_unique<FakeWebStateObserver>(&web_state); |
| |
| // Add the WebState to the view hierarchy so the visibilitychange event is |
| // fired. |
| UIWindow* window = GetAnyKeyWindow(); |
| [window addSubview:web_state.GetView()]; |
| |
| // Load the HTML content. |
| CRWWebController* web_controller = web_state.GetWebController(); |
| NSString* html_content = @"<html><head><script>" |
| "document.addEventListener('visibilitychange', " |
| "function() {document.body.innerHTML = " |
| "document.visibilityState;});</script>" |
| "</head><body>Hello world</body></html>"; |
| [web_controller loadHTML:html_content forURL:GURL("http://example.org")]; |
| |
| ASSERT_TRUE(test::WaitForWebViewContainingText(&web_state, "Hello world")); |
| |
| // Check that covering the WebState is notifying the observers that it is |
| // hidden and that the visibilitychange event is fired |
| ASSERT_EQ(nullptr, observer->was_hidden_info()); |
| |
| web_state.DidCoverWebContent(); |
| ASSERT_TRUE(test::WaitForWebViewContainingText(&web_state, "hidden")); |
| ASSERT_NE(nullptr, observer->was_hidden_info()); |
| EXPECT_EQ(&web_state, observer->was_hidden_info()->web_state); |
| |
| // Check that revealing the WebState is notifying the observers that it is |
| // shown and that the visibilitychange event is fired |
| ASSERT_EQ(nullptr, observer->was_shown_info()); |
| |
| web_state.DidRevealWebContent(); |
| ASSERT_TRUE(test::WaitForWebViewContainingText(&web_state, "visible")); |
| ASSERT_NE(nullptr, observer->was_shown_info()); |
| EXPECT_EQ(&web_state, observer->was_shown_info()->web_state); |
| |
| // Cleanup. |
| [web_state.GetView() removeFromSuperview]; |
| } |
| |
| // Test that changing visibility update the WebState last active time. |
| TEST_F(WebStateImplTest, LastActiveTimeUpdatedWhenBecomeVisible) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| base::Time last_active_time = web_state.GetLastActiveTime(); |
| base::Time creation_time = web_state.GetCreationTime(); |
| |
| // Spin the RunLoop a bit to ensure that the active time changes. |
| { |
| base::RunLoop run_loop; |
| base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(1)); |
| run_loop.Run(); |
| } |
| |
| // Check that the last active time has not changed. |
| EXPECT_EQ(web_state.GetLastActiveTime(), last_active_time); |
| |
| // Mark the WebState has visible. The last active time should be updated. |
| web_state.WasShown(); |
| EXPECT_GT(web_state.GetLastActiveTime(), last_active_time); |
| EXPECT_EQ(web_state.GetCreationTime(), creation_time); |
| } |
| |
| // Tests that at creation the last active time is initialized to the creation |
| // time if unspecified in CreateParams. |
| TEST_F(WebStateImplTest, LastActiveTimeSetOnCreation) { |
| WebStateImpl web_state = |
| WebStateImpl(WebState::CreateParams(GetBrowserState())); |
| |
| EXPECT_NE(web_state.GetLastActiveTime(), base::Time()); |
| EXPECT_EQ(web_state.GetLastActiveTime(), web_state.GetCreationTime()); |
| } |
| |
| // Tests that at creation the last active time is initialized to the time |
| // specified in CreateParams. |
| TEST_F(WebStateImplTest, LastActiveTimeSetOnCreationToCreateParamsValue) { |
| const base::Time last_active_time = base::Time::Now() + base::Days(1); |
| WebState::CreateParams params = WebState::CreateParams(GetBrowserState()); |
| params.last_active_time = last_active_time; |
| |
| WebStateImpl web_state = WebStateImpl(params); |
| |
| EXPECT_NE(web_state.GetLastActiveTime(), base::Time()); |
| EXPECT_NE(web_state.GetLastActiveTime(), web_state.GetCreationTime()); |
| EXPECT_EQ(web_state.GetLastActiveTime(), last_active_time); |
| } |
| |
| // Tests that at creation the last active time is initialized to the time |
| // specified in CreateParams, even if set to the epoch. |
| TEST_F(WebStateImplTest, LastActiveTimeCanBeForcedToEpochViaCreateParams) { |
| WebState::CreateParams params = WebState::CreateParams(GetBrowserState()); |
| params.last_active_time = base::Time(); |
| |
| WebStateImpl web_state = WebStateImpl(params); |
| |
| EXPECT_EQ(web_state.GetLastActiveTime(), base::Time()); |
| EXPECT_NE(web_state.GetLastActiveTime(), web_state.GetCreationTime()); |
| } |
| |
| // Tests that WebState sessionState data can be read and writen. |
| TEST_F(WebStateImplTest, ReadAndWriteSessionStateData) { |
| // Create a WebState, navigate and capture the session state data. |
| WebStateImpl web_state = |
| WebStateImpl(web::WebState::CreateParams(GetBrowserState())); |
| |
| CRWWebController* web_controller = web_state.GetWebController(); |
| NSString* html_content = @"<html><body>Hello world</body></html>"; |
| [web_controller loadHTML:html_content forURL:GURL("http://example.org")]; |
| |
| NSData* data = web_state.SessionStateData(); |
| EXPECT_NE(nullptr, data); |
| |
| // Create another WebState, set the session state and check the two WebState |
| // eventually display the same URL. |
| WebStateImpl other_web_state = |
| WebStateImpl(web::WebState::CreateParams(GetBrowserState())); |
| other_web_state.SetSessionStateData(data); |
| |
| // Use pointers as the block cannot reference WebState via object. |
| WebStateImpl* web_state_ptr = &web_state; |
| WebStateImpl* other_web_state_ptr = &other_web_state; |
| |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return web_state_ptr->GetVisibleURL() == |
| other_web_state_ptr->GetVisibleURL(); |
| })); |
| } |
| |
| // Tests that SerializeMetadataToProto() can be called on an unrealized |
| // or realized WebState. |
| TEST_F(WebStateImplTest, SerializeMetadataToProto) { |
| const std::u16string title = u"Title"; |
| const base::Time creation_time = base::Time::Now(); |
| const GURL visible_url = GURL("testwebui://test/"); |
| |
| proto::WebStateStorage storage = CreateWebStateStorage( |
| NavigationManager::WebLoadParams(visible_url), title, |
| /*created_with_opener=*/false, UserAgentType::MOBILE, creation_time); |
| ASSERT_TRUE(storage.has_metadata()); |
| |
| proto::WebStateMetadataStorage original_metadata; |
| original_metadata.Swap(storage.mutable_metadata()); |
| |
| // Create an unrealized WebState. |
| web::WebStateImpl web_state = |
| WebStateImpl(GetBrowserState(), WebStateID::NewUnique(), |
| original_metadata, base::ReturnValueOnce(std::move(storage)), |
| base::ReturnValueOnce<NSData*>(nil)); |
| |
| // Check that the metadata can be fetched from the unrealized WebState. |
| { |
| proto::WebStateMetadataStorage metadata; |
| web_state.SerializeMetadataToProto(metadata); |
| |
| EXPECT_EQ(metadata.navigation_item_count(), 1); |
| EXPECT_EQ(TimeFromProto(metadata.creation_time()), creation_time); |
| EXPECT_EQ(TimeFromProto(metadata.last_active_time()), creation_time); |
| EXPECT_EQ(metadata.active_page().page_title(), base::UTF16ToUTF8(title)); |
| EXPECT_EQ(metadata.active_page().page_url(), visible_url.spec()); |
| } |
| |
| // Force realization of the WebState. |
| web_state.ForceRealized(); |
| ASSERT_TRUE(web_state.IsRealized()); |
| |
| // Calling WasShown() will change the last active time for the WebState. |
| web_state.WasShown(); |
| ASSERT_NE(web_state.GetLastActiveTime(), creation_time); |
| |
| // Check that the metadata can be fetched from the WebState after realization. |
| { |
| proto::WebStateMetadataStorage metadata; |
| web_state.SerializeMetadataToProto(metadata); |
| |
| EXPECT_EQ(metadata.navigation_item_count(), 1); |
| EXPECT_EQ(TimeFromProto(metadata.creation_time()), creation_time); |
| EXPECT_NE(TimeFromProto(metadata.last_active_time()), creation_time); |
| EXPECT_EQ(metadata.active_page().page_title(), base::UTF16ToUTF8(title)); |
| EXPECT_EQ(metadata.active_page().page_url(), visible_url.spec()); |
| } |
| } |
| |
| } // namespace web |