Fix breakages in https://codereview.chromium.org/1155713003/
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_web_controller_unittest.mm
blob56fd77a09054f38ab70470793c0ac3f0966d25e9
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;
45 @end
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;
56 @end
58 @implementation TestWebController (PrivateTesting)
60 - (void)reloadInternal {
61   // Empty implementation to prevent the need to mock out a huge number of
62   // calls.
65 @end
67 // Used to mock CRWWebDelegate methods with C++ params.
68 @interface MockInteractionLoader : OCMockComplexTypeHelper
69 // popupURL passed to webController:shouldBlockPopupWithURL:sourceURL:
70 // Used for testing.
71 @property(nonatomic, assign) GURL popupURL;
72 // sourceURL passed to webController:shouldBlockPopupWithURL:sourceURL:
73 // Used for testing.
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:|
83 // call.
84 @property(nonatomic, readonly) net::SSLInfo SSLInfo;
85 // SSL status received in |presentSSLError:forSSLStatus:recoverable:callback:|
86 // call.
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:|
92 // call.
93 @property(nonatomic, readonly) SSLErrorCallback shouldContinueCallback;
94 @end
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&,
112                                             NSString*,
113                                             BOOL);
115 - (instancetype)initWithRepresentedObject:(id)representedObject {
116   self = [super initWithRepresentedObject:representedObject];
117   if (self) {
118     _blockPopups = YES;
119   }
120   return self;
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])(
144       url);
147 - (BOOL)webController:(CRWWebController*)webController
148     shouldBlockPopupWithURL:(const GURL&)popupURL
149                   sourceURL:(const GURL&)sourceURL {
150   self.popupURL = popupURL;
151   self.sourceURL = sourceURL;
152   return _blockPopups;
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 {
168   _SSLInfo = info;
169   _SSLStatus = status;
170   _recoverable = recoverable;
171   _shouldContinueCallback = shouldContinue;
174 @end
176 @interface CountingObserver : NSObject<CRWWebControllerObserver>
178 @property(nonatomic, readonly) int pageLoadedCount;
179 @property(nonatomic, readonly) int messageCount;
180 @end
182 @implementation CountingObserver
183 @synthesize pageLoadedCount = _pageLoadedCount;
184 @synthesize messageCount = _messageCount;
186 - (void)pageLoaded:(CRWWebController*)webController {
187   ++_pageLoadedCount;
190 - (BOOL)handleCommand:(const base::DictionaryValue&)command
191         webController:(CRWWebController*)webController
192     userIsInteracting:(BOOL)userIsInteracting
193             originURL:(const GURL&)originURL {
194   ++_messageCount;
195   return YES;
198 - (NSString*)commandPrefix {
199   return @"wctest";
202 @end
204 namespace {
206 NSString* const kGetMessageQueueJavaScript =
207     @"window.__gCrWeb === undefined ? '' : __gCrWeb.message.getMessageQueue()";
209 NSString* kCheckURLJavaScript =
210     @"try{"
211      "if(!window.__gCrWeb_CachedRequest||"
212      "!(window.__gCrWeb_CachedRequestDocument===window.document)){"
213      "window.__gCrWeb_CachedRequest = new XMLHttpRequest();"
214      "window.__gCrWeb_CachedRequestDocument = window.document;"
215      "}"
216      "window.__gCrWeb_CachedRequest.open('POST',"
217      "'https://localhost:0/crwebiossecurity',false);"
218      "window.__gCrWeb_CachedRequest.send();"
219      "}catch(e){"
220      "try{"
221      "window.__gCrWeb_CachedRequest.open('POST',"
222      "'/crwebiossecurity/b86b97a1-2ce0-44fd-a074-e2158790c98d',false);"
223      "window.__gCrWeb_CachedRequest.send();"
224      "}catch(e2){}"
225      "}"
226      "window.location.href";
228 NSString* kTestURLString = @"http://www.google.com/";
230 NSMutableURLRequest* requestForCrWebInvokeCommandAndKey(NSString* command,
231                                                         NSString* key) {
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 {
252  protected:
253   virtual void SetUp() override {
254     WebTestT::SetUp();
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"];
280   }
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();
289   }
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> {
305  protected:
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'; }"];
318     return result;
319   }
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];
326   }
329 class CRWWKWebViewWebControllerTest
330     : public WebControllerTest<web::WKWebViewWebTest> {
331  protected:
332   void SetUp() override {
333     CR_TEST_REQUIRES_WK_WEB_VIEW();
334     WebControllerTest<web::WKWebViewWebTest>::SetUp();
335   }
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
345                        options:0
346                        context:nullptr];
347     [[result stub] addObserver:OCMOCK_ANY
348                     forKeyPath:@"scrollView.backgroundColor"
349                        options:0
350                        context:nullptr];
352     [[result stub] removeObserver:webController_ forKeyPath:OCMOCK_ANY];
353     [[result stub] removeObserver:OCMOCK_ANY
354                        forKeyPath:@"scrollView.backgroundColor"];
356     return result;
357   }
358   void SimulateLoadRequest(NSURLRequest* request) const override {
359     // TODO(eugenebut): implement this method.
360   }
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);
390   }
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];
412   // Queue messages.
413   [webController_ setJsMessageQueueThrottled:YES];
414   NSMutableURLRequest* request =
415       requestForCrWebInvokeCommandAndKey(@"wctest.currentwindow1", valid_key);
416   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
417   SimulateLoadRequest(request);
418   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];
443   // Queue messages.
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];
453   request =
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];
476   // Queue message.
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.
481   [request
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];
503   // Queue message.
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
508   // is different.
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\", "
532                                 "\"url\" : \"\", "
533                                 "\"referrerPolicy\" : \"default\" }]";
535   SEL selector =
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);
544        }];
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),
557                YES);
560 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithInteraction) {
561   NSString* messageQueueJSON = @"[{"
562                                 "\"command\" : \"window.open\", "
563                                 "\"target\" : \"newtab\", "
564                                 "\"url\" : \"http://chromium.org\", "
565                                 "\"referrerPolicy\" : \"default\" }]";
567   SEL selector =
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);
576        }];
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),
589                YES);
592 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithFinishingInteraction) {
593   NSString* messageQueueJSON = @"[{"
594                                 "\"command\" : \"window.open\", "
595                                 "\"target\" : \"newtab\", "
596                                 "\"url\" : \"http://chromium.org\", "
597                                 "\"referrerPolicy\" : \"default\" }]";
599   SEL selector =
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);
608        }];
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),
620                YES);
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),
638                NO);
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),
663                YES);
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),
682                YES);
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),
701                YES);
704 TEST_F(CRWUIWebViewWebControllerTest, UnicodeEncoding) {
705   base::scoped_nsobject<UIWebView> testWebView(
706       [[UIWebView alloc] initWithFrame:CGRectZero]);
707   NSArray* unicodeArray = @[
708     @"ascii",
709     @"unicode £´∑∂∆˚√˜ß∂",
710     @"˜ǯ˜Â‚·´ÎÔ´„ÅÒ",
711     @"adª™£nÎÍlansdn",
712     @"אבדלמצש",
713     @"صسخبئغفىي",
714     @"ऒतॲहड़६ॼ",
715   ];
716   for (NSString* unicodeString in unicodeArray) {
717     NSString* encodeJS =
718         [NSString stringWithFormat:@"encodeURIComponent('%@');", unicodeString];
719     NSString* encodedString =
720         [testWebView stringByEvaluatingJavaScriptFromString:encodeJS];
721     NSString* decodedString = [encodedString
722         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
723     EXPECT_NSEQ(unicodeString, decodedString);
724   }
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);
746   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 = @[
769     @"http://one.com",
770     @"http://two.com/",
771     @"http://three.com/bar",
772     @"http://four.com/bar/",
773     @"five",
774     @"/six",
775     @"/seven/",
776     @""
777   ];
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]];
784     }
785   }
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)]);
794     }
795   }
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)]);
803     }
804   }
805   for (unsigned start_index = 0; start_index < [urlsWithFragments count];
806        ++start_index) {
807     NSString* start = urlsWithFragments[start_index];
808     for (unsigned end_index = 0; end_index < [urlsNoFragments count];
809          ++end_index) {
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)]);
817       } else {
818         // Start contains a fragment and matches end: An empty fragment is
819         // added.
820         EXPECT_EQ(MAKE_URL([end stringByAppendingString:@"#"]),
821                   [this->webController_
822                       updateURLForHistoryNavigationFromURL:MAKE_URL(start)
823                                                      toURL:MAKE_URL(end)]);
824       }
825     }
826   }
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());
838   NSError* error =
839       [NSError errorWithDomain:NSURLErrorDomain
840                           code:NSURLErrorServerCertificateHasUnknownRoot
841                       userInfo:nil];
842   [static_cast<id<WKNavigationDelegate>>(webController_.get()) webView:nil
843                                           didFailProvisionalNavigation:nil
844                                                              withError:error];
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,
865            SuppressPolicy) {
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
880 // is needed;
881 class CRWUIWebControllerPageScrollStateTest : public web::UIWebViewWebTest {
882  protected:
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);
895   }
898 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
899 // is needed;
900 class CRWWKWebControllerPageScrollStateTest : public web::WKWebViewWebTest {
901  protected:
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);
914   }
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.
922   // crbug.com/453530
923   if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE)
924     return;
925 #endif
926   this->LoadHtml(@"<html><head>"
927                   "<meta name='viewport' content="
928                   "'width=device-width,maximum-scale=5,initial-scale=1.0,"
929                   "user-scalable=no'"
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;
949   });
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;
978   });
980   EXPECT_FLOAT_EQ(3, scrollView.zoomScale / scrollView.minimumZoomScale);
983 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
984            CRWWKWebControllerPageScrollStateTest,
985            AtTop) {
986   // This test fails on iPhone 6/6+ with WKWebView; skip until it's fixed.
987   // crbug.com/453105
988   if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE &&
989       IsIPhone6Or6Plus())
990     return;
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;
1010   });
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);
1031                  }];
1033   // Wait until JavaScript is evaluated.
1034   base::test::ios::WaitUntilCondition(^bool() {
1035     return completionBlockWasCalled;
1036   });
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());
1048   item->SetURL(url);
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 {
1085  protected:
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);
1099     } copy]);
1100   }
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_;
1114   });
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;
1124   }]
1125       performSelectorOnMainThread:@selector(
1126                                       stringByEvaluatingJavaScriptFromString:)
1127                        withObject:GetTestScript()
1128                     waitUntilDone:NO];
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,
1148            Evaluation) {
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"));
1163   // Change windowID.
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 {
1175  protected:
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]);
1183   }
1185   base::scoped_nsobject<KeyboardAppearanceListener> keyboardListener_;
1188 TEST_F(WebControllerKeyboardTest, DismissKeyboard) {
1189   LoadHtml(@"<html><head></head><body><form><input id=\"textField\" /></form>"
1190            @"</body></html>");
1192   // Get the webview.
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];
1214   });
1215   EXPECT_TRUE([keyboardListener_ isKeyboardVisible]);
1217   // Method to test.
1218   [webController_ dismissKeyboard];
1220   base::test::ios::WaitUntilCondition(^bool() {
1221     return ![keyboardListener_ isKeyboardVisible];
1222   });
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];
1232 #endif
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
1253 // is needed;
1254 typedef web::UIWebViewWebTest CRWUIWebControllerObserversTest;
1255 typedef web::WKWebViewWebTest CRWWKWebControllerObserversTest;
1257 // Tests that CRWWebControllerObservers are called.
1258 WEB_TEST_F(CRWUIWebControllerObserversTest,
1259            CRWWKWebControllerObserversTest,
1260            Observers) {
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]);
1279   // Matching prefix.
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 {
1291  protected:
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]];
1311     BOOL yes = YES;
1312     [[[sessionController stub] andReturnValue:OCMOCK_VALUE(yes)] isOpenedByDOM];
1313     [child_ webStateImpl]->GetNavigationManagerImpl().SetSessionController(
1314         sessionController);
1316     LoadHtml(@"<html><body></body></html>");
1317   }
1318   void TearDown() override {
1319     EXPECT_OCMOCK_VERIFY(delegate_);
1320     [webController_ setDelegate:nil];
1321     [child_ close];
1323     WKWebViewWebTest::TearDown();
1324   }
1325   // Executes JavaScript that opens a new window and returns evaluation result
1326   // as a string.
1327   NSString* OpenWindowByDOM() {
1328     NSString* const kOpenWindowScript =
1329         @"var w = window.open('javascript:void(0);', target='_blank');"
1330          "w.toString();";
1331     NSString* windowJSObject = EvaluateJavaScriptAsString(kOpenWindowScript);
1332     WaitForBackgroundTasks();
1333     return windowJSObject;
1334   }
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
1353 // window.
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);
1363       }];
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();
1375   SEL selector =
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);
1385       }];
1387   ASSERT_FALSE([webController_ userIsInteracting]);
1388   EXPECT_NSEQ(@"", OpenWindowByDOM());
1389   base::test::ios::WaitUntilCondition(^bool() {
1390     return [delegate_ blockedPopupInfo];
1391   });
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);
1412       }];
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];
1429   });
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 {
1437  protected:
1438   void SetUp() override {
1439     CR_TEST_REQUIRES_WK_WEB_VIEW();
1440     WKWebViewWebTest::SetUp();
1441     webView_.reset(web::CreateTerminatedWKWebView());
1442     [webController_ injectWebView:webView_];
1443   }
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
1463 // evaluation.
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.";
1474                  }];
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.
1481 }  // namespace