SetPageStateWithUserScalableEnabled is flaky.
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_web_controller_unittest.mm
blobb20203c1b975e84a6e9b49ad26ce87e65928787e
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 // TODO(iOS): Flaky on the bots. crbug/496140
957 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
958            CRWWKWebControllerPageScrollStateTest,
959            FLAKY_SetPageStateWithUserScalableEnabled) {
960   this->LoadHtml(@"<html><head>"
961                   "<meta name='viewport' content="
962                   "'width=device-width,maximum-scale=10,initial-scale=1.0'"
963                   " /></head><body>Test</body></html>");
965   ui::test::uiview_utils::ForceViewRendering([this->webController_ view]);
966   web::PageScrollState scrollState =
967       this->CreateTestScrollState(CGPointMake(1.0, 1.0),  // scroll offset
968                                   3.0,                    // relative zoom scale
969                                   1.0,   // original minimum zoom scale
970                                   10.0,  // original maximum zoom scale
971                                   1.0);  // original zoom scale
972   [this->webController_ setPageScrollState:scrollState];
974   // setPageState: is async; wait for its completion.
975   id webView = [[this->webController_ view] subviews][0];
976   UIScrollView* scrollView = [webView scrollView];
977   base::test::ios::WaitUntilCondition(^bool() {
978     return [scrollView contentOffset].x == 1.0f;
979   });
981   EXPECT_FLOAT_EQ(3, scrollView.zoomScale / scrollView.minimumZoomScale);
984 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
985            CRWWKWebControllerPageScrollStateTest,
986            AtTop) {
987   // This test fails on iPhone 6/6+ with WKWebView; skip until it's fixed.
988   // crbug.com/453105
989   if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE &&
990       IsIPhone6Or6Plus())
991     return;
993   this->LoadHtml(@"<html><head>"
994                   "<meta name='viewport' content="
995                   "'width=device-width,maximum-scale=5.0,initial-scale=1.0'"
996                   " /></head><body>Test</body></html>");
997   ASSERT_TRUE(this->webController_.get().atTop);
999   web::PageScrollState scrollState =
1000       this->CreateTestScrollState(CGPointMake(0.0, 30.0),  // scroll offset
1001                                   5.0,   // relative zoom scale
1002                                   1.0,   // original minimum zoom scale
1003                                   5.0,   // original maximum zoom scale
1004                                   1.0);  // original zoom scale
1005   [this->webController_ setPageScrollState:scrollState];
1007   // setPageState: is async; wait for its completion.
1008   id webView = [[this->webController_ view] subviews][0];
1009   base::test::ios::WaitUntilCondition(^bool() {
1010     return [[webView scrollView] contentOffset].y == 30.0f;
1011   });
1013   ASSERT_FALSE([this->webController_ atTop]);
1016 // Tests that evaluateJavaScript:completionHandler: properly forwards the
1017 // call to UIWebView.
1018 TEST_F(CRWUIWebViewWebControllerTest, JavaScriptEvaluation) {
1019   NSString* kTestScript = @"script";
1020   NSString* kTestResult = @"result";
1021   NSString* scriptWithIDCheck =
1022       [webController_ scriptByAddingWindowIDCheckForScript:kTestScript];
1023   [[[mockWebView_ stub] andReturn:kTestResult]
1024       stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1026   __block BOOL completionBlockWasCalled = NO;
1027   [webController_ evaluateJavaScript:kTestScript
1028                  stringResultHandler:^(NSString* string, NSError* error) {
1029                    completionBlockWasCalled = YES;
1030                    EXPECT_NSEQ(kTestResult, string);
1031                    EXPECT_EQ(nil, error);
1032                  }];
1034   // Wait until JavaScript is evaluated.
1035   base::test::ios::WaitUntilCondition(^bool() {
1036     return completionBlockWasCalled;
1037   });
1038   EXPECT_TRUE(completionBlockWasCalled);
1041 TEST_F(CRWUIWebViewWebControllerTest, POSTRequestCache) {
1042   GURL url("http://www.google.fr/");
1043   NSString* mixedCaseCookieHeaderName = @"cOoKiE";
1044   NSString* otherHeaderName = @"Myheader";
1045   NSString* otherHeaderValue = @"A";
1046   NSString* otherHeaderIncorrectValue = @"C";
1048   scoped_ptr<web::NavigationItemImpl> item(new web::NavigationItemImpl());
1049   item->SetURL(url);
1050   item->SetTransitionType(ui::PAGE_TRANSITION_FORM_SUBMIT);
1051   item->set_is_renderer_initiated(true);
1052   base::scoped_nsobject<CRWSessionEntry> currentEntry(
1053       [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass()]);
1054   base::scoped_nsobject<NSMutableURLRequest> request(
1055       [[NSMutableURLRequest alloc] initWithURL:net::NSURLWithGURL(url)]);
1056   [request setHTTPMethod:@"POST"];
1057   [request setValue:otherHeaderValue forHTTPHeaderField:otherHeaderName];
1058   [request setValue:@"B" forHTTPHeaderField:mixedCaseCookieHeaderName];
1059   // No data is cached initially.
1060   EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1061   EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1062   // Streams are not cached.
1063   [request setHTTPBodyStream:[NSInputStream inputStreamWithData:[NSData data]]];
1064   [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1065   EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1066   EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1067   [request setHTTPBodyStream:nil];
1068   // POST Data is cached. Cookie headers are stripped, no matter their case.
1069   base::scoped_nsobject<NSData> body([[NSData alloc] init]);
1070   [request setHTTPBody:body];
1071   [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1072   EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1073   EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1074               @{otherHeaderName : otherHeaderValue});
1075   // A new request will not change the cached version.
1076   base::scoped_nsobject<NSData> body2([[NSData alloc] init]);
1077   [request setValue:otherHeaderIncorrectValue
1078       forHTTPHeaderField:otherHeaderName];
1079   [request setHTTPBody:body2];
1080   EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1081   EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1082               @{otherHeaderName : otherHeaderValue});
1085 class WebControllerJSEvaluationTest : public CRWUIWebViewWebControllerTest {
1086  protected:
1087   void SetUp() override {
1088     WebControllerTest::SetUp();
1090     NSString* kTestResult = @"result";
1091     completionBlockWasCalled_ = NO;
1092     NSString* scriptWithIDCheck =
1093         [webController_ scriptByAddingWindowIDCheckForScript:GetTestScript()];
1094     [[[mockWebView_ stub] andReturn:kTestResult]
1095         stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1096     completionHandler_.reset([^(NSString* string, NSError* error) {
1097       completionBlockWasCalled_ = YES;
1098       EXPECT_NSEQ(kTestResult, string);
1099       EXPECT_EQ(nil, error);
1100     } copy]);
1101   }
1102   NSString* GetTestScript() const { return @"script"; }
1103   base::scoped_nsprotocol<web::JavaScriptCompletion> completionHandler_;
1104   BOOL completionBlockWasCalled_;
1107 // Tests that -evaluateJavaScript:stringResultHandler: properly forwards
1108 // the call to the UIWebView.
1109 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluation) {
1110   [webController_ evaluateJavaScript:GetTestScript()
1111                  stringResultHandler:completionHandler_];
1112   // Wait until JavaScript is evaluated.
1113   base::test::ios::WaitUntilCondition(^bool() {
1114     return this->completionBlockWasCalled_;
1115   });
1116   EXPECT_TRUE(completionBlockWasCalled_);
1119 // Tests that -evaluateUserJavaScript:stringResultHandler: properly
1120 // forwards the call to the UIWebView.
1121 TEST_F(WebControllerJSEvaluationTest, UserJavaScriptEvaluation) {
1122   __block BOOL method_called = NO;
1123   [[[mockWebView_ stub] andDo:^(NSInvocation*) {
1124     method_called = YES;
1125   }]
1126       performSelectorOnMainThread:@selector(
1127                                       stringByEvaluatingJavaScriptFromString:)
1128                        withObject:GetTestScript()
1129                     waitUntilDone:NO];
1130   [webController_ evaluateUserJavaScript:GetTestScript()];
1131   EXPECT_TRUE(method_called);
1134 // Tests that -evaluateJavaScript:stringResultHandler: does not crash
1135 // on a nil completionHandler.
1136 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluationNilHandler) {
1137   [webController_ evaluateJavaScript:GetTestScript() stringResultHandler:nil];
1140 // Real UIWebView is required for JSEvaluationTest.
1141 typedef web::UIWebViewWebTest CRWUIWebControllerJSEvaluationTest;
1143 // Real WKWebView is required for JSEvaluationTest.
1144 typedef web::WKWebViewWebTest CRWWKWebControllerJSEvaluationTest;
1146 // Tests that a script correctly evaluates to string.
1147 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1148            CRWWKWebControllerJSEvaluationTest,
1149            Evaluation) {
1150   this->LoadHtml(@"<p></p>");
1151   EXPECT_NSEQ(@"true", this->EvaluateJavaScriptAsString(@"true"));
1152   EXPECT_NSEQ(@"false", this->EvaluateJavaScriptAsString(@"false"));
1155 // Tests that a script is not evaluated on windowID mismatch.
1156 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1157            CRWWKWebControllerJSEvaluationTest,
1158            WindowIDMissmatch) {
1159   this->LoadHtml(@"<p></p>");
1160   // Script is evaluated since windowID is matched.
1161   this->EvaluateJavaScriptAsString(@"window.test1 = '1';");
1162   EXPECT_NSEQ(@"1", this->EvaluateJavaScriptAsString(@"window.test1"));
1164   // Change windowID.
1165   this->EvaluateJavaScriptAsString(@"__gCrWeb['windowId'] = '';");
1167   // Script is not evaluated because of windowID mismatch.
1168   this->EvaluateJavaScriptAsString(@"window.test2 = '2';");
1169   EXPECT_NSEQ(@"", this->EvaluateJavaScriptAsString(@"window.test2"));
1172 // A separate test class is used for testing the keyboard dismissal, as none of
1173 // the setup in |CRWUIWebViewWebControllerTest| is needed; keyboard appearance
1174 // is tracked by the KeyboardAppearanceListener.
1175 class WebControllerKeyboardTest : public web::UIWebViewWebTest {
1176  protected:
1177   void SetUp() override {
1178     UIWebViewWebTest::SetUp();
1179     // Close any outstanding alert boxes.
1180     ui::test::uiview_utils::CancelAlerts();
1182     // Sets up the listener for keyboard activation/deactivation notifications.
1183     keyboardListener_.reset([[KeyboardAppearanceListener alloc] init]);
1184   }
1186   base::scoped_nsobject<KeyboardAppearanceListener> keyboardListener_;
1189 TEST_F(WebControllerKeyboardTest, DismissKeyboard) {
1190   LoadHtml(@"<html><head></head><body><form><input id=\"textField\" /></form>"
1191            @"</body></html>");
1193   // Get the webview.
1194   UIWebView* webView =
1195       (UIWebView*)[[[webController_ view] subviews] objectAtIndex:0];
1196   EXPECT_TRUE(webView);
1198   // Create the window and add the webview.
1199   base::scoped_nsobject<UIWindow> window(
1200       [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]);
1201   [window makeKeyAndVisible];
1202   [window addSubview:webView];
1204   // Set the flag to allow focus() in code in UIWebView.
1205   EXPECT_TRUE([webView keyboardDisplayRequiresUserAction]);
1206   [webView setKeyboardDisplayRequiresUserAction:NO];
1207   EXPECT_FALSE([webView keyboardDisplayRequiresUserAction]);
1209   // Set the focus on the textField.
1210   EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1211   [webView stringByEvaluatingJavaScriptFromString:
1212                @"document.getElementById(\"textField\").focus();"];
1213   base::test::ios::WaitUntilCondition(^bool() {
1214     return [keyboardListener_ isKeyboardVisible];
1215   });
1216   EXPECT_TRUE([keyboardListener_ isKeyboardVisible]);
1218   // Method to test.
1219   [webController_ dismissKeyboard];
1221   base::test::ios::WaitUntilCondition(^bool() {
1222     return ![keyboardListener_ isKeyboardVisible];
1223   });
1224   EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1227 TEST_F(CRWWKWebViewWebControllerTest, WebURLWithTrustLevel) {
1228   CR_TEST_REQUIRES_WK_WEB_VIEW();
1230   [[[mockWebView_ stub] andReturn:[NSURL URLWithString:kTestURLString]] URL];
1231 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1232   [[[mockWebView_ stub] andReturnBool:NO] hasOnlySecureContent];
1233 #endif
1235   // Stub out the injection process.
1236   [[mockWebView_ stub] evaluateJavaScript:OCMOCK_ANY
1237                         completionHandler:OCMOCK_ANY];
1239   // Simulate registering load request to avoid failing page load simulation.
1240   [webController_ simulateLoadRequestWithURL:GURL([kTestURLString UTF8String])];
1241   // Simulate a page load to trigger a URL update.
1242   [static_cast<id<WKNavigationDelegate>>(webController_.get())
1243                   webView:mockWebView_
1244       didCommitNavigation:nil];
1246   web::URLVerificationTrustLevel trust_level = web::kNone;
1247   GURL gurl = [webController_ currentURLWithTrustLevel:&trust_level];
1249   EXPECT_EQ(gurl, GURL(base::SysNSStringToUTF8(kTestURLString)));
1250   EXPECT_EQ(web::kAbsolute, trust_level);
1253 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
1254 // is needed;
1255 typedef web::UIWebViewWebTest CRWUIWebControllerObserversTest;
1256 typedef web::WKWebViewWebTest CRWWKWebControllerObserversTest;
1258 // Tests that CRWWebControllerObservers are called.
1259 WEB_TEST_F(CRWUIWebControllerObserversTest,
1260            CRWWKWebControllerObserversTest,
1261            Observers) {
1262   base::scoped_nsobject<CountingObserver> observer(
1263       [[CountingObserver alloc] init]);
1264   CRWWebController* web_controller = this->webController_;
1265   EXPECT_EQ(0u, [web_controller observerCount]);
1266   [web_controller addObserver:observer];
1267   EXPECT_EQ(1u, [web_controller observerCount]);
1269   EXPECT_EQ(0, [observer pageLoadedCount]);
1270   [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), false);
1271   EXPECT_EQ(0, [observer pageLoadedCount]);
1272   [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), true);
1273   EXPECT_EQ(1, [observer pageLoadedCount]);
1275   EXPECT_EQ(0, [observer messageCount]);
1276   // Non-matching prefix.
1277   EXPECT_FALSE([web_controller webStateImpl]->OnScriptCommandReceived(
1278       "a", base::DictionaryValue(), GURL("http://test"), true));
1279   EXPECT_EQ(0, [observer messageCount]);
1280   // Matching prefix.
1281   EXPECT_TRUE([web_controller webStateImpl]->OnScriptCommandReceived(
1282       base::SysNSStringToUTF8([observer commandPrefix]) + ".foo",
1283       base::DictionaryValue(), GURL("http://test"), true));
1284   EXPECT_EQ(1, [observer messageCount]);
1286   [web_controller removeObserver:observer];
1287   EXPECT_EQ(0u, [web_controller observerCount]);
1290 // Test fixture for window.open tests.
1291 class CRWWKWebControllerWindowOpenTest : public web::WKWebViewWebTest {
1292  protected:
1293   void SetUp() override {
1294     CR_TEST_REQUIRES_WK_WEB_VIEW();
1295     WKWebViewWebTest::SetUp();
1297     // Configure web delegate.
1298     delegate_.reset([[MockInteractionLoader alloc]
1299         initWithRepresentedObject:
1300             [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)]]);
1301     ASSERT_TRUE([delegate_ blockPopups]);
1302     [webController_ setDelegate:delegate_];
1304     // Configure child web controller.
1305     child_.reset(CreateWebController());
1306     [child_ setWebUsageEnabled:YES];
1307     [delegate_ setChildWebController:child_];
1309     // Configure child web controller's session controller mock.
1310     id sessionController =
1311         [OCMockObject niceMockForClass:[CRWSessionController class]];
1312     BOOL yes = YES;
1313     [[[sessionController stub] andReturnValue:OCMOCK_VALUE(yes)] isOpenedByDOM];
1314     [child_ webStateImpl]->GetNavigationManagerImpl().SetSessionController(
1315         sessionController);
1317     LoadHtml(@"<html><body></body></html>");
1318   }
1319   void TearDown() override {
1320     EXPECT_OCMOCK_VERIFY(delegate_);
1321     [webController_ setDelegate:nil];
1322     [child_ close];
1324     WKWebViewWebTest::TearDown();
1325   }
1326   // Executes JavaScript that opens a new window and returns evaluation result
1327   // as a string.
1328   NSString* OpenWindowByDOM() {
1329     NSString* const kOpenWindowScript =
1330         @"var w = window.open('javascript:void(0);', target='_blank');"
1331          "w.toString();";
1332     NSString* windowJSObject = EvaluateJavaScriptAsString(kOpenWindowScript);
1333     WaitForBackgroundTasks();
1334     return windowJSObject;
1335   }
1336   // A CRWWebDelegate mock used for testing.
1337   base::scoped_nsobject<id> delegate_;
1338   // A child CRWWebController used for testing.
1339   base::scoped_nsobject<CRWWebController> child_;
1342 // Tests that absence of web delegate is handled gracefully.
1343 TEST_F(CRWWKWebControllerWindowOpenTest, NoDelegate) {
1344   CR_TEST_REQUIRES_WK_WEB_VIEW();
1346   [webController_ setDelegate:nil];
1348   EXPECT_NSEQ(@"", OpenWindowByDOM());
1350   EXPECT_FALSE([delegate_ blockedPopupInfo]);
1353 // Tests that window.open triggered by user gesture opens a new non-popup
1354 // window.
1355 TEST_F(CRWWKWebControllerWindowOpenTest, OpenWithUserGesture) {
1356   CR_TEST_REQUIRES_WK_WEB_VIEW();
1358   SEL selector = @selector(webPageOrderedOpenBlankWithReferrer:inBackground:);
1359   [delegate_ onSelector:selector
1360       callBlockExpectation:^(const web::Referrer& referrer,
1361                              BOOL in_background) {
1362         EXPECT_EQ("", referrer.url.spec());
1363         EXPECT_FALSE(in_background);
1364       }];
1366   [webController_ touched:YES];
1367   EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1368   EXPECT_FALSE([delegate_ blockedPopupInfo]);
1371 // Tests that window.open executed w/o user gesture does not open a new window.
1372 // Once the blocked popup is allowed a new window is opened.
1373 TEST_F(CRWWKWebControllerWindowOpenTest, AllowPopup) {
1374   CR_TEST_REQUIRES_WK_WEB_VIEW();
1376   SEL selector =
1377       @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
1378   [delegate_ onSelector:selector
1379       callBlockExpectation:^(const GURL& new_window_url,
1380                              const web::Referrer& referrer,
1381                              NSString* obsoleted_window_name,
1382                              BOOL in_background) {
1383         EXPECT_EQ("javascript:void(0);", new_window_url.spec());
1384         EXPECT_EQ("", referrer.url.spec());
1385         EXPECT_FALSE(in_background);
1386       }];
1388   ASSERT_FALSE([webController_ userIsInteracting]);
1389   EXPECT_NSEQ(@"", OpenWindowByDOM());
1390   base::test::ios::WaitUntilCondition(^bool() {
1391     return [delegate_ blockedPopupInfo];
1392   });
1394   if ([delegate_ blockedPopupInfo])
1395     [delegate_ blockedPopupInfo]->ShowPopup();
1397   EXPECT_EQ("", [delegate_ sourceURL].spec());
1398   EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1401 // Tests that window.open executed w/o user gesture opens a new window, assuming
1402 // that delegate allows popups.
1403 TEST_F(CRWWKWebControllerWindowOpenTest, DontBlockPopup) {
1404   CR_TEST_REQUIRES_WK_WEB_VIEW();
1406   [delegate_ setBlockPopups:NO];
1407   SEL selector = @selector(webPageOrderedOpenBlankWithReferrer:inBackground:);
1408   [delegate_ onSelector:selector
1409       callBlockExpectation:^(const web::Referrer& referrer,
1410                              BOOL in_background) {
1411         EXPECT_EQ("", referrer.url.spec());
1412         EXPECT_FALSE(in_background);
1413       }];
1415   EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1416   EXPECT_FALSE([delegate_ blockedPopupInfo]);
1418   EXPECT_EQ("", [delegate_ sourceURL].spec());
1419   EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1422 // Tests that window.open executed w/o user gesture does not open a new window.
1423 TEST_F(CRWWKWebControllerWindowOpenTest, BlockPopup) {
1424   CR_TEST_REQUIRES_WK_WEB_VIEW();
1426   ASSERT_FALSE([webController_ userIsInteracting]);
1427   EXPECT_NSEQ(@"", OpenWindowByDOM());
1428   base::test::ios::WaitUntilCondition(^bool() {
1429     return [delegate_ blockedPopupInfo];
1430   });
1432   EXPECT_EQ("", [delegate_ sourceURL].spec());
1433   EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1436 // Fixture class to test WKWebView crashes.
1437 class CRWWKWebControllerWebProcessTest : public web::WKWebViewWebTest {
1438  protected:
1439   void SetUp() override {
1440     CR_TEST_REQUIRES_WK_WEB_VIEW();
1441     WKWebViewWebTest::SetUp();
1442     webView_.reset(web::CreateTerminatedWKWebView());
1443     [webController_ injectWebView:webView_];
1444   }
1445   base::scoped_nsobject<WKWebView> webView_;
1448 // Tests that -[CRWWebDelegate webControllerWebProcessDidCrash:] is called
1449 // when WKWebView web process has crashed.
1450 TEST_F(CRWWKWebControllerWebProcessTest, Crash) {
1451   CR_TEST_REQUIRES_WK_WEB_VIEW();
1453   id delegate = [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
1454   [[delegate expect] webControllerWebProcessDidCrash:this->webController_];
1456   [this->webController_ setDelegate:delegate];
1457   web::SimulateWKWebViewCrash(webView_);
1459   EXPECT_OCMOCK_VERIFY(delegate);
1460   [this->webController_ setDelegate:nil];
1463 // Tests that WKWebView does not crash if deallocation happens during JavaScript
1464 // evaluation.
1465 typedef web::WKWebViewWebTest DeallocationDuringJSEvaluation;
1466 TEST_F(DeallocationDuringJSEvaluation, NoCrash) {
1467   CR_TEST_REQUIRES_WK_WEB_VIEW();
1469   this->LoadHtml(@"<html><body></body></html>");
1470   [webController_ evaluateJavaScript:@"null"
1471                  stringResultHandler:^(NSString* value, NSError*) {
1472                    // Block can not be empty in order to reproduce the crash:
1473                    // https://bugs.webkit.org/show_bug.cgi?id=140203
1474                    VLOG(1) << "Script has been flushed.";
1475                  }];
1476   // -evaluateJavaScript:stringResultHandler: is asynchronous so JavaScript
1477   // evaluation will not happen until TearDown, which deallocates
1478   // CRWWebController, which in its turn will deallocate WKWebView to create a
1479   // crashy condition.
1482 }  // namespace