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 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
957 CRWWKWebControllerPageScrollStateTest,
958 SetPageStateWithUserScalableEnabled) {
959 this->LoadHtml(@"<html><head>"
960 "<meta name='viewport' content="
961 "'width=device-width,maximum-scale=10,initial-scale=1.0'"
962 " /></head><body>Test</body></html>");
964 ui::test::uiview_utils::ForceViewRendering([this->webController_ view]);
965 web::PageScrollState scrollState =
966 this->CreateTestScrollState(CGPointMake(1.0, 1.0), // scroll offset
967 3.0, // relative zoom scale
968 1.0, // original minimum zoom scale
969 10.0, // original maximum zoom scale
970 1.0); // original zoom scale
971 [this->webController_ setPageScrollState:scrollState];
973 // setPageState: is async; wait for its completion.
974 id webView = [[this->webController_ view] subviews][0];
975 UIScrollView* scrollView = [webView scrollView];
976 base::test::ios::WaitUntilCondition(^bool() {
977 return [scrollView contentOffset].x == 1.0f;
980 EXPECT_FLOAT_EQ(3, scrollView.zoomScale / scrollView.minimumZoomScale);
983 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
984 CRWWKWebControllerPageScrollStateTest,
986 // This test fails on iPhone 6/6+ with WKWebView; skip until it's fixed.
988 if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE &&
992 this->LoadHtml(@"<html><head>"
993 "<meta name='viewport' content="
994 "'width=device-width,maximum-scale=5.0,initial-scale=1.0'"
995 " /></head><body>Test</body></html>");
996 ASSERT_TRUE(this->webController_.get().atTop);
998 web::PageScrollState scrollState =
999 this->CreateTestScrollState(CGPointMake(0.0, 30.0), // scroll offset
1000 5.0, // relative zoom scale
1001 1.0, // original minimum zoom scale
1002 5.0, // original maximum zoom scale
1003 1.0); // original zoom scale
1004 [this->webController_ setPageScrollState:scrollState];
1006 // setPageState: is async; wait for its completion.
1007 id webView = [[this->webController_ view] subviews][0];
1008 base::test::ios::WaitUntilCondition(^bool() {
1009 return [[webView scrollView] contentOffset].y == 30.0f;
1012 ASSERT_FALSE([this->webController_ atTop]);
1015 // Tests that evaluateJavaScript:completionHandler: properly forwards the
1016 // call to UIWebView.
1017 TEST_F(CRWUIWebViewWebControllerTest, JavaScriptEvaluation) {
1018 NSString* kTestScript = @"script";
1019 NSString* kTestResult = @"result";
1020 NSString* scriptWithIDCheck =
1021 [webController_ scriptByAddingWindowIDCheckForScript:kTestScript];
1022 [[[mockWebView_ stub] andReturn:kTestResult]
1023 stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1025 __block BOOL completionBlockWasCalled = NO;
1026 [webController_ evaluateJavaScript:kTestScript
1027 stringResultHandler:^(NSString* string, NSError* error) {
1028 completionBlockWasCalled = YES;
1029 EXPECT_NSEQ(kTestResult, string);
1030 EXPECT_EQ(nil, error);
1033 // Wait until JavaScript is evaluated.
1034 base::test::ios::WaitUntilCondition(^bool() {
1035 return completionBlockWasCalled;
1037 EXPECT_TRUE(completionBlockWasCalled);
1040 TEST_F(CRWUIWebViewWebControllerTest, POSTRequestCache) {
1041 GURL url("http://www.google.fr/");
1042 NSString* mixedCaseCookieHeaderName = @"cOoKiE";
1043 NSString* otherHeaderName = @"Myheader";
1044 NSString* otherHeaderValue = @"A";
1045 NSString* otherHeaderIncorrectValue = @"C";
1047 scoped_ptr<web::NavigationItemImpl> item(new web::NavigationItemImpl());
1049 item->SetTransitionType(ui::PAGE_TRANSITION_FORM_SUBMIT);
1050 item->set_is_renderer_initiated(true);
1051 base::scoped_nsobject<CRWSessionEntry> currentEntry(
1052 [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass()]);
1053 base::scoped_nsobject<NSMutableURLRequest> request(
1054 [[NSMutableURLRequest alloc] initWithURL:net::NSURLWithGURL(url)]);
1055 [request setHTTPMethod:@"POST"];
1056 [request setValue:otherHeaderValue forHTTPHeaderField:otherHeaderName];
1057 [request setValue:@"B" forHTTPHeaderField:mixedCaseCookieHeaderName];
1058 // No data is cached initially.
1059 EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1060 EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1061 // Streams are not cached.
1062 [request setHTTPBodyStream:[NSInputStream inputStreamWithData:[NSData data]]];
1063 [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1064 EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1065 EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1066 [request setHTTPBodyStream:nil];
1067 // POST Data is cached. Cookie headers are stripped, no matter their case.
1068 base::scoped_nsobject<NSData> body([[NSData alloc] init]);
1069 [request setHTTPBody:body];
1070 [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1071 EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1072 EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1073 @{otherHeaderName : otherHeaderValue});
1074 // A new request will not change the cached version.
1075 base::scoped_nsobject<NSData> body2([[NSData alloc] init]);
1076 [request setValue:otherHeaderIncorrectValue
1077 forHTTPHeaderField:otherHeaderName];
1078 [request setHTTPBody:body2];
1079 EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1080 EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1081 @{otherHeaderName : otherHeaderValue});
1084 class WebControllerJSEvaluationTest : public CRWUIWebViewWebControllerTest {
1086 void SetUp() override {
1087 WebControllerTest::SetUp();
1089 NSString* kTestResult = @"result";
1090 completionBlockWasCalled_ = NO;
1091 NSString* scriptWithIDCheck =
1092 [webController_ scriptByAddingWindowIDCheckForScript:GetTestScript()];
1093 [[[mockWebView_ stub] andReturn:kTestResult]
1094 stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1095 completionHandler_.reset([^(NSString* string, NSError* error) {
1096 completionBlockWasCalled_ = YES;
1097 EXPECT_NSEQ(kTestResult, string);
1098 EXPECT_EQ(nil, error);
1101 NSString* GetTestScript() const { return @"script"; }
1102 base::scoped_nsprotocol<web::JavaScriptCompletion> completionHandler_;
1103 BOOL completionBlockWasCalled_;
1106 // Tests that -evaluateJavaScript:stringResultHandler: properly forwards
1107 // the call to the UIWebView.
1108 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluation) {
1109 [webController_ evaluateJavaScript:GetTestScript()
1110 stringResultHandler:completionHandler_];
1111 // Wait until JavaScript is evaluated.
1112 base::test::ios::WaitUntilCondition(^bool() {
1113 return this->completionBlockWasCalled_;
1115 EXPECT_TRUE(completionBlockWasCalled_);
1118 // Tests that -evaluateUserJavaScript:stringResultHandler: properly
1119 // forwards the call to the UIWebView.
1120 TEST_F(WebControllerJSEvaluationTest, UserJavaScriptEvaluation) {
1121 __block BOOL method_called = NO;
1122 [[[mockWebView_ stub] andDo:^(NSInvocation*) {
1123 method_called = YES;
1125 performSelectorOnMainThread:@selector(
1126 stringByEvaluatingJavaScriptFromString:)
1127 withObject:GetTestScript()
1129 [webController_ evaluateUserJavaScript:GetTestScript()];
1130 EXPECT_TRUE(method_called);
1133 // Tests that -evaluateJavaScript:stringResultHandler: does not crash
1134 // on a nil completionHandler.
1135 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluationNilHandler) {
1136 [webController_ evaluateJavaScript:GetTestScript() stringResultHandler:nil];
1139 // Real UIWebView is required for JSEvaluationTest.
1140 typedef web::UIWebViewWebTest CRWUIWebControllerJSEvaluationTest;
1142 // Real WKWebView is required for JSEvaluationTest.
1143 typedef web::WKWebViewWebTest CRWWKWebControllerJSEvaluationTest;
1145 // Tests that a script correctly evaluates to string.
1146 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1147 CRWWKWebControllerJSEvaluationTest,
1149 this->LoadHtml(@"<p></p>");
1150 EXPECT_NSEQ(@"true", this->EvaluateJavaScriptAsString(@"true"));
1151 EXPECT_NSEQ(@"false", this->EvaluateJavaScriptAsString(@"false"));
1154 // Tests that a script is not evaluated on windowID mismatch.
1155 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1156 CRWWKWebControllerJSEvaluationTest,
1157 WindowIDMissmatch) {
1158 this->LoadHtml(@"<p></p>");
1159 // Script is evaluated since windowID is matched.
1160 this->EvaluateJavaScriptAsString(@"window.test1 = '1';");
1161 EXPECT_NSEQ(@"1", this->EvaluateJavaScriptAsString(@"window.test1"));
1164 this->EvaluateJavaScriptAsString(@"__gCrWeb['windowId'] = '';");
1166 // Script is not evaluated because of windowID mismatch.
1167 this->EvaluateJavaScriptAsString(@"window.test2 = '2';");
1168 EXPECT_NSEQ(@"", this->EvaluateJavaScriptAsString(@"window.test2"));
1171 // A separate test class is used for testing the keyboard dismissal, as none of
1172 // the setup in |CRWUIWebViewWebControllerTest| is needed; keyboard appearance
1173 // is tracked by the KeyboardAppearanceListener.
1174 class WebControllerKeyboardTest : public web::UIWebViewWebTest {
1176 void SetUp() override {
1177 UIWebViewWebTest::SetUp();
1178 // Close any outstanding alert boxes.
1179 ui::test::uiview_utils::CancelAlerts();
1181 // Sets up the listener for keyboard activation/deactivation notifications.
1182 keyboardListener_.reset([[KeyboardAppearanceListener alloc] init]);
1185 base::scoped_nsobject<KeyboardAppearanceListener> keyboardListener_;
1188 TEST_F(WebControllerKeyboardTest, DismissKeyboard) {
1189 LoadHtml(@"<html><head></head><body><form><input id=\"textField\" /></form>"
1193 UIWebView* webView =
1194 (UIWebView*)[[[webController_ view] subviews] objectAtIndex:0];
1195 EXPECT_TRUE(webView);
1197 // Create the window and add the webview.
1198 base::scoped_nsobject<UIWindow> window(
1199 [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]);
1200 [window makeKeyAndVisible];
1201 [window addSubview:webView];
1203 // Set the flag to allow focus() in code in UIWebView.
1204 EXPECT_TRUE([webView keyboardDisplayRequiresUserAction]);
1205 [webView setKeyboardDisplayRequiresUserAction:NO];
1206 EXPECT_FALSE([webView keyboardDisplayRequiresUserAction]);
1208 // Set the focus on the textField.
1209 EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1210 [webView stringByEvaluatingJavaScriptFromString:
1211 @"document.getElementById(\"textField\").focus();"];
1212 base::test::ios::WaitUntilCondition(^bool() {
1213 return [keyboardListener_ isKeyboardVisible];
1215 EXPECT_TRUE([keyboardListener_ isKeyboardVisible]);
1218 [webController_ dismissKeyboard];
1220 base::test::ios::WaitUntilCondition(^bool() {
1221 return ![keyboardListener_ isKeyboardVisible];
1223 EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1226 TEST_F(CRWWKWebViewWebControllerTest, WebURLWithTrustLevel) {
1227 CR_TEST_REQUIRES_WK_WEB_VIEW();
1229 [[[mockWebView_ stub] andReturn:[NSURL URLWithString:kTestURLString]] URL];
1230 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1231 [[[mockWebView_ stub] andReturnBool:NO] hasOnlySecureContent];
1234 // Stub out the injection process.
1235 [[mockWebView_ stub] evaluateJavaScript:OCMOCK_ANY
1236 completionHandler:OCMOCK_ANY];
1238 // Simulate registering load request to avoid failing page load simulation.
1239 [webController_ simulateLoadRequestWithURL:GURL([kTestURLString UTF8String])];
1240 // Simulate a page load to trigger a URL update.
1241 [static_cast<id<WKNavigationDelegate>>(webController_.get())
1242 webView:mockWebView_
1243 didCommitNavigation:nil];
1245 web::URLVerificationTrustLevel trust_level = web::kNone;
1246 GURL gurl = [webController_ currentURLWithTrustLevel:&trust_level];
1248 EXPECT_EQ(gurl, GURL(base::SysNSStringToUTF8(kTestURLString)));
1249 EXPECT_EQ(web::kAbsolute, trust_level);
1252 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
1254 typedef web::UIWebViewWebTest CRWUIWebControllerObserversTest;
1255 typedef web::WKWebViewWebTest CRWWKWebControllerObserversTest;
1257 // Tests that CRWWebControllerObservers are called.
1258 WEB_TEST_F(CRWUIWebControllerObserversTest,
1259 CRWWKWebControllerObserversTest,
1261 base::scoped_nsobject<CountingObserver> observer(
1262 [[CountingObserver alloc] init]);
1263 CRWWebController* web_controller = this->webController_;
1264 EXPECT_EQ(0u, [web_controller observerCount]);
1265 [web_controller addObserver:observer];
1266 EXPECT_EQ(1u, [web_controller observerCount]);
1268 EXPECT_EQ(0, [observer pageLoadedCount]);
1269 [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), false);
1270 EXPECT_EQ(0, [observer pageLoadedCount]);
1271 [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), true);
1272 EXPECT_EQ(1, [observer pageLoadedCount]);
1274 EXPECT_EQ(0, [observer messageCount]);
1275 // Non-matching prefix.
1276 EXPECT_FALSE([web_controller webStateImpl]->OnScriptCommandReceived(
1277 "a", base::DictionaryValue(), GURL("http://test"), true));
1278 EXPECT_EQ(0, [observer messageCount]);
1280 EXPECT_TRUE([web_controller webStateImpl]->OnScriptCommandReceived(
1281 base::SysNSStringToUTF8([observer commandPrefix]) + ".foo",
1282 base::DictionaryValue(), GURL("http://test"), true));
1283 EXPECT_EQ(1, [observer messageCount]);
1285 [web_controller removeObserver:observer];
1286 EXPECT_EQ(0u, [web_controller observerCount]);
1289 // Test fixture for window.open tests.
1290 class CRWWKWebControllerWindowOpenTest : public web::WKWebViewWebTest {
1292 void SetUp() override {
1293 CR_TEST_REQUIRES_WK_WEB_VIEW();
1294 WKWebViewWebTest::SetUp();
1296 // Configure web delegate.
1297 delegate_.reset([[MockInteractionLoader alloc]
1298 initWithRepresentedObject:
1299 [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)]]);
1300 ASSERT_TRUE([delegate_ blockPopups]);
1301 [webController_ setDelegate:delegate_];
1303 // Configure child web controller.
1304 child_.reset(CreateWebController());
1305 [child_ setWebUsageEnabled:YES];
1306 [delegate_ setChildWebController:child_];
1308 // Configure child web controller's session controller mock.
1309 id sessionController =
1310 [OCMockObject niceMockForClass:[CRWSessionController class]];
1312 [[[sessionController stub] andReturnValue:OCMOCK_VALUE(yes)] isOpenedByDOM];
1313 [child_ webStateImpl]->GetNavigationManagerImpl().SetSessionController(
1316 LoadHtml(@"<html><body></body></html>");
1318 void TearDown() override {
1319 EXPECT_OCMOCK_VERIFY(delegate_);
1320 [webController_ setDelegate:nil];
1323 WKWebViewWebTest::TearDown();
1325 // Executes JavaScript that opens a new window and returns evaluation result
1327 NSString* OpenWindowByDOM() {
1328 NSString* const kOpenWindowScript =
1329 @"var w = window.open('javascript:void(0);', target='_blank');"
1331 NSString* windowJSObject = EvaluateJavaScriptAsString(kOpenWindowScript);
1332 WaitForBackgroundTasks();
1333 return windowJSObject;
1335 // A CRWWebDelegate mock used for testing.
1336 base::scoped_nsobject<id> delegate_;
1337 // A child CRWWebController used for testing.
1338 base::scoped_nsobject<CRWWebController> child_;
1341 // Tests that absence of web delegate is handled gracefully.
1342 TEST_F(CRWWKWebControllerWindowOpenTest, NoDelegate) {
1343 CR_TEST_REQUIRES_WK_WEB_VIEW();
1345 [webController_ setDelegate:nil];
1347 EXPECT_NSEQ(@"", OpenWindowByDOM());
1349 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1352 // Tests that window.open triggered by user gesture opens a new non-popup
1354 TEST_F(CRWWKWebControllerWindowOpenTest, OpenWithUserGesture) {
1355 CR_TEST_REQUIRES_WK_WEB_VIEW();
1357 SEL selector = @selector(webPageOrderedOpenBlankWithReferrer:inBackground:);
1358 [delegate_ onSelector:selector
1359 callBlockExpectation:^(const web::Referrer& referrer,
1360 BOOL in_background) {
1361 EXPECT_EQ("", referrer.url.spec());
1362 EXPECT_FALSE(in_background);
1365 [webController_ touched:YES];
1366 EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1367 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1370 // Tests that window.open executed w/o user gesture does not open a new window.
1371 // Once the blocked popup is allowed a new window is opened.
1372 TEST_F(CRWWKWebControllerWindowOpenTest, AllowPopup) {
1373 CR_TEST_REQUIRES_WK_WEB_VIEW();
1376 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
1377 [delegate_ onSelector:selector
1378 callBlockExpectation:^(const GURL& new_window_url,
1379 const web::Referrer& referrer,
1380 NSString* obsoleted_window_name,
1381 BOOL in_background) {
1382 EXPECT_EQ("javascript:void(0);", new_window_url.spec());
1383 EXPECT_EQ("", referrer.url.spec());
1384 EXPECT_FALSE(in_background);
1387 ASSERT_FALSE([webController_ userIsInteracting]);
1388 EXPECT_NSEQ(@"", OpenWindowByDOM());
1389 base::test::ios::WaitUntilCondition(^bool() {
1390 return [delegate_ blockedPopupInfo];
1393 if ([delegate_ blockedPopupInfo])
1394 [delegate_ blockedPopupInfo]->ShowPopup();
1396 EXPECT_EQ("", [delegate_ sourceURL].spec());
1397 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1400 // Tests that window.open executed w/o user gesture opens a new window, assuming
1401 // that delegate allows popups.
1402 TEST_F(CRWWKWebControllerWindowOpenTest, DontBlockPopup) {
1403 CR_TEST_REQUIRES_WK_WEB_VIEW();
1405 [delegate_ setBlockPopups:NO];
1406 SEL selector = @selector(webPageOrderedOpenBlankWithReferrer:inBackground:);
1407 [delegate_ onSelector:selector
1408 callBlockExpectation:^(const web::Referrer& referrer,
1409 BOOL in_background) {
1410 EXPECT_EQ("", referrer.url.spec());
1411 EXPECT_FALSE(in_background);
1414 EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1415 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1417 EXPECT_EQ("", [delegate_ sourceURL].spec());
1418 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1421 // Tests that window.open executed w/o user gesture does not open a new window.
1422 TEST_F(CRWWKWebControllerWindowOpenTest, BlockPopup) {
1423 CR_TEST_REQUIRES_WK_WEB_VIEW();
1425 ASSERT_FALSE([webController_ userIsInteracting]);
1426 EXPECT_NSEQ(@"", OpenWindowByDOM());
1427 base::test::ios::WaitUntilCondition(^bool() {
1428 return [delegate_ blockedPopupInfo];
1431 EXPECT_EQ("", [delegate_ sourceURL].spec());
1432 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1435 // Fixture class to test WKWebView crashes.
1436 class CRWWKWebControllerWebProcessTest : public web::WKWebViewWebTest {
1438 void SetUp() override {
1439 CR_TEST_REQUIRES_WK_WEB_VIEW();
1440 WKWebViewWebTest::SetUp();
1441 webView_.reset(web::CreateTerminatedWKWebView());
1442 [webController_ injectWebView:webView_];
1444 base::scoped_nsobject<WKWebView> webView_;
1447 // Tests that -[CRWWebDelegate webControllerWebProcessDidCrash:] is called
1448 // when WKWebView web process has crashed.
1449 TEST_F(CRWWKWebControllerWebProcessTest, Crash) {
1450 CR_TEST_REQUIRES_WK_WEB_VIEW();
1452 id delegate = [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
1453 [[delegate expect] webControllerWebProcessDidCrash:this->webController_];
1455 [this->webController_ setDelegate:delegate];
1456 web::SimulateWKWebViewCrash(webView_);
1458 EXPECT_OCMOCK_VERIFY(delegate);
1459 [this->webController_ setDelegate:nil];
1462 // Tests that WKWebView does not crash if deallocation happens during JavaScript
1464 typedef web::WKWebViewWebTest DeallocationDuringJSEvaluation;
1465 TEST_F(DeallocationDuringJSEvaluation, NoCrash) {
1466 CR_TEST_REQUIRES_WK_WEB_VIEW();
1468 this->LoadHtml(@"<html><body></body></html>");
1469 [webController_ evaluateJavaScript:@"null"
1470 stringResultHandler:^(NSString* value, NSError*) {
1471 // Block can not be empty in order to reproduce the crash:
1472 // https://bugs.webkit.org/show_bug.cgi?id=140203
1473 VLOG(1) << "Script has been flushed.";
1475 // -evaluateJavaScript:stringResultHandler: is asynchronous so JavaScript
1476 // evaluation will not happen until TearDown, which deallocates
1477 // CRWWebController, which in its turn will deallocate WKWebView to create a
1478 // crashy condition.