1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "ios/web/web_state/ui/crw_web_controller.h"
7 #import <UIKit/UIKit.h>
8 #import <WebKit/WebKit.h>
10 #include "base/mac/scoped_nsobject.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "base/test/histogram_tester.h"
13 #import "base/test/ios/wait_util.h"
14 #include "base/values.h"
15 #import "ios/testing/ocmock_complex_type_helper.h"
16 #include "ios/web/navigation/crw_session_controller.h"
17 #include "ios/web/navigation/crw_session_entry.h"
18 #include "ios/web/navigation/navigation_item_impl.h"
19 #import "ios/web/navigation/navigation_manager_impl.h"
20 #include "ios/web/public/referrer.h"
21 #include "ios/web/public/test/test_web_view_content_view.h"
22 #include "ios/web/public/test/web_test_util.h"
23 #import "ios/web/public/web_state/crw_web_controller_observer.h"
24 #import "ios/web/public/web_state/ui/crw_content_view.h"
25 #import "ios/web/public/web_state/ui/crw_web_view_content_view.h"
26 #include "ios/web/public/web_state/url_verification_constants.h"
27 #include "ios/web/test/web_test.h"
28 #import "ios/web/test/wk_web_view_crash_utils.h"
29 #include "ios/web/web_state/blocked_popup_info.h"
30 #import "ios/web/web_state/js/crw_js_invoke_parameter_queue.h"
31 #import "ios/web/web_state/ui/crw_ui_web_view_web_controller.h"
32 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
33 #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
34 #import "ios/web/web_state/web_state_impl.h"
35 #import "net/base/mac/url_conversions.h"
36 #include "net/ssl/ssl_info.h"
37 #include "testing/gtest/include/gtest/gtest.h"
38 #include "testing/gtest_mac.h"
39 #include "third_party/ocmock/OCMock/OCMock.h"
40 #include "third_party/ocmock/gtest_support.h"
41 #include "third_party/ocmock/ocmock_extensions.h"
42 #import "ui/base/test/ios/keyboard_appearance_listener.h"
43 #include "ui/base/test/ios/ui_view_test_utils.h"
45 using web::NavigationManagerImpl;
47 @interface TestWebController (PrivateTesting)
48 - (void)reloadInternal;
51 @interface CRWWebController (PrivateAPI)
52 @property(nonatomic, readwrite) web::PageDisplayState pageDisplayState;
53 @property(nonatomic, readonly) CRWWebControllerContainerView* containerView;
54 - (void)setJsMessageQueueThrottled:(BOOL)throttle;
55 - (void)removeDocumentLoadCommandsFromQueue;
56 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
57 toURL:(const GURL&)endURL;
58 - (BOOL)checkForUnexpectedURLChange;
59 - (void)injectEarlyInjectionScripts;
60 - (void)stopExpectingURLChangeIfNecessary;
63 @implementation TestWebController (PrivateTesting)
65 - (void)reloadInternal {
66 // Empty implementation to prevent the need to mock out a huge number of
72 // Used to mock CRWWebDelegate methods with C++ params.
73 @interface MockInteractionLoader : OCMockComplexTypeHelper
74 // popupURL passed to webController:shouldBlockPopupWithURL:sourceURL:
76 @property(nonatomic, assign) GURL popupURL;
77 // sourceURL passed to webController:shouldBlockPopupWithURL:sourceURL:
79 @property(nonatomic, assign) GURL sourceURL;
80 // Whether or not the delegate should block popups.
81 @property(nonatomic, assign) BOOL blockPopups;
82 // A web controller that will be returned by webPageOrdered... methods.
83 @property(nonatomic, assign) CRWWebController* childWebController;
84 // Blocked popup info received in |webController:didBlockPopup:| call.
85 // nullptr if that delegate method was not called.
86 @property(nonatomic, readonly) web::BlockedPopupInfo* blockedPopupInfo;
87 // SSL info received in |presentSSLError:forSSLStatus:recoverable:callback:|
89 @property(nonatomic, readonly) net::SSLInfo SSLInfo;
90 // SSL status received in |presentSSLError:forSSLStatus:recoverable:callback:|
92 @property(nonatomic, readonly) web::SSLStatus SSLStatus;
93 // Recoverable flag received in
94 // |presentSSLError:forSSLStatus:recoverable:callback:| call.
95 @property(nonatomic, readonly) BOOL recoverable;
96 // Callback received in |presentSSLError:forSSLStatus:recoverable:callback:|
98 @property(nonatomic, readonly) SSLErrorCallback shouldContinueCallback;
101 @implementation MockInteractionLoader {
102 // Backs up the property with the same name.
103 scoped_ptr<web::BlockedPopupInfo> _blockedPopupInfo;
105 @synthesize popupURL = _popupURL;
106 @synthesize sourceURL = _sourceURL;
107 @synthesize blockPopups = _blockPopups;
108 @synthesize childWebController = _childWebController;
109 @synthesize SSLInfo = _SSLInfo;
110 @synthesize SSLStatus = _SSLStatus;
111 @synthesize recoverable = _recoverable;
112 @synthesize shouldContinueCallback = _shouldContinueCallback;
114 typedef void (^webPageOrderedOpenBlankBlockType)(const web::Referrer&, BOOL);
115 typedef void (^webPageOrderedOpenBlockType)(const GURL&,
116 const web::Referrer&,
120 - (instancetype)initWithRepresentedObject:(id)representedObject {
121 self = [super initWithRepresentedObject:representedObject];
128 - (CRWWebController*)webPageOrderedOpenBlankWithReferrer:
129 (const web::Referrer&)referrer
130 inBackground:(BOOL)inBackground {
131 static_cast<webPageOrderedOpenBlankBlockType>([self blockForSelector:_cmd])(
132 referrer, inBackground);
133 return _childWebController;
136 - (CRWWebController*)webPageOrderedOpen:(const GURL&)url
137 referrer:(const web::Referrer&)referrer
138 windowName:(NSString*)windowName
139 inBackground:(BOOL)inBackground {
140 static_cast<webPageOrderedOpenBlockType>([self blockForSelector:_cmd])(
141 url, referrer, windowName, inBackground);
142 return _childWebController;
145 typedef BOOL (^openExternalURLBlockType)(const GURL&);
147 - (BOOL)openExternalURL:(const GURL&)url {
148 return static_cast<openExternalURLBlockType>([self blockForSelector:_cmd])(
152 - (BOOL)webController:(CRWWebController*)webController
153 shouldBlockPopupWithURL:(const GURL&)popupURL
154 sourceURL:(const GURL&)sourceURL {
155 self.popupURL = popupURL;
156 self.sourceURL = sourceURL;
160 - (void)webController:(CRWWebController*)webController
161 didBlockPopup:(const web::BlockedPopupInfo&)blockedPopupInfo {
162 _blockedPopupInfo.reset(new web::BlockedPopupInfo(blockedPopupInfo));
165 - (web::BlockedPopupInfo*)blockedPopupInfo {
166 return _blockedPopupInfo.get();
169 - (void)presentSSLError:(const net::SSLInfo&)info
170 forSSLStatus:(const web::SSLStatus&)status
171 recoverable:(BOOL)recoverable
172 callback:(SSLErrorCallback)shouldContinue {
175 _recoverable = recoverable;
176 _shouldContinueCallback = shouldContinue;
181 @interface CountingObserver : NSObject<CRWWebControllerObserver>
183 @property(nonatomic, readonly) int pageLoadedCount;
184 @property(nonatomic, readonly) int messageCount;
187 @implementation CountingObserver
188 @synthesize pageLoadedCount = _pageLoadedCount;
189 @synthesize messageCount = _messageCount;
191 - (void)pageLoaded:(CRWWebController*)webController {
195 - (BOOL)handleCommand:(const base::DictionaryValue&)command
196 webController:(CRWWebController*)webController
197 userIsInteracting:(BOOL)userIsInteracting
198 originURL:(const GURL&)originURL {
203 - (NSString*)commandPrefix {
211 NSString* const kGetMessageQueueJavaScript =
212 @"window.__gCrWeb === undefined ? '' : __gCrWeb.message.getMessageQueue()";
214 NSString* kCheckURLJavaScript =
216 "window.__gCrWeb_Verifying = true;"
217 "if(!window.__gCrWeb_CachedRequest||"
218 "!(window.__gCrWeb_CachedRequestDocument===window.document)){"
219 "window.__gCrWeb_CachedRequest = new XMLHttpRequest();"
220 "window.__gCrWeb_CachedRequestDocument = window.document;"
222 "window.__gCrWeb_CachedRequest.open('POST',"
223 "'https://localhost:0/crwebiossecurity',false);"
224 "window.__gCrWeb_CachedRequest.send();"
227 "window.__gCrWeb_CachedRequest.open('POST',"
228 "'/crwebiossecurity/b86b97a1-2ce0-44fd-a074-e2158790c98d',false);"
229 "window.__gCrWeb_CachedRequest.send();"
232 "delete window.__gCrWeb_Verifying;"
233 "window.location.href";
235 NSString* kTestURLString = @"http://www.google.com/";
237 NSMutableURLRequest* requestForCrWebInvokeCommandAndKey(NSString* command,
239 NSString* fullCommand =
240 [NSString stringWithFormat:@"[{\"command\":\"%@\"}]", command];
241 NSString* escapedCommand = [fullCommand
242 stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
243 NSString* urlString =
244 [NSString stringWithFormat:@"crwebinvoke://%@/#%@", key, escapedCommand];
245 return [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
248 // Returns true if the current device is a large iPhone (6 or 6+).
249 bool IsIPhone6Or6Plus() {
250 UIUserInterfaceIdiom idiom = [[UIDevice currentDevice] userInterfaceIdiom];
251 return (idiom == UIUserInterfaceIdiomPhone &&
252 CGRectGetHeight([[UIScreen mainScreen] nativeBounds]) >= 1334.0);
255 // Returns HTML for an optionally zoomable test page with |zoom_state|.
256 enum PageScalabilityType {
257 PAGE_SCALABILITY_DISABLED = 0,
258 PAGE_SCALABILITY_ENABLED,
260 NSString* GetHTMLForZoomState(const web::PageZoomState& zoom_state,
261 PageScalabilityType scalability_type) {
262 NSString* const kHTMLFormat =
263 @"<html><head><meta name='viewport' content="
264 "'width=%f,maximum-scale=%f,initial-scale=%f,"
265 "user-scalable=%@'/></head><body>Test</body></html>";
266 CGFloat width = CGRectGetWidth([UIScreen mainScreen].bounds) /
267 zoom_state.minimum_zoom_scale();
268 BOOL scalability_enabled = scalability_type == PAGE_SCALABILITY_ENABLED;
269 return [NSString stringWithFormat:kHTMLFormat, width,
270 zoom_state.maximum_zoom_scale(),
271 zoom_state.zoom_scale(),
272 scalability_enabled ? @"yes" : @"no"];
275 // Forces |webController|'s view to render and waits until |webController|'s
276 // PageZoomState matches |zoom_state|.
277 void WaitForZoomRendering(CRWWebController* webController,
278 const web::PageZoomState& zoom_state) {
279 ui::test::uiview_utils::ForceViewRendering(webController.view);
280 base::test::ios::WaitUntilCondition(^bool() {
281 return webController.pageDisplayState.zoom_state() == zoom_state;
285 // A mixin class for testing CRWWKWebViewWebController or
286 // CRWUIWebViewWebController. Stubs out WebView and child CRWWebController.
287 template <typename WebTestT>
288 class WebControllerTest : public WebTestT {
290 virtual void SetUp() override {
292 mockWebView_.reset(CreateMockWebView());
293 mockScrollView_.reset([[UIScrollView alloc] init]);
294 [[[mockWebView_ stub] andReturn:mockScrollView_.get()] scrollView];
296 id originalMockDelegate =
297 [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
298 mockDelegate_.reset([[MockInteractionLoader alloc]
299 initWithRepresentedObject:originalMockDelegate]);
300 [WebTestT::webController_ setDelegate:mockDelegate_];
301 base::scoped_nsobject<TestWebViewContentView> webViewContentView(
302 [[TestWebViewContentView alloc] initWithMockWebView:mockWebView_
303 scrollView:mockScrollView_]);
304 [WebTestT::webController_ injectWebViewContentView:webViewContentView];
306 NavigationManagerImpl& navigationManager =
307 [WebTestT::webController_ webStateImpl]->GetNavigationManagerImpl();
308 navigationManager.InitializeSession(@"name", nil, NO, 0);
309 [navigationManager.GetSessionController()
310 addPendingEntry:GURL("http://www.google.com/?q=foo#bar")
311 referrer:web::Referrer()
312 transition:ui::PAGE_TRANSITION_TYPED
313 rendererInitiated:NO];
314 // Set up child CRWWebController.
315 mockChildWebController_.reset([[OCMockObject
316 mockForProtocol:@protocol(CRWWebControllerScripting)] retain]);
317 [[[mockDelegate_ stub] andReturn:mockChildWebController_.get()]
318 webController:WebTestT::webController_
319 scriptingInterfaceForWindowNamed:@"http://www.google.com/#newtab"];
322 virtual void TearDown() override {
323 EXPECT_OCMOCK_VERIFY(mockDelegate_);
324 EXPECT_OCMOCK_VERIFY(mockChildWebController_.get());
325 EXPECT_OCMOCK_VERIFY(mockWebView_);
326 [WebTestT::webController_ resetInjectedWebViewContentView];
327 [WebTestT::webController_ setDelegate:nil];
328 WebTestT::TearDown();
331 // Creates WebView mock.
332 virtual UIView* CreateMockWebView() const = 0;
334 // Simulates a load request delegate call from the web view.
335 virtual void SimulateLoadRequest(NSURLRequest* request) const = 0;
337 base::scoped_nsobject<UIScrollView> mockScrollView_;
338 base::scoped_nsobject<id> mockWebView_;
339 base::scoped_nsobject<id> mockDelegate_;
340 base::scoped_nsobject<id> mockChildWebController_;
343 class CRWUIWebViewWebControllerTest
344 : public WebControllerTest<web::WebTestWithUIWebViewWebController> {
346 UIView* CreateMockWebView() const override {
347 id result = [[OCMockObject mockForClass:[UIWebView class]] retain];
348 [[[result stub] andReturn:nil] request];
349 [[result stub] setDelegate:OCMOCK_ANY]; // Called by resetInjectedWebView
350 // Stub out the injection process.
351 [[[result stub] andReturn:@"object"]
352 stringByEvaluatingJavaScriptFromString:
353 @"try { typeof __gCrWeb; } catch (e) { 'undefined'; }"];
354 [[[result stub] andReturn:@"object"]
355 stringByEvaluatingJavaScriptFromString:@"try { typeof "
356 @"__gCrWeb.windowIdObject; } "
357 @"catch (e) { 'undefined'; }"];
360 void SimulateLoadRequest(NSURLRequest* request) const override {
361 id<UIWebViewDelegate> delegate =
362 static_cast<id<UIWebViewDelegate>>(webController_.get());
363 [delegate webView:(UIWebView*)mockWebView_
364 shouldStartLoadWithRequest:request
365 navigationType:UIWebViewNavigationTypeLinkClicked];
369 class CRWWKWebViewWebControllerTest
370 : public WebControllerTest<web::WebTestWithWKWebViewWebController> {
372 void SetUp() override {
373 CR_TEST_REQUIRES_WK_WEB_VIEW();
374 WebControllerTest<web::WebTestWithWKWebViewWebController>::SetUp();
376 UIView* CreateMockWebView() const override {
377 id result = [[OCMockObject mockForClass:[WKWebView class]] retain];
379 // Called by resetInjectedWebView
380 [[result stub] configuration];
381 [[result stub] setNavigationDelegate:OCMOCK_ANY];
382 [[result stub] setUIDelegate:OCMOCK_ANY];
383 [[result stub] addObserver:webController_
384 forKeyPath:OCMOCK_ANY
387 [[result stub] addObserver:OCMOCK_ANY
388 forKeyPath:@"scrollView.backgroundColor"
392 [[result stub] removeObserver:webController_ forKeyPath:OCMOCK_ANY];
393 [[result stub] removeObserver:OCMOCK_ANY
394 forKeyPath:@"scrollView.backgroundColor"];
398 void SimulateLoadRequest(NSURLRequest* request) const override {
399 // TODO(eugenebut): implement this method.
403 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithIncorrectKey) {
404 NSURLRequest* request = [NSURLRequest
405 requestWithURL:[NSURL URLWithString:@"crwebinvoke:invalidkey#commands"]];
407 SimulateLoadRequest(request);
410 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithLargeQueue) {
411 // Pre-define test window id.
412 [webController_ setWindowId:@"12345678901234567890123456789012"];
413 NSString* valid_key = [webController_ windowId];
414 [[[mockWebView_ stub] andReturn:kTestURLString]
415 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
417 // Install an observer to handle custom command messages.
418 base::scoped_nsobject<CountingObserver> observer(
419 [[CountingObserver alloc] init]);
420 [webController_ addObserver:observer];
422 // Queue a lot of messages.
423 [webController_ setJsMessageQueueThrottled:YES];
424 const int kNumQueuedMessages = 1000;
425 for (int i = 0; i < kNumQueuedMessages; ++i) {
426 NSMutableURLRequest* request =
427 requestForCrWebInvokeCommandAndKey(@"wctest.largequeue", valid_key);
428 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
429 SimulateLoadRequest(request);
432 // Verify the queue still works and all messages are delivered.
433 [webController_ setJsMessageQueueThrottled:NO];
434 EXPECT_EQ(kNumQueuedMessages, [observer messageCount]);
436 [webController_ removeObserver:observer];
439 TEST_F(CRWUIWebViewWebControllerTest,
440 CrWebInvokeWithAllMessagesFromCurrentWindow) {
441 // Pre-define test window id.
442 [webController_ setWindowId:@"12345678901234567890123456789012"];
443 NSString* valid_key = [webController_ windowId];
444 [[[mockWebView_ stub] andReturn:kTestURLString]
445 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
447 // Install an observer to handle custom command messages.
448 base::scoped_nsobject<CountingObserver> observer(
449 [[CountingObserver alloc] init]);
450 [webController_ addObserver:observer];
453 [webController_ setJsMessageQueueThrottled:YES];
454 NSMutableURLRequest* request =
455 requestForCrWebInvokeCommandAndKey(@"wctest.currentwindow1", valid_key);
456 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
457 SimulateLoadRequest(request);
459 requestForCrWebInvokeCommandAndKey(@"wctest.currentwindow2", valid_key);
460 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
461 SimulateLoadRequest(request);
463 // Verify the behavior.
464 [webController_ setJsMessageQueueThrottled:NO];
465 EXPECT_EQ(2, [observer messageCount]);
467 [webController_ removeObserver:observer];
470 TEST_F(CRWUIWebViewWebControllerTest,
471 CrWebInvokeWithMessagesFromDifferentWindows) {
472 // Pre-define test window id.
473 [webController_ setWindowId:@"DEADBEEFDEADBEEFDEADBEEFDEADBEEF"];
474 NSString* valid_key = [webController_ windowId];
475 [[[mockWebView_ stub] andReturn:kTestURLString]
476 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
478 // Install an observer to handle custom command messages.
479 base::scoped_nsobject<CountingObserver> observer(
480 [[CountingObserver alloc] init]);
481 [webController_ addObserver:observer];
484 [webController_ setJsMessageQueueThrottled:YES];
485 NSMutableURLRequest* request =
486 requestForCrWebInvokeCommandAndKey(@"wctest.diffwindow1", valid_key);
487 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
488 SimulateLoadRequest(request);
490 // Second queued message will come with a new window id.
491 [webController_ setWindowId:@"12345678901234567890123456789012"];
492 valid_key = [webController_ windowId];
494 requestForCrWebInvokeCommandAndKey(@"wctest.diffwindow2", valid_key);
495 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
496 SimulateLoadRequest(request);
498 [webController_ setJsMessageQueueThrottled:NO];
499 EXPECT_EQ(1, [observer messageCount]);
501 [webController_ removeObserver:observer];
504 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithSameOrigin) {
505 // Pre-define test window id.
506 [webController_ setWindowId:@"12345678901234567890123456789012"];
507 NSString* valid_key = [webController_ windowId];
508 [[[mockWebView_ stub] andReturn:@"http://www.google.com/foo"]
509 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
511 // Install an observer to handle custom command messages.
512 base::scoped_nsobject<CountingObserver> observer(
513 [[CountingObserver alloc] init]);
514 [webController_ addObserver:observer];
517 [webController_ setJsMessageQueueThrottled:YES];
518 NSMutableURLRequest* request =
519 requestForCrWebInvokeCommandAndKey(@"wctest.sameorigin", valid_key);
520 // Make originURL different from currentURL but keep the origin the same.
522 setMainDocumentURL:[NSURL URLWithString:@"http://www.google.com/bar"]];
523 SimulateLoadRequest(request);
524 // Verify the behavior.
525 [webController_ setJsMessageQueueThrottled:NO];
526 EXPECT_EQ(1, [observer messageCount]);
528 [webController_ removeObserver:observer];
531 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithDifferentOrigin) {
532 // Pre-define test window id.
533 [webController_ setWindowId:@"12345678901234567890123456789012"];
534 NSString* valid_key = [webController_ windowId];
535 [[[mockWebView_ stub] andReturn:@"http://www.google.com/"]
536 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
538 // Install an observer to handle custom command messages.
539 base::scoped_nsobject<CountingObserver> observer(
540 [[CountingObserver alloc] init]);
541 [webController_ addObserver:observer];
544 [webController_ setJsMessageQueueThrottled:YES];
545 NSMutableURLRequest* request =
546 requestForCrWebInvokeCommandAndKey(@"wctest.difforigin", valid_key);
547 // Make originURL have a different scheme from currentURL so that the origin
549 [request setMainDocumentURL:[NSURL URLWithString:@"https://www.google.com/"]];
550 SimulateLoadRequest(request);
551 // Verify the behavior.
552 [webController_ setJsMessageQueueThrottled:NO];
553 EXPECT_EQ(0, [observer messageCount]);
555 [webController_ removeObserver:observer];
558 TEST_F(CRWUIWebViewWebControllerTest, EmptyMessageQueue) {
559 [[[mockWebView_ stub] andReturn:@"[]"]
560 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
562 NSURLRequest* request =
563 [NSURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
565 SimulateLoadRequest(request);
568 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenBlankURL) {
569 NSString* messageQueueJSON = @"[{"
570 "\"command\" : \"window.open\", "
571 "\"target\" : \"newtab\", "
573 "\"referrerPolicy\" : \"default\" }]";
576 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
577 [mockDelegate_ onSelector:selector
578 callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
579 NSString* windowName, BOOL inBackground) {
580 EXPECT_EQ(url, GURL("about:blank"));
581 EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
582 EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
583 EXPECT_FALSE(inBackground);
585 [[[mockWebView_ stub] andReturn:messageQueueJSON]
586 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
587 [[[mockWebView_ stub] andReturn:kTestURLString]
588 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
590 NSMutableURLRequest* request =
591 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
592 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
593 [webController_ touched:YES];
594 SimulateLoadRequest(request);
596 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
600 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithInteraction) {
601 NSString* messageQueueJSON = @"[{"
602 "\"command\" : \"window.open\", "
603 "\"target\" : \"newtab\", "
604 "\"url\" : \"http://chromium.org\", "
605 "\"referrerPolicy\" : \"default\" }]";
608 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
609 [mockDelegate_ onSelector:selector
610 callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
611 NSString* windowName, BOOL inBackground) {
612 EXPECT_EQ(url, GURL("http://chromium.org"));
613 EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
614 EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
615 EXPECT_FALSE(inBackground);
617 [[[mockWebView_ stub] andReturn:messageQueueJSON]
618 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
619 [[[mockWebView_ stub] andReturn:kTestURLString]
620 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
622 NSMutableURLRequest* request =
623 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
624 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
625 [webController_ touched:YES];
626 SimulateLoadRequest(request);
628 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
632 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithFinishingInteraction) {
633 NSString* messageQueueJSON = @"[{"
634 "\"command\" : \"window.open\", "
635 "\"target\" : \"newtab\", "
636 "\"url\" : \"http://chromium.org\", "
637 "\"referrerPolicy\" : \"default\" }]";
640 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
641 [mockDelegate_ onSelector:selector
642 callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
643 NSString* windowName, BOOL inBackground) {
644 EXPECT_EQ(url, GURL("http://chromium.org"));
645 EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
646 EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
647 EXPECT_FALSE(inBackground);
649 [[[mockWebView_ stub] andReturn:kTestURLString]
650 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
652 NSMutableURLRequest* request =
653 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
654 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
655 [webController_ touched:YES];
656 [webController_ touched:NO];
657 SimulateLoadRequest(request);
659 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
663 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithoutInteraction) {
664 NSString* messageQueueJSON = @"[{"
665 "\"command\" : \"window.open\", "
666 "\"target\" : \"newtab\", "
667 "\"url\" : \"http://chromium.org\", "
668 "\"referrerPolicy\" : \"default\" }]";
669 [[[mockWebView_ stub] andReturn:kTestURLString]
670 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
672 NSMutableURLRequest* request =
673 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
674 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
675 SimulateLoadRequest(request);
677 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
680 MockInteractionLoader* delegate = (MockInteractionLoader*)mockDelegate_;
681 EXPECT_EQ("http://www.google.com/?q=foo#bar", [delegate sourceURL].spec());
682 EXPECT_EQ("http://chromium.org/", [delegate popupURL].spec());
684 EXPECT_TRUE([delegate blockedPopupInfo]);
687 TEST_F(CRWUIWebViewWebControllerTest, WindowClose) {
688 NSString* messageQueueJSON = @"[{"
689 "\"command\" : \"window.close\", "
690 "\"target\" : \"newtab\" }]";
692 [[mockChildWebController_ expect] orderClose];
694 NSMutableURLRequest* request =
695 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
696 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
697 [[[mockWebView_ stub] andReturn:kTestURLString]
698 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
700 SimulateLoadRequest(request);
702 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
706 TEST_F(CRWUIWebViewWebControllerTest, WindowStop) {
707 NSString* messageQueueJSON = @"[{"
708 "\"command\" : \"window.stop\", "
709 "\"target\" : \"newtab\" }]";
711 [[mockChildWebController_ expect] stopLoading];
713 NSMutableURLRequest* request =
714 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
715 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
716 [[[mockWebView_ stub] andReturn:kTestURLString]
717 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
719 SimulateLoadRequest(request);
721 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
725 TEST_F(CRWUIWebViewWebControllerTest, DocumentWrite) {
726 NSString* messageQueueJSON = @"[{"
727 "\"command\" : \"window.document.write\", "
728 "\"target\" : \"newtab\", "
729 "\"html\" : \"<html></html>\" }]";
731 [[mockChildWebController_ expect] loadHTML:@"<html></html>"];
732 [[[mockWebView_ stub] andReturn:kTestURLString]
733 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
735 NSMutableURLRequest* request =
736 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
737 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
738 SimulateLoadRequest(request);
740 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
744 TEST_F(CRWUIWebViewWebControllerTest, UnicodeEncoding) {
745 base::scoped_nsobject<UIWebView> testWebView(
746 [[UIWebView alloc] initWithFrame:CGRectZero]);
747 NSArray* unicodeArray = @[
749 @"unicode £´∑∂∆˚√˜ß∂",
756 for (NSString* unicodeString in unicodeArray) {
758 [NSString stringWithFormat:@"encodeURIComponent('%@');", unicodeString];
759 NSString* encodedString =
760 [testWebView stringByEvaluatingJavaScriptFromString:encodeJS];
761 NSString* decodedString = [encodedString
762 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
763 EXPECT_NSEQ(unicodeString, decodedString);
767 // Tests the removal of document.loaded and document.present commands in the
768 // CRWJSInvokeParameterQueue. Only applicable for UIWebView.
769 TEST_F(CRWUIWebViewWebControllerTest, PageLoadCommandRemoval) {
770 [[[mockWebView_ stub] andReturn:@"[]"]
771 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
772 [[[mockWebView_ stub] andReturn:kTestURLString]
773 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
775 // Pre-define test window id.
776 [webController_ setWindowId:@"12345678901234567890123456789012"];
777 NSString* valid_key = [webController_ windowId];
779 // Add some commands to the queue.
780 [webController_ setJsMessageQueueThrottled:YES];
781 NSURLRequest* request =
782 requestForCrWebInvokeCommandAndKey(@"document.present", valid_key);
783 SimulateLoadRequest(request);
784 request = requestForCrWebInvokeCommandAndKey(@"document.loaded", valid_key);
785 SimulateLoadRequest(request);
787 requestForCrWebInvokeCommandAndKey(@"window.history.forward", valid_key);
788 SimulateLoadRequest(request);
790 // Check the queue size before and after removing document load commands.
791 NSUInteger expectedBeforeCount = 3;
792 NSUInteger expectedAfterCount = 1;
793 ASSERT_EQ(expectedBeforeCount,
794 [[static_cast<CRWUIWebViewWebController*>(webController_)
795 jsInvokeParameterQueue] queueLength]);
796 [webController_ removeDocumentLoadCommandsFromQueue];
797 ASSERT_EQ(expectedAfterCount,
798 [[static_cast<CRWUIWebViewWebController*>(webController_)
799 jsInvokeParameterQueue] queueLength]);
800 [webController_ setJsMessageQueueThrottled:NO];
803 #define MAKE_URL(url_string) GURL([url_string UTF8String])
805 WEB_TEST_F(CRWUIWebViewWebControllerTest,
806 CRWWKWebViewWebControllerTest,
807 URLForHistoryNavigation) {
808 NSArray* urlsNoFragments = @[
811 @"http://three.com/bar",
812 @"http://four.com/bar/",
819 NSArray* fragments = @[ @"#", @"#bar" ];
820 NSMutableArray* urlsWithFragments = [NSMutableArray array];
821 for (NSString* url in urlsNoFragments) {
822 for (NSString* fragment in fragments) {
823 [urlsWithFragments addObject:[url stringByAppendingString:fragment]];
827 // No start fragment: the end url is never changed.
828 for (NSString* start in urlsNoFragments) {
829 for (NSString* end in urlsWithFragments) {
830 EXPECT_EQ(MAKE_URL(end),
831 [this->webController_
832 updateURLForHistoryNavigationFromURL:MAKE_URL(start)
833 toURL:MAKE_URL(end)]);
836 // Both contain fragments: the end url is never changed.
837 for (NSString* start in urlsWithFragments) {
838 for (NSString* end in urlsWithFragments) {
839 EXPECT_EQ(MAKE_URL(end),
840 [this->webController_
841 updateURLForHistoryNavigationFromURL:MAKE_URL(start)
842 toURL:MAKE_URL(end)]);
845 for (unsigned start_index = 0; start_index < [urlsWithFragments count];
847 NSString* start = urlsWithFragments[start_index];
848 for (unsigned end_index = 0; end_index < [urlsNoFragments count];
850 NSString* end = urlsNoFragments[end_index];
851 if (start_index / 2 != end_index) {
852 // The URLs have nothing in common, they are left untouched.
853 EXPECT_EQ(MAKE_URL(end),
854 [this->webController_
855 updateURLForHistoryNavigationFromURL:MAKE_URL(start)
856 toURL:MAKE_URL(end)]);
858 // Start contains a fragment and matches end: An empty fragment is
860 EXPECT_EQ(MAKE_URL([end stringByAppendingString:@"#"]),
861 [this->webController_
862 updateURLForHistoryNavigationFromURL:MAKE_URL(start)
863 toURL:MAKE_URL(end)]);
869 // This test requires iOS net stack.
870 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
871 // Tests that presentSSLError:forSSLStatus:recoverable:callback: is called with
872 // correct arguments if WKWebView fails to load a page with bad SSL cert.
873 TEST_F(CRWWKWebViewWebControllerTest, SSLError) {
874 CR_TEST_REQUIRES_WK_WEB_VIEW();
876 ASSERT_FALSE([mockDelegate_ SSLInfo].is_valid());
879 [NSError errorWithDomain:NSURLErrorDomain
880 code:NSURLErrorServerCertificateHasUnknownRoot
882 WKWebView* webView = static_cast<WKWebView*>([webController_ webView]);
883 [static_cast<id<WKNavigationDelegate>>(webController_.get()) webView:webView
884 didFailProvisionalNavigation:nil
887 // Verify correctness of delegate's method arguments.
888 EXPECT_TRUE([mockDelegate_ SSLInfo].is_valid());
889 EXPECT_EQ(net::CERT_STATUS_INVALID, [mockDelegate_ SSLInfo].cert_status);
890 EXPECT_EQ(net::CERT_STATUS_INVALID, [mockDelegate_ SSLStatus].cert_status);
891 EXPECT_EQ(web::SECURITY_STYLE_AUTHENTICATION_BROKEN,
892 [mockDelegate_ SSLStatus].security_style);
893 EXPECT_FALSE([mockDelegate_ recoverable]);
894 EXPECT_FALSE([mockDelegate_ shouldContinueCallback]);
896 #endif // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
898 // None of the |CRWUIWebViewWebControllerTest| setup is needed;
899 typedef web::WebTestWithUIWebViewWebController
900 CRWUIWebControllerPageDialogsOpenPolicyTest;
902 // None of the |CRWWKWebViewWebControllerTest| setup is needed;
903 typedef web::WebTestWithWKWebViewWebController
904 CRWWKWebControllerPageDialogsOpenPolicyTest;
906 WEB_TEST_F(CRWUIWebControllerPageDialogsOpenPolicyTest,
907 CRWWKWebControllerPageDialogsOpenPolicyTest,
909 this->LoadHtml(@"<html><body></body></html>");
910 id delegate = [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
911 [[delegate expect] webControllerDidSuppressDialog:this->webController_];
913 [this->webController_ setDelegate:delegate];
914 [this->webController_ setPageDialogOpenPolicy:web::DIALOG_POLICY_SUPPRESS];
915 this->RunJavaScript(@"alert('')");
917 this->WaitForBackgroundTasks();
918 EXPECT_OCMOCK_VERIFY(delegate);
919 [this->webController_ setDelegate:nil];
922 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
924 class CRWUIWebControllerPageScrollStateTest
925 : public web::WebTestWithUIWebViewWebController {
927 // Returns a web::PageDisplayState that will scroll a UIWebView to
928 // |scrollOffset| and zoom the content by |relativeZoomScale|.
929 inline web::PageDisplayState CreateTestPageDisplayState(
930 CGPoint scroll_offset,
931 CGFloat relative_zoom_scale,
932 CGFloat original_minimum_zoom_scale,
933 CGFloat original_maximum_zoom_scale,
934 CGFloat original_zoom_scale) const {
935 return web::PageDisplayState(
936 scroll_offset.x, scroll_offset.y,
937 original_minimum_zoom_scale / relative_zoom_scale,
938 original_maximum_zoom_scale / relative_zoom_scale, 1.0);
942 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
944 class CRWWKWebControllerPageScrollStateTest
945 : public web::WebTestWithWKWebViewWebController {
947 // Returns a web::PageDisplayState that will scroll a WKWebView to
948 // |scrollOffset| and zoom the content by |relativeZoomScale|.
949 inline web::PageDisplayState CreateTestPageDisplayState(
950 CGPoint scroll_offset,
951 CGFloat relative_zoom_scale,
952 CGFloat original_minimum_zoom_scale,
953 CGFloat original_maximum_zoom_scale,
954 CGFloat original_zoom_scale) const {
955 return web::PageDisplayState(
956 scroll_offset.x, scroll_offset.y, original_minimum_zoom_scale,
957 original_maximum_zoom_scale,
958 relative_zoom_scale * original_minimum_zoom_scale);
962 // TODO(iOS): Flaky on the bots. crbug/493427
963 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
964 CRWWKWebControllerPageScrollStateTest,
965 FLAKY_SetPageDisplayStateWithUserScalableDisabled) {
966 #if !TARGET_IPHONE_SIMULATOR
967 // This test fails flakily on device with WKWebView, so skip it there.
969 if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE)
972 web::PageZoomState zoom_state(1.0, 5.0, 1.0);
973 this->LoadHtml(GetHTMLForZoomState(zoom_state, PAGE_SCALABILITY_DISABLED));
974 CRWWebController* web_controller = this->webController_.get();
975 WaitForZoomRendering(web_controller, zoom_state);
976 web::PageZoomState original_zoom_state =
977 web_controller.pageDisplayState.zoom_state();
979 web::NavigationManager* nagivation_manager =
980 web_controller.webState->GetNavigationManager();
981 nagivation_manager->GetLastCommittedItem()->SetPageDisplayState(
982 this->CreateTestPageDisplayState(CGPointMake(1.0, 1.0), // scroll offset
983 3.0, // relative zoom scale
984 1.0, // original minimum zoom scale
985 5.0, // original maximum zoom scale
986 1.0)); // original zoom scale
987 [web_controller restoreStateFromHistory];
989 // |-restoreStateFromHistory| is async; wait for its completion.
990 base::test::ios::WaitUntilCondition(^bool() {
991 return web_controller.pageDisplayState.scroll_state().offset_x() == 1.0;
994 ASSERT_EQ(original_zoom_state, web_controller.pageDisplayState.zoom_state());
997 // TODO(iOS): Flaky on the bots. crbug/493427
998 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
999 CRWWKWebControllerPageScrollStateTest,
1000 FLAKY_SetPageDisplayStateWithUserScalableEnabled) {
1001 web::PageZoomState zoom_state(1.0, 10.0, 1.0);
1002 this->LoadHtml(GetHTMLForZoomState(zoom_state, PAGE_SCALABILITY_ENABLED));
1003 CRWWebController* web_controller = this->webController_.get();
1004 WaitForZoomRendering(web_controller, zoom_state);
1006 web::NavigationManager* nagivation_manager =
1007 web_controller.webState->GetNavigationManager();
1008 nagivation_manager->GetLastCommittedItem()->SetPageDisplayState(
1009 this->CreateTestPageDisplayState(CGPointMake(1.0, 1.0), // scroll offset
1010 3.0, // relative zoom scale
1011 1.0, // original minimum zoom scale
1012 10.0, // original maximum zoom scale
1013 1.0)); // original zoom scale
1014 [web_controller restoreStateFromHistory];
1016 // |-restoreStateFromHistory| is async; wait for its completion.
1017 base::test::ios::WaitUntilCondition(^bool() {
1018 return web_controller.pageDisplayState.scroll_state().offset_x() == 1.0;
1021 web::PageZoomState final_zoom_state =
1022 web_controller.pageDisplayState.zoom_state();
1023 EXPECT_FLOAT_EQ(3, final_zoom_state.zoom_scale() /
1024 final_zoom_state.minimum_zoom_scale());
1027 // TODO(iOS): Flaky on the bots. crbug/493427
1028 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
1029 CRWWKWebControllerPageScrollStateTest,
1031 // This test fails on iPhone 6/6+ with WKWebView; skip until it's fixed.
1033 if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE &&
1037 web::PageZoomState zoom_state = web::PageZoomState(1.0, 5.0, 1.0);
1038 this->LoadHtml(GetHTMLForZoomState(zoom_state, PAGE_SCALABILITY_ENABLED));
1039 CRWWebController* web_controller = this->webController_.get();
1040 WaitForZoomRendering(web_controller, zoom_state);
1041 ASSERT_TRUE(web_controller.atTop);
1043 web::NavigationManager* nagivation_manager =
1044 web_controller.webState->GetNavigationManager();
1045 nagivation_manager->GetLastCommittedItem()->SetPageDisplayState(
1046 this->CreateTestPageDisplayState(CGPointMake(0.0, 30.0), // scroll offset
1047 5.0, // relative zoom scale
1048 1.0, // original minimum zoom scale
1049 5.0, // original maximum zoom scale
1050 1.0)); // original zoom scale
1051 [web_controller restoreStateFromHistory];
1053 // |-restoreStateFromHistory| is async; wait for its completion.
1054 base::test::ios::WaitUntilCondition(^bool() {
1055 return web_controller.pageDisplayState.scroll_state().offset_y() == 30.0;
1058 ASSERT_FALSE(web_controller.atTop);
1061 // Tests that evaluateJavaScript:completionHandler: properly forwards the
1062 // call to UIWebView.
1063 TEST_F(CRWUIWebViewWebControllerTest, JavaScriptEvaluation) {
1064 NSString* kTestScript = @"script";
1065 NSString* kTestResult = @"result";
1066 NSString* scriptWithIDCheck =
1067 [webController_ scriptByAddingWindowIDCheckForScript:kTestScript];
1068 [[[mockWebView_ stub] andReturn:kTestResult]
1069 stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1071 __block BOOL completionBlockWasCalled = NO;
1072 [webController_ evaluateJavaScript:kTestScript
1073 stringResultHandler:^(NSString* string, NSError* error) {
1074 completionBlockWasCalled = YES;
1075 EXPECT_NSEQ(kTestResult, string);
1076 EXPECT_EQ(nil, error);
1079 // Wait until JavaScript is evaluated.
1080 base::test::ios::WaitUntilCondition(^bool() {
1081 return completionBlockWasCalled;
1083 EXPECT_TRUE(completionBlockWasCalled);
1086 TEST_F(CRWUIWebViewWebControllerTest, POSTRequestCache) {
1087 GURL url("http://www.google.fr/");
1088 NSString* mixedCaseCookieHeaderName = @"cOoKiE";
1089 NSString* otherHeaderName = @"Myheader";
1090 NSString* otherHeaderValue = @"A";
1091 NSString* otherHeaderIncorrectValue = @"C";
1093 scoped_ptr<web::NavigationItemImpl> item(new web::NavigationItemImpl());
1095 item->SetTransitionType(ui::PAGE_TRANSITION_FORM_SUBMIT);
1096 item->set_is_renderer_initiated(true);
1097 base::scoped_nsobject<CRWSessionEntry> currentEntry(
1098 [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass()]);
1099 base::scoped_nsobject<NSMutableURLRequest> request(
1100 [[NSMutableURLRequest alloc] initWithURL:net::NSURLWithGURL(url)]);
1101 [request setHTTPMethod:@"POST"];
1102 [request setValue:otherHeaderValue forHTTPHeaderField:otherHeaderName];
1103 [request setValue:@"B" forHTTPHeaderField:mixedCaseCookieHeaderName];
1104 // No data is cached initially.
1105 EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1106 EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1107 // Streams are not cached.
1108 [request setHTTPBodyStream:[NSInputStream inputStreamWithData:[NSData data]]];
1109 [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1110 EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1111 EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1112 [request setHTTPBodyStream:nil];
1113 // POST Data is cached. Cookie headers are stripped, no matter their case.
1114 base::scoped_nsobject<NSData> body([[NSData alloc] init]);
1115 [request setHTTPBody:body];
1116 [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1117 EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1118 EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1119 @{otherHeaderName : otherHeaderValue});
1120 // A new request will not change the cached version.
1121 base::scoped_nsobject<NSData> body2([[NSData alloc] init]);
1122 [request setValue:otherHeaderIncorrectValue
1123 forHTTPHeaderField:otherHeaderName];
1124 [request setHTTPBody:body2];
1125 EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1126 EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1127 @{otherHeaderName : otherHeaderValue});
1130 class WebControllerJSEvaluationTest : public CRWUIWebViewWebControllerTest {
1132 void SetUp() override {
1133 WebControllerTest::SetUp();
1135 NSString* kTestResult = @"result";
1136 completionBlockWasCalled_ = NO;
1137 NSString* scriptWithIDCheck =
1138 [webController_ scriptByAddingWindowIDCheckForScript:GetTestScript()];
1139 [[[mockWebView_ stub] andReturn:kTestResult]
1140 stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1141 completionHandler_.reset([^(NSString* string, NSError* error) {
1142 completionBlockWasCalled_ = YES;
1143 EXPECT_NSEQ(kTestResult, string);
1144 EXPECT_EQ(nil, error);
1147 NSString* GetTestScript() const { return @"script"; }
1148 base::scoped_nsprotocol<web::JavaScriptCompletion> completionHandler_;
1149 BOOL completionBlockWasCalled_;
1152 // Tests that -evaluateJavaScript:stringResultHandler: properly forwards
1153 // the call to the UIWebView.
1154 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluation) {
1155 [webController_ evaluateJavaScript:GetTestScript()
1156 stringResultHandler:completionHandler_];
1157 // Wait until JavaScript is evaluated.
1158 base::test::ios::WaitUntilCondition(^bool() {
1159 return this->completionBlockWasCalled_;
1161 EXPECT_TRUE(completionBlockWasCalled_);
1164 // Tests that -evaluateUserJavaScript:stringResultHandler: properly
1165 // forwards the call to the UIWebView.
1166 TEST_F(WebControllerJSEvaluationTest, UserJavaScriptEvaluation) {
1167 __block BOOL method_called = NO;
1168 [[[mockWebView_ stub] andDo:^(NSInvocation*) {
1169 method_called = YES;
1171 performSelectorOnMainThread:@selector(
1172 stringByEvaluatingJavaScriptFromString:)
1173 withObject:GetTestScript()
1175 [webController_ evaluateUserJavaScript:GetTestScript()];
1176 EXPECT_TRUE(method_called);
1179 // Tests that -evaluateJavaScript:stringResultHandler: does not crash
1180 // on a nil completionHandler.
1181 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluationNilHandler) {
1182 [webController_ evaluateJavaScript:GetTestScript() stringResultHandler:nil];
1185 // Real UIWebView is required for JSEvaluationTest.
1186 typedef web::WebTestWithUIWebViewWebController
1187 CRWUIWebControllerJSEvaluationTest;
1189 // Real WKWebView is required for JSEvaluationTest.
1190 typedef web::WebTestWithWKWebViewWebController
1191 CRWWKWebControllerJSEvaluationTest;
1193 // Tests that a script correctly evaluates to string.
1194 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1195 CRWWKWebControllerJSEvaluationTest,
1197 this->LoadHtml(@"<p></p>");
1198 EXPECT_NSEQ(@"true", this->EvaluateJavaScriptAsString(@"true"));
1199 EXPECT_NSEQ(@"false", this->EvaluateJavaScriptAsString(@"false"));
1202 // Tests that a script is not evaluated on windowID mismatch.
1203 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1204 CRWWKWebControllerJSEvaluationTest,
1205 WindowIDMissmatch) {
1206 this->LoadHtml(@"<p></p>");
1207 // Script is evaluated since windowID is matched.
1208 this->EvaluateJavaScriptAsString(@"window.test1 = '1';");
1209 EXPECT_NSEQ(@"1", this->EvaluateJavaScriptAsString(@"window.test1"));
1212 this->EvaluateJavaScriptAsString(@"__gCrWeb['windowId'] = '';");
1214 // Script is not evaluated because of windowID mismatch.
1215 this->EvaluateJavaScriptAsString(@"window.test2 = '2';");
1216 EXPECT_NSEQ(@"", this->EvaluateJavaScriptAsString(@"window.test2"));
1219 // A separate test class is used for testing the keyboard dismissal, as none of
1220 // the setup in |CRWUIWebViewWebControllerTest| is needed; keyboard appearance
1221 // is tracked by the KeyboardAppearanceListener.
1222 class WebControllerKeyboardTest
1223 : public web::WebTestWithUIWebViewWebController {
1225 void SetUp() override {
1226 web::WebTestWithUIWebViewWebController::SetUp();
1227 // Close any outstanding alert boxes.
1228 ui::test::uiview_utils::CancelAlerts();
1230 // Sets up the listener for keyboard activation/deactivation notifications.
1231 keyboardListener_.reset([[KeyboardAppearanceListener alloc] init]);
1234 base::scoped_nsobject<KeyboardAppearanceListener> keyboardListener_;
1237 TEST_F(WebControllerKeyboardTest, DismissKeyboard) {
1238 LoadHtml(@"<html><head></head><body><form><input id=\"textField\" /></form>"
1242 UIWebView* webView = static_cast<UIWebView*>(
1243 [webController_ containerView].webViewContentView.webView);
1244 EXPECT_TRUE(webView);
1246 // Create the window and add the webview.
1247 base::scoped_nsobject<UIWindow> window(
1248 [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]);
1249 [window makeKeyAndVisible];
1250 [window addSubview:webView];
1252 // Set the flag to allow focus() in code in UIWebView.
1253 EXPECT_TRUE([webView keyboardDisplayRequiresUserAction]);
1254 [webView setKeyboardDisplayRequiresUserAction:NO];
1255 EXPECT_FALSE([webView keyboardDisplayRequiresUserAction]);
1257 // Set the focus on the textField.
1258 EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1259 [webView stringByEvaluatingJavaScriptFromString:
1260 @"document.getElementById(\"textField\").focus();"];
1261 base::test::ios::WaitUntilCondition(^bool() {
1262 return [keyboardListener_ isKeyboardVisible];
1264 EXPECT_TRUE([keyboardListener_ isKeyboardVisible]);
1267 [webController_ dismissKeyboard];
1269 base::test::ios::WaitUntilCondition(^bool() {
1270 return ![keyboardListener_ isKeyboardVisible];
1272 EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1275 TEST_F(CRWWKWebViewWebControllerTest, WebURLWithTrustLevel) {
1276 CR_TEST_REQUIRES_WK_WEB_VIEW();
1278 [[[mockWebView_ stub] andReturn:[NSURL URLWithString:kTestURLString]] URL];
1279 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1280 [[[mockWebView_ stub] andReturnBool:NO] hasOnlySecureContent];
1283 // Stub out the injection process.
1284 [[mockWebView_ stub] evaluateJavaScript:OCMOCK_ANY
1285 completionHandler:OCMOCK_ANY];
1287 // Simulate registering load request to avoid failing page load simulation.
1288 [webController_ simulateLoadRequestWithURL:GURL([kTestURLString UTF8String])];
1289 // Simulate a page load to trigger a URL update.
1290 [static_cast<id<WKNavigationDelegate>>(webController_.get())
1291 webView:mockWebView_
1292 didCommitNavigation:nil];
1294 web::URLVerificationTrustLevel trust_level = web::kNone;
1295 GURL gurl = [webController_ currentURLWithTrustLevel:&trust_level];
1297 EXPECT_EQ(gurl, GURL(base::SysNSStringToUTF8(kTestURLString)));
1298 EXPECT_EQ(web::kAbsolute, trust_level);
1301 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
1303 typedef web::WebTestWithUIWebViewWebController CRWUIWebControllerObserversTest;
1304 typedef web::WebTestWithWKWebViewWebController CRWWKWebControllerObserversTest;
1306 // Tests that CRWWebControllerObservers are called.
1307 WEB_TEST_F(CRWUIWebControllerObserversTest,
1308 CRWWKWebControllerObserversTest,
1310 base::scoped_nsobject<CountingObserver> observer(
1311 [[CountingObserver alloc] init]);
1312 CRWWebController* web_controller = this->webController_;
1313 EXPECT_EQ(0u, [web_controller observerCount]);
1314 [web_controller addObserver:observer];
1315 EXPECT_EQ(1u, [web_controller observerCount]);
1317 EXPECT_EQ(0, [observer pageLoadedCount]);
1318 [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), false);
1319 EXPECT_EQ(0, [observer pageLoadedCount]);
1320 [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), true);
1321 EXPECT_EQ(1, [observer pageLoadedCount]);
1323 EXPECT_EQ(0, [observer messageCount]);
1324 // Non-matching prefix.
1325 EXPECT_FALSE([web_controller webStateImpl]->OnScriptCommandReceived(
1326 "a", base::DictionaryValue(), GURL("http://test"), true));
1327 EXPECT_EQ(0, [observer messageCount]);
1329 EXPECT_TRUE([web_controller webStateImpl]->OnScriptCommandReceived(
1330 base::SysNSStringToUTF8([observer commandPrefix]) + ".foo",
1331 base::DictionaryValue(), GURL("http://test"), true));
1332 EXPECT_EQ(1, [observer messageCount]);
1334 [web_controller removeObserver:observer];
1335 EXPECT_EQ(0u, [web_controller observerCount]);
1338 // Test fixture for window.open tests.
1339 class CRWWKWebControllerWindowOpenTest
1340 : public web::WebTestWithWKWebViewWebController {
1342 void SetUp() override {
1343 CR_TEST_REQUIRES_WK_WEB_VIEW();
1344 web::WebTestWithWKWebViewWebController::SetUp();
1346 // Configure web delegate.
1347 delegate_.reset([[MockInteractionLoader alloc]
1348 initWithRepresentedObject:
1349 [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)]]);
1350 ASSERT_TRUE([delegate_ blockPopups]);
1351 [webController_ setDelegate:delegate_];
1353 // Configure child web controller.
1354 child_.reset(CreateWebController());
1355 [child_ setWebUsageEnabled:YES];
1356 [delegate_ setChildWebController:child_];
1358 // Configure child web controller's session controller mock.
1359 id sessionController =
1360 [OCMockObject niceMockForClass:[CRWSessionController class]];
1362 [[[sessionController stub] andReturnValue:OCMOCK_VALUE(yes)] isOpenedByDOM];
1363 [child_ webStateImpl]->GetNavigationManagerImpl().SetSessionController(
1366 LoadHtml(@"<html><body></body></html>");
1368 void TearDown() override {
1369 EXPECT_OCMOCK_VERIFY(delegate_);
1370 [webController_ setDelegate:nil];
1373 web::WebTestWithWKWebViewWebController::TearDown();
1375 // Executes JavaScript that opens a new window and returns evaluation result
1377 NSString* OpenWindowByDOM() {
1378 NSString* const kOpenWindowScript =
1379 @"var w = window.open('javascript:void(0);', target='_blank');"
1381 NSString* windowJSObject = EvaluateJavaScriptAsString(kOpenWindowScript);
1382 WaitForBackgroundTasks();
1383 return windowJSObject;
1385 // A CRWWebDelegate mock used for testing.
1386 base::scoped_nsobject<id> delegate_;
1387 // A child CRWWebController used for testing.
1388 base::scoped_nsobject<CRWWebController> child_;
1391 // Tests that absence of web delegate is handled gracefully.
1392 TEST_F(CRWWKWebControllerWindowOpenTest, NoDelegate) {
1393 CR_TEST_REQUIRES_WK_WEB_VIEW();
1395 [webController_ setDelegate:nil];
1397 EXPECT_NSEQ(@"", OpenWindowByDOM());
1399 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1402 // Tests that window.open triggered by user gesture opens a new non-popup
1404 TEST_F(CRWWKWebControllerWindowOpenTest, OpenWithUserGesture) {
1405 CR_TEST_REQUIRES_WK_WEB_VIEW();
1407 SEL selector = @selector(webPageOrderedOpenBlankWithReferrer:inBackground:);
1408 [delegate_ onSelector:selector
1409 callBlockExpectation:^(const web::Referrer& referrer,
1410 BOOL in_background) {
1411 EXPECT_EQ("", referrer.url.spec());
1412 EXPECT_FALSE(in_background);
1415 [webController_ touched:YES];
1416 EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1417 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1420 // Tests that window.open executed w/o user gesture does not open a new window.
1421 // Once the blocked popup is allowed a new window is opened.
1422 TEST_F(CRWWKWebControllerWindowOpenTest, AllowPopup) {
1423 CR_TEST_REQUIRES_WK_WEB_VIEW();
1426 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
1427 [delegate_ onSelector:selector
1428 callBlockExpectation:^(const GURL& new_window_url,
1429 const web::Referrer& referrer,
1430 NSString* obsoleted_window_name,
1431 BOOL in_background) {
1432 EXPECT_EQ("javascript:void(0);", new_window_url.spec());
1433 EXPECT_EQ("", referrer.url.spec());
1434 EXPECT_FALSE(in_background);
1437 ASSERT_FALSE([webController_ userIsInteracting]);
1438 EXPECT_NSEQ(@"", OpenWindowByDOM());
1439 base::test::ios::WaitUntilCondition(^bool() {
1440 return [delegate_ blockedPopupInfo];
1443 if ([delegate_ blockedPopupInfo])
1444 [delegate_ blockedPopupInfo]->ShowPopup();
1446 EXPECT_EQ("", [delegate_ sourceURL].spec());
1447 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1450 // Tests that window.open executed w/o user gesture opens a new window, assuming
1451 // that delegate allows popups.
1452 TEST_F(CRWWKWebControllerWindowOpenTest, DontBlockPopup) {
1453 CR_TEST_REQUIRES_WK_WEB_VIEW();
1455 [delegate_ setBlockPopups:NO];
1456 SEL selector = @selector(webPageOrderedOpenBlankWithReferrer:inBackground:);
1457 [delegate_ onSelector:selector
1458 callBlockExpectation:^(const web::Referrer& referrer,
1459 BOOL in_background) {
1460 EXPECT_EQ("", referrer.url.spec());
1461 EXPECT_FALSE(in_background);
1464 EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1465 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1467 EXPECT_EQ("", [delegate_ sourceURL].spec());
1468 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1471 // Tests that window.open executed w/o user gesture does not open a new window.
1472 TEST_F(CRWWKWebControllerWindowOpenTest, BlockPopup) {
1473 CR_TEST_REQUIRES_WK_WEB_VIEW();
1475 ASSERT_FALSE([webController_ userIsInteracting]);
1476 EXPECT_NSEQ(@"", OpenWindowByDOM());
1477 base::test::ios::WaitUntilCondition(^bool() {
1478 return [delegate_ blockedPopupInfo];
1481 EXPECT_EQ("", [delegate_ sourceURL].spec());
1482 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1485 // Fixture class to test WKWebView crashes.
1486 class CRWWKWebControllerWebProcessTest
1487 : public web::WebTestWithWKWebViewWebController {
1489 void SetUp() override {
1490 CR_TEST_REQUIRES_WK_WEB_VIEW();
1491 web::WebTestWithWKWebViewWebController::SetUp();
1492 webView_.reset(web::CreateTerminatedWKWebView());
1493 base::scoped_nsobject<TestWebViewContentView> webViewContentView(
1494 [[TestWebViewContentView alloc]
1495 initWithMockWebView:webView_
1496 scrollView:[webView_ scrollView]]);
1497 [webController_ injectWebViewContentView:webViewContentView];
1499 base::scoped_nsobject<WKWebView> webView_;
1502 // Tests that -[CRWWebDelegate webControllerWebProcessDidCrash:] is called
1503 // when WKWebView web process has crashed.
1504 TEST_F(CRWWKWebControllerWebProcessTest, Crash) {
1505 CR_TEST_REQUIRES_WK_WEB_VIEW();
1507 id delegate = [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
1508 [[delegate expect] webControllerWebProcessDidCrash:this->webController_];
1510 [this->webController_ setDelegate:delegate];
1511 web::SimulateWKWebViewCrash(webView_);
1513 EXPECT_OCMOCK_VERIFY(delegate);
1514 [this->webController_ setDelegate:nil];
1517 // Tests that WKWebView does not crash if deallocation happens during JavaScript
1519 typedef web::WebTestWithWKWebViewWebController DeallocationDuringJSEvaluation;
1520 TEST_F(DeallocationDuringJSEvaluation, NoCrash) {
1521 CR_TEST_REQUIRES_WK_WEB_VIEW();
1523 this->LoadHtml(@"<html><body></body></html>");
1524 [webController_ evaluateJavaScript:@"null"
1525 stringResultHandler:^(NSString* value, NSError*) {
1526 // Block can not be empty in order to reproduce the crash:
1527 // https://bugs.webkit.org/show_bug.cgi?id=140203
1528 VLOG(1) << "Script has been flushed.";
1530 // -evaluateJavaScript:stringResultHandler: is asynchronous so JavaScript
1531 // evaluation will not happen until TearDown, which deallocates
1532 // CRWWebController, which in its turn will deallocate WKWebView to create a
1533 // crashy condition.