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/web_test_util.h"
22 #import "ios/web/public/web_state/crw_web_controller_observer.h"
23 #include "ios/web/public/web_state/url_verification_constants.h"
24 #include "ios/web/test/web_test.h"
25 #import "ios/web/test/wk_web_view_crash_utils.h"
26 #include "ios/web/web_state/blocked_popup_info.h"
27 #import "ios/web/web_state/js/crw_js_invoke_parameter_queue.h"
28 #import "ios/web/web_state/ui/crw_ui_web_view_web_controller.h"
29 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
30 #import "ios/web/web_state/web_state_impl.h"
31 #import "net/base/mac/url_conversions.h"
32 #include "net/ssl/ssl_info.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34 #include "testing/gtest_mac.h"
35 #include "third_party/ocmock/OCMock/OCMock.h"
36 #include "third_party/ocmock/gtest_support.h"
37 #include "third_party/ocmock/ocmock_extensions.h"
38 #import "ui/base/test/ios/keyboard_appearance_listener.h"
39 #include "ui/base/test/ios/ui_view_test_utils.h"
41 using web::NavigationManagerImpl;
43 @interface TestWebController (PrivateTesting)
44 - (void)reloadInternal;
47 @interface CRWWebController (PrivateAPI)
48 - (void)setPageScrollState:(const web::PageScrollState&)scrollState;
49 - (void)setJsMessageQueueThrottled:(BOOL)throttle;
50 - (void)removeDocumentLoadCommandsFromQueue;
51 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
52 toURL:(const GURL&)endURL;
53 - (BOOL)checkForUnexpectedURLChange;
54 - (void)injectEarlyInjectionScripts;
55 - (void)stopExpectingURLChangeIfNecessary;
58 @implementation TestWebController (PrivateTesting)
60 - (void)reloadInternal {
61 // Empty implementation to prevent the need to mock out a huge number of
67 // Used to mock CRWWebDelegate methods with C++ params.
68 @interface MockInteractionLoader : OCMockComplexTypeHelper
69 // popupURL passed to webController:shouldBlockPopupWithURL:sourceURL:
71 @property(nonatomic, assign) GURL popupURL;
72 // sourceURL passed to webController:shouldBlockPopupWithURL:sourceURL:
74 @property(nonatomic, assign) GURL sourceURL;
75 // Whether or not the delegate should block popups.
76 @property(nonatomic, assign) BOOL blockPopups;
77 // A web controller that will be returned by webPageOrdered... methods.
78 @property(nonatomic, assign) CRWWebController* childWebController;
79 // Blocked popup info received in |webController:didBlockPopup:| call.
80 // nullptr if that delegate method was not called.
81 @property(nonatomic, readonly) web::BlockedPopupInfo* blockedPopupInfo;
82 // SSL info received in |presentSSLError:forSSLStatus:recoverable:callback:|
84 @property(nonatomic, readonly) net::SSLInfo SSLInfo;
85 // SSL status received in |presentSSLError:forSSLStatus:recoverable:callback:|
87 @property(nonatomic, readonly) web::SSLStatus SSLStatus;
88 // Recoverable flag received in
89 // |presentSSLError:forSSLStatus:recoverable:callback:| call.
90 @property(nonatomic, readonly) BOOL recoverable;
91 // Callback received in |presentSSLError:forSSLStatus:recoverable:callback:|
93 @property(nonatomic, readonly) SSLErrorCallback shouldContinueCallback;
96 @implementation MockInteractionLoader {
97 // Backs up the property with the same name.
98 scoped_ptr<web::BlockedPopupInfo> _blockedPopupInfo;
100 @synthesize popupURL = _popupURL;
101 @synthesize sourceURL = _sourceURL;
102 @synthesize blockPopups = _blockPopups;
103 @synthesize childWebController = _childWebController;
104 @synthesize SSLInfo = _SSLInfo;
105 @synthesize SSLStatus = _SSLStatus;
106 @synthesize recoverable = _recoverable;
107 @synthesize shouldContinueCallback = _shouldContinueCallback;
109 typedef void (^webPageOrderedOpenBlankBlockType)(const web::Referrer&, BOOL);
110 typedef void (^webPageOrderedOpenBlockType)(const GURL&,
111 const web::Referrer&,
115 - (instancetype)initWithRepresentedObject:(id)representedObject {
116 self = [super initWithRepresentedObject:representedObject];
123 - (CRWWebController*)webPageOrderedOpenBlankWithReferrer:
124 (const web::Referrer&)referrer
125 inBackground:(BOOL)inBackground {
126 static_cast<webPageOrderedOpenBlankBlockType>([self blockForSelector:_cmd])(
127 referrer, inBackground);
128 return _childWebController;
131 - (CRWWebController*)webPageOrderedOpen:(const GURL&)url
132 referrer:(const web::Referrer&)referrer
133 windowName:(NSString*)windowName
134 inBackground:(BOOL)inBackground {
135 static_cast<webPageOrderedOpenBlockType>([self blockForSelector:_cmd])(
136 url, referrer, windowName, inBackground);
137 return _childWebController;
140 typedef BOOL (^openExternalURLBlockType)(const GURL&);
142 - (BOOL)openExternalURL:(const GURL&)url {
143 return static_cast<openExternalURLBlockType>([self blockForSelector:_cmd])(
147 - (BOOL)webController:(CRWWebController*)webController
148 shouldBlockPopupWithURL:(const GURL&)popupURL
149 sourceURL:(const GURL&)sourceURL {
150 self.popupURL = popupURL;
151 self.sourceURL = sourceURL;
155 - (void)webController:(CRWWebController*)webController
156 didBlockPopup:(const web::BlockedPopupInfo&)blockedPopupInfo {
157 _blockedPopupInfo.reset(new web::BlockedPopupInfo(blockedPopupInfo));
160 - (web::BlockedPopupInfo*)blockedPopupInfo {
161 return _blockedPopupInfo.get();
164 - (void)presentSSLError:(const net::SSLInfo&)info
165 forSSLStatus:(const web::SSLStatus&)status
166 recoverable:(BOOL)recoverable
167 callback:(SSLErrorCallback)shouldContinue {
170 _recoverable = recoverable;
171 _shouldContinueCallback = shouldContinue;
176 @interface CountingObserver : NSObject<CRWWebControllerObserver>
178 @property(nonatomic, readonly) int pageLoadedCount;
179 @property(nonatomic, readonly) int messageCount;
182 @implementation CountingObserver
183 @synthesize pageLoadedCount = _pageLoadedCount;
184 @synthesize messageCount = _messageCount;
186 - (void)pageLoaded:(CRWWebController*)webController {
190 - (BOOL)handleCommand:(const base::DictionaryValue&)command
191 webController:(CRWWebController*)webController
192 userIsInteracting:(BOOL)userIsInteracting
193 originURL:(const GURL&)originURL {
198 - (NSString*)commandPrefix {
206 NSString* const kGetMessageQueueJavaScript =
207 @"window.__gCrWeb === undefined ? '' : __gCrWeb.message.getMessageQueue()";
209 NSString* kCheckURLJavaScript =
211 "if(!window.__gCrWeb_CachedRequest||"
212 "!(window.__gCrWeb_CachedRequestDocument===window.document)){"
213 "window.__gCrWeb_CachedRequest = new XMLHttpRequest();"
214 "window.__gCrWeb_CachedRequestDocument = window.document;"
216 "window.__gCrWeb_CachedRequest.open('POST',"
217 "'https://localhost:0/crwebiossecurity',false);"
218 "window.__gCrWeb_CachedRequest.send();"
221 "window.__gCrWeb_CachedRequest.open('POST',"
222 "'/crwebiossecurity/b86b97a1-2ce0-44fd-a074-e2158790c98d',false);"
223 "window.__gCrWeb_CachedRequest.send();"
226 "window.location.href";
228 NSString* kTestURLString = @"http://www.google.com/";
230 NSMutableURLRequest* requestForCrWebInvokeCommandAndKey(NSString* command,
232 NSString* fullCommand =
233 [NSString stringWithFormat:@"[{\"command\":\"%@\"}]", command];
234 NSString* escapedCommand = [fullCommand
235 stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
236 NSString* urlString =
237 [NSString stringWithFormat:@"crwebinvoke://%@/#%@", key, escapedCommand];
238 return [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
241 // Returns true if the current device is a large iPhone (6 or 6+).
242 bool IsIPhone6Or6Plus() {
243 UIUserInterfaceIdiom idiom = [[UIDevice currentDevice] userInterfaceIdiom];
244 return (idiom == UIUserInterfaceIdiomPhone &&
245 CGRectGetHeight([[UIScreen mainScreen] nativeBounds]) >= 1334.0);
248 // A mixin class for testing CRWWKWebViewWebController or
249 // CRWUIWebViewWebController. Stubs out WebView and child CRWWebController.
250 template <typename WebTestT>
251 class WebControllerTest : public WebTestT {
253 virtual void SetUp() override {
255 mockWebView_.reset(CreateMockWebView());
256 mockScrollView_.reset([[UIScrollView alloc] init]);
257 [[[mockWebView_ stub] andReturn:mockScrollView_.get()] scrollView];
259 id originalMockDelegate =
260 [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
261 mockDelegate_.reset([[MockInteractionLoader alloc]
262 initWithRepresentedObject:originalMockDelegate]);
263 [WebTestT::webController_ setDelegate:mockDelegate_];
264 [WebTestT::webController_ injectWebView:(UIView*)mockWebView_];
266 NavigationManagerImpl& navigationManager =
267 [WebTestT::webController_ webStateImpl]->GetNavigationManagerImpl();
268 navigationManager.InitializeSession(@"name", nil, NO, 0);
269 [navigationManager.GetSessionController()
270 addPendingEntry:GURL("http://www.google.com/?q=foo#bar")
271 referrer:web::Referrer()
272 transition:ui::PAGE_TRANSITION_TYPED
273 rendererInitiated:NO];
274 // Set up child CRWWebController.
275 mockChildWebController_.reset([[OCMockObject
276 mockForProtocol:@protocol(CRWWebControllerScripting)] retain]);
277 [[[mockDelegate_ stub] andReturn:mockChildWebController_.get()]
278 webController:WebTestT::webController_
279 scriptingInterfaceForWindowNamed:@"http://www.google.com/#newtab"];
282 virtual void TearDown() override {
283 EXPECT_OCMOCK_VERIFY(mockDelegate_);
284 EXPECT_OCMOCK_VERIFY(mockChildWebController_.get());
285 EXPECT_OCMOCK_VERIFY(mockWebView_);
286 [WebTestT::webController_ resetInjectedWebView];
287 [WebTestT::webController_ setDelegate:nil];
288 WebTestT::TearDown();
291 // Creates WebView mock.
292 virtual UIView* CreateMockWebView() const = 0;
294 // Simulates a load request delegate call from the web view.
295 virtual void SimulateLoadRequest(NSURLRequest* request) const = 0;
297 base::scoped_nsobject<UIScrollView> mockScrollView_;
298 base::scoped_nsobject<id> mockWebView_;
299 base::scoped_nsobject<id> mockDelegate_;
300 base::scoped_nsobject<id> mockChildWebController_;
303 class CRWUIWebViewWebControllerTest
304 : public WebControllerTest<web::UIWebViewWebTest> {
306 UIView* CreateMockWebView() const override {
307 id result = [[OCMockObject mockForClass:[UIWebView class]] retain];
308 [[[result stub] andReturn:nil] request];
309 [[result stub] setDelegate:OCMOCK_ANY]; // Called by resetInjectedWebView
310 // Stub out the injection process.
311 [[[result stub] andReturn:@"object"]
312 stringByEvaluatingJavaScriptFromString:
313 @"try { typeof __gCrWeb; } catch (e) { 'undefined'; }"];
314 [[[result stub] andReturn:@"object"]
315 stringByEvaluatingJavaScriptFromString:@"try { typeof "
316 @"__gCrWeb.windowIdObject; } "
317 @"catch (e) { 'undefined'; }"];
320 void SimulateLoadRequest(NSURLRequest* request) const override {
321 id<UIWebViewDelegate> delegate =
322 static_cast<id<UIWebViewDelegate>>(webController_.get());
323 [delegate webView:(UIWebView*)mockWebView_
324 shouldStartLoadWithRequest:request
325 navigationType:UIWebViewNavigationTypeLinkClicked];
329 class CRWWKWebViewWebControllerTest
330 : public WebControllerTest<web::WKWebViewWebTest> {
332 void SetUp() override {
333 CR_TEST_REQUIRES_WK_WEB_VIEW();
334 WebControllerTest<web::WKWebViewWebTest>::SetUp();
336 UIView* CreateMockWebView() const override {
337 id result = [[OCMockObject mockForClass:[WKWebView class]] retain];
339 // Called by resetInjectedWebView
340 [[result stub] configuration];
341 [[result stub] setNavigationDelegate:OCMOCK_ANY];
342 [[result stub] setUIDelegate:OCMOCK_ANY];
343 [[result stub] addObserver:webController_
344 forKeyPath:OCMOCK_ANY
347 [[result stub] addObserver:OCMOCK_ANY
348 forKeyPath:@"scrollView.backgroundColor"
352 [[result stub] removeObserver:webController_ forKeyPath:OCMOCK_ANY];
353 [[result stub] removeObserver:OCMOCK_ANY
354 forKeyPath:@"scrollView.backgroundColor"];
358 void SimulateLoadRequest(NSURLRequest* request) const override {
359 // TODO(eugenebut): implement this method.
363 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithIncorrectKey) {
364 NSURLRequest* request = [NSURLRequest
365 requestWithURL:[NSURL URLWithString:@"crwebinvoke:invalidkey#commands"]];
367 SimulateLoadRequest(request);
370 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithLargeQueue) {
371 // Pre-define test window id.
372 [webController_ setWindowId:@"12345678901234567890123456789012"];
373 NSString* valid_key = [webController_ windowId];
374 [[[mockWebView_ stub] andReturn:kTestURLString]
375 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
377 // Install an observer to handle custom command messages.
378 base::scoped_nsobject<CountingObserver> observer(
379 [[CountingObserver alloc] init]);
380 [webController_ addObserver:observer];
382 // Queue a lot of messages.
383 [webController_ setJsMessageQueueThrottled:YES];
384 const int kNumQueuedMessages = 1000;
385 for (int i = 0; i < kNumQueuedMessages; ++i) {
386 NSMutableURLRequest* request =
387 requestForCrWebInvokeCommandAndKey(@"wctest.largequeue", valid_key);
388 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
389 SimulateLoadRequest(request);
392 // Verify the queue still works and all messages are delivered.
393 [webController_ setJsMessageQueueThrottled:NO];
394 EXPECT_EQ(kNumQueuedMessages, [observer messageCount]);
396 [webController_ removeObserver:observer];
399 TEST_F(CRWUIWebViewWebControllerTest,
400 CrWebInvokeWithAllMessagesFromCurrentWindow) {
401 // Pre-define test window id.
402 [webController_ setWindowId:@"12345678901234567890123456789012"];
403 NSString* valid_key = [webController_ windowId];
404 [[[mockWebView_ stub] andReturn:kTestURLString]
405 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
407 // Install an observer to handle custom command messages.
408 base::scoped_nsobject<CountingObserver> observer(
409 [[CountingObserver alloc] init]);
410 [webController_ addObserver:observer];
413 [webController_ setJsMessageQueueThrottled:YES];
414 NSMutableURLRequest* request =
415 requestForCrWebInvokeCommandAndKey(@"wctest.currentwindow1", valid_key);
416 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
417 SimulateLoadRequest(request);
419 requestForCrWebInvokeCommandAndKey(@"wctest.currentwindow2", valid_key);
420 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
421 SimulateLoadRequest(request);
423 // Verify the behavior.
424 [webController_ setJsMessageQueueThrottled:NO];
425 EXPECT_EQ(2, [observer messageCount]);
427 [webController_ removeObserver:observer];
430 TEST_F(CRWUIWebViewWebControllerTest,
431 CrWebInvokeWithMessagesFromDifferentWindows) {
432 // Pre-define test window id.
433 [webController_ setWindowId:@"DEADBEEFDEADBEEFDEADBEEFDEADBEEF"];
434 NSString* valid_key = [webController_ windowId];
435 [[[mockWebView_ stub] andReturn:kTestURLString]
436 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
438 // Install an observer to handle custom command messages.
439 base::scoped_nsobject<CountingObserver> observer(
440 [[CountingObserver alloc] init]);
441 [webController_ addObserver:observer];
444 [webController_ setJsMessageQueueThrottled:YES];
445 NSMutableURLRequest* request =
446 requestForCrWebInvokeCommandAndKey(@"wctest.diffwindow1", valid_key);
447 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
448 SimulateLoadRequest(request);
450 // Second queued message will come with a new window id.
451 [webController_ setWindowId:@"12345678901234567890123456789012"];
452 valid_key = [webController_ windowId];
454 requestForCrWebInvokeCommandAndKey(@"wctest.diffwindow2", valid_key);
455 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
456 SimulateLoadRequest(request);
458 [webController_ setJsMessageQueueThrottled:NO];
459 EXPECT_EQ(1, [observer messageCount]);
461 [webController_ removeObserver:observer];
464 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithSameOrigin) {
465 // Pre-define test window id.
466 [webController_ setWindowId:@"12345678901234567890123456789012"];
467 NSString* valid_key = [webController_ windowId];
468 [[[mockWebView_ stub] andReturn:@"http://www.google.com/foo"]
469 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
471 // Install an observer to handle custom command messages.
472 base::scoped_nsobject<CountingObserver> observer(
473 [[CountingObserver alloc] init]);
474 [webController_ addObserver:observer];
477 [webController_ setJsMessageQueueThrottled:YES];
478 NSMutableURLRequest* request =
479 requestForCrWebInvokeCommandAndKey(@"wctest.sameorigin", valid_key);
480 // Make originURL different from currentURL but keep the origin the same.
482 setMainDocumentURL:[NSURL URLWithString:@"http://www.google.com/bar"]];
483 SimulateLoadRequest(request);
484 // Verify the behavior.
485 [webController_ setJsMessageQueueThrottled:NO];
486 EXPECT_EQ(1, [observer messageCount]);
488 [webController_ removeObserver:observer];
491 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithDifferentOrigin) {
492 // Pre-define test window id.
493 [webController_ setWindowId:@"12345678901234567890123456789012"];
494 NSString* valid_key = [webController_ windowId];
495 [[[mockWebView_ stub] andReturn:@"http://www.google.com/"]
496 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
498 // Install an observer to handle custom command messages.
499 base::scoped_nsobject<CountingObserver> observer(
500 [[CountingObserver alloc] init]);
501 [webController_ addObserver:observer];
504 [webController_ setJsMessageQueueThrottled:YES];
505 NSMutableURLRequest* request =
506 requestForCrWebInvokeCommandAndKey(@"wctest.difforigin", valid_key);
507 // Make originURL have a different scheme from currentURL so that the origin
509 [request setMainDocumentURL:[NSURL URLWithString:@"https://www.google.com/"]];
510 SimulateLoadRequest(request);
511 // Verify the behavior.
512 [webController_ setJsMessageQueueThrottled:NO];
513 EXPECT_EQ(0, [observer messageCount]);
515 [webController_ removeObserver:observer];
518 TEST_F(CRWUIWebViewWebControllerTest, EmptyMessageQueue) {
519 [[[mockWebView_ stub] andReturn:@"[]"]
520 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
522 NSURLRequest* request =
523 [NSURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
525 SimulateLoadRequest(request);
528 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenBlankURL) {
529 NSString* messageQueueJSON = @"[{"
530 "\"command\" : \"window.open\", "
531 "\"target\" : \"newtab\", "
533 "\"referrerPolicy\" : \"default\" }]";
536 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
537 [mockDelegate_ onSelector:selector
538 callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
539 NSString* windowName, BOOL inBackground) {
540 EXPECT_EQ(url, GURL("about:blank"));
541 EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
542 EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
543 EXPECT_FALSE(inBackground);
545 [[[mockWebView_ stub] andReturn:messageQueueJSON]
546 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
547 [[[mockWebView_ stub] andReturn:kTestURLString]
548 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
550 NSMutableURLRequest* request =
551 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
552 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
553 [webController_ touched:YES];
554 SimulateLoadRequest(request);
556 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
560 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithInteraction) {
561 NSString* messageQueueJSON = @"[{"
562 "\"command\" : \"window.open\", "
563 "\"target\" : \"newtab\", "
564 "\"url\" : \"http://chromium.org\", "
565 "\"referrerPolicy\" : \"default\" }]";
568 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
569 [mockDelegate_ onSelector:selector
570 callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
571 NSString* windowName, BOOL inBackground) {
572 EXPECT_EQ(url, GURL("http://chromium.org"));
573 EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
574 EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
575 EXPECT_FALSE(inBackground);
577 [[[mockWebView_ stub] andReturn:messageQueueJSON]
578 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
579 [[[mockWebView_ stub] andReturn:kTestURLString]
580 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
582 NSMutableURLRequest* request =
583 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
584 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
585 [webController_ touched:YES];
586 SimulateLoadRequest(request);
588 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
592 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithFinishingInteraction) {
593 NSString* messageQueueJSON = @"[{"
594 "\"command\" : \"window.open\", "
595 "\"target\" : \"newtab\", "
596 "\"url\" : \"http://chromium.org\", "
597 "\"referrerPolicy\" : \"default\" }]";
600 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
601 [mockDelegate_ onSelector:selector
602 callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
603 NSString* windowName, BOOL inBackground) {
604 EXPECT_EQ(url, GURL("http://chromium.org"));
605 EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
606 EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
607 EXPECT_FALSE(inBackground);
609 [[[mockWebView_ stub] andReturn:kTestURLString]
610 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
612 NSMutableURLRequest* request =
613 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
614 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
615 [webController_ touched:YES];
616 [webController_ touched:NO];
617 SimulateLoadRequest(request);
619 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
623 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithoutInteraction) {
624 NSString* messageQueueJSON = @"[{"
625 "\"command\" : \"window.open\", "
626 "\"target\" : \"newtab\", "
627 "\"url\" : \"http://chromium.org\", "
628 "\"referrerPolicy\" : \"default\" }]";
629 [[[mockWebView_ stub] andReturn:kTestURLString]
630 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
632 NSMutableURLRequest* request =
633 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
634 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
635 SimulateLoadRequest(request);
637 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
640 MockInteractionLoader* delegate = (MockInteractionLoader*)mockDelegate_;
641 EXPECT_EQ("http://www.google.com/?q=foo#bar", [delegate sourceURL].spec());
642 EXPECT_EQ("http://chromium.org/", [delegate popupURL].spec());
644 EXPECT_TRUE([delegate blockedPopupInfo]);
647 TEST_F(CRWUIWebViewWebControllerTest, WindowClose) {
648 NSString* messageQueueJSON = @"[{"
649 "\"command\" : \"window.close\", "
650 "\"target\" : \"newtab\" }]";
652 [[mockChildWebController_ expect] orderClose];
654 NSMutableURLRequest* request =
655 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
656 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
657 [[[mockWebView_ stub] andReturn:kTestURLString]
658 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
660 SimulateLoadRequest(request);
662 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
666 TEST_F(CRWUIWebViewWebControllerTest, WindowStop) {
667 NSString* messageQueueJSON = @"[{"
668 "\"command\" : \"window.stop\", "
669 "\"target\" : \"newtab\" }]";
671 [[mockChildWebController_ expect] stopLoading];
673 NSMutableURLRequest* request =
674 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
675 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
676 [[[mockWebView_ stub] andReturn:kTestURLString]
677 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
679 SimulateLoadRequest(request);
681 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
685 TEST_F(CRWUIWebViewWebControllerTest, DocumentWrite) {
686 NSString* messageQueueJSON = @"[{"
687 "\"command\" : \"window.document.write\", "
688 "\"target\" : \"newtab\", "
689 "\"html\" : \"<html></html>\" }]";
691 [[mockChildWebController_ expect] loadHTML:@"<html></html>"];
692 [[[mockWebView_ stub] andReturn:kTestURLString]
693 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
695 NSMutableURLRequest* request =
696 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
697 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
698 SimulateLoadRequest(request);
700 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
704 TEST_F(CRWUIWebViewWebControllerTest, UnicodeEncoding) {
705 base::scoped_nsobject<UIWebView> testWebView(
706 [[UIWebView alloc] initWithFrame:CGRectZero]);
707 NSArray* unicodeArray = @[
709 @"unicode £´∑∂∆˚√˜ß∂",
716 for (NSString* unicodeString in unicodeArray) {
718 [NSString stringWithFormat:@"encodeURIComponent('%@');", unicodeString];
719 NSString* encodedString =
720 [testWebView stringByEvaluatingJavaScriptFromString:encodeJS];
721 NSString* decodedString = [encodedString
722 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
723 EXPECT_NSEQ(unicodeString, decodedString);
727 // Tests the removal of document.loaded and document.present commands in the
728 // CRWJSInvokeParameterQueue. Only applicable for UIWebView.
729 TEST_F(CRWUIWebViewWebControllerTest, PageLoadCommandRemoval) {
730 [[[mockWebView_ stub] andReturn:@"[]"]
731 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
732 [[[mockWebView_ stub] andReturn:kTestURLString]
733 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
735 // Pre-define test window id.
736 [webController_ setWindowId:@"12345678901234567890123456789012"];
737 NSString* valid_key = [webController_ windowId];
739 // Add some commands to the queue.
740 [webController_ setJsMessageQueueThrottled:YES];
741 NSURLRequest* request =
742 requestForCrWebInvokeCommandAndKey(@"document.present", valid_key);
743 SimulateLoadRequest(request);
744 request = requestForCrWebInvokeCommandAndKey(@"document.loaded", valid_key);
745 SimulateLoadRequest(request);
747 requestForCrWebInvokeCommandAndKey(@"window.history.forward", valid_key);
748 SimulateLoadRequest(request);
750 // Check the queue size before and after removing document load commands.
751 NSUInteger expectedBeforeCount = 3;
752 NSUInteger expectedAfterCount = 1;
753 ASSERT_EQ(expectedBeforeCount,
754 [[static_cast<CRWUIWebViewWebController*>(webController_)
755 jsInvokeParameterQueue] queueLength]);
756 [webController_ removeDocumentLoadCommandsFromQueue];
757 ASSERT_EQ(expectedAfterCount,
758 [[static_cast<CRWUIWebViewWebController*>(webController_)
759 jsInvokeParameterQueue] queueLength]);
760 [webController_ setJsMessageQueueThrottled:NO];
763 #define MAKE_URL(url_string) GURL([url_string UTF8String])
765 WEB_TEST_F(CRWUIWebViewWebControllerTest,
766 CRWWKWebViewWebControllerTest,
767 URLForHistoryNavigation) {
768 NSArray* urlsNoFragments = @[
771 @"http://three.com/bar",
772 @"http://four.com/bar/",
779 NSArray* fragments = @[ @"#", @"#bar" ];
780 NSMutableArray* urlsWithFragments = [NSMutableArray array];
781 for (NSString* url in urlsNoFragments) {
782 for (NSString* fragment in fragments) {
783 [urlsWithFragments addObject:[url stringByAppendingString:fragment]];
787 // No start fragment: the end url is never changed.
788 for (NSString* start in urlsNoFragments) {
789 for (NSString* end in urlsWithFragments) {
790 EXPECT_EQ(MAKE_URL(end),
791 [this->webController_
792 updateURLForHistoryNavigationFromURL:MAKE_URL(start)
793 toURL:MAKE_URL(end)]);
796 // Both contain fragments: the end url is never changed.
797 for (NSString* start in urlsWithFragments) {
798 for (NSString* end in urlsWithFragments) {
799 EXPECT_EQ(MAKE_URL(end),
800 [this->webController_
801 updateURLForHistoryNavigationFromURL:MAKE_URL(start)
802 toURL:MAKE_URL(end)]);
805 for (unsigned start_index = 0; start_index < [urlsWithFragments count];
807 NSString* start = urlsWithFragments[start_index];
808 for (unsigned end_index = 0; end_index < [urlsNoFragments count];
810 NSString* end = urlsNoFragments[end_index];
811 if (start_index / 2 != end_index) {
812 // The URLs have nothing in common, they are left untouched.
813 EXPECT_EQ(MAKE_URL(end),
814 [this->webController_
815 updateURLForHistoryNavigationFromURL:MAKE_URL(start)
816 toURL:MAKE_URL(end)]);
818 // Start contains a fragment and matches end: An empty fragment is
820 EXPECT_EQ(MAKE_URL([end stringByAppendingString:@"#"]),
821 [this->webController_
822 updateURLForHistoryNavigationFromURL:MAKE_URL(start)
823 toURL:MAKE_URL(end)]);
829 // This test requires iOS net stack.
830 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
831 // Tests that presentSSLError:forSSLStatus:recoverable:callback: is called with
832 // correct arguments if WKWebView fails to load a page with bad SSL cert.
833 TEST_F(CRWWKWebViewWebControllerTest, SSLError) {
834 CR_TEST_REQUIRES_WK_WEB_VIEW();
836 ASSERT_FALSE([mockDelegate_ SSLInfo].is_valid());
839 [NSError errorWithDomain:NSURLErrorDomain
840 code:NSURLErrorServerCertificateHasUnknownRoot
842 [static_cast<id<WKNavigationDelegate>>(webController_.get()) webView:nil
843 didFailProvisionalNavigation:nil
846 // Verify correctness of delegate's method arguments.
847 EXPECT_TRUE([mockDelegate_ SSLInfo].is_valid());
848 EXPECT_EQ(net::CERT_STATUS_INVALID, [mockDelegate_ SSLInfo].cert_status);
849 EXPECT_EQ(net::CERT_STATUS_INVALID, [mockDelegate_ SSLStatus].cert_status);
850 EXPECT_EQ(web::SECURITY_STYLE_AUTHENTICATION_BROKEN,
851 [mockDelegate_ SSLStatus].security_style);
852 EXPECT_FALSE([mockDelegate_ recoverable]);
853 EXPECT_FALSE([mockDelegate_ shouldContinueCallback]);
855 #endif // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
857 // None of the |CRWUIWebViewWebControllerTest| setup is needed;
858 typedef web::UIWebViewWebTest CRWUIWebControllerPageDialogsOpenPolicyTest;
860 // None of the |CRWWKWebViewWebControllerTest| setup is needed;
861 typedef web::WKWebViewWebTest CRWWKWebControllerPageDialogsOpenPolicyTest;
863 WEB_TEST_F(CRWUIWebControllerPageDialogsOpenPolicyTest,
864 CRWWKWebControllerPageDialogsOpenPolicyTest,
866 this->LoadHtml(@"<html><body></body></html>");
867 id delegate = [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
868 [[delegate expect] webControllerDidSuppressDialog:this->webController_];
870 [this->webController_ setDelegate:delegate];
871 [this->webController_ setPageDialogOpenPolicy:web::DIALOG_POLICY_SUPPRESS];
872 this->RunJavaScript(@"alert('')");
874 this->WaitForBackgroundTasks();
875 EXPECT_OCMOCK_VERIFY(delegate);
876 [this->webController_ setDelegate:nil];
879 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
881 class CRWUIWebControllerPageScrollStateTest : public web::UIWebViewWebTest {
883 // Returns a web::PageScrollState that will scroll a UIWebView to
884 // |scrollOffset| and zoom the content by |relativeZoomScale|.
885 inline web::PageScrollState CreateTestScrollState(
886 CGPoint scroll_offset,
887 CGFloat relative_zoom_scale,
888 CGFloat original_minimum_zoom_scale,
889 CGFloat original_maximum_zoom_scale,
890 CGFloat original_zoom_scale) const {
891 return web::PageScrollState(
892 scroll_offset.x, scroll_offset.y,
893 original_minimum_zoom_scale / relative_zoom_scale,
894 original_maximum_zoom_scale / relative_zoom_scale, 1.0);
898 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
900 class CRWWKWebControllerPageScrollStateTest : public web::WKWebViewWebTest {
902 // Returns a web::PageScrollState that will scroll a WKWebView to
903 // |scrollOffset| and zoom the content by |relativeZoomScale|.
904 inline web::PageScrollState CreateTestScrollState(
905 CGPoint scroll_offset,
906 CGFloat relative_zoom_scale,
907 CGFloat original_minimum_zoom_scale,
908 CGFloat original_maximum_zoom_scale,
909 CGFloat original_zoom_scale) const {
910 return web::PageScrollState(
911 scroll_offset.x, scroll_offset.y, original_minimum_zoom_scale,
912 original_maximum_zoom_scale,
913 relative_zoom_scale * original_minimum_zoom_scale);
917 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
918 CRWWKWebControllerPageScrollStateTest,
919 SetPageStateWithUserScalableDisabled) {
920 #if !TARGET_IPHONE_SIMULATOR
921 // This test fails flakily on device with WKWebView, so skip it there.
923 if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE)
926 this->LoadHtml(@"<html><head>"
927 "<meta name='viewport' content="
928 "'width=device-width,maximum-scale=5,initial-scale=1.0,"
930 " /></head><body></body></html>");
931 UIScrollView* scrollView =
932 [[[this->webController_ view] subviews][0] scrollView];
933 float originZoomScale = scrollView.zoomScale;
934 float originMinimumZoomScale = scrollView.minimumZoomScale;
935 float originMaximumZoomScale = scrollView.maximumZoomScale;
937 web::PageScrollState scrollState =
938 this->CreateTestScrollState(CGPointMake(1.0, 1.0), // scroll offset
939 3.0, // relative zoom scale
940 1.0, // original minimum zoom scale
941 5.0, // original maximum zoom scale
942 1.0); // original zoom scale
943 [this->webController_ setPageScrollState:scrollState];
945 // setPageState: is async; wait for its completion.
946 scrollView = [[[this->webController_ view] subviews][0] scrollView];
947 base::test::ios::WaitUntilCondition(^bool() {
948 return [scrollView contentOffset].x == 1.0f;
951 ASSERT_EQ(originZoomScale, scrollView.zoomScale);
952 ASSERT_EQ(originMinimumZoomScale, scrollView.minimumZoomScale);
953 ASSERT_EQ(originMaximumZoomScale, scrollView.maximumZoomScale);
956 // TODO(iOS): Flaky on the bots. crbug/496140
957 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
958 CRWWKWebControllerPageScrollStateTest,
959 FLAKY_SetPageStateWithUserScalableEnabled) {
960 this->LoadHtml(@"<html><head>"
961 "<meta name='viewport' content="
962 "'width=device-width,maximum-scale=10,initial-scale=1.0'"
963 " /></head><body>Test</body></html>");
965 ui::test::uiview_utils::ForceViewRendering([this->webController_ view]);
966 web::PageScrollState scrollState =
967 this->CreateTestScrollState(CGPointMake(1.0, 1.0), // scroll offset
968 3.0, // relative zoom scale
969 1.0, // original minimum zoom scale
970 10.0, // original maximum zoom scale
971 1.0); // original zoom scale
972 [this->webController_ setPageScrollState:scrollState];
974 // setPageState: is async; wait for its completion.
975 id webView = [[this->webController_ view] subviews][0];
976 UIScrollView* scrollView = [webView scrollView];
977 base::test::ios::WaitUntilCondition(^bool() {
978 return [scrollView contentOffset].x == 1.0f;
981 EXPECT_FLOAT_EQ(3, scrollView.zoomScale / scrollView.minimumZoomScale);
984 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
985 CRWWKWebControllerPageScrollStateTest,
987 // This test fails on iPhone 6/6+ with WKWebView; skip until it's fixed.
989 if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE &&
993 this->LoadHtml(@"<html><head>"
994 "<meta name='viewport' content="
995 "'width=device-width,maximum-scale=5.0,initial-scale=1.0'"
996 " /></head><body>Test</body></html>");
997 ASSERT_TRUE(this->webController_.get().atTop);
999 web::PageScrollState scrollState =
1000 this->CreateTestScrollState(CGPointMake(0.0, 30.0), // scroll offset
1001 5.0, // relative zoom scale
1002 1.0, // original minimum zoom scale
1003 5.0, // original maximum zoom scale
1004 1.0); // original zoom scale
1005 [this->webController_ setPageScrollState:scrollState];
1007 // setPageState: is async; wait for its completion.
1008 id webView = [[this->webController_ view] subviews][0];
1009 base::test::ios::WaitUntilCondition(^bool() {
1010 return [[webView scrollView] contentOffset].y == 30.0f;
1013 ASSERT_FALSE([this->webController_ atTop]);
1016 // Tests that evaluateJavaScript:completionHandler: properly forwards the
1017 // call to UIWebView.
1018 TEST_F(CRWUIWebViewWebControllerTest, JavaScriptEvaluation) {
1019 NSString* kTestScript = @"script";
1020 NSString* kTestResult = @"result";
1021 NSString* scriptWithIDCheck =
1022 [webController_ scriptByAddingWindowIDCheckForScript:kTestScript];
1023 [[[mockWebView_ stub] andReturn:kTestResult]
1024 stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1026 __block BOOL completionBlockWasCalled = NO;
1027 [webController_ evaluateJavaScript:kTestScript
1028 stringResultHandler:^(NSString* string, NSError* error) {
1029 completionBlockWasCalled = YES;
1030 EXPECT_NSEQ(kTestResult, string);
1031 EXPECT_EQ(nil, error);
1034 // Wait until JavaScript is evaluated.
1035 base::test::ios::WaitUntilCondition(^bool() {
1036 return completionBlockWasCalled;
1038 EXPECT_TRUE(completionBlockWasCalled);
1041 TEST_F(CRWUIWebViewWebControllerTest, POSTRequestCache) {
1042 GURL url("http://www.google.fr/");
1043 NSString* mixedCaseCookieHeaderName = @"cOoKiE";
1044 NSString* otherHeaderName = @"Myheader";
1045 NSString* otherHeaderValue = @"A";
1046 NSString* otherHeaderIncorrectValue = @"C";
1048 scoped_ptr<web::NavigationItemImpl> item(new web::NavigationItemImpl());
1050 item->SetTransitionType(ui::PAGE_TRANSITION_FORM_SUBMIT);
1051 item->set_is_renderer_initiated(true);
1052 base::scoped_nsobject<CRWSessionEntry> currentEntry(
1053 [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass()]);
1054 base::scoped_nsobject<NSMutableURLRequest> request(
1055 [[NSMutableURLRequest alloc] initWithURL:net::NSURLWithGURL(url)]);
1056 [request setHTTPMethod:@"POST"];
1057 [request setValue:otherHeaderValue forHTTPHeaderField:otherHeaderName];
1058 [request setValue:@"B" forHTTPHeaderField:mixedCaseCookieHeaderName];
1059 // No data is cached initially.
1060 EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1061 EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1062 // Streams are not cached.
1063 [request setHTTPBodyStream:[NSInputStream inputStreamWithData:[NSData data]]];
1064 [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1065 EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1066 EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1067 [request setHTTPBodyStream:nil];
1068 // POST Data is cached. Cookie headers are stripped, no matter their case.
1069 base::scoped_nsobject<NSData> body([[NSData alloc] init]);
1070 [request setHTTPBody:body];
1071 [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1072 EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1073 EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1074 @{otherHeaderName : otherHeaderValue});
1075 // A new request will not change the cached version.
1076 base::scoped_nsobject<NSData> body2([[NSData alloc] init]);
1077 [request setValue:otherHeaderIncorrectValue
1078 forHTTPHeaderField:otherHeaderName];
1079 [request setHTTPBody:body2];
1080 EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1081 EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1082 @{otherHeaderName : otherHeaderValue});
1085 class WebControllerJSEvaluationTest : public CRWUIWebViewWebControllerTest {
1087 void SetUp() override {
1088 WebControllerTest::SetUp();
1090 NSString* kTestResult = @"result";
1091 completionBlockWasCalled_ = NO;
1092 NSString* scriptWithIDCheck =
1093 [webController_ scriptByAddingWindowIDCheckForScript:GetTestScript()];
1094 [[[mockWebView_ stub] andReturn:kTestResult]
1095 stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1096 completionHandler_.reset([^(NSString* string, NSError* error) {
1097 completionBlockWasCalled_ = YES;
1098 EXPECT_NSEQ(kTestResult, string);
1099 EXPECT_EQ(nil, error);
1102 NSString* GetTestScript() const { return @"script"; }
1103 base::scoped_nsprotocol<web::JavaScriptCompletion> completionHandler_;
1104 BOOL completionBlockWasCalled_;
1107 // Tests that -evaluateJavaScript:stringResultHandler: properly forwards
1108 // the call to the UIWebView.
1109 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluation) {
1110 [webController_ evaluateJavaScript:GetTestScript()
1111 stringResultHandler:completionHandler_];
1112 // Wait until JavaScript is evaluated.
1113 base::test::ios::WaitUntilCondition(^bool() {
1114 return this->completionBlockWasCalled_;
1116 EXPECT_TRUE(completionBlockWasCalled_);
1119 // Tests that -evaluateUserJavaScript:stringResultHandler: properly
1120 // forwards the call to the UIWebView.
1121 TEST_F(WebControllerJSEvaluationTest, UserJavaScriptEvaluation) {
1122 __block BOOL method_called = NO;
1123 [[[mockWebView_ stub] andDo:^(NSInvocation*) {
1124 method_called = YES;
1126 performSelectorOnMainThread:@selector(
1127 stringByEvaluatingJavaScriptFromString:)
1128 withObject:GetTestScript()
1130 [webController_ evaluateUserJavaScript:GetTestScript()];
1131 EXPECT_TRUE(method_called);
1134 // Tests that -evaluateJavaScript:stringResultHandler: does not crash
1135 // on a nil completionHandler.
1136 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluationNilHandler) {
1137 [webController_ evaluateJavaScript:GetTestScript() stringResultHandler:nil];
1140 // Real UIWebView is required for JSEvaluationTest.
1141 typedef web::UIWebViewWebTest CRWUIWebControllerJSEvaluationTest;
1143 // Real WKWebView is required for JSEvaluationTest.
1144 typedef web::WKWebViewWebTest CRWWKWebControllerJSEvaluationTest;
1146 // Tests that a script correctly evaluates to string.
1147 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1148 CRWWKWebControllerJSEvaluationTest,
1150 this->LoadHtml(@"<p></p>");
1151 EXPECT_NSEQ(@"true", this->EvaluateJavaScriptAsString(@"true"));
1152 EXPECT_NSEQ(@"false", this->EvaluateJavaScriptAsString(@"false"));
1155 // Tests that a script is not evaluated on windowID mismatch.
1156 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1157 CRWWKWebControllerJSEvaluationTest,
1158 WindowIDMissmatch) {
1159 this->LoadHtml(@"<p></p>");
1160 // Script is evaluated since windowID is matched.
1161 this->EvaluateJavaScriptAsString(@"window.test1 = '1';");
1162 EXPECT_NSEQ(@"1", this->EvaluateJavaScriptAsString(@"window.test1"));
1165 this->EvaluateJavaScriptAsString(@"__gCrWeb['windowId'] = '';");
1167 // Script is not evaluated because of windowID mismatch.
1168 this->EvaluateJavaScriptAsString(@"window.test2 = '2';");
1169 EXPECT_NSEQ(@"", this->EvaluateJavaScriptAsString(@"window.test2"));
1172 // A separate test class is used for testing the keyboard dismissal, as none of
1173 // the setup in |CRWUIWebViewWebControllerTest| is needed; keyboard appearance
1174 // is tracked by the KeyboardAppearanceListener.
1175 class WebControllerKeyboardTest : public web::UIWebViewWebTest {
1177 void SetUp() override {
1178 UIWebViewWebTest::SetUp();
1179 // Close any outstanding alert boxes.
1180 ui::test::uiview_utils::CancelAlerts();
1182 // Sets up the listener for keyboard activation/deactivation notifications.
1183 keyboardListener_.reset([[KeyboardAppearanceListener alloc] init]);
1186 base::scoped_nsobject<KeyboardAppearanceListener> keyboardListener_;
1189 TEST_F(WebControllerKeyboardTest, DismissKeyboard) {
1190 LoadHtml(@"<html><head></head><body><form><input id=\"textField\" /></form>"
1194 UIWebView* webView =
1195 (UIWebView*)[[[webController_ view] subviews] objectAtIndex:0];
1196 EXPECT_TRUE(webView);
1198 // Create the window and add the webview.
1199 base::scoped_nsobject<UIWindow> window(
1200 [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]);
1201 [window makeKeyAndVisible];
1202 [window addSubview:webView];
1204 // Set the flag to allow focus() in code in UIWebView.
1205 EXPECT_TRUE([webView keyboardDisplayRequiresUserAction]);
1206 [webView setKeyboardDisplayRequiresUserAction:NO];
1207 EXPECT_FALSE([webView keyboardDisplayRequiresUserAction]);
1209 // Set the focus on the textField.
1210 EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1211 [webView stringByEvaluatingJavaScriptFromString:
1212 @"document.getElementById(\"textField\").focus();"];
1213 base::test::ios::WaitUntilCondition(^bool() {
1214 return [keyboardListener_ isKeyboardVisible];
1216 EXPECT_TRUE([keyboardListener_ isKeyboardVisible]);
1219 [webController_ dismissKeyboard];
1221 base::test::ios::WaitUntilCondition(^bool() {
1222 return ![keyboardListener_ isKeyboardVisible];
1224 EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1227 TEST_F(CRWWKWebViewWebControllerTest, WebURLWithTrustLevel) {
1228 CR_TEST_REQUIRES_WK_WEB_VIEW();
1230 [[[mockWebView_ stub] andReturn:[NSURL URLWithString:kTestURLString]] URL];
1231 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1232 [[[mockWebView_ stub] andReturnBool:NO] hasOnlySecureContent];
1235 // Stub out the injection process.
1236 [[mockWebView_ stub] evaluateJavaScript:OCMOCK_ANY
1237 completionHandler:OCMOCK_ANY];
1239 // Simulate registering load request to avoid failing page load simulation.
1240 [webController_ simulateLoadRequestWithURL:GURL([kTestURLString UTF8String])];
1241 // Simulate a page load to trigger a URL update.
1242 [static_cast<id<WKNavigationDelegate>>(webController_.get())
1243 webView:mockWebView_
1244 didCommitNavigation:nil];
1246 web::URLVerificationTrustLevel trust_level = web::kNone;
1247 GURL gurl = [webController_ currentURLWithTrustLevel:&trust_level];
1249 EXPECT_EQ(gurl, GURL(base::SysNSStringToUTF8(kTestURLString)));
1250 EXPECT_EQ(web::kAbsolute, trust_level);
1253 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
1255 typedef web::UIWebViewWebTest CRWUIWebControllerObserversTest;
1256 typedef web::WKWebViewWebTest CRWWKWebControllerObserversTest;
1258 // Tests that CRWWebControllerObservers are called.
1259 WEB_TEST_F(CRWUIWebControllerObserversTest,
1260 CRWWKWebControllerObserversTest,
1262 base::scoped_nsobject<CountingObserver> observer(
1263 [[CountingObserver alloc] init]);
1264 CRWWebController* web_controller = this->webController_;
1265 EXPECT_EQ(0u, [web_controller observerCount]);
1266 [web_controller addObserver:observer];
1267 EXPECT_EQ(1u, [web_controller observerCount]);
1269 EXPECT_EQ(0, [observer pageLoadedCount]);
1270 [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), false);
1271 EXPECT_EQ(0, [observer pageLoadedCount]);
1272 [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), true);
1273 EXPECT_EQ(1, [observer pageLoadedCount]);
1275 EXPECT_EQ(0, [observer messageCount]);
1276 // Non-matching prefix.
1277 EXPECT_FALSE([web_controller webStateImpl]->OnScriptCommandReceived(
1278 "a", base::DictionaryValue(), GURL("http://test"), true));
1279 EXPECT_EQ(0, [observer messageCount]);
1281 EXPECT_TRUE([web_controller webStateImpl]->OnScriptCommandReceived(
1282 base::SysNSStringToUTF8([observer commandPrefix]) + ".foo",
1283 base::DictionaryValue(), GURL("http://test"), true));
1284 EXPECT_EQ(1, [observer messageCount]);
1286 [web_controller removeObserver:observer];
1287 EXPECT_EQ(0u, [web_controller observerCount]);
1290 // Test fixture for window.open tests.
1291 class CRWWKWebControllerWindowOpenTest : public web::WKWebViewWebTest {
1293 void SetUp() override {
1294 CR_TEST_REQUIRES_WK_WEB_VIEW();
1295 WKWebViewWebTest::SetUp();
1297 // Configure web delegate.
1298 delegate_.reset([[MockInteractionLoader alloc]
1299 initWithRepresentedObject:
1300 [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)]]);
1301 ASSERT_TRUE([delegate_ blockPopups]);
1302 [webController_ setDelegate:delegate_];
1304 // Configure child web controller.
1305 child_.reset(CreateWebController());
1306 [child_ setWebUsageEnabled:YES];
1307 [delegate_ setChildWebController:child_];
1309 // Configure child web controller's session controller mock.
1310 id sessionController =
1311 [OCMockObject niceMockForClass:[CRWSessionController class]];
1313 [[[sessionController stub] andReturnValue:OCMOCK_VALUE(yes)] isOpenedByDOM];
1314 [child_ webStateImpl]->GetNavigationManagerImpl().SetSessionController(
1317 LoadHtml(@"<html><body></body></html>");
1319 void TearDown() override {
1320 EXPECT_OCMOCK_VERIFY(delegate_);
1321 [webController_ setDelegate:nil];
1324 WKWebViewWebTest::TearDown();
1326 // Executes JavaScript that opens a new window and returns evaluation result
1328 NSString* OpenWindowByDOM() {
1329 NSString* const kOpenWindowScript =
1330 @"var w = window.open('javascript:void(0);', target='_blank');"
1332 NSString* windowJSObject = EvaluateJavaScriptAsString(kOpenWindowScript);
1333 WaitForBackgroundTasks();
1334 return windowJSObject;
1336 // A CRWWebDelegate mock used for testing.
1337 base::scoped_nsobject<id> delegate_;
1338 // A child CRWWebController used for testing.
1339 base::scoped_nsobject<CRWWebController> child_;
1342 // Tests that absence of web delegate is handled gracefully.
1343 TEST_F(CRWWKWebControllerWindowOpenTest, NoDelegate) {
1344 CR_TEST_REQUIRES_WK_WEB_VIEW();
1346 [webController_ setDelegate:nil];
1348 EXPECT_NSEQ(@"", OpenWindowByDOM());
1350 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1353 // Tests that window.open triggered by user gesture opens a new non-popup
1355 TEST_F(CRWWKWebControllerWindowOpenTest, OpenWithUserGesture) {
1356 CR_TEST_REQUIRES_WK_WEB_VIEW();
1358 SEL selector = @selector(webPageOrderedOpenBlankWithReferrer:inBackground:);
1359 [delegate_ onSelector:selector
1360 callBlockExpectation:^(const web::Referrer& referrer,
1361 BOOL in_background) {
1362 EXPECT_EQ("", referrer.url.spec());
1363 EXPECT_FALSE(in_background);
1366 [webController_ touched:YES];
1367 EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1368 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1371 // Tests that window.open executed w/o user gesture does not open a new window.
1372 // Once the blocked popup is allowed a new window is opened.
1373 TEST_F(CRWWKWebControllerWindowOpenTest, AllowPopup) {
1374 CR_TEST_REQUIRES_WK_WEB_VIEW();
1377 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
1378 [delegate_ onSelector:selector
1379 callBlockExpectation:^(const GURL& new_window_url,
1380 const web::Referrer& referrer,
1381 NSString* obsoleted_window_name,
1382 BOOL in_background) {
1383 EXPECT_EQ("javascript:void(0);", new_window_url.spec());
1384 EXPECT_EQ("", referrer.url.spec());
1385 EXPECT_FALSE(in_background);
1388 ASSERT_FALSE([webController_ userIsInteracting]);
1389 EXPECT_NSEQ(@"", OpenWindowByDOM());
1390 base::test::ios::WaitUntilCondition(^bool() {
1391 return [delegate_ blockedPopupInfo];
1394 if ([delegate_ blockedPopupInfo])
1395 [delegate_ blockedPopupInfo]->ShowPopup();
1397 EXPECT_EQ("", [delegate_ sourceURL].spec());
1398 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1401 // Tests that window.open executed w/o user gesture opens a new window, assuming
1402 // that delegate allows popups.
1403 TEST_F(CRWWKWebControllerWindowOpenTest, DontBlockPopup) {
1404 CR_TEST_REQUIRES_WK_WEB_VIEW();
1406 [delegate_ setBlockPopups:NO];
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 EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1416 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1418 EXPECT_EQ("", [delegate_ sourceURL].spec());
1419 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1422 // Tests that window.open executed w/o user gesture does not open a new window.
1423 TEST_F(CRWWKWebControllerWindowOpenTest, BlockPopup) {
1424 CR_TEST_REQUIRES_WK_WEB_VIEW();
1426 ASSERT_FALSE([webController_ userIsInteracting]);
1427 EXPECT_NSEQ(@"", OpenWindowByDOM());
1428 base::test::ios::WaitUntilCondition(^bool() {
1429 return [delegate_ blockedPopupInfo];
1432 EXPECT_EQ("", [delegate_ sourceURL].spec());
1433 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1436 // Fixture class to test WKWebView crashes.
1437 class CRWWKWebControllerWebProcessTest : public web::WKWebViewWebTest {
1439 void SetUp() override {
1440 CR_TEST_REQUIRES_WK_WEB_VIEW();
1441 WKWebViewWebTest::SetUp();
1442 webView_.reset(web::CreateTerminatedWKWebView());
1443 [webController_ injectWebView:webView_];
1445 base::scoped_nsobject<WKWebView> webView_;
1448 // Tests that -[CRWWebDelegate webControllerWebProcessDidCrash:] is called
1449 // when WKWebView web process has crashed.
1450 TEST_F(CRWWKWebControllerWebProcessTest, Crash) {
1451 CR_TEST_REQUIRES_WK_WEB_VIEW();
1453 id delegate = [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
1454 [[delegate expect] webControllerWebProcessDidCrash:this->webController_];
1456 [this->webController_ setDelegate:delegate];
1457 web::SimulateWKWebViewCrash(webView_);
1459 EXPECT_OCMOCK_VERIFY(delegate);
1460 [this->webController_ setDelegate:nil];
1463 // Tests that WKWebView does not crash if deallocation happens during JavaScript
1465 typedef web::WKWebViewWebTest DeallocationDuringJSEvaluation;
1466 TEST_F(DeallocationDuringJSEvaluation, NoCrash) {
1467 CR_TEST_REQUIRES_WK_WEB_VIEW();
1469 this->LoadHtml(@"<html><body></body></html>");
1470 [webController_ evaluateJavaScript:@"null"
1471 stringResultHandler:^(NSString* value, NSError*) {
1472 // Block can not be empty in order to reproduce the crash:
1473 // https://bugs.webkit.org/show_bug.cgi?id=140203
1474 VLOG(1) << "Script has been flushed.";
1476 // -evaluateJavaScript:stringResultHandler: is asynchronous so JavaScript
1477 // evaluation will not happen until TearDown, which deallocates
1478 // CRWWebController, which in its turn will deallocate WKWebView to create a
1479 // crashy condition.