| // Copyright 2012 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/web/web_state/ui/crw_web_controller.h" |
| |
| #import <WebKit/WebKit.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/mac/foundation_util.h" |
| #include "base/scoped_observer.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/utf_string_conversions.h" |
| #import "base/test/ios/wait_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #import "ios/testing/ocmock_complex_type_helper.h" |
| #import "ios/web/navigation/crw_session_controller.h" |
| #import "ios/web/navigation/navigation_item_impl.h" |
| #import "ios/web/navigation/navigation_manager_impl.h" |
| #import "ios/web/public/crw_navigation_item_storage.h" |
| #import "ios/web/public/crw_session_storage.h" |
| #import "ios/web/public/download/download_controller.h" |
| #import "ios/web/public/download/download_task.h" |
| #include "ios/web/public/features.h" |
| #include "ios/web/public/referrer.h" |
| #include "ios/web/public/test/fakes/fake_download_controller_delegate.h" |
| #import "ios/web/public/test/fakes/test_native_content.h" |
| #import "ios/web/public/test/fakes/test_native_content_provider.h" |
| #import "ios/web/public/test/fakes/test_web_client.h" |
| #import "ios/web/public/test/fakes/test_web_state_delegate.h" |
| #include "ios/web/public/test/fakes/test_web_state_observer.h" |
| #import "ios/web/public/test/fakes/test_web_view_content_view.h" |
| #import "ios/web/public/test/web_view_content_test_util.h" |
| #import "ios/web/public/web_state/ui/crw_content_view.h" |
| #import "ios/web/public/web_state/ui/crw_native_content.h" |
| #import "ios/web/public/web_state/ui/crw_native_content_provider.h" |
| #import "ios/web/public/web_state/ui/crw_web_view_content_view.h" |
| #include "ios/web/public/web_state/url_verification_constants.h" |
| #include "ios/web/public/web_state/web_state_observer.h" |
| #import "ios/web/test/fakes/crw_fake_back_forward_list.h" |
| #import "ios/web/test/fakes/crw_fake_wk_navigation_action.h" |
| #import "ios/web/test/fakes/crw_fake_wk_navigation_response.h" |
| #include "ios/web/test/test_url_constants.h" |
| #import "ios/web/test/web_test_with_web_controller.h" |
| #import "ios/web/test/wk_web_view_crash_utils.h" |
| #import "ios/web/web_state/ui/crw_web_controller_container_view.h" |
| #import "ios/web/web_state/ui/web_view_js_utils.h" |
| #import "ios/web/web_state/web_state_impl.h" |
| #import "ios/web/web_state/wk_web_view_security_util.h" |
| #import "net/base/mac/url_conversions.h" |
| #include "net/cert/x509_util_ios_and_mac.h" |
| #include "net/ssl/ssl_info.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/test_data_directory.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #import "testing/gtest_mac.h" |
| #include "third_party/ocmock/OCMock/OCMock.h" |
| #include "third_party/ocmock/gtest_support.h" |
| #include "third_party/ocmock/ocmock_extensions.h" |
| #include "url/scheme_host_port.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using base::test::ios::WaitUntilConditionOrTimeout; |
| using base::test::ios::kWaitForPageLoadTimeout; |
| |
| namespace web { |
| namespace { |
| |
| // Syntactically invalid URL per rfc3986. |
| const char kInvalidURL[] = "http://%3"; |
| |
| const char kTestDataURL[] = "data:text/html,"; |
| |
| const char kTestURLString[] = "http://www.google.com/"; |
| const char kTestAppSpecificURL[] = "testwebui://test/"; |
| |
| const char kTestMimeType[] = "application/vnd.test"; |
| |
| // Returns HTML for an optionally zoomable test page with |zoom_state|. |
| enum PageScalabilityType { |
| PAGE_SCALABILITY_DISABLED = 0, |
| PAGE_SCALABILITY_ENABLED, |
| }; |
| |
| // Tests in this file are parameterized on this enum to test both |
| // LegacyNavigationManagerImpl and WKBasedNavigationManagerImpl. |
| enum class NavigationManagerChoice { |
| LEGACY, |
| WK_BASED, |
| }; |
| |
| // A mixin class that encapsulates the parameterization of navigation manager |
| // choice. |
| class ProgrammaticTestMixin |
| : public ::testing::WithParamInterface<NavigationManagerChoice> { |
| public: |
| void SetUp() { |
| if (GetParam() == NavigationManagerChoice::LEGACY) { |
| scoped_feature_list_.InitAndDisableFeature( |
| features::kSlimNavigationManager); |
| } else { |
| scoped_feature_list_.InitAndEnableFeature( |
| features::kSlimNavigationManager); |
| } |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Base class for WebTestWithWebState tests to enable navigation manager mixin. |
| class ProgrammaticWebTestWithWebState : public WebTestWithWebState, |
| public ProgrammaticTestMixin { |
| public: |
| void SetUp() override { |
| ProgrammaticTestMixin::SetUp(); |
| WebTestWithWebState::SetUp(); |
| } |
| }; |
| |
| // Base class for WebTestWithWebController tests to enable navigation manager |
| // mixin. |
| class ProgrammaticWebTestWithWebController : public WebTestWithWebController, |
| public ProgrammaticTestMixin { |
| public: |
| void SetUp() override { |
| ProgrammaticTestMixin::SetUp(); |
| WebTestWithWebState::SetUp(); |
| } |
| }; |
| |
| // Macro to simplify instantiation of parameterized tests. |
| #define INSTANTIATE_TEST_CASES(cls) \ |
| INSTANTIATE_TEST_CASE_P( \ |
| Programmatic##cls, cls, \ |
| ::testing::Values(NavigationManagerChoice::LEGACY, \ |
| NavigationManagerChoice::WK_BASED)); |
| |
| } // namespace |
| |
| // Test fixture for testing CRWWebController. Stubs out web view. |
| class CRWWebControllerTest : public WebTestWithWebController, |
| public ProgrammaticTestMixin { |
| protected: |
| CRWWebControllerTest() |
| : WebTestWithWebController(std::make_unique<TestWebClient>()) {} |
| |
| void SetUp() override { |
| ProgrammaticTestMixin::SetUp(); |
| WebTestWithWebController::SetUp(); |
| mock_web_view_ = CreateMockWebView(); |
| scroll_view_ = [[UIScrollView alloc] init]; |
| SetWebViewURL(@(kTestURLString)); |
| [[[mock_web_view_ stub] andReturn:scroll_view_] scrollView]; |
| |
| TestWebViewContentView* web_view_content_view = |
| [[TestWebViewContentView alloc] initWithMockWebView:mock_web_view_ |
| scrollView:scroll_view_]; |
| [web_controller() injectWebViewContentView:web_view_content_view]; |
| } |
| |
| void TearDown() override { |
| EXPECT_OCMOCK_VERIFY(mock_web_view_); |
| [web_controller() resetInjectedWebViewContentView]; |
| WebTestWithWebController::TearDown(); |
| } |
| |
| TestWebClient* GetWebClient() override { |
| return static_cast<TestWebClient*>( |
| WebTestWithWebController::GetWebClient()); |
| } |
| |
| // The value for web view OCMock objects to expect for |-setFrame:|. |
| CGRect GetExpectedWebViewFrame() const { |
| CGSize container_view_size = |
| UIApplication.sharedApplication.keyWindow.bounds.size; |
| container_view_size.height -= |
| CGRectGetHeight([UIApplication sharedApplication].statusBarFrame); |
| return {CGPointZero, container_view_size}; |
| } |
| |
| void SetWebViewURL(NSString* url_string) { |
| test_url_ = [NSURL URLWithString:url_string]; |
| } |
| |
| // Creates WebView mock. |
| UIView* CreateMockWebView() { |
| id result = [OCMockObject mockForClass:[WKWebView class]]; |
| [[result stub] serverTrust]; |
| |
| mock_wk_list_ = [[CRWFakeBackForwardList alloc] init]; |
| OCMStub([result backForwardList]).andReturn(mock_wk_list_); |
| // This uses |andDo| rather than |andReturn| since the URL it returns needs |
| // to change when |test_url_| changes. |
| OCMStub([result URL]).andDo(^(NSInvocation* invocation) { |
| [invocation setReturnValue:&test_url_]; |
| }); |
| [[result stub] setNavigationDelegate:[OCMArg checkWithBlock:^(id delegate) { |
| navigation_delegate_ = delegate; |
| return YES; |
| }]]; |
| [[result stub] setUIDelegate:OCMOCK_ANY]; |
| [[result stub] setFrame:GetExpectedWebViewFrame()]; |
| [[result stub] addObserver:web_controller() |
| forKeyPath:OCMOCK_ANY |
| options:0 |
| context:nullptr]; |
| [[result stub] removeObserver:web_controller() forKeyPath:OCMOCK_ANY]; |
| [[result stub] evaluateJavaScript:OCMOCK_ANY completionHandler:OCMOCK_ANY]; |
| [[result stub] setAllowsBackForwardNavigationGestures:YES]; |
| [[result stub] setAllowsBackForwardNavigationGestures:NO]; |
| |
| return result; |
| } |
| |
| __weak id<WKNavigationDelegate> navigation_delegate_; |
| UIScrollView* scroll_view_; |
| id mock_web_view_; |
| CRWFakeBackForwardList* mock_wk_list_; |
| NSURL* test_url_; |
| }; |
| |
| // Tests that AllowCertificateError is called with correct arguments if |
| // WKWebView fails to load a page with bad SSL cert. |
| TEST_P(CRWWebControllerTest, SslCertError) { |
| // Last arguments passed to AllowCertificateError must be in default state. |
| ASSERT_FALSE(GetWebClient()->last_cert_error_code()); |
| ASSERT_FALSE(GetWebClient()->last_cert_error_ssl_info().is_valid()); |
| ASSERT_FALSE(GetWebClient()->last_cert_error_ssl_info().cert_status); |
| ASSERT_FALSE(GetWebClient()->last_cert_error_request_url().is_valid()); |
| ASSERT_TRUE(GetWebClient()->last_cert_error_overridable()); |
| |
| scoped_refptr<net::X509Certificate> cert = |
| net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem"); |
| ASSERT_TRUE(cert); |
| base::ScopedCFTypeRef<CFMutableArrayRef> chain( |
| net::x509_util::CreateSecCertificateArrayForX509Certificate(cert.get())); |
| ASSERT_TRUE(chain); |
| |
| GURL url("https://chromium.test"); |
| NSError* error = |
| [NSError errorWithDomain:NSURLErrorDomain |
| code:NSURLErrorServerCertificateHasUnknownRoot |
| userInfo:@{ |
| kNSErrorPeerCertificateChainKey : |
| base::mac::CFToNSCast(chain.get()), |
| kNSErrorFailingURLKey : net::NSURLWithGURL(url), |
| }]; |
| NSObject* navigation = [[NSObject alloc] init]; |
| [navigation_delegate_ webView:mock_web_view_ |
| didStartProvisionalNavigation:static_cast<WKNavigation*>(navigation)]; |
| [navigation_delegate_ webView:mock_web_view_ |
| didFailProvisionalNavigation:static_cast<WKNavigation*>(navigation) |
| withError:error]; |
| |
| // Verify correctness of AllowCertificateError method call. |
| EXPECT_EQ(net::ERR_CERT_INVALID, GetWebClient()->last_cert_error_code()); |
| EXPECT_TRUE(GetWebClient()->last_cert_error_ssl_info().is_valid()); |
| EXPECT_EQ(net::CERT_STATUS_INVALID, |
| GetWebClient()->last_cert_error_ssl_info().cert_status); |
| EXPECT_EQ(url, GetWebClient()->last_cert_error_request_url()); |
| EXPECT_FALSE(GetWebClient()->last_cert_error_overridable()); |
| } |
| |
| // Tests that when a placeholder navigation is preempted by another navigation, |
| // WebStateObservers get neither a DidStartNavigation nor a DidFinishNavigation |
| // call for the corresponding native URL navigation. |
| TEST_P(CRWWebControllerTest, AbortNativeUrlNavigation) { |
| // The legacy navigation manager doesn't have the concept of placeholder |
| // navigations. |
| if (!GetWebClient()->IsSlimNavigationManagerEnabled()) |
| return; |
| GURL native_url( |
| url::SchemeHostPort(kTestNativeContentScheme, "ui", 0).Serialize()); |
| NSString* placeholder_url = [NSString |
| stringWithFormat:@"%s%s", "about:blank?for=", native_url.spec().c_str()]; |
| TestWebStateObserver observer(web_state()); |
| |
| WKNavigation* navigation = |
| static_cast<WKNavigation*>([[NSObject alloc] init]); |
| [static_cast<WKWebView*>([[mock_web_view_ stub] andReturn:navigation]) |
| loadRequest:OCMOCK_ANY]; |
| TestNativeContentProvider* mock_native_provider = |
| [[TestNativeContentProvider alloc] init]; |
| TestNativeContent* content = |
| [[TestNativeContent alloc] initWithURL:native_url virtualURL:native_url]; |
| [mock_native_provider setController:content forURL:native_url]; |
| [web_controller() setNativeProvider:mock_native_provider]; |
| |
| AddPendingItem(native_url, ui::PAGE_TRANSITION_TYPED); |
| |
| // Trigger a placeholder navigation. |
| [web_controller() loadCurrentURL]; |
| |
| // Simulate the WKNavigationDelegate callbacks for the placeholder navigation |
| // arriving after another pending item has already been created. |
| AddPendingItem(GURL(kTestURLString), ui::PAGE_TRANSITION_TYPED); |
| SetWebViewURL(placeholder_url); |
| [navigation_delegate_ webView:mock_web_view_ |
| didStartProvisionalNavigation:navigation]; |
| [navigation_delegate_ webView:mock_web_view_ didCommitNavigation:navigation]; |
| [navigation_delegate_ webView:mock_web_view_ didFinishNavigation:navigation]; |
| |
| EXPECT_FALSE(observer.did_start_navigation_info()); |
| EXPECT_FALSE(observer.did_finish_navigation_info()); |
| } |
| |
| // Tests allowsBackForwardNavigationGestures default value and negating this |
| // property. |
| TEST_P(CRWWebControllerTest, SetAllowsBackForwardNavigationGestures) { |
| if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) { |
| EXPECT_TRUE(web_controller().allowsBackForwardNavigationGestures); |
| web_controller().allowsBackForwardNavigationGestures = NO; |
| EXPECT_FALSE(web_controller().allowsBackForwardNavigationGestures); |
| } else { |
| EXPECT_FALSE(web_controller().allowsBackForwardNavigationGestures); |
| web_controller().allowsBackForwardNavigationGestures = YES; |
| EXPECT_TRUE(web_controller().allowsBackForwardNavigationGestures); |
| } |
| } |
| |
| INSTANTIATE_TEST_CASES(CRWWebControllerTest); |
| |
| // Test fixture to test JavaScriptDialogPresenter. |
| class JavaScriptDialogPresenterTest : public ProgrammaticWebTestWithWebState { |
| protected: |
| JavaScriptDialogPresenterTest() : page_url_("https://chromium.test/") {} |
| void SetUp() override { |
| ProgrammaticWebTestWithWebState::SetUp(); |
| LoadHtml(@"<html><body></body></html>", page_url_); |
| web_state()->SetDelegate(&test_web_delegate_); |
| } |
| void TearDown() override { |
| web_state()->SetDelegate(nullptr); |
| ProgrammaticWebTestWithWebState::TearDown(); |
| } |
| TestJavaScriptDialogPresenter* js_dialog_presenter() { |
| return test_web_delegate_.GetTestJavaScriptDialogPresenter(); |
| } |
| const std::vector<TestJavaScriptDialog>& requested_dialogs() { |
| return js_dialog_presenter()->requested_dialogs(); |
| } |
| const GURL& page_url() { return page_url_; } |
| |
| private: |
| TestWebStateDelegate test_web_delegate_; |
| GURL page_url_; |
| }; |
| |
| // Tests that window.alert dialog is shown. |
| TEST_P(JavaScriptDialogPresenterTest, Alert) { |
| ASSERT_TRUE(requested_dialogs().empty()); |
| |
| ExecuteJavaScript(@"alert('test')"); |
| |
| ASSERT_EQ(1U, requested_dialogs().size()); |
| TestJavaScriptDialog dialog = requested_dialogs()[0]; |
| EXPECT_EQ(web_state(), dialog.web_state); |
| EXPECT_EQ(page_url(), dialog.origin_url); |
| EXPECT_EQ(JAVASCRIPT_DIALOG_TYPE_ALERT, dialog.java_script_dialog_type); |
| EXPECT_NSEQ(@"test", dialog.message_text); |
| EXPECT_FALSE(dialog.default_prompt_text); |
| }; |
| |
| // Tests that window.confirm dialog is shown and its result is true. |
| TEST_P(JavaScriptDialogPresenterTest, ConfirmWithTrue) { |
| ASSERT_TRUE(requested_dialogs().empty()); |
| |
| js_dialog_presenter()->set_callback_success_argument(true); |
| |
| EXPECT_NSEQ(@YES, ExecuteJavaScript(@"confirm('test')")); |
| |
| ASSERT_EQ(1U, requested_dialogs().size()); |
| TestJavaScriptDialog dialog = requested_dialogs()[0]; |
| EXPECT_EQ(web_state(), dialog.web_state); |
| EXPECT_EQ(page_url(), dialog.origin_url); |
| EXPECT_EQ(JAVASCRIPT_DIALOG_TYPE_CONFIRM, dialog.java_script_dialog_type); |
| EXPECT_NSEQ(@"test", dialog.message_text); |
| EXPECT_FALSE(dialog.default_prompt_text); |
| } |
| |
| // Tests that window.confirm dialog is shown and its result is false. |
| TEST_P(JavaScriptDialogPresenterTest, ConfirmWithFalse) { |
| ASSERT_TRUE(requested_dialogs().empty()); |
| |
| EXPECT_NSEQ(@NO, ExecuteJavaScript(@"confirm('test')")); |
| |
| ASSERT_EQ(1U, requested_dialogs().size()); |
| TestJavaScriptDialog dialog = requested_dialogs()[0]; |
| EXPECT_EQ(web_state(), dialog.web_state); |
| EXPECT_EQ(page_url(), dialog.origin_url); |
| EXPECT_EQ(JAVASCRIPT_DIALOG_TYPE_CONFIRM, dialog.java_script_dialog_type); |
| EXPECT_NSEQ(@"test", dialog.message_text); |
| EXPECT_FALSE(dialog.default_prompt_text); |
| } |
| |
| // Tests that window.prompt dialog is shown. |
| TEST_P(JavaScriptDialogPresenterTest, Prompt) { |
| ASSERT_TRUE(requested_dialogs().empty()); |
| |
| js_dialog_presenter()->set_callback_user_input_argument(@"Maybe"); |
| |
| EXPECT_NSEQ(@"Maybe", ExecuteJavaScript(@"prompt('Yes?', 'No')")); |
| |
| ASSERT_EQ(1U, requested_dialogs().size()); |
| TestJavaScriptDialog dialog = requested_dialogs()[0]; |
| EXPECT_EQ(web_state(), dialog.web_state); |
| EXPECT_EQ(page_url(), dialog.origin_url); |
| EXPECT_EQ(JAVASCRIPT_DIALOG_TYPE_PROMPT, dialog.java_script_dialog_type); |
| EXPECT_NSEQ(@"Yes?", dialog.message_text); |
| EXPECT_NSEQ(@"No", dialog.default_prompt_text); |
| } |
| |
| INSTANTIATE_TEST_CASES(JavaScriptDialogPresenterTest); |
| |
| // Test fixture for testing visible security state. |
| typedef ProgrammaticWebTestWithWebState CRWWebStateSecurityStateTest; |
| |
| // Tests that loading HTTP page updates the SSLStatus. |
| TEST_P(CRWWebStateSecurityStateTest, LoadHttpPage) { |
| TestWebStateObserver observer(web_state()); |
| ASSERT_FALSE(observer.did_change_visible_security_state_info()); |
| LoadHtml(@"<html><body></body></html>", GURL("http://chromium.test")); |
| NavigationManager* nav_manager = web_state()->GetNavigationManager(); |
| NavigationItem* item = nav_manager->GetLastCommittedItem(); |
| EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED, item->GetSSL().security_style); |
| ASSERT_TRUE(observer.did_change_visible_security_state_info()); |
| EXPECT_EQ(web_state(), |
| observer.did_change_visible_security_state_info()->web_state); |
| } |
| |
| INSTANTIATE_TEST_CASES(CRWWebStateSecurityStateTest); |
| |
| // Real WKWebView is required for CRWWebControllerInvalidUrlTest. |
| typedef ProgrammaticWebTestWithWebState CRWWebControllerInvalidUrlTest; |
| |
| // Tests that web controller does not navigate to about:blank if iframe src |
| // has invalid url. Web controller loads about:blank if page navigates to |
| // invalid url, but should do nothing if navigation is performed in iframe. This |
| // test prevents crbug.com/694865 regression. |
| TEST_P(CRWWebControllerInvalidUrlTest, IFrameWithInvalidURL) { |
| GURL url("http://chromium.test"); |
| ASSERT_FALSE(GURL(kInvalidURL).is_valid()); |
| LoadHtml([NSString stringWithFormat:@"<iframe src='%s'/>", kInvalidURL], url); |
| EXPECT_EQ(url, web_state()->GetLastCommittedURL()); |
| } |
| |
| INSTANTIATE_TEST_CASES(CRWWebControllerInvalidUrlTest); |
| |
| // Real WKWebView is required for CRWWebControllerMessageFromIFrame. |
| typedef ProgrammaticWebTestWithWebState CRWWebControllerMessageFromIFrame; |
| |
| // Tests that invalid message from iframe does not cause a crash. |
| TEST_P(CRWWebControllerMessageFromIFrame, InvalidMessage) { |
| static NSString* const kHTMLIFrameSendsInvalidMessage = |
| @"<body><iframe name='f'></iframe></body>"; |
| |
| LoadHtml(kHTMLIFrameSendsInvalidMessage); |
| |
| // Sending unknown command from iframe should not cause a crash. |
| ExecuteJavaScript( |
| @"var bad_message = {'command' : 'unknown.command'};" |
| "frames['f'].__gCrWeb.message.invokeOnHost(bad_message);"); |
| } |
| |
| INSTANTIATE_TEST_CASES(CRWWebControllerMessageFromIFrame); |
| |
| // Real WKWebView is required for CRWWebControllerJSExecutionTest. |
| typedef ProgrammaticWebTestWithWebController CRWWebControllerJSExecutionTest; |
| |
| // Tests that a script correctly evaluates to boolean. |
| TEST_P(CRWWebControllerJSExecutionTest, Execution) { |
| LoadHtml(@"<p></p>"); |
| EXPECT_NSEQ(@YES, ExecuteJavaScript(@"true")); |
| EXPECT_NSEQ(@NO, ExecuteJavaScript(@"false")); |
| } |
| |
| INSTANTIATE_TEST_CASES(CRWWebControllerJSExecutionTest); |
| |
| // Test fixture to test decidePolicyForNavigationResponse:decisionHandler: |
| // delegate method. |
| class CRWWebControllerResponseTest : public CRWWebControllerTest { |
| protected: |
| CRWWebControllerResponseTest() : download_delegate_(download_controller()) {} |
| |
| // Calls webView:decidePolicyForNavigationResponse:decisionHandler: callback |
| // and waits for decision handler call. Returns false if decision handler call |
| // times out. |
| bool CallDecidePolicyForNavigationResponseWithResponse( |
| NSURLResponse* response, |
| BOOL for_main_frame, |
| BOOL can_show_mime_type, |
| WKNavigationResponsePolicy* out_policy) WARN_UNUSED_RESULT { |
| CRWFakeWKNavigationResponse* navigation_response = |
| [[CRWFakeWKNavigationResponse alloc] init]; |
| navigation_response.response = response; |
| navigation_response.forMainFrame = for_main_frame; |
| navigation_response.canShowMIMEType = can_show_mime_type; |
| |
| // Call decidePolicyForNavigationResponse and wait for decisionHandler's |
| // callback. |
| __block bool callback_called = false; |
| if (for_main_frame) { |
| // webView:didStartProvisionalNavigation: is not called for iframes. |
| [navigation_delegate_ webView:mock_web_view_ |
| didStartProvisionalNavigation:nil]; |
| } |
| [navigation_delegate_ webView:mock_web_view_ |
| decidePolicyForNavigationResponse:navigation_response |
| decisionHandler:^(WKNavigationResponsePolicy policy) { |
| *out_policy = policy; |
| callback_called = true; |
| }]; |
| return WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return callback_called; |
| }); |
| } |
| |
| DownloadController* download_controller() { |
| return DownloadController::FromBrowserState(GetBrowserState()); |
| } |
| |
| FakeDownloadControllerDelegate download_delegate_; |
| }; |
| |
| // Tests that webView:decidePolicyForNavigationResponse:decisionHandler: allows |
| // renderer-initiated navigations in main frame for http: URLs. |
| TEST_P(CRWWebControllerResponseTest, AllowRendererInitiatedResponse) { |
| // Simulate regular navigation response with text/html MIME type. |
| NSURLResponse* response = [[NSHTTPURLResponse alloc] |
| initWithURL:[NSURL URLWithString:@(kTestURLString)] |
| statusCode:200 |
| HTTPVersion:nil |
| headerFields:nil]; |
| WKNavigationResponsePolicy policy = WKNavigationResponsePolicyCancel; |
| ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse( |
| response, /*for_main_frame=*/YES, /*can_show_mime_type=*/YES, &policy)); |
| EXPECT_EQ(WKNavigationResponsePolicyAllow, policy); |
| |
| // Verify that download task was not created for html response. |
| ASSERT_TRUE(download_delegate_.alive_download_tasks().empty()); |
| } |
| |
| // Tests that webView:decidePolicyForNavigationResponse:decisionHandler: allows |
| // renderer-initiated navigations in iframe for data: URLs. |
| TEST_P(CRWWebControllerResponseTest, |
| AllowRendererInitiatedDataUrlResponseInIFrame) { |
| // Simulate data:// url response with text/html MIME type. |
| SetWebViewURL(@(kTestDataURL)); |
| NSURLResponse* response = [[NSHTTPURLResponse alloc] |
| initWithURL:[NSURL URLWithString:@(kTestDataURL)] |
| statusCode:200 |
| HTTPVersion:nil |
| headerFields:nil]; |
| WKNavigationResponsePolicy policy = WKNavigationResponsePolicyCancel; |
| ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse( |
| response, /*for_main_frame=*/NO, /*can_show_mime_type=*/YES, &policy)); |
| EXPECT_EQ(WKNavigationResponsePolicyAllow, policy); |
| |
| // Verify that download task was not created for html response. |
| ASSERT_TRUE(download_delegate_.alive_download_tasks().empty()); |
| } |
| |
| // Tests that webView:decidePolicyForNavigationResponse:decisionHandler: blocks |
| // rendering data URLs for renderer-initiated navigations in main frame to |
| // prevent abusive behavior (crbug.com/890558). |
| TEST_P(CRWWebControllerResponseTest, |
| BlockRendererInitiatedDataUrlResponseInMainFrame) { |
| // Simulate data:// url response with text/html MIME type. |
| SetWebViewURL(@(kTestDataURL)); |
| NSURLResponse* response = [[NSHTTPURLResponse alloc] |
| initWithURL:[NSURL URLWithString:@(kTestDataURL)] |
| statusCode:200 |
| HTTPVersion:nil |
| headerFields:nil]; |
| WKNavigationResponsePolicy policy = WKNavigationResponsePolicyAllow; |
| ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse( |
| response, /*for_main_frame=*/YES, /*can_show_mime_type=*/YES, &policy)); |
| EXPECT_EQ(WKNavigationResponsePolicyCancel, policy); |
| |
| // Verify that download task was not created for html response. |
| ASSERT_TRUE(download_delegate_.alive_download_tasks().empty()); |
| } |
| |
| // Tests that webView:decidePolicyForNavigationResponse:decisionHandler: allows |
| // rendering data URLs for browser-initiated navigations in main frame. |
| TEST_P(CRWWebControllerResponseTest, |
| AllowBrowserInitiatedDataUrlResponseInMainFrame) { |
| // Simulate data:// url response with text/html MIME type. |
| GURL url(kTestDataURL); |
| AddPendingItem(url, ui::PAGE_TRANSITION_TYPED); |
| SetWebViewURL(@(kTestDataURL)); |
| NSURLResponse* response = [[NSHTTPURLResponse alloc] |
| initWithURL:[NSURL URLWithString:@(kTestDataURL)] |
| statusCode:200 |
| HTTPVersion:nil |
| headerFields:nil]; |
| WKNavigationResponsePolicy policy = WKNavigationResponsePolicyCancel; |
| ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse( |
| response, /*for_main_frame=*/YES, /*can_show_mime_type=*/YES, &policy)); |
| EXPECT_EQ(WKNavigationResponsePolicyAllow, policy); |
| |
| // Verify that download task was not created for html response. |
| ASSERT_TRUE(download_delegate_.alive_download_tasks().empty()); |
| } |
| |
| // Tests that webView:decidePolicyForNavigationResponse:decisionHandler: creates |
| // the DownloadTask for NSURLResponse. |
| TEST_P(CRWWebControllerResponseTest, DownloadWithNSURLResponse) { |
| // Simulate download response. |
| int64_t content_length = 10; |
| NSURLResponse* response = |
| [[NSURLResponse alloc] initWithURL:[NSURL URLWithString:@(kTestURLString)] |
| MIMEType:@(kTestMimeType) |
| expectedContentLength:content_length |
| textEncodingName:nil]; |
| WKNavigationResponsePolicy policy = WKNavigationResponsePolicyAllow; |
| ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse( |
| response, /*for_main_frame=*/YES, /*can_show_mime_type=*/NO, &policy)); |
| EXPECT_EQ(WKNavigationResponsePolicyCancel, policy); |
| |
| // Verify that download task was created. |
| ASSERT_EQ(1U, download_delegate_.alive_download_tasks().size()); |
| DownloadTask* task = |
| download_delegate_.alive_download_tasks()[0].second.get(); |
| ASSERT_TRUE(task); |
| EXPECT_TRUE(task->GetIndentifier()); |
| EXPECT_EQ(kTestURLString, task->GetOriginalUrl()); |
| EXPECT_EQ(content_length, task->GetTotalBytes()); |
| EXPECT_EQ("", task->GetContentDisposition()); |
| EXPECT_EQ(kTestMimeType, task->GetMimeType()); |
| EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( |
| task->GetTransitionType(), |
| ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT)); |
| } |
| |
| // Tests that webView:decidePolicyForNavigationResponse:decisionHandler: creates |
| // the DownloadTask for NSHTTPURLResponse. |
| TEST_P(CRWWebControllerResponseTest, DownloadWithNSHTTPURLResponse) { |
| // Simulate download response. |
| const char kContentDisposition[] = "attachment; filename=download.test"; |
| NSURLResponse* response = [[NSHTTPURLResponse alloc] |
| initWithURL:[NSURL URLWithString:@(kTestURLString)] |
| statusCode:200 |
| HTTPVersion:nil |
| headerFields:@{ |
| @"content-disposition" : @(kContentDisposition), |
| }]; |
| WKNavigationResponsePolicy policy = WKNavigationResponsePolicyAllow; |
| ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse( |
| response, /*for_main_frame=*/YES, /*can_show_mime_type=*/NO, &policy)); |
| EXPECT_EQ(WKNavigationResponsePolicyCancel, policy); |
| |
| // Verify that download task was created. |
| ASSERT_EQ(1U, download_delegate_.alive_download_tasks().size()); |
| DownloadTask* task = |
| download_delegate_.alive_download_tasks()[0].second.get(); |
| ASSERT_TRUE(task); |
| EXPECT_TRUE(task->GetIndentifier()); |
| EXPECT_EQ(kTestURLString, task->GetOriginalUrl()); |
| EXPECT_EQ(-1, task->GetTotalBytes()); |
| EXPECT_EQ(kContentDisposition, task->GetContentDisposition()); |
| EXPECT_EQ("", task->GetMimeType()); |
| EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( |
| task->GetTransitionType(), |
| ui::PageTransition::PAGE_TRANSITION_CLIENT_REDIRECT)); |
| } |
| |
| // Tests that webView:decidePolicyForNavigationResponse:decisionHandler: creates |
| // the DownloadTask for NSHTTPURLResponse and iframes. |
| TEST_P(CRWWebControllerResponseTest, IFrameDownloadWithNSHTTPURLResponse) { |
| // Simulate download response. |
| const char kContentDisposition[] = "attachment; filename=download.test"; |
| NSURLResponse* response = [[NSHTTPURLResponse alloc] |
| initWithURL:[NSURL URLWithString:@(kTestURLString)] |
| statusCode:200 |
| HTTPVersion:nil |
| headerFields:@{ |
| @"content-disposition" : @(kContentDisposition), |
| }]; |
| WKNavigationResponsePolicy policy = WKNavigationResponsePolicyAllow; |
| ASSERT_TRUE(CallDecidePolicyForNavigationResponseWithResponse( |
| response, /*for_main_frame=*/NO, /*can_show_mime_type=*/NO, &policy)); |
| EXPECT_EQ(WKNavigationResponsePolicyCancel, policy); |
| |
| // Verify that download task was created. |
| ASSERT_EQ(1U, download_delegate_.alive_download_tasks().size()); |
| DownloadTask* task = |
| download_delegate_.alive_download_tasks()[0].second.get(); |
| ASSERT_TRUE(task); |
| EXPECT_TRUE(task->GetIndentifier()); |
| EXPECT_EQ(kTestURLString, task->GetOriginalUrl()); |
| EXPECT_EQ(-1, task->GetTotalBytes()); |
| EXPECT_EQ(kContentDisposition, task->GetContentDisposition()); |
| EXPECT_EQ("", task->GetMimeType()); |
| EXPECT_TRUE(ui::PageTransitionTypeIncludingQualifiersIs( |
| task->GetTransitionType(), |
| ui::PageTransition::PAGE_TRANSITION_AUTO_SUBFRAME)); |
| } |
| |
| // Tests |currentURLWithTrustLevel:| method. |
| TEST_P(CRWWebControllerTest, CurrentUrlWithTrustLevel) { |
| GURL url("http://chromium.test"); |
| AddPendingItem(url, ui::PAGE_TRANSITION_TYPED); |
| |
| [[[mock_web_view_ stub] andReturnBool:NO] hasOnlySecureContent]; |
| [static_cast<WKWebView*>([[mock_web_view_ stub] andReturn:@""]) title]; |
| SetWebViewURL(@"http://chromium.test"); |
| |
| // Stub out the injection process. |
| [[mock_web_view_ stub] evaluateJavaScript:OCMOCK_ANY |
| completionHandler:OCMOCK_ANY]; |
| |
| // Simulate a page load to trigger a URL update. |
| [navigation_delegate_ webView:mock_web_view_ |
| didStartProvisionalNavigation:nil]; |
| [mock_wk_list_ setCurrentURL:@"http://chromium.test"]; |
| [navigation_delegate_ webView:mock_web_view_ didCommitNavigation:nil]; |
| |
| URLVerificationTrustLevel trust_level = kNone; |
| EXPECT_EQ(url, [web_controller() currentURLWithTrustLevel:&trust_level]); |
| EXPECT_EQ(kAbsolute, trust_level); |
| } |
| |
| INSTANTIATE_TEST_CASES(CRWWebControllerResponseTest); |
| |
| // Test fixture to test decidePolicyForNavigationAction:decisionHandler: |
| // decisionHandler's callback result. |
| class CRWWebControllerPolicyDeciderTest : public CRWWebControllerTest { |
| protected: |
| void SetUp() override { |
| CRWWebControllerTest::SetUp(); |
| } |
| // Calls webView:decidePolicyForNavigationAction:decisionHandler: callback |
| // and waits for decision handler call. Returns false if decision handler |
| // policy parameter didn't match |expected_policy| or if the call timed out. |
| bool VerifyDecidePolicyForNavigationAction( |
| NSURLRequest* request, |
| WKNavigationActionPolicy expected_policy) WARN_UNUSED_RESULT { |
| CRWFakeWKNavigationAction* navigation_action = |
| [[CRWFakeWKNavigationAction alloc] init]; |
| navigation_action.request = request; |
| |
| // Call decidePolicyForNavigationResponse and wait for decisionHandler's |
| // callback. |
| __block bool policy_match = false; |
| __block bool callback_called = false; |
| [navigation_delegate_ webView:mock_web_view_ |
| decidePolicyForNavigationAction:navigation_action |
| decisionHandler:^(WKNavigationActionPolicy policy) { |
| policy_match = expected_policy == policy; |
| callback_called = true; |
| }]; |
| callback_called = WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return callback_called; |
| }); |
| return policy_match; |
| } |
| }; |
| |
| // Tests that App specific URLs in iframes are allowed if the main frame is App |
| // specific URL. App specific pages have elevated privileges and WKWebView uses |
| // the same renderer process for all page frames. With that running App specific |
| // pages are not allowed in the same process as a web site from the internet. |
| TEST_P(CRWWebControllerPolicyDeciderTest, |
| AllowAppSpecificIFrameFromAppSpecificPage) { |
| NSURL* app_url = [NSURL URLWithString:@(kTestAppSpecificURL)]; |
| NSMutableURLRequest* app_url_request = |
| [NSMutableURLRequest requestWithURL:app_url]; |
| app_url_request.mainDocumentURL = app_url; |
| EXPECT_TRUE(VerifyDecidePolicyForNavigationAction( |
| app_url_request, WKNavigationActionPolicyAllow)); |
| } |
| |
| // Tests that App specific URLs in iframes are not allowed if the main frame is |
| // not App specific URL. |
| TEST_P(CRWWebControllerPolicyDeciderTest, |
| DisallowAppSpecificIFrameFromRegularPage) { |
| NSURL* app_url = [NSURL URLWithString:@(kTestAppSpecificURL)]; |
| NSMutableURLRequest* app_url_request = |
| [NSMutableURLRequest requestWithURL:app_url]; |
| app_url_request.mainDocumentURL = [NSURL URLWithString:@(kTestURLString)]; |
| EXPECT_TRUE(VerifyDecidePolicyForNavigationAction( |
| app_url_request, WKNavigationActionPolicyCancel)); |
| } |
| |
| // Tests that blob URL navigation is allowed. |
| TEST_P(CRWWebControllerPolicyDeciderTest, BlobUrl) { |
| NSURL* blob_url = [NSURL URLWithString:@"blob://aslfkh-asdkjh"]; |
| NSMutableURLRequest* blob_url_request = |
| [NSMutableURLRequest requestWithURL:blob_url]; |
| EXPECT_TRUE(VerifyDecidePolicyForNavigationAction( |
| blob_url_request, WKNavigationActionPolicyAllow)); |
| } |
| |
| INSTANTIATE_TEST_CASES(CRWWebControllerPolicyDeciderTest); |
| |
| // Test fixture for testing CRWWebController presenting native content. |
| class CRWWebControllerNativeContentTest |
| : public ProgrammaticWebTestWithWebController { |
| protected: |
| void SetUp() override { |
| ProgrammaticWebTestWithWebController::SetUp(); |
| mock_native_provider_ = [[TestNativeContentProvider alloc] init]; |
| [web_controller() setNativeProvider:mock_native_provider_]; |
| } |
| |
| void Load(const GURL& URL) { |
| TestWebStateObserver observer(web_state()); |
| NavigationManagerImpl& navigation_manager = |
| [web_controller() webStateImpl]->GetNavigationManagerImpl(); |
| navigation_manager.AddPendingItem( |
| URL, Referrer(), ui::PAGE_TRANSITION_TYPED, |
| NavigationInitiationType::BROWSER_INITIATED, |
| NavigationManager::UserAgentOverrideOption::INHERIT); |
| [web_controller() loadCurrentURL]; |
| |
| // Native URL is loaded asynchronously with WKBasedNavigationManager. Wait |
| // for navigation to finish before asserting. |
| if (GetWebClient()->IsSlimNavigationManagerEnabled()) { |
| TestWebStateObserver* observer_ptr = &observer; |
| ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return observer_ptr->did_finish_navigation_info() != nullptr; |
| })); |
| } |
| } |
| |
| TestNativeContentProvider* mock_native_provider_; |
| }; |
| |
| // Tests WebState and NavigationManager correctly return native content URL. |
| TEST_P(CRWWebControllerNativeContentTest, NativeContentURL) { |
| GURL url_to_load(kTestAppSpecificURL); |
| TestNativeContent* content = |
| [[TestNativeContent alloc] initWithURL:url_to_load virtualURL:GURL()]; |
| [mock_native_provider_ setController:content forURL:url_to_load]; |
| Load(url_to_load); |
| URLVerificationTrustLevel trust_level = kNone; |
| GURL gurl = [web_controller() currentURLWithTrustLevel:&trust_level]; |
| EXPECT_EQ(gurl, url_to_load); |
| EXPECT_EQ(kAbsolute, trust_level); |
| EXPECT_EQ([web_controller() webState]->GetVisibleURL(), url_to_load); |
| NavigationManagerImpl& navigationManager = |
| [web_controller() webStateImpl]->GetNavigationManagerImpl(); |
| EXPECT_EQ(navigationManager.GetVisibleItem()->GetURL(), url_to_load); |
| EXPECT_EQ(navigationManager.GetVisibleItem()->GetVirtualURL(), url_to_load); |
| EXPECT_EQ(navigationManager.GetLastCommittedItem()->GetURL(), url_to_load); |
| EXPECT_EQ(navigationManager.GetLastCommittedItem()->GetVirtualURL(), |
| url_to_load); |
| } |
| |
| // Tests WebState and NavigationManager correctly return native content URL and |
| // VirtualURL |
| TEST_P(CRWWebControllerNativeContentTest, NativeContentVirtualURL) { |
| GURL url_to_load(kTestAppSpecificURL); |
| GURL virtual_url(kTestURLString); |
| TestNativeContent* content = |
| [[TestNativeContent alloc] initWithURL:virtual_url |
| virtualURL:virtual_url]; |
| [mock_native_provider_ setController:content forURL:url_to_load]; |
| Load(url_to_load); |
| URLVerificationTrustLevel trust_level = kNone; |
| GURL gurl = [web_controller() currentURLWithTrustLevel:&trust_level]; |
| EXPECT_EQ(gurl, virtual_url); |
| EXPECT_EQ(kAbsolute, trust_level); |
| EXPECT_EQ([web_controller() webState]->GetVisibleURL(), virtual_url); |
| NavigationManagerImpl& navigationManager = |
| [web_controller() webStateImpl]->GetNavigationManagerImpl(); |
| EXPECT_EQ(navigationManager.GetVisibleItem()->GetURL(), url_to_load); |
| EXPECT_EQ(navigationManager.GetVisibleItem()->GetVirtualURL(), virtual_url); |
| EXPECT_EQ(navigationManager.GetLastCommittedItem()->GetURL(), url_to_load); |
| EXPECT_EQ(navigationManager.GetLastCommittedItem()->GetVirtualURL(), |
| virtual_url); |
| } |
| |
| INSTANTIATE_TEST_CASES(CRWWebControllerNativeContentTest); |
| |
| // Test fixture for window.open tests. |
| class WindowOpenByDomTest : public ProgrammaticWebTestWithWebController { |
| protected: |
| WindowOpenByDomTest() : opener_url_("http://test") {} |
| |
| void SetUp() override { |
| ProgrammaticWebTestWithWebController::SetUp(); |
| web_state()->SetDelegate(&delegate_); |
| LoadHtml(@"<html><body></body></html>", opener_url_); |
| } |
| // Executes JavaScript that opens a new window and returns evaluation result |
| // as a string. |
| id OpenWindowByDom() { |
| NSString* const kOpenWindowScript = |
| @"w = window.open('javascript:void(0);', target='_blank');" |
| "w ? w.toString() : null;"; |
| id windowJSObject = ExecuteJavaScript(kOpenWindowScript); |
| return windowJSObject; |
| } |
| |
| // Executes JavaScript that closes previously opened window. |
| void CloseWindow() { ExecuteJavaScript(@"w.close()"); } |
| |
| // URL of a page which opens child windows. |
| const GURL opener_url_; |
| TestWebStateDelegate delegate_; |
| }; |
| |
| // Tests that absence of web state delegate is handled gracefully. |
| TEST_P(WindowOpenByDomTest, NoDelegate) { |
| web_state()->SetDelegate(nullptr); |
| |
| EXPECT_NSEQ([NSNull null], OpenWindowByDom()); |
| |
| EXPECT_TRUE(delegate_.child_windows().empty()); |
| EXPECT_TRUE(delegate_.popups().empty()); |
| } |
| |
| // Tests that window.open triggered by user gesture opens a new non-popup |
| // window. |
| TEST_P(WindowOpenByDomTest, OpenWithUserGesture) { |
| [web_controller() touched:YES]; |
| EXPECT_NSEQ(@"[object Window]", OpenWindowByDom()); |
| |
| ASSERT_EQ(1U, delegate_.child_windows().size()); |
| ASSERT_TRUE(delegate_.child_windows()[0]); |
| EXPECT_TRUE(delegate_.popups().empty()); |
| } |
| |
| // Tests that window.open executed w/o user gesture does not open a new window, |
| // but blocks popup instead. |
| TEST_P(WindowOpenByDomTest, BlockPopup) { |
| ASSERT_FALSE([web_controller() userIsInteracting]); |
| EXPECT_NSEQ([NSNull null], OpenWindowByDom()); |
| |
| EXPECT_TRUE(delegate_.child_windows().empty()); |
| ASSERT_EQ(1U, delegate_.popups().size()); |
| EXPECT_EQ(GURL("javascript:void(0);"), delegate_.popups()[0].url); |
| EXPECT_EQ(opener_url_, delegate_.popups()[0].opener_url); |
| } |
| |
| // Tests that window.open executed w/o user gesture opens a new window, assuming |
| // that delegate allows popups. |
| TEST_P(WindowOpenByDomTest, DontBlockPopup) { |
| delegate_.allow_popups(opener_url_); |
| EXPECT_NSEQ(@"[object Window]", OpenWindowByDom()); |
| |
| ASSERT_EQ(1U, delegate_.child_windows().size()); |
| ASSERT_TRUE(delegate_.child_windows()[0]); |
| EXPECT_TRUE(delegate_.popups().empty()); |
| } |
| |
| // Tests that window.close closes the web state. |
| TEST_P(WindowOpenByDomTest, CloseWindow) { |
| delegate_.allow_popups(opener_url_); |
| ASSERT_NSEQ(@"[object Window]", OpenWindowByDom()); |
| |
| ASSERT_EQ(1U, delegate_.child_windows().size()); |
| ASSERT_TRUE(delegate_.child_windows()[0]); |
| EXPECT_TRUE(delegate_.popups().empty()); |
| |
| delegate_.child_windows()[0]->SetDelegate(&delegate_); |
| CloseWindow(); |
| |
| EXPECT_TRUE(delegate_.child_windows().empty()); |
| EXPECT_TRUE(delegate_.popups().empty()); |
| } |
| |
| INSTANTIATE_TEST_CASES(WindowOpenByDomTest); |
| |
| // Tests page title changes. |
| typedef ProgrammaticWebTestWithWebState CRWWebControllerTitleTest; |
| TEST_P(CRWWebControllerTitleTest, TitleChange) { |
| // Observes and waits for TitleWasSet call. |
| class TitleObserver : public WebStateObserver { |
| public: |
| TitleObserver() = default; |
| |
| // Returns number of times |TitleWasSet| was called. |
| int title_change_count() { return title_change_count_; } |
| // WebStateObserver overrides: |
| void TitleWasSet(WebState* web_state) override { title_change_count_++; } |
| void WebStateDestroyed(WebState* web_state) override { NOTREACHED(); } |
| |
| private: |
| int title_change_count_ = 0; |
| |
| DISALLOW_COPY_AND_ASSIGN(TitleObserver); |
| }; |
| |
| TitleObserver observer; |
| ScopedObserver<WebState, WebStateObserver> scoped_observer(&observer); |
| scoped_observer.Add(web_state()); |
| ASSERT_EQ(0, observer.title_change_count()); |
| |
| // Expect TitleWasSet callback after the page is loaded and due to WKWebView |
| // title change KVO. |
| LoadHtml(@"<title>Title1</title>"); |
| EXPECT_EQ("Title1", base::UTF16ToUTF8(web_state()->GetTitle())); |
| EXPECT_EQ(2, observer.title_change_count()); |
| |
| // Expect at least one more TitleWasSet callback after changing title via |
| // JavaScript. On iOS 10 WKWebView fires 3 callbacks after JS excucution |
| // with the following title changes: "Title2", "" and "Title2". |
| // TODO(crbug.com/696104): There should be only 2 calls of TitleWasSet. |
| // Fix expecteation when WKWebView stops sending extra KVO calls. |
| ExecuteJavaScript(@"window.document.title = 'Title2';"); |
| EXPECT_EQ("Title2", base::UTF16ToUTF8(web_state()->GetTitle())); |
| EXPECT_GE(observer.title_change_count(), 3); |
| }; |
| |
| // Tests that fragment change navigations use title from the previous page. |
| TEST_P(CRWWebControllerTitleTest, FragmentChangeNavigationsUsePreviousTitle) { |
| LoadHtml(@"<title>Title1</title>"); |
| ASSERT_EQ("Title1", base::UTF16ToUTF8(web_state()->GetTitle())); |
| ExecuteJavaScript(@"window.location.hash = '#1'"); |
| EXPECT_EQ("Title1", base::UTF16ToUTF8(web_state()->GetTitle())); |
| } |
| |
| INSTANTIATE_TEST_CASES(CRWWebControllerTitleTest); |
| |
| // Test fixture for JavaScript execution. |
| class ScriptExecutionTest : public ProgrammaticWebTestWithWebController { |
| protected: |
| // Calls |executeUserJavaScript:completionHandler:|, waits for script |
| // execution completion, and synchronously returns the result. |
| id ExecuteUserJavaScript(NSString* java_script, NSError** error) { |
| __block id script_result = nil; |
| __block NSError* script_error = nil; |
| __block bool script_executed = false; |
| [web_controller() |
| executeUserJavaScript:java_script |
| completionHandler:^(id local_result, NSError* local_error) { |
| script_result = local_result; |
| script_error = local_error; |
| script_executed = true; |
| }]; |
| |
| WaitForCondition(^{ |
| return script_executed; |
| }); |
| |
| if (error) { |
| *error = script_error; |
| } |
| return script_result; |
| } |
| }; |
| |
| // Tests evaluating user script on an http page. |
| TEST_P(ScriptExecutionTest, UserScriptOnHttpPage) { |
| LoadHtml(@"<html></html>", GURL(kTestURLString)); |
| NSError* error = nil; |
| EXPECT_NSEQ(@0, ExecuteUserJavaScript(@"window.w = 0;", &error)); |
| EXPECT_FALSE(error); |
| |
| EXPECT_NSEQ(@0, ExecuteJavaScript(@"window.w")); |
| }; |
| |
| // Tests evaluating user script on app-specific page. Pages with app-specific |
| // URLs have elevated privileges and JavaScript execution should not be allowed |
| // for them. |
| TEST_P(ScriptExecutionTest, UserScriptOnAppSpecificPage) { |
| LoadHtml(@"<html></html>", GURL(kTestURLString)); |
| |
| // Change last committed URL to app-specific URL. |
| NavigationManagerImpl& nav_manager = |
| [web_controller() webStateImpl]->GetNavigationManagerImpl(); |
| nav_manager.AddPendingItem( |
| GURL(kTestAppSpecificURL), Referrer(), ui::PAGE_TRANSITION_TYPED, |
| NavigationInitiationType::BROWSER_INITIATED, |
| NavigationManager::UserAgentOverrideOption::INHERIT); |
| nav_manager.CommitPendingItem(); |
| |
| NSError* error = nil; |
| EXPECT_FALSE(ExecuteUserJavaScript(@"window.w = 0;", &error)); |
| ASSERT_TRUE(error); |
| EXPECT_NSEQ(kJSEvaluationErrorDomain, error.domain); |
| EXPECT_EQ(JS_EVALUATION_ERROR_CODE_NO_WEB_VIEW, error.code); |
| |
| EXPECT_FALSE(ExecuteJavaScript(@"window.w")); |
| }; |
| |
| INSTANTIATE_TEST_CASES(ScriptExecutionTest); |
| |
| // Fixture class to test WKWebView crashes. |
| class CRWWebControllerWebProcessTest |
| : public ProgrammaticWebTestWithWebController { |
| protected: |
| void SetUp() override { |
| ProgrammaticWebTestWithWebController::SetUp(); |
| webView_ = BuildTerminatedWKWebView(); |
| TestWebViewContentView* webViewContentView = [[TestWebViewContentView alloc] |
| initWithMockWebView:webView_ |
| scrollView:[webView_ scrollView]]; |
| [web_controller() injectWebViewContentView:webViewContentView]; |
| |
| // This test intentionally crashes the render process. |
| SetIgnoreRenderProcessCrashesDuringTesting(true); |
| } |
| WKWebView* webView_; |
| }; |
| |
| // Tests that WebStateDelegate::RenderProcessGone is called when WKWebView web |
| // process has crashed. |
| TEST_P(CRWWebControllerWebProcessTest, Crash) { |
| ASSERT_TRUE([web_controller() isViewAlive]); |
| ASSERT_FALSE([web_controller() isWebProcessCrashed]); |
| ASSERT_FALSE(web_state()->IsCrashed()); |
| ASSERT_FALSE(web_state()->IsEvicted()); |
| |
| TestWebStateObserver observer(web_state()); |
| TestWebStateObserver* observer_ptr = &observer; |
| SimulateWKWebViewCrash(webView_); |
| base::test::ios::WaitUntilCondition(^bool() { |
| return observer_ptr->render_process_gone_info(); |
| }); |
| EXPECT_EQ(web_state(), observer.render_process_gone_info()->web_state); |
| EXPECT_FALSE([web_controller() isViewAlive]); |
| EXPECT_TRUE([web_controller() isWebProcessCrashed]); |
| EXPECT_TRUE(web_state()->IsCrashed()); |
| EXPECT_TRUE(web_state()->IsEvicted()); |
| }; |
| |
| // Tests that WebState is considered as evicted but not crashed when calling |
| // SetWebUsageEnabled(false). |
| TEST_P(CRWWebControllerWebProcessTest, Eviction) { |
| ASSERT_TRUE([web_controller() isViewAlive]); |
| ASSERT_FALSE([web_controller() isWebProcessCrashed]); |
| ASSERT_FALSE(web_state()->IsCrashed()); |
| ASSERT_FALSE(web_state()->IsEvicted()); |
| |
| web_state()->SetWebUsageEnabled(false); |
| EXPECT_FALSE([web_controller() isViewAlive]); |
| EXPECT_FALSE([web_controller() isWebProcessCrashed]); |
| EXPECT_FALSE(web_state()->IsCrashed()); |
| EXPECT_TRUE(web_state()->IsEvicted()); |
| }; |
| |
| INSTANTIATE_TEST_CASES(CRWWebControllerWebProcessTest); |
| |
| #undef INSTANTIATE_TEST_CASES |
| |
| } // namespace web |