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/ios/ios_util.h"
11 #include "base/mac/scoped_nsobject.h"
12 #include "base/strings/sys_string_conversions.h"
13 #include "base/test/histogram_tester.h"
14 #import "base/test/ios/wait_util.h"
15 #include "base/values.h"
16 #import "ios/testing/ocmock_complex_type_helper.h"
17 #include "ios/web/navigation/crw_session_controller.h"
18 #include "ios/web/navigation/crw_session_entry.h"
19 #include "ios/web/navigation/navigation_item_impl.h"
20 #import "ios/web/navigation/navigation_manager_impl.h"
21 #include "ios/web/public/referrer.h"
22 #include "ios/web/public/test/test_web_view_content_view.h"
23 #include "ios/web/public/test/web_test_util.h"
24 #import "ios/web/public/web_state/crw_web_controller_observer.h"
25 #import "ios/web/public/web_state/ui/crw_content_view.h"
26 #import "ios/web/public/web_state/ui/crw_web_view_content_view.h"
27 #include "ios/web/public/web_state/url_verification_constants.h"
28 #include "ios/web/test/web_test.h"
29 #import "ios/web/test/wk_web_view_crash_utils.h"
30 #include "ios/web/web_state/blocked_popup_info.h"
31 #import "ios/web/web_state/js/crw_js_invoke_parameter_queue.h"
32 #import "ios/web/web_state/ui/crw_ui_web_view_web_controller.h"
33 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
34 #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
35 #import "ios/web/web_state/web_state_impl.h"
36 #import "net/base/mac/url_conversions.h"
37 #include "net/ssl/ssl_info.h"
38 #include "testing/gtest/include/gtest/gtest.h"
39 #include "testing/gtest_mac.h"
40 #include "third_party/ocmock/OCMock/OCMock.h"
41 #include "third_party/ocmock/gtest_support.h"
42 #include "third_party/ocmock/ocmock_extensions.h"
43 #import "ui/base/test/ios/keyboard_appearance_listener.h"
44 #include "ui/base/test/ios/ui_view_test_utils.h"
46 using web::NavigationManagerImpl;
48 @interface TestWebController (PrivateTesting)
49 - (void)reloadInternal;
52 @interface CRWWebController (PrivateAPI)
53 @property(nonatomic, readwrite) web::PageDisplayState pageDisplayState;
54 @property(nonatomic, readonly) CRWWebControllerContainerView* containerView;
55 - (void)setJsMessageQueueThrottled:(BOOL)throttle;
56 - (void)removeDocumentLoadCommandsFromQueue;
57 - (GURL)URLForHistoryNavigationFromItem:(web::NavigationItem*)fromItem
58 toItem:(web::NavigationItem*)toItem;
59 - (BOOL)checkForUnexpectedURLChange;
60 - (void)injectEarlyInjectionScripts;
61 - (void)stopExpectingURLChangeIfNecessary;
64 @implementation TestWebController (PrivateTesting)
66 - (void)reloadInternal {
67 // Empty implementation to prevent the need to mock out a huge number of
73 // Used to mock CRWWebDelegate methods with C++ params.
74 @interface MockInteractionLoader : OCMockComplexTypeHelper
75 // popupURL passed to webController:shouldBlockPopupWithURL:sourceURL:
77 @property(nonatomic, assign) GURL popupURL;
78 // sourceURL passed to webController:shouldBlockPopupWithURL:sourceURL:
80 @property(nonatomic, assign) GURL sourceURL;
81 // Whether or not the delegate should block popups.
82 @property(nonatomic, assign) BOOL blockPopups;
83 // A web controller that will be returned by webPageOrdered... methods.
84 @property(nonatomic, assign) CRWWebController* childWebController;
85 // Blocked popup info received in |webController:didBlockPopup:| call.
86 // nullptr if that delegate method was not called.
87 @property(nonatomic, readonly) web::BlockedPopupInfo* blockedPopupInfo;
88 // SSL info received in |presentSSLError:forSSLStatus:recoverable:callback:|
90 @property(nonatomic, readonly) net::SSLInfo SSLInfo;
91 // SSL status received in |presentSSLError:forSSLStatus:recoverable:callback:|
93 @property(nonatomic, readonly) web::SSLStatus SSLStatus;
94 // Recoverable flag received in
95 // |presentSSLError:forSSLStatus:recoverable:callback:| call.
96 @property(nonatomic, readonly) BOOL recoverable;
97 // Callback received in |presentSSLError:forSSLStatus:recoverable:callback:|
99 @property(nonatomic, readonly) SSLErrorCallback shouldContinueCallback;
102 @implementation MockInteractionLoader {
103 // Backs up the property with the same name.
104 scoped_ptr<web::BlockedPopupInfo> _blockedPopupInfo;
106 @synthesize popupURL = _popupURL;
107 @synthesize sourceURL = _sourceURL;
108 @synthesize blockPopups = _blockPopups;
109 @synthesize childWebController = _childWebController;
110 @synthesize SSLInfo = _SSLInfo;
111 @synthesize SSLStatus = _SSLStatus;
112 @synthesize recoverable = _recoverable;
113 @synthesize shouldContinueCallback = _shouldContinueCallback;
115 typedef void (^webPageOrderedOpenBlankBlockType)(const web::Referrer&, BOOL);
116 typedef void (^webPageOrderedOpenBlockType)(const GURL&,
117 const web::Referrer&,
121 - (instancetype)initWithRepresentedObject:(id)representedObject {
122 self = [super initWithRepresentedObject:representedObject];
129 - (CRWWebController*)webPageOrderedOpenBlankWithReferrer:
130 (const web::Referrer&)referrer
131 inBackground:(BOOL)inBackground {
132 static_cast<webPageOrderedOpenBlankBlockType>([self blockForSelector:_cmd])(
133 referrer, inBackground);
134 return _childWebController;
137 - (CRWWebController*)webPageOrderedOpen:(const GURL&)url
138 referrer:(const web::Referrer&)referrer
139 windowName:(NSString*)windowName
140 inBackground:(BOOL)inBackground {
141 static_cast<webPageOrderedOpenBlockType>([self blockForSelector:_cmd])(
142 url, referrer, windowName, inBackground);
143 return _childWebController;
146 typedef BOOL (^openExternalURLBlockType)(const GURL&);
148 - (BOOL)openExternalURL:(const GURL&)url {
149 return static_cast<openExternalURLBlockType>([self blockForSelector:_cmd])(
153 - (BOOL)webController:(CRWWebController*)webController
154 shouldBlockPopupWithURL:(const GURL&)popupURL
155 sourceURL:(const GURL&)sourceURL {
156 self.popupURL = popupURL;
157 self.sourceURL = sourceURL;
161 - (void)webController:(CRWWebController*)webController
162 didBlockPopup:(const web::BlockedPopupInfo&)blockedPopupInfo {
163 _blockedPopupInfo.reset(new web::BlockedPopupInfo(blockedPopupInfo));
166 - (web::BlockedPopupInfo*)blockedPopupInfo {
167 return _blockedPopupInfo.get();
170 - (void)presentSSLError:(const net::SSLInfo&)info
171 forSSLStatus:(const web::SSLStatus&)status
172 recoverable:(BOOL)recoverable
173 callback:(SSLErrorCallback)shouldContinue {
176 _recoverable = recoverable;
177 _shouldContinueCallback = shouldContinue;
182 @interface CountingObserver : NSObject<CRWWebControllerObserver>
184 @property(nonatomic, readonly) int pageLoadedCount;
185 @property(nonatomic, readonly) int messageCount;
188 @implementation CountingObserver
189 @synthesize pageLoadedCount = _pageLoadedCount;
190 @synthesize messageCount = _messageCount;
192 - (void)pageLoaded:(CRWWebController*)webController {
196 - (BOOL)handleCommand:(const base::DictionaryValue&)command
197 webController:(CRWWebController*)webController
198 userIsInteracting:(BOOL)userIsInteracting
199 originURL:(const GURL&)originURL {
204 - (NSString*)commandPrefix {
212 NSString* const kGetMessageQueueJavaScript =
213 @"window.__gCrWeb === undefined ? '' : __gCrWeb.message.getMessageQueue()";
215 NSString* kCheckURLJavaScript =
217 "window.__gCrWeb_Verifying = true;"
218 "if(!window.__gCrWeb_CachedRequest||"
219 "!(window.__gCrWeb_CachedRequestDocument===window.document)){"
220 "window.__gCrWeb_CachedRequest = new XMLHttpRequest();"
221 "window.__gCrWeb_CachedRequestDocument = window.document;"
223 "window.__gCrWeb_CachedRequest.open('POST',"
224 "'https://localhost:0/crwebiossecurity',false);"
225 "window.__gCrWeb_CachedRequest.send();"
228 "window.__gCrWeb_CachedRequest.open('POST',"
229 "'/crwebiossecurity/b86b97a1-2ce0-44fd-a074-e2158790c98d',false);"
230 "window.__gCrWeb_CachedRequest.send();"
233 "delete window.__gCrWeb_Verifying;"
234 "window.location.href";
236 NSString* kTestURLString = @"http://www.google.com/";
238 NSMutableURLRequest* requestForCrWebInvokeCommandAndKey(NSString* command,
240 NSString* fullCommand =
241 [NSString stringWithFormat:@"[{\"command\":\"%@\"}]", command];
242 NSString* escapedCommand = [fullCommand
243 stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
244 NSString* urlString =
245 [NSString stringWithFormat:@"crwebinvoke://%@/#%@", key, escapedCommand];
246 return [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
249 // Returns true if the current device is a large iPhone (6 or 6+).
250 bool IsIPhone6Or6Plus() {
251 UIUserInterfaceIdiom idiom = [[UIDevice currentDevice] userInterfaceIdiom];
252 return (idiom == UIUserInterfaceIdiomPhone &&
253 CGRectGetHeight([[UIScreen mainScreen] nativeBounds]) >= 1334.0);
256 // Returns HTML for an optionally zoomable test page with |zoom_state|.
257 enum PageScalabilityType {
258 PAGE_SCALABILITY_DISABLED = 0,
259 PAGE_SCALABILITY_ENABLED,
261 NSString* GetHTMLForZoomState(const web::PageZoomState& zoom_state,
262 PageScalabilityType scalability_type) {
263 NSString* const kHTMLFormat =
264 @"<html><head><meta name='viewport' content="
265 "'width=%f,minimum-scale=%f,maximum-scale=%f,initial-scale=%f,"
266 "user-scalable=%@'/></head><body>Test</body></html>";
267 CGFloat width = CGRectGetWidth([UIScreen mainScreen].bounds) /
268 zoom_state.minimum_zoom_scale();
269 BOOL scalability_enabled = scalability_type == PAGE_SCALABILITY_ENABLED;
271 stringWithFormat:kHTMLFormat, width, zoom_state.minimum_zoom_scale(),
272 zoom_state.maximum_zoom_scale(), zoom_state.zoom_scale(),
273 scalability_enabled ? @"yes" : @"no"];
276 // Forces |webController|'s view to render and waits until |webController|'s
277 // PageZoomState matches |zoom_state|.
278 void WaitForZoomRendering(CRWWebController* webController,
279 const web::PageZoomState& zoom_state) {
280 ui::test::uiview_utils::ForceViewRendering(webController.view);
281 base::test::ios::WaitUntilCondition(^bool() {
282 return webController.pageDisplayState.zoom_state() == zoom_state;
286 // A mixin class for testing CRWWKWebViewWebController or
287 // CRWUIWebViewWebController. Stubs out WebView and child CRWWebController.
288 template <typename WebTestT>
289 class WebControllerTest : public WebTestT {
291 virtual void SetUp() override {
293 mockWebView_.reset(CreateMockWebView());
294 mockScrollView_.reset([[UIScrollView alloc] init]);
295 [[[mockWebView_ stub] andReturn:mockScrollView_.get()] scrollView];
297 id originalMockDelegate =
298 [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
299 mockDelegate_.reset([[MockInteractionLoader alloc]
300 initWithRepresentedObject:originalMockDelegate]);
301 [WebTestT::webController_ setDelegate:mockDelegate_];
302 base::scoped_nsobject<TestWebViewContentView> webViewContentView(
303 [[TestWebViewContentView alloc] initWithMockWebView:mockWebView_
304 scrollView:mockScrollView_]);
305 [WebTestT::webController_ injectWebViewContentView:webViewContentView];
307 NavigationManagerImpl& navigationManager =
308 [WebTestT::webController_ webStateImpl]->GetNavigationManagerImpl();
309 navigationManager.InitializeSession(@"name", nil, NO, 0);
310 [navigationManager.GetSessionController()
311 addPendingEntry:GURL("http://www.google.com/?q=foo#bar")
312 referrer:web::Referrer()
313 transition:ui::PAGE_TRANSITION_TYPED
314 rendererInitiated:NO];
315 // Set up child CRWWebController.
316 mockChildWebController_.reset([[OCMockObject
317 mockForProtocol:@protocol(CRWWebControllerScripting)] retain]);
318 [[[mockDelegate_ stub] andReturn:mockChildWebController_.get()]
319 webController:WebTestT::webController_
320 scriptingInterfaceForWindowNamed:@"http://www.google.com/#newtab"];
323 virtual void TearDown() override {
324 EXPECT_OCMOCK_VERIFY(mockDelegate_);
325 EXPECT_OCMOCK_VERIFY(mockChildWebController_.get());
326 EXPECT_OCMOCK_VERIFY(mockWebView_);
327 [WebTestT::webController_ resetInjectedWebViewContentView];
328 [WebTestT::webController_ setDelegate:nil];
329 WebTestT::TearDown();
332 // Creates WebView mock.
333 virtual UIView* CreateMockWebView() const = 0;
335 // Simulates a load request delegate call from the web view.
336 virtual void SimulateLoadRequest(NSURLRequest* request) const = 0;
338 base::scoped_nsobject<UIScrollView> mockScrollView_;
339 base::scoped_nsobject<id> mockWebView_;
340 base::scoped_nsobject<id> mockDelegate_;
341 base::scoped_nsobject<id> mockChildWebController_;
344 class CRWUIWebViewWebControllerTest
345 : public WebControllerTest<web::WebTestWithUIWebViewWebController> {
347 UIView* CreateMockWebView() const override {
348 id result = [[OCMockObject mockForClass:[UIWebView class]] retain];
349 [[[result stub] andReturn:nil] request];
350 [[result stub] setDelegate:OCMOCK_ANY]; // Called by resetInjectedWebView
351 // Stub out the injection process.
352 [[[result stub] andReturn:@"object"]
353 stringByEvaluatingJavaScriptFromString:
354 @"try { typeof __gCrWeb; } catch (e) { 'undefined'; }"];
355 [[[result stub] andReturn:@"object"]
356 stringByEvaluatingJavaScriptFromString:@"try { typeof "
357 @"__gCrWeb.windowIdObject; } "
358 @"catch (e) { 'undefined'; }"];
361 void SimulateLoadRequest(NSURLRequest* request) const override {
362 id<UIWebViewDelegate> delegate =
363 static_cast<id<UIWebViewDelegate>>(webController_.get());
364 [delegate webView:(UIWebView*)mockWebView_
365 shouldStartLoadWithRequest:request
366 navigationType:UIWebViewNavigationTypeLinkClicked];
370 class CRWWKWebViewWebControllerTest
371 : public WebControllerTest<web::WebTestWithWKWebViewWebController> {
373 void SetUp() override {
374 CR_TEST_REQUIRES_WK_WEB_VIEW();
375 WebControllerTest<web::WebTestWithWKWebViewWebController>::SetUp();
377 UIView* CreateMockWebView() const override {
378 id result = [[OCMockObject mockForClass:[WKWebView class]] retain];
380 // Called by resetInjectedWebView
381 [[result stub] configuration];
382 [[result stub] backForwardList];
383 [[result stub] setNavigationDelegate:OCMOCK_ANY];
384 [[result stub] setUIDelegate:OCMOCK_ANY];
385 [[result stub] addObserver:webController_
386 forKeyPath:OCMOCK_ANY
389 [[result stub] addObserver:OCMOCK_ANY
390 forKeyPath:@"scrollView.backgroundColor"
394 [[result stub] removeObserver:webController_ forKeyPath:OCMOCK_ANY];
395 [[result stub] removeObserver:OCMOCK_ANY
396 forKeyPath:@"scrollView.backgroundColor"];
400 void SimulateLoadRequest(NSURLRequest* request) const override {
401 // TODO(eugenebut): implement this method.
405 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithIncorrectKey) {
406 NSURLRequest* request = [NSURLRequest
407 requestWithURL:[NSURL URLWithString:@"crwebinvoke:invalidkey#commands"]];
409 SimulateLoadRequest(request);
412 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithLargeQueue) {
413 // Pre-define test window id.
414 [webController_ setWindowId:@"12345678901234567890123456789012"];
415 NSString* valid_key = [webController_ windowId];
416 [[[mockWebView_ stub] andReturn:kTestURLString]
417 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
419 // Install an observer to handle custom command messages.
420 base::scoped_nsobject<CountingObserver> observer(
421 [[CountingObserver alloc] init]);
422 [webController_ addObserver:observer];
424 // Queue a lot of messages.
425 [webController_ setJsMessageQueueThrottled:YES];
426 const int kNumQueuedMessages = 1000;
427 for (int i = 0; i < kNumQueuedMessages; ++i) {
428 NSMutableURLRequest* request =
429 requestForCrWebInvokeCommandAndKey(@"wctest.largequeue", valid_key);
430 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
431 SimulateLoadRequest(request);
434 // Verify the queue still works and all messages are delivered.
435 [webController_ setJsMessageQueueThrottled:NO];
436 EXPECT_EQ(kNumQueuedMessages, [observer messageCount]);
438 [webController_ removeObserver:observer];
441 TEST_F(CRWUIWebViewWebControllerTest,
442 CrWebInvokeWithAllMessagesFromCurrentWindow) {
443 // Pre-define test window id.
444 [webController_ setWindowId:@"12345678901234567890123456789012"];
445 NSString* valid_key = [webController_ windowId];
446 [[[mockWebView_ stub] andReturn:kTestURLString]
447 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
449 // Install an observer to handle custom command messages.
450 base::scoped_nsobject<CountingObserver> observer(
451 [[CountingObserver alloc] init]);
452 [webController_ addObserver:observer];
455 [webController_ setJsMessageQueueThrottled:YES];
456 NSMutableURLRequest* request =
457 requestForCrWebInvokeCommandAndKey(@"wctest.currentwindow1", valid_key);
458 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
459 SimulateLoadRequest(request);
461 requestForCrWebInvokeCommandAndKey(@"wctest.currentwindow2", valid_key);
462 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
463 SimulateLoadRequest(request);
465 // Verify the behavior.
466 [webController_ setJsMessageQueueThrottled:NO];
467 EXPECT_EQ(2, [observer messageCount]);
469 [webController_ removeObserver:observer];
472 TEST_F(CRWUIWebViewWebControllerTest,
473 CrWebInvokeWithMessagesFromDifferentWindows) {
474 // Pre-define test window id.
475 [webController_ setWindowId:@"DEADBEEFDEADBEEFDEADBEEFDEADBEEF"];
476 NSString* valid_key = [webController_ windowId];
477 [[[mockWebView_ stub] andReturn:kTestURLString]
478 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
480 // Install an observer to handle custom command messages.
481 base::scoped_nsobject<CountingObserver> observer(
482 [[CountingObserver alloc] init]);
483 [webController_ addObserver:observer];
486 [webController_ setJsMessageQueueThrottled:YES];
487 NSMutableURLRequest* request =
488 requestForCrWebInvokeCommandAndKey(@"wctest.diffwindow1", valid_key);
489 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
490 SimulateLoadRequest(request);
492 // Second queued message will come with a new window id.
493 [webController_ setWindowId:@"12345678901234567890123456789012"];
494 valid_key = [webController_ windowId];
496 requestForCrWebInvokeCommandAndKey(@"wctest.diffwindow2", valid_key);
497 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
498 SimulateLoadRequest(request);
500 [webController_ setJsMessageQueueThrottled:NO];
501 EXPECT_EQ(1, [observer messageCount]);
503 [webController_ removeObserver:observer];
506 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithSameOrigin) {
507 // Pre-define test window id.
508 [webController_ setWindowId:@"12345678901234567890123456789012"];
509 NSString* valid_key = [webController_ windowId];
510 [[[mockWebView_ stub] andReturn:@"http://www.google.com/foo"]
511 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
513 // Install an observer to handle custom command messages.
514 base::scoped_nsobject<CountingObserver> observer(
515 [[CountingObserver alloc] init]);
516 [webController_ addObserver:observer];
519 [webController_ setJsMessageQueueThrottled:YES];
520 NSMutableURLRequest* request =
521 requestForCrWebInvokeCommandAndKey(@"wctest.sameorigin", valid_key);
522 // Make originURL different from currentURL but keep the origin the same.
524 setMainDocumentURL:[NSURL URLWithString:@"http://www.google.com/bar"]];
525 SimulateLoadRequest(request);
526 // Verify the behavior.
527 [webController_ setJsMessageQueueThrottled:NO];
528 EXPECT_EQ(1, [observer messageCount]);
530 [webController_ removeObserver:observer];
533 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithDifferentOrigin) {
534 // Pre-define test window id.
535 [webController_ setWindowId:@"12345678901234567890123456789012"];
536 NSString* valid_key = [webController_ windowId];
537 [[[mockWebView_ stub] andReturn:@"http://www.google.com/"]
538 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
540 // Install an observer to handle custom command messages.
541 base::scoped_nsobject<CountingObserver> observer(
542 [[CountingObserver alloc] init]);
543 [webController_ addObserver:observer];
546 [webController_ setJsMessageQueueThrottled:YES];
547 NSMutableURLRequest* request =
548 requestForCrWebInvokeCommandAndKey(@"wctest.difforigin", valid_key);
549 // Make originURL have a different scheme from currentURL so that the origin
551 [request setMainDocumentURL:[NSURL URLWithString:@"https://www.google.com/"]];
552 SimulateLoadRequest(request);
553 // Verify the behavior.
554 [webController_ setJsMessageQueueThrottled:NO];
555 EXPECT_EQ(0, [observer messageCount]);
557 [webController_ removeObserver:observer];
560 TEST_F(CRWUIWebViewWebControllerTest, EmptyMessageQueue) {
561 [[[mockWebView_ stub] andReturn:@"[]"]
562 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
564 NSURLRequest* request =
565 [NSURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
567 SimulateLoadRequest(request);
570 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenBlankURL) {
571 NSString* messageQueueJSON = @"[{"
572 "\"command\" : \"window.open\", "
573 "\"target\" : \"newtab\", "
575 "\"referrerPolicy\" : \"default\" }]";
578 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
579 [mockDelegate_ onSelector:selector
580 callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
581 NSString* windowName, BOOL inBackground) {
582 EXPECT_EQ(url, GURL("about:blank"));
583 EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
584 EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
585 EXPECT_FALSE(inBackground);
587 [[[mockWebView_ stub] andReturn:messageQueueJSON]
588 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
589 [[[mockWebView_ stub] andReturn:kTestURLString]
590 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
592 NSMutableURLRequest* request =
593 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
594 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
595 [webController_ touched:YES];
596 SimulateLoadRequest(request);
598 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
602 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithInteraction) {
603 NSString* messageQueueJSON = @"[{"
604 "\"command\" : \"window.open\", "
605 "\"target\" : \"newtab\", "
606 "\"url\" : \"http://chromium.org\", "
607 "\"referrerPolicy\" : \"default\" }]";
610 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
611 [mockDelegate_ onSelector:selector
612 callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
613 NSString* windowName, BOOL inBackground) {
614 EXPECT_EQ(url, GURL("http://chromium.org"));
615 EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
616 EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
617 EXPECT_FALSE(inBackground);
619 [[[mockWebView_ stub] andReturn:messageQueueJSON]
620 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
621 [[[mockWebView_ stub] andReturn:kTestURLString]
622 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
624 NSMutableURLRequest* request =
625 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
626 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
627 [webController_ touched:YES];
628 SimulateLoadRequest(request);
630 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
634 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithFinishingInteraction) {
635 NSString* messageQueueJSON = @"[{"
636 "\"command\" : \"window.open\", "
637 "\"target\" : \"newtab\", "
638 "\"url\" : \"http://chromium.org\", "
639 "\"referrerPolicy\" : \"default\" }]";
642 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
643 [mockDelegate_ onSelector:selector
644 callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
645 NSString* windowName, BOOL inBackground) {
646 EXPECT_EQ(url, GURL("http://chromium.org"));
647 EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
648 EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
649 EXPECT_FALSE(inBackground);
651 [[[mockWebView_ stub] andReturn:kTestURLString]
652 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
654 NSMutableURLRequest* request =
655 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
656 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
657 [webController_ touched:YES];
658 [webController_ touched:NO];
659 SimulateLoadRequest(request);
661 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
665 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithoutInteraction) {
666 NSString* messageQueueJSON = @"[{"
667 "\"command\" : \"window.open\", "
668 "\"target\" : \"newtab\", "
669 "\"url\" : \"http://chromium.org\", "
670 "\"referrerPolicy\" : \"default\" }]";
671 [[[mockWebView_ stub] andReturn:kTestURLString]
672 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
674 NSMutableURLRequest* request =
675 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
676 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
677 SimulateLoadRequest(request);
679 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
682 MockInteractionLoader* delegate = (MockInteractionLoader*)mockDelegate_;
683 EXPECT_EQ("http://www.google.com/?q=foo#bar", [delegate sourceURL].spec());
684 EXPECT_EQ("http://chromium.org/", [delegate popupURL].spec());
686 EXPECT_TRUE([delegate blockedPopupInfo]);
689 TEST_F(CRWUIWebViewWebControllerTest, WindowClose) {
690 NSString* messageQueueJSON = @"[{"
691 "\"command\" : \"window.close\", "
692 "\"target\" : \"newtab\" }]";
694 [[mockChildWebController_ expect] orderClose];
696 NSMutableURLRequest* request =
697 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
698 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
699 [[[mockWebView_ stub] andReturn:kTestURLString]
700 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
702 SimulateLoadRequest(request);
704 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
708 TEST_F(CRWUIWebViewWebControllerTest, WindowStop) {
709 NSString* messageQueueJSON = @"[{"
710 "\"command\" : \"window.stop\", "
711 "\"target\" : \"newtab\" }]";
713 [[mockChildWebController_ expect] stopLoading];
715 NSMutableURLRequest* request =
716 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
717 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
718 [[[mockWebView_ stub] andReturn:kTestURLString]
719 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
721 SimulateLoadRequest(request);
723 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
727 TEST_F(CRWUIWebViewWebControllerTest, DocumentWrite) {
728 NSString* messageQueueJSON = @"[{"
729 "\"command\" : \"window.document.write\", "
730 "\"target\" : \"newtab\", "
731 "\"html\" : \"<html></html>\" }]";
733 [[mockChildWebController_ expect] loadHTML:@"<html></html>"];
734 [[[mockWebView_ stub] andReturn:kTestURLString]
735 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
737 NSMutableURLRequest* request =
738 [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
739 [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
740 SimulateLoadRequest(request);
742 LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
746 TEST_F(CRWUIWebViewWebControllerTest, UnicodeEncoding) {
747 base::scoped_nsobject<UIWebView> testWebView(
748 [[UIWebView alloc] initWithFrame:CGRectZero]);
749 NSArray* unicodeArray = @[
751 @"unicode £´∑∂∆˚√˜ß∂",
758 for (NSString* unicodeString in unicodeArray) {
760 [NSString stringWithFormat:@"encodeURIComponent('%@');", unicodeString];
761 NSString* encodedString =
762 [testWebView stringByEvaluatingJavaScriptFromString:encodeJS];
763 NSString* decodedString = [encodedString
764 stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
765 EXPECT_NSEQ(unicodeString, decodedString);
769 // Tests the removal of document.loaded and document.present commands in the
770 // CRWJSInvokeParameterQueue. Only applicable for UIWebView.
771 TEST_F(CRWUIWebViewWebControllerTest, PageLoadCommandRemoval) {
772 [[[mockWebView_ stub] andReturn:@"[]"]
773 stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
774 [[[mockWebView_ stub] andReturn:kTestURLString]
775 stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
777 // Pre-define test window id.
778 [webController_ setWindowId:@"12345678901234567890123456789012"];
779 NSString* valid_key = [webController_ windowId];
781 // Add some commands to the queue.
782 [webController_ setJsMessageQueueThrottled:YES];
783 NSURLRequest* request =
784 requestForCrWebInvokeCommandAndKey(@"document.present", valid_key);
785 SimulateLoadRequest(request);
786 request = requestForCrWebInvokeCommandAndKey(@"document.loaded", valid_key);
787 SimulateLoadRequest(request);
789 requestForCrWebInvokeCommandAndKey(@"window.history.forward", valid_key);
790 SimulateLoadRequest(request);
792 // Check the queue size before and after removing document load commands.
793 NSUInteger expectedBeforeCount = 3;
794 NSUInteger expectedAfterCount = 1;
795 ASSERT_EQ(expectedBeforeCount,
796 [[static_cast<CRWUIWebViewWebController*>(webController_)
797 jsInvokeParameterQueue] queueLength]);
798 [webController_ removeDocumentLoadCommandsFromQueue];
799 ASSERT_EQ(expectedAfterCount,
800 [[static_cast<CRWUIWebViewWebController*>(webController_)
801 jsInvokeParameterQueue] queueLength]);
802 [webController_ setJsMessageQueueThrottled:NO];
805 #define MAKE_URL(url_string) GURL([url_string UTF8String])
807 WEB_TEST_F(CRWUIWebViewWebControllerTest,
808 CRWWKWebViewWebControllerTest,
809 URLForHistoryNavigation) {
810 NSArray* urlsNoFragments = @[
813 @"http://three.com/bar",
814 @"http://four.com/bar/",
821 NSArray* fragments = @[ @"#", @"#bar" ];
822 NSMutableArray* urlsWithFragments = [NSMutableArray array];
823 for (NSString* url in urlsNoFragments) {
824 for (NSString* fragment in fragments) {
825 [urlsWithFragments addObject:[url stringByAppendingString:fragment]];
828 web::NavigationItemImpl fromItem;
829 web::NavigationItemImpl toItem;
831 // No start fragment: the end url is never changed.
832 for (NSString* start in urlsNoFragments) {
833 for (NSString* end in urlsWithFragments) {
834 fromItem.SetURL(MAKE_URL(start));
835 toItem.SetURL(MAKE_URL(end));
836 EXPECT_EQ(MAKE_URL(end),
837 [this->webController_ URLForHistoryNavigationFromItem:&fromItem
841 // Both contain fragments: the end url is never changed.
842 for (NSString* start in urlsWithFragments) {
843 for (NSString* end in urlsWithFragments) {
844 fromItem.SetURL(MAKE_URL(start));
845 toItem.SetURL(MAKE_URL(end));
846 EXPECT_EQ(MAKE_URL(end),
847 [this->webController_ URLForHistoryNavigationFromItem:&fromItem
851 for (unsigned start_index = 0; start_index < [urlsWithFragments count];
853 NSString* start = urlsWithFragments[start_index];
854 for (unsigned end_index = 0; end_index < [urlsNoFragments count];
856 NSString* end = urlsNoFragments[end_index];
857 if (start_index / 2 != end_index) {
858 // The URLs have nothing in common, they are left untouched.
859 fromItem.SetURL(MAKE_URL(start));
860 toItem.SetURL(MAKE_URL(end));
861 EXPECT_EQ(MAKE_URL(end), [this->webController_
862 URLForHistoryNavigationFromItem:&fromItem
865 // Start contains a fragment and matches end: An empty fragment is
867 fromItem.SetURL(MAKE_URL(start));
868 toItem.SetURL(MAKE_URL(end));
870 MAKE_URL([end stringByAppendingString:@"#"]),
871 [this->webController_ URLForHistoryNavigationFromItem:&fromItem
878 // This test requires iOS net stack.
879 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
880 // Tests that presentSSLError:forSSLStatus:recoverable:callback: is called with
881 // correct arguments if WKWebView fails to load a page with bad SSL cert.
882 TEST_F(CRWWKWebViewWebControllerTest, SSLError) {
883 CR_TEST_REQUIRES_WK_WEB_VIEW();
885 ASSERT_FALSE([mockDelegate_ SSLInfo].is_valid());
888 [NSError errorWithDomain:NSURLErrorDomain
889 code:NSURLErrorServerCertificateHasUnknownRoot
891 WKWebView* webView = static_cast<WKWebView*>([webController_ webView]);
892 [static_cast<id<WKNavigationDelegate>>(webController_.get()) webView:webView
893 didFailProvisionalNavigation:nil
896 // Verify correctness of delegate's method arguments.
897 EXPECT_TRUE([mockDelegate_ SSLInfo].is_valid());
898 EXPECT_EQ(net::CERT_STATUS_INVALID, [mockDelegate_ SSLInfo].cert_status);
899 EXPECT_EQ(net::CERT_STATUS_INVALID, [mockDelegate_ SSLStatus].cert_status);
900 EXPECT_EQ(web::SECURITY_STYLE_AUTHENTICATION_BROKEN,
901 [mockDelegate_ SSLStatus].security_style);
902 EXPECT_FALSE([mockDelegate_ recoverable]);
903 EXPECT_FALSE([mockDelegate_ shouldContinueCallback]);
905 #endif // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
907 // None of the |CRWUIWebViewWebControllerTest| setup is needed;
908 typedef web::WebTestWithUIWebViewWebController
909 CRWUIWebControllerPageDialogsOpenPolicyTest;
911 // None of the |CRWWKWebViewWebControllerTest| setup is needed;
912 typedef web::WebTestWithWKWebViewWebController
913 CRWWKWebControllerPageDialogsOpenPolicyTest;
915 WEB_TEST_F(CRWUIWebControllerPageDialogsOpenPolicyTest,
916 CRWWKWebControllerPageDialogsOpenPolicyTest,
918 this->LoadHtml(@"<html><body></body></html>");
919 id delegate = [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
920 [[delegate expect] webControllerDidSuppressDialog:this->webController_];
922 [this->webController_ setDelegate:delegate];
923 [this->webController_ setPageDialogOpenPolicy:web::DIALOG_POLICY_SUPPRESS];
924 this->RunJavaScript(@"alert('')");
926 this->WaitForBackgroundTasks();
927 EXPECT_OCMOCK_VERIFY(delegate);
928 [this->webController_ setDelegate:nil];
931 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
933 class CRWUIWebControllerPageScrollStateTest
934 : public web::WebTestWithUIWebViewWebController {
936 // Returns a web::PageDisplayState that will scroll a UIWebView to
937 // |scrollOffset| and zoom the content by |relativeZoomScale|.
938 inline web::PageDisplayState CreateTestPageDisplayState(
939 CGPoint scroll_offset,
940 CGFloat relative_zoom_scale,
941 CGFloat original_minimum_zoom_scale,
942 CGFloat original_maximum_zoom_scale,
943 CGFloat original_zoom_scale) const {
944 return web::PageDisplayState(
945 scroll_offset.x, scroll_offset.y,
946 original_minimum_zoom_scale / relative_zoom_scale,
947 original_maximum_zoom_scale / relative_zoom_scale, 1.0);
951 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
953 class CRWWKWebControllerPageScrollStateTest
954 : public web::WebTestWithWKWebViewWebController {
956 // Returns a web::PageDisplayState that will scroll a WKWebView to
957 // |scrollOffset| and zoom the content by |relativeZoomScale|.
958 inline web::PageDisplayState CreateTestPageDisplayState(
959 CGPoint scroll_offset,
960 CGFloat relative_zoom_scale,
961 CGFloat original_minimum_zoom_scale,
962 CGFloat original_maximum_zoom_scale,
963 CGFloat original_zoom_scale) const {
964 return web::PageDisplayState(
965 scroll_offset.x, scroll_offset.y, original_minimum_zoom_scale,
966 original_maximum_zoom_scale,
967 relative_zoom_scale * original_minimum_zoom_scale);
971 // TODO(iOS): Flaky on the bots. crbug/493427
972 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
973 CRWWKWebControllerPageScrollStateTest,
974 FLAKY_SetPageDisplayStateWithUserScalableDisabled) {
975 #if !TARGET_IPHONE_SIMULATOR
976 // This test fails flakily on device with WKWebView, so skip it there.
978 if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE)
981 web::PageZoomState zoom_state(1.0, 5.0, 1.0);
982 this->LoadHtml(GetHTMLForZoomState(zoom_state, PAGE_SCALABILITY_DISABLED));
983 CRWWebController* web_controller = this->webController_.get();
984 WaitForZoomRendering(web_controller, zoom_state);
985 web::PageZoomState original_zoom_state =
986 web_controller.pageDisplayState.zoom_state();
988 web::NavigationManager* nagivation_manager =
989 web_controller.webState->GetNavigationManager();
990 nagivation_manager->GetLastCommittedItem()->SetPageDisplayState(
991 this->CreateTestPageDisplayState(CGPointMake(1.0, 1.0), // scroll offset
992 3.0, // relative zoom scale
993 1.0, // original minimum zoom scale
994 5.0, // original maximum zoom scale
995 1.0)); // original zoom scale
996 [web_controller restoreStateFromHistory];
998 // |-restoreStateFromHistory| is async; wait for its completion.
999 base::test::ios::WaitUntilCondition(^bool() {
1000 return web_controller.pageDisplayState.scroll_state().offset_x() == 1.0;
1003 ASSERT_EQ(original_zoom_state, web_controller.pageDisplayState.zoom_state());
1006 // TODO(iOS): Flaky on the bots. crbug/493427
1007 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
1008 CRWWKWebControllerPageScrollStateTest,
1009 FLAKY_SetPageDisplayStateWithUserScalableEnabled) {
1010 web::PageZoomState zoom_state(1.0, 10.0, 1.0);
1011 this->LoadHtml(GetHTMLForZoomState(zoom_state, PAGE_SCALABILITY_ENABLED));
1012 CRWWebController* web_controller = this->webController_.get();
1013 WaitForZoomRendering(web_controller, zoom_state);
1015 web::NavigationManager* nagivation_manager =
1016 web_controller.webState->GetNavigationManager();
1017 nagivation_manager->GetLastCommittedItem()->SetPageDisplayState(
1018 this->CreateTestPageDisplayState(CGPointMake(1.0, 1.0), // scroll offset
1019 3.0, // relative zoom scale
1020 1.0, // original minimum zoom scale
1021 10.0, // original maximum zoom scale
1022 1.0)); // original zoom scale
1023 [web_controller restoreStateFromHistory];
1025 // |-restoreStateFromHistory| is async; wait for its completion.
1026 base::test::ios::WaitUntilCondition(^bool() {
1027 return web_controller.pageDisplayState.scroll_state().offset_x() == 1.0;
1030 web::PageZoomState final_zoom_state =
1031 web_controller.pageDisplayState.zoom_state();
1032 EXPECT_FLOAT_EQ(3, final_zoom_state.zoom_scale() /
1033 final_zoom_state.minimum_zoom_scale());
1036 // TODO(iOS): Flaky on the bots. crbug/493427
1037 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
1038 CRWWKWebControllerPageScrollStateTest,
1040 // This test fails on iPhone 6/6+ with WKWebView; skip until it's fixed.
1042 if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE &&
1046 web::PageZoomState zoom_state = web::PageZoomState(1.0, 5.0, 1.0);
1047 this->LoadHtml(GetHTMLForZoomState(zoom_state, PAGE_SCALABILITY_ENABLED));
1048 CRWWebController* web_controller = this->webController_.get();
1049 WaitForZoomRendering(web_controller, zoom_state);
1050 ASSERT_TRUE(web_controller.atTop);
1052 web::NavigationManager* nagivation_manager =
1053 web_controller.webState->GetNavigationManager();
1054 nagivation_manager->GetLastCommittedItem()->SetPageDisplayState(
1055 this->CreateTestPageDisplayState(CGPointMake(0.0, 30.0), // scroll offset
1056 5.0, // relative zoom scale
1057 1.0, // original minimum zoom scale
1058 5.0, // original maximum zoom scale
1059 1.0)); // original zoom scale
1060 [web_controller restoreStateFromHistory];
1062 // |-restoreStateFromHistory| is async; wait for its completion.
1063 base::test::ios::WaitUntilCondition(^bool() {
1064 return web_controller.pageDisplayState.scroll_state().offset_y() == 30.0;
1067 ASSERT_FALSE(web_controller.atTop);
1070 // Tests that evaluateJavaScript:completionHandler: properly forwards the
1071 // call to UIWebView.
1072 TEST_F(CRWUIWebViewWebControllerTest, JavaScriptEvaluation) {
1073 NSString* kTestScript = @"script";
1074 NSString* kTestResult = @"result";
1075 NSString* scriptWithIDCheck =
1076 [webController_ scriptByAddingWindowIDCheckForScript:kTestScript];
1077 [[[mockWebView_ stub] andReturn:kTestResult]
1078 stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1080 __block BOOL completionBlockWasCalled = NO;
1081 [webController_ evaluateJavaScript:kTestScript
1082 stringResultHandler:^(NSString* string, NSError* error) {
1083 completionBlockWasCalled = YES;
1084 EXPECT_NSEQ(kTestResult, string);
1085 EXPECT_EQ(nil, error);
1088 // Wait until JavaScript is evaluated.
1089 base::test::ios::WaitUntilCondition(^bool() {
1090 return completionBlockWasCalled;
1092 EXPECT_TRUE(completionBlockWasCalled);
1095 TEST_F(CRWUIWebViewWebControllerTest, POSTRequestCache) {
1096 GURL url("http://www.google.fr/");
1097 NSString* mixedCaseCookieHeaderName = @"cOoKiE";
1098 NSString* otherHeaderName = @"Myheader";
1099 NSString* otherHeaderValue = @"A";
1100 NSString* otherHeaderIncorrectValue = @"C";
1102 scoped_ptr<web::NavigationItemImpl> item(new web::NavigationItemImpl());
1104 item->SetTransitionType(ui::PAGE_TRANSITION_FORM_SUBMIT);
1105 item->set_is_renderer_initiated(true);
1106 base::scoped_nsobject<CRWSessionEntry> currentEntry(
1107 [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass()]);
1108 base::scoped_nsobject<NSMutableURLRequest> request(
1109 [[NSMutableURLRequest alloc] initWithURL:net::NSURLWithGURL(url)]);
1110 [request setHTTPMethod:@"POST"];
1111 [request setValue:otherHeaderValue forHTTPHeaderField:otherHeaderName];
1112 [request setValue:@"B" forHTTPHeaderField:mixedCaseCookieHeaderName];
1113 // No data is cached initially.
1114 EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1115 EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1116 // Streams are not cached.
1117 [request setHTTPBodyStream:[NSInputStream inputStreamWithData:[NSData data]]];
1118 [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1119 EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1120 EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1121 [request setHTTPBodyStream:nil];
1122 // POST Data is cached. Cookie headers are stripped, no matter their case.
1123 base::scoped_nsobject<NSData> body([[NSData alloc] init]);
1124 [request setHTTPBody:body];
1125 [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1126 EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1127 EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1128 @{otherHeaderName : otherHeaderValue});
1129 // A new request will not change the cached version.
1130 base::scoped_nsobject<NSData> body2([[NSData alloc] init]);
1131 [request setValue:otherHeaderIncorrectValue
1132 forHTTPHeaderField:otherHeaderName];
1133 [request setHTTPBody:body2];
1134 EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1135 EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1136 @{otherHeaderName : otherHeaderValue});
1139 class WebControllerJSEvaluationTest : public CRWUIWebViewWebControllerTest {
1141 void SetUp() override {
1142 WebControllerTest::SetUp();
1144 NSString* kTestResult = @"result";
1145 completionBlockWasCalled_ = NO;
1146 NSString* scriptWithIDCheck =
1147 [webController_ scriptByAddingWindowIDCheckForScript:GetTestScript()];
1148 [[[mockWebView_ stub] andReturn:kTestResult]
1149 stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1150 completionHandler_.reset([^(NSString* string, NSError* error) {
1151 completionBlockWasCalled_ = YES;
1152 EXPECT_NSEQ(kTestResult, string);
1153 EXPECT_EQ(nil, error);
1156 NSString* GetTestScript() const { return @"script"; }
1157 base::scoped_nsprotocol<web::JavaScriptCompletion> completionHandler_;
1158 BOOL completionBlockWasCalled_;
1161 // Tests that -evaluateJavaScript:stringResultHandler: properly forwards
1162 // the call to the UIWebView.
1163 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluation) {
1164 [webController_ evaluateJavaScript:GetTestScript()
1165 stringResultHandler:completionHandler_];
1166 // Wait until JavaScript is evaluated.
1167 base::test::ios::WaitUntilCondition(^bool() {
1168 return this->completionBlockWasCalled_;
1170 EXPECT_TRUE(completionBlockWasCalled_);
1173 // Tests that -evaluateUserJavaScript:stringResultHandler: properly
1174 // forwards the call to the UIWebView.
1175 TEST_F(WebControllerJSEvaluationTest, UserJavaScriptEvaluation) {
1176 __block BOOL method_called = NO;
1177 [[[mockWebView_ stub] andDo:^(NSInvocation*) {
1178 method_called = YES;
1180 performSelectorOnMainThread:@selector(
1181 stringByEvaluatingJavaScriptFromString:)
1182 withObject:GetTestScript()
1184 [webController_ evaluateUserJavaScript:GetTestScript()];
1185 EXPECT_TRUE(method_called);
1188 // Tests that -evaluateJavaScript:stringResultHandler: does not crash
1189 // on a nil completionHandler.
1190 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluationNilHandler) {
1191 [webController_ evaluateJavaScript:GetTestScript() stringResultHandler:nil];
1194 // Real UIWebView is required for JSEvaluationTest.
1195 typedef web::WebTestWithUIWebViewWebController
1196 CRWUIWebControllerJSEvaluationTest;
1198 // Real WKWebView is required for JSEvaluationTest.
1199 typedef web::WebTestWithWKWebViewWebController
1200 CRWWKWebControllerJSEvaluationTest;
1202 // Tests that a script correctly evaluates to string.
1203 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1204 CRWWKWebControllerJSEvaluationTest,
1206 this->LoadHtml(@"<p></p>");
1207 EXPECT_NSEQ(@"true", this->EvaluateJavaScriptAsString(@"true"));
1208 EXPECT_NSEQ(@"false", this->EvaluateJavaScriptAsString(@"false"));
1211 // Tests that a script is not evaluated on windowID mismatch.
1212 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1213 CRWWKWebControllerJSEvaluationTest,
1214 WindowIDMissmatch) {
1215 this->LoadHtml(@"<p></p>");
1216 // Script is evaluated since windowID is matched.
1217 this->EvaluateJavaScriptAsString(@"window.test1 = '1';");
1218 EXPECT_NSEQ(@"1", this->EvaluateJavaScriptAsString(@"window.test1"));
1221 this->EvaluateJavaScriptAsString(@"__gCrWeb['windowId'] = '';");
1223 // Script is not evaluated because of windowID mismatch.
1224 this->EvaluateJavaScriptAsString(@"window.test2 = '2';");
1225 EXPECT_NSEQ(@"", this->EvaluateJavaScriptAsString(@"window.test2"));
1228 // A separate test class is used for testing the keyboard dismissal, as none of
1229 // the setup in |CRWUIWebViewWebControllerTest| is needed; keyboard appearance
1230 // is tracked by the KeyboardAppearanceListener.
1231 class WebControllerKeyboardTest
1232 : public web::WebTestWithUIWebViewWebController {
1234 void SetUp() override {
1235 web::WebTestWithUIWebViewWebController::SetUp();
1236 // Close any outstanding alert boxes.
1237 ui::test::uiview_utils::CancelAlerts();
1239 // Sets up the listener for keyboard activation/deactivation notifications.
1240 keyboardListener_.reset([[KeyboardAppearanceListener alloc] init]);
1243 base::scoped_nsobject<KeyboardAppearanceListener> keyboardListener_;
1246 TEST_F(WebControllerKeyboardTest, DismissKeyboard) {
1247 LoadHtml(@"<html><head></head><body><form><input id=\"textField\" /></form>"
1251 UIWebView* webView = static_cast<UIWebView*>(
1252 [webController_ containerView].webViewContentView.webView);
1253 EXPECT_TRUE(webView);
1255 // Create the window and add the webview.
1256 base::scoped_nsobject<UIWindow> window(
1257 [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]);
1258 [window makeKeyAndVisible];
1259 [window addSubview:webView];
1261 // Set the flag to allow focus() in code in UIWebView.
1262 EXPECT_TRUE([webView keyboardDisplayRequiresUserAction]);
1263 [webView setKeyboardDisplayRequiresUserAction:NO];
1264 EXPECT_FALSE([webView keyboardDisplayRequiresUserAction]);
1266 // Set the focus on the textField.
1267 EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1268 [webView stringByEvaluatingJavaScriptFromString:
1269 @"document.getElementById(\"textField\").focus();"];
1270 base::test::ios::WaitUntilCondition(^bool() {
1271 return [keyboardListener_ isKeyboardVisible];
1273 EXPECT_TRUE([keyboardListener_ isKeyboardVisible]);
1276 [webController_ dismissKeyboard];
1278 base::test::ios::WaitUntilCondition(^bool() {
1279 return ![keyboardListener_ isKeyboardVisible];
1281 EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1284 TEST_F(CRWWKWebViewWebControllerTest, WebURLWithTrustLevel) {
1285 CR_TEST_REQUIRES_WK_WEB_VIEW();
1287 [[[mockWebView_ stub] andReturn:[NSURL URLWithString:kTestURLString]] URL];
1288 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1289 [[[mockWebView_ stub] andReturnBool:NO] hasOnlySecureContent];
1292 // Stub out the injection process.
1293 [[mockWebView_ stub] evaluateJavaScript:OCMOCK_ANY
1294 completionHandler:OCMOCK_ANY];
1296 // Simulate registering load request to avoid failing page load simulation.
1297 [webController_ simulateLoadRequestWithURL:GURL([kTestURLString UTF8String])];
1298 // Simulate a page load to trigger a URL update.
1299 [static_cast<id<WKNavigationDelegate>>(webController_.get())
1300 webView:mockWebView_
1301 didCommitNavigation:nil];
1303 web::URLVerificationTrustLevel trust_level = web::kNone;
1304 GURL gurl = [webController_ currentURLWithTrustLevel:&trust_level];
1306 EXPECT_EQ(gurl, GURL(base::SysNSStringToUTF8(kTestURLString)));
1307 EXPECT_EQ(web::kAbsolute, trust_level);
1310 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
1312 typedef web::WebTestWithUIWebViewWebController CRWUIWebControllerObserversTest;
1313 typedef web::WebTestWithWKWebViewWebController CRWWKWebControllerObserversTest;
1315 // Tests that CRWWebControllerObservers are called.
1316 WEB_TEST_F(CRWUIWebControllerObserversTest,
1317 CRWWKWebControllerObserversTest,
1319 base::scoped_nsobject<CountingObserver> observer(
1320 [[CountingObserver alloc] init]);
1321 CRWWebController* web_controller = this->webController_;
1322 EXPECT_EQ(0u, [web_controller observerCount]);
1323 [web_controller addObserver:observer];
1324 EXPECT_EQ(1u, [web_controller observerCount]);
1326 EXPECT_EQ(0, [observer pageLoadedCount]);
1327 [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), false);
1328 EXPECT_EQ(0, [observer pageLoadedCount]);
1329 [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), true);
1330 EXPECT_EQ(1, [observer pageLoadedCount]);
1332 EXPECT_EQ(0, [observer messageCount]);
1333 // Non-matching prefix.
1334 EXPECT_FALSE([web_controller webStateImpl]->OnScriptCommandReceived(
1335 "a", base::DictionaryValue(), GURL("http://test"), true));
1336 EXPECT_EQ(0, [observer messageCount]);
1338 EXPECT_TRUE([web_controller webStateImpl]->OnScriptCommandReceived(
1339 base::SysNSStringToUTF8([observer commandPrefix]) + ".foo",
1340 base::DictionaryValue(), GURL("http://test"), true));
1341 EXPECT_EQ(1, [observer messageCount]);
1343 [web_controller removeObserver:observer];
1344 EXPECT_EQ(0u, [web_controller observerCount]);
1347 // Test fixture for window.open tests.
1348 class CRWWKWebControllerWindowOpenTest
1349 : public web::WebTestWithWKWebViewWebController {
1351 void SetUp() override {
1352 CR_TEST_REQUIRES_WK_WEB_VIEW();
1353 web::WebTestWithWKWebViewWebController::SetUp();
1355 // Configure web delegate.
1356 delegate_.reset([[MockInteractionLoader alloc]
1357 initWithRepresentedObject:
1358 [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)]]);
1359 ASSERT_TRUE([delegate_ blockPopups]);
1360 [webController_ setDelegate:delegate_];
1362 // Configure child web controller.
1363 child_.reset(CreateWebController());
1364 [child_ setWebUsageEnabled:YES];
1365 [delegate_ setChildWebController:child_];
1367 // Configure child web controller's session controller mock.
1368 id sessionController =
1369 [OCMockObject niceMockForClass:[CRWSessionController class]];
1371 [[[sessionController stub] andReturnValue:OCMOCK_VALUE(yes)] isOpenedByDOM];
1372 [child_ webStateImpl]->GetNavigationManagerImpl().SetSessionController(
1375 LoadHtml(@"<html><body></body></html>");
1377 void TearDown() override {
1378 EXPECT_OCMOCK_VERIFY(delegate_);
1379 [webController_ setDelegate:nil];
1382 web::WebTestWithWKWebViewWebController::TearDown();
1384 // Executes JavaScript that opens a new window and returns evaluation result
1386 NSString* OpenWindowByDOM() {
1387 NSString* const kOpenWindowScript =
1388 @"var w = window.open('javascript:void(0);', target='_blank');"
1390 NSString* windowJSObject = EvaluateJavaScriptAsString(kOpenWindowScript);
1391 WaitForBackgroundTasks();
1392 return windowJSObject;
1394 // A CRWWebDelegate mock used for testing.
1395 base::scoped_nsobject<id> delegate_;
1396 // A child CRWWebController used for testing.
1397 base::scoped_nsobject<CRWWebController> child_;
1400 // Tests that absence of web delegate is handled gracefully.
1401 TEST_F(CRWWKWebControllerWindowOpenTest, NoDelegate) {
1402 CR_TEST_REQUIRES_WK_WEB_VIEW();
1404 [webController_ setDelegate:nil];
1406 EXPECT_NSEQ(@"", OpenWindowByDOM());
1408 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1411 // Tests that window.open triggered by user gesture opens a new non-popup
1413 TEST_F(CRWWKWebControllerWindowOpenTest, OpenWithUserGesture) {
1414 CR_TEST_REQUIRES_WK_WEB_VIEW();
1416 SEL selector = @selector(webPageOrderedOpenBlankWithReferrer:inBackground:);
1417 [delegate_ onSelector:selector
1418 callBlockExpectation:^(const web::Referrer& referrer,
1419 BOOL in_background) {
1420 EXPECT_EQ("", referrer.url.spec());
1421 EXPECT_FALSE(in_background);
1424 [webController_ touched:YES];
1425 EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1426 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1429 // Tests that window.open executed w/o user gesture does not open a new window.
1430 // Once the blocked popup is allowed a new window is opened.
1431 TEST_F(CRWWKWebControllerWindowOpenTest, AllowPopup) {
1432 CR_TEST_REQUIRES_WK_WEB_VIEW();
1435 @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
1436 [delegate_ onSelector:selector
1437 callBlockExpectation:^(const GURL& new_window_url,
1438 const web::Referrer& referrer,
1439 NSString* obsoleted_window_name,
1440 BOOL in_background) {
1441 EXPECT_EQ("javascript:void(0);", new_window_url.spec());
1442 EXPECT_EQ("", referrer.url.spec());
1443 EXPECT_FALSE(in_background);
1446 ASSERT_FALSE([webController_ userIsInteracting]);
1447 EXPECT_NSEQ(@"", OpenWindowByDOM());
1448 base::test::ios::WaitUntilCondition(^bool() {
1449 return [delegate_ blockedPopupInfo];
1452 if ([delegate_ blockedPopupInfo])
1453 [delegate_ blockedPopupInfo]->ShowPopup();
1455 EXPECT_EQ("", [delegate_ sourceURL].spec());
1456 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1459 // Tests that window.open executed w/o user gesture opens a new window, assuming
1460 // that delegate allows popups.
1461 TEST_F(CRWWKWebControllerWindowOpenTest, DontBlockPopup) {
1462 CR_TEST_REQUIRES_WK_WEB_VIEW();
1464 [delegate_ setBlockPopups:NO];
1465 SEL selector = @selector(webPageOrderedOpenBlankWithReferrer:inBackground:);
1466 [delegate_ onSelector:selector
1467 callBlockExpectation:^(const web::Referrer& referrer,
1468 BOOL in_background) {
1469 EXPECT_EQ("", referrer.url.spec());
1470 EXPECT_FALSE(in_background);
1473 EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1474 EXPECT_FALSE([delegate_ blockedPopupInfo]);
1476 EXPECT_EQ("", [delegate_ sourceURL].spec());
1477 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1480 // Tests that window.open executed w/o user gesture does not open a new window.
1481 TEST_F(CRWWKWebControllerWindowOpenTest, BlockPopup) {
1482 CR_TEST_REQUIRES_WK_WEB_VIEW();
1484 ASSERT_FALSE([webController_ userIsInteracting]);
1485 EXPECT_NSEQ(@"", OpenWindowByDOM());
1486 base::test::ios::WaitUntilCondition(^bool() {
1487 return [delegate_ blockedPopupInfo];
1490 EXPECT_EQ("", [delegate_ sourceURL].spec());
1491 EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1494 // Fixture class to test WKWebView crashes.
1495 class CRWWKWebControllerWebProcessTest
1496 : public web::WebTestWithWKWebViewWebController {
1498 void SetUp() override {
1499 CR_TEST_REQUIRES_WK_WEB_VIEW();
1500 web::WebTestWithWKWebViewWebController::SetUp();
1501 webView_.reset(web::CreateTerminatedWKWebView());
1502 base::scoped_nsobject<TestWebViewContentView> webViewContentView(
1503 [[TestWebViewContentView alloc]
1504 initWithMockWebView:webView_
1505 scrollView:[webView_ scrollView]]);
1506 [webController_ injectWebViewContentView:webViewContentView];
1508 base::scoped_nsobject<WKWebView> webView_;
1511 // Tests that -[CRWWebDelegate webControllerWebProcessDidCrash:] is called
1512 // when WKWebView web process has crashed.
1513 TEST_F(CRWWKWebControllerWebProcessTest, Crash) {
1514 CR_TEST_REQUIRES_WK_WEB_VIEW();
1516 id delegate = [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
1517 [[delegate expect] webControllerWebProcessDidCrash:this->webController_];
1519 [this->webController_ setDelegate:delegate];
1520 web::SimulateWKWebViewCrash(webView_);
1522 EXPECT_OCMOCK_VERIFY(delegate);
1523 [this->webController_ setDelegate:nil];
1526 // Tests that WKWebView does not crash if deallocation happens during JavaScript
1528 typedef web::WebTestWithWKWebViewWebController DeallocationDuringJSEvaluation;
1529 TEST_F(DeallocationDuringJSEvaluation, NoCrash) {
1530 CR_TEST_REQUIRES_WK_WEB_VIEW();
1532 this->LoadHtml(@"<html><body></body></html>");
1533 [webController_ evaluateJavaScript:@"null"
1534 stringResultHandler:^(NSString* value, NSError*) {
1535 // Block can not be empty in order to reproduce the crash:
1536 // https://bugs.webkit.org/show_bug.cgi?id=140203
1537 VLOG(1) << "Script has been flushed.";
1539 // -evaluateJavaScript:stringResultHandler: is asynchronous so JavaScript
1540 // evaluation will not happen until TearDown, which deallocates
1541 // CRWWebController, which in its turn will deallocate WKWebView to create a
1542 // crashy condition.