Add ICU message format support
[chromium-blink-merge.git] / ios / web / web_state / ui / crw_web_controller_unittest.mm
blob70611969852e69939faa7d8ed463e7bd75d2521e
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/test_web_view_content_view.h"
22 #include "ios/web/public/test/web_test_util.h"
23 #import "ios/web/public/web_state/crw_web_controller_observer.h"
24 #import "ios/web/public/web_state/ui/crw_content_view.h"
25 #import "ios/web/public/web_state/ui/crw_web_view_content_view.h"
26 #include "ios/web/public/web_state/url_verification_constants.h"
27 #include "ios/web/test/web_test.h"
28 #import "ios/web/test/wk_web_view_crash_utils.h"
29 #include "ios/web/web_state/blocked_popup_info.h"
30 #import "ios/web/web_state/js/crw_js_invoke_parameter_queue.h"
31 #import "ios/web/web_state/ui/crw_ui_web_view_web_controller.h"
32 #import "ios/web/web_state/ui/crw_web_controller+protected.h"
33 #import "ios/web/web_state/ui/crw_web_controller_container_view.h"
34 #import "ios/web/web_state/web_state_impl.h"
35 #import "net/base/mac/url_conversions.h"
36 #include "net/ssl/ssl_info.h"
37 #include "testing/gtest/include/gtest/gtest.h"
38 #include "testing/gtest_mac.h"
39 #include "third_party/ocmock/OCMock/OCMock.h"
40 #include "third_party/ocmock/gtest_support.h"
41 #include "third_party/ocmock/ocmock_extensions.h"
42 #import "ui/base/test/ios/keyboard_appearance_listener.h"
43 #include "ui/base/test/ios/ui_view_test_utils.h"
45 using web::NavigationManagerImpl;
47 @interface TestWebController (PrivateTesting)
48 - (void)reloadInternal;
49 @end
51 @interface CRWWebController (PrivateAPI)
52 @property(nonatomic, readwrite) web::PageDisplayState pageDisplayState;
53 @property(nonatomic, readonly) CRWWebControllerContainerView* containerView;
54 - (void)setJsMessageQueueThrottled:(BOOL)throttle;
55 - (void)removeDocumentLoadCommandsFromQueue;
56 - (GURL)updateURLForHistoryNavigationFromURL:(const GURL&)startURL
57                                        toURL:(const GURL&)endURL;
58 - (BOOL)checkForUnexpectedURLChange;
59 - (void)injectEarlyInjectionScripts;
60 - (void)stopExpectingURLChangeIfNecessary;
61 @end
63 @implementation TestWebController (PrivateTesting)
65 - (void)reloadInternal {
66   // Empty implementation to prevent the need to mock out a huge number of
67   // calls.
70 @end
72 // Used to mock CRWWebDelegate methods with C++ params.
73 @interface MockInteractionLoader : OCMockComplexTypeHelper
74 // popupURL passed to webController:shouldBlockPopupWithURL:sourceURL:
75 // Used for testing.
76 @property(nonatomic, assign) GURL popupURL;
77 // sourceURL passed to webController:shouldBlockPopupWithURL:sourceURL:
78 // Used for testing.
79 @property(nonatomic, assign) GURL sourceURL;
80 // Whether or not the delegate should block popups.
81 @property(nonatomic, assign) BOOL blockPopups;
82 // A web controller that will be returned by webPageOrdered... methods.
83 @property(nonatomic, assign) CRWWebController* childWebController;
84 // Blocked popup info received in |webController:didBlockPopup:| call.
85 // nullptr if that delegate method was not called.
86 @property(nonatomic, readonly) web::BlockedPopupInfo* blockedPopupInfo;
87 // SSL info received in |presentSSLError:forSSLStatus:recoverable:callback:|
88 // call.
89 @property(nonatomic, readonly) net::SSLInfo SSLInfo;
90 // SSL status received in |presentSSLError:forSSLStatus:recoverable:callback:|
91 // call.
92 @property(nonatomic, readonly) web::SSLStatus SSLStatus;
93 // Recoverable flag received in
94 // |presentSSLError:forSSLStatus:recoverable:callback:| call.
95 @property(nonatomic, readonly) BOOL recoverable;
96 // Callback received in |presentSSLError:forSSLStatus:recoverable:callback:|
97 // call.
98 @property(nonatomic, readonly) SSLErrorCallback shouldContinueCallback;
99 @end
101 @implementation MockInteractionLoader {
102   // Backs up the property with the same name.
103   scoped_ptr<web::BlockedPopupInfo> _blockedPopupInfo;
105 @synthesize popupURL = _popupURL;
106 @synthesize sourceURL = _sourceURL;
107 @synthesize blockPopups = _blockPopups;
108 @synthesize childWebController = _childWebController;
109 @synthesize SSLInfo = _SSLInfo;
110 @synthesize SSLStatus = _SSLStatus;
111 @synthesize recoverable = _recoverable;
112 @synthesize shouldContinueCallback = _shouldContinueCallback;
114 typedef void (^webPageOrderedOpenBlankBlockType)(const web::Referrer&, BOOL);
115 typedef void (^webPageOrderedOpenBlockType)(const GURL&,
116                                             const web::Referrer&,
117                                             NSString*,
118                                             BOOL);
120 - (instancetype)initWithRepresentedObject:(id)representedObject {
121   self = [super initWithRepresentedObject:representedObject];
122   if (self) {
123     _blockPopups = YES;
124   }
125   return self;
128 - (CRWWebController*)webPageOrderedOpenBlankWithReferrer:
129     (const web::Referrer&)referrer
130                                             inBackground:(BOOL)inBackground {
131   static_cast<webPageOrderedOpenBlankBlockType>([self blockForSelector:_cmd])(
132       referrer, inBackground);
133   return _childWebController;
136 - (CRWWebController*)webPageOrderedOpen:(const GURL&)url
137                                referrer:(const web::Referrer&)referrer
138                              windowName:(NSString*)windowName
139                            inBackground:(BOOL)inBackground {
140   static_cast<webPageOrderedOpenBlockType>([self blockForSelector:_cmd])(
141       url, referrer, windowName, inBackground);
142   return _childWebController;
145 typedef BOOL (^openExternalURLBlockType)(const GURL&);
147 - (BOOL)openExternalURL:(const GURL&)url {
148   return static_cast<openExternalURLBlockType>([self blockForSelector:_cmd])(
149       url);
152 - (BOOL)webController:(CRWWebController*)webController
153     shouldBlockPopupWithURL:(const GURL&)popupURL
154                   sourceURL:(const GURL&)sourceURL {
155   self.popupURL = popupURL;
156   self.sourceURL = sourceURL;
157   return _blockPopups;
160 - (void)webController:(CRWWebController*)webController
161         didBlockPopup:(const web::BlockedPopupInfo&)blockedPopupInfo {
162   _blockedPopupInfo.reset(new web::BlockedPopupInfo(blockedPopupInfo));
165 - (web::BlockedPopupInfo*)blockedPopupInfo {
166   return _blockedPopupInfo.get();
169 - (void)presentSSLError:(const net::SSLInfo&)info
170            forSSLStatus:(const web::SSLStatus&)status
171             recoverable:(BOOL)recoverable
172                callback:(SSLErrorCallback)shouldContinue {
173   _SSLInfo = info;
174   _SSLStatus = status;
175   _recoverable = recoverable;
176   _shouldContinueCallback = shouldContinue;
179 @end
181 @interface CountingObserver : NSObject<CRWWebControllerObserver>
183 @property(nonatomic, readonly) int pageLoadedCount;
184 @property(nonatomic, readonly) int messageCount;
185 @end
187 @implementation CountingObserver
188 @synthesize pageLoadedCount = _pageLoadedCount;
189 @synthesize messageCount = _messageCount;
191 - (void)pageLoaded:(CRWWebController*)webController {
192   ++_pageLoadedCount;
195 - (BOOL)handleCommand:(const base::DictionaryValue&)command
196         webController:(CRWWebController*)webController
197     userIsInteracting:(BOOL)userIsInteracting
198             originURL:(const GURL&)originURL {
199   ++_messageCount;
200   return YES;
203 - (NSString*)commandPrefix {
204   return @"wctest";
207 @end
209 namespace {
211 NSString* const kGetMessageQueueJavaScript =
212     @"window.__gCrWeb === undefined ? '' : __gCrWeb.message.getMessageQueue()";
214 NSString* kCheckURLJavaScript =
215     @"try{"
216      "window.__gCrWeb_Verifying = true;"
217      "if(!window.__gCrWeb_CachedRequest||"
218      "!(window.__gCrWeb_CachedRequestDocument===window.document)){"
219      "window.__gCrWeb_CachedRequest = new XMLHttpRequest();"
220      "window.__gCrWeb_CachedRequestDocument = window.document;"
221      "}"
222      "window.__gCrWeb_CachedRequest.open('POST',"
223      "'https://localhost:0/crwebiossecurity',false);"
224      "window.__gCrWeb_CachedRequest.send();"
225      "}catch(e){"
226      "try{"
227      "window.__gCrWeb_CachedRequest.open('POST',"
228      "'/crwebiossecurity/b86b97a1-2ce0-44fd-a074-e2158790c98d',false);"
229      "window.__gCrWeb_CachedRequest.send();"
230      "}catch(e2){}"
231      "}"
232      "delete window.__gCrWeb_Verifying;"
233      "window.location.href";
235 NSString* kTestURLString = @"http://www.google.com/";
237 NSMutableURLRequest* requestForCrWebInvokeCommandAndKey(NSString* command,
238                                                         NSString* key) {
239   NSString* fullCommand =
240       [NSString stringWithFormat:@"[{\"command\":\"%@\"}]", command];
241   NSString* escapedCommand = [fullCommand
242       stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
243   NSString* urlString =
244       [NSString stringWithFormat:@"crwebinvoke://%@/#%@", key, escapedCommand];
245   return [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
248 // Returns true if the current device is a large iPhone (6 or 6+).
249 bool IsIPhone6Or6Plus() {
250   UIUserInterfaceIdiom idiom = [[UIDevice currentDevice] userInterfaceIdiom];
251   return (idiom == UIUserInterfaceIdiomPhone &&
252           CGRectGetHeight([[UIScreen mainScreen] nativeBounds]) >= 1334.0);
255 // Returns HTML for an optionally zoomable test page with |zoom_state|.
256 enum PageScalabilityType {
257   PAGE_SCALABILITY_DISABLED = 0,
258   PAGE_SCALABILITY_ENABLED,
260 NSString* GetHTMLForZoomState(const web::PageZoomState& zoom_state,
261                               PageScalabilityType scalability_type) {
262   NSString* const kHTMLFormat =
263       @"<html><head><meta name='viewport' content="
264        "'width=%f,maximum-scale=%f,initial-scale=%f,"
265        "user-scalable=%@'/></head><body>Test</body></html>";
266   CGFloat width = CGRectGetWidth([UIScreen mainScreen].bounds) /
267       zoom_state.minimum_zoom_scale();
268   BOOL scalability_enabled = scalability_type == PAGE_SCALABILITY_ENABLED;
269   return [NSString stringWithFormat:kHTMLFormat, width,
270                                     zoom_state.maximum_zoom_scale(),
271                                     zoom_state.zoom_scale(),
272                                     scalability_enabled ? @"yes" : @"no"];
275 // Forces |webController|'s view to render and waits until |webController|'s
276 // PageZoomState matches |zoom_state|.
277 void WaitForZoomRendering(CRWWebController* webController,
278                           const web::PageZoomState& zoom_state) {
279   ui::test::uiview_utils::ForceViewRendering(webController.view);
280   base::test::ios::WaitUntilCondition(^bool() {
281     return webController.pageDisplayState.zoom_state() == zoom_state;
282   });
285 // A mixin class for testing CRWWKWebViewWebController or
286 // CRWUIWebViewWebController. Stubs out WebView and child CRWWebController.
287 template <typename WebTestT>
288 class WebControllerTest : public WebTestT {
289  protected:
290   virtual void SetUp() override {
291     WebTestT::SetUp();
292     mockWebView_.reset(CreateMockWebView());
293     mockScrollView_.reset([[UIScrollView alloc] init]);
294     [[[mockWebView_ stub] andReturn:mockScrollView_.get()] scrollView];
296     id originalMockDelegate =
297         [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
298     mockDelegate_.reset([[MockInteractionLoader alloc]
299         initWithRepresentedObject:originalMockDelegate]);
300     [WebTestT::webController_ setDelegate:mockDelegate_];
301     base::scoped_nsobject<TestWebViewContentView> webViewContentView(
302         [[TestWebViewContentView alloc] initWithMockWebView:mockWebView_
303                                                  scrollView:mockScrollView_]);
304     [WebTestT::webController_ injectWebViewContentView:webViewContentView];
306     NavigationManagerImpl& navigationManager =
307         [WebTestT::webController_ webStateImpl]->GetNavigationManagerImpl();
308     navigationManager.InitializeSession(@"name", nil, NO, 0);
309     [navigationManager.GetSessionController()
310           addPendingEntry:GURL("http://www.google.com/?q=foo#bar")
311                  referrer:web::Referrer()
312                transition:ui::PAGE_TRANSITION_TYPED
313         rendererInitiated:NO];
314     // Set up child CRWWebController.
315     mockChildWebController_.reset([[OCMockObject
316         mockForProtocol:@protocol(CRWWebControllerScripting)] retain]);
317     [[[mockDelegate_ stub] andReturn:mockChildWebController_.get()]
318                            webController:WebTestT::webController_
319         scriptingInterfaceForWindowNamed:@"http://www.google.com/#newtab"];
320   }
322   virtual void TearDown() override {
323     EXPECT_OCMOCK_VERIFY(mockDelegate_);
324     EXPECT_OCMOCK_VERIFY(mockChildWebController_.get());
325     EXPECT_OCMOCK_VERIFY(mockWebView_);
326     [WebTestT::webController_ resetInjectedWebViewContentView];
327     [WebTestT::webController_ setDelegate:nil];
328     WebTestT::TearDown();
329   }
331   // Creates WebView mock.
332   virtual UIView* CreateMockWebView() const = 0;
334   // Simulates a load request delegate call from the web view.
335   virtual void SimulateLoadRequest(NSURLRequest* request) const = 0;
337   base::scoped_nsobject<UIScrollView> mockScrollView_;
338   base::scoped_nsobject<id> mockWebView_;
339   base::scoped_nsobject<id> mockDelegate_;
340   base::scoped_nsobject<id> mockChildWebController_;
343 class CRWUIWebViewWebControllerTest
344     : public WebControllerTest<web::WebTestWithUIWebViewWebController> {
345  protected:
346   UIView* CreateMockWebView() const override {
347     id result = [[OCMockObject mockForClass:[UIWebView class]] retain];
348     [[[result stub] andReturn:nil] request];
349     [[result stub] setDelegate:OCMOCK_ANY];  // Called by resetInjectedWebView
350     // Stub out the injection process.
351     [[[result stub] andReturn:@"object"]
352         stringByEvaluatingJavaScriptFromString:
353             @"try { typeof __gCrWeb; } catch (e) { 'undefined'; }"];
354     [[[result stub] andReturn:@"object"]
355         stringByEvaluatingJavaScriptFromString:@"try { typeof "
356                                                @"__gCrWeb.windowIdObject; } "
357                                                @"catch (e) { 'undefined'; }"];
358     return result;
359   }
360   void SimulateLoadRequest(NSURLRequest* request) const override {
361     id<UIWebViewDelegate> delegate =
362         static_cast<id<UIWebViewDelegate>>(webController_.get());
363     [delegate webView:(UIWebView*)mockWebView_
364         shouldStartLoadWithRequest:request
365                     navigationType:UIWebViewNavigationTypeLinkClicked];
366   }
369 class CRWWKWebViewWebControllerTest
370     : public WebControllerTest<web::WebTestWithWKWebViewWebController> {
371  protected:
372   void SetUp() override {
373     CR_TEST_REQUIRES_WK_WEB_VIEW();
374     WebControllerTest<web::WebTestWithWKWebViewWebController>::SetUp();
375   }
376   UIView* CreateMockWebView() const override {
377     id result = [[OCMockObject mockForClass:[WKWebView class]] retain];
379     // Called by resetInjectedWebView
380     [[result stub] configuration];
381     [[result stub] setNavigationDelegate:OCMOCK_ANY];
382     [[result stub] setUIDelegate:OCMOCK_ANY];
383     [[result stub] addObserver:webController_
384                     forKeyPath:OCMOCK_ANY
385                        options:0
386                        context:nullptr];
387     [[result stub] addObserver:OCMOCK_ANY
388                     forKeyPath:@"scrollView.backgroundColor"
389                        options:0
390                        context:nullptr];
392     [[result stub] removeObserver:webController_ forKeyPath:OCMOCK_ANY];
393     [[result stub] removeObserver:OCMOCK_ANY
394                        forKeyPath:@"scrollView.backgroundColor"];
396     return result;
397   }
398   void SimulateLoadRequest(NSURLRequest* request) const override {
399     // TODO(eugenebut): implement this method.
400   }
403 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithIncorrectKey) {
404   NSURLRequest* request = [NSURLRequest
405       requestWithURL:[NSURL URLWithString:@"crwebinvoke:invalidkey#commands"]];
407   SimulateLoadRequest(request);
410 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithLargeQueue) {
411   // Pre-define test window id.
412   [webController_ setWindowId:@"12345678901234567890123456789012"];
413   NSString* valid_key = [webController_ windowId];
414   [[[mockWebView_ stub] andReturn:kTestURLString]
415       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
417   // Install an observer to handle custom command messages.
418   base::scoped_nsobject<CountingObserver> observer(
419       [[CountingObserver alloc] init]);
420   [webController_ addObserver:observer];
422   // Queue a lot of messages.
423   [webController_ setJsMessageQueueThrottled:YES];
424   const int kNumQueuedMessages = 1000;
425   for (int i = 0; i < kNumQueuedMessages; ++i) {
426     NSMutableURLRequest* request =
427         requestForCrWebInvokeCommandAndKey(@"wctest.largequeue", valid_key);
428     [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
429     SimulateLoadRequest(request);
430   }
432   // Verify the queue still works and all messages are delivered.
433   [webController_ setJsMessageQueueThrottled:NO];
434   EXPECT_EQ(kNumQueuedMessages, [observer messageCount]);
436   [webController_ removeObserver:observer];
439 TEST_F(CRWUIWebViewWebControllerTest,
440        CrWebInvokeWithAllMessagesFromCurrentWindow) {
441   // Pre-define test window id.
442   [webController_ setWindowId:@"12345678901234567890123456789012"];
443   NSString* valid_key = [webController_ windowId];
444   [[[mockWebView_ stub] andReturn:kTestURLString]
445       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
447   // Install an observer to handle custom command messages.
448   base::scoped_nsobject<CountingObserver> observer(
449       [[CountingObserver alloc] init]);
450   [webController_ addObserver:observer];
452   // Queue messages.
453   [webController_ setJsMessageQueueThrottled:YES];
454   NSMutableURLRequest* request =
455       requestForCrWebInvokeCommandAndKey(@"wctest.currentwindow1", valid_key);
456   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
457   SimulateLoadRequest(request);
458   request =
459       requestForCrWebInvokeCommandAndKey(@"wctest.currentwindow2", valid_key);
460   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
461   SimulateLoadRequest(request);
463   // Verify the behavior.
464   [webController_ setJsMessageQueueThrottled:NO];
465   EXPECT_EQ(2, [observer messageCount]);
467   [webController_ removeObserver:observer];
470 TEST_F(CRWUIWebViewWebControllerTest,
471        CrWebInvokeWithMessagesFromDifferentWindows) {
472   // Pre-define test window id.
473   [webController_ setWindowId:@"DEADBEEFDEADBEEFDEADBEEFDEADBEEF"];
474   NSString* valid_key = [webController_ windowId];
475   [[[mockWebView_ stub] andReturn:kTestURLString]
476       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
478   // Install an observer to handle custom command messages.
479   base::scoped_nsobject<CountingObserver> observer(
480       [[CountingObserver alloc] init]);
481   [webController_ addObserver:observer];
483   // Queue messages.
484   [webController_ setJsMessageQueueThrottled:YES];
485   NSMutableURLRequest* request =
486       requestForCrWebInvokeCommandAndKey(@"wctest.diffwindow1", valid_key);
487   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
488   SimulateLoadRequest(request);
490   // Second queued message will come with a new window id.
491   [webController_ setWindowId:@"12345678901234567890123456789012"];
492   valid_key = [webController_ windowId];
493   request =
494       requestForCrWebInvokeCommandAndKey(@"wctest.diffwindow2", valid_key);
495   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
496   SimulateLoadRequest(request);
498   [webController_ setJsMessageQueueThrottled:NO];
499   EXPECT_EQ(1, [observer messageCount]);
501   [webController_ removeObserver:observer];
504 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithSameOrigin) {
505   // Pre-define test window id.
506   [webController_ setWindowId:@"12345678901234567890123456789012"];
507   NSString* valid_key = [webController_ windowId];
508   [[[mockWebView_ stub] andReturn:@"http://www.google.com/foo"]
509       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
511   // Install an observer to handle custom command messages.
512   base::scoped_nsobject<CountingObserver> observer(
513       [[CountingObserver alloc] init]);
514   [webController_ addObserver:observer];
516   // Queue message.
517   [webController_ setJsMessageQueueThrottled:YES];
518   NSMutableURLRequest* request =
519       requestForCrWebInvokeCommandAndKey(@"wctest.sameorigin", valid_key);
520   // Make originURL different from currentURL but keep the origin the same.
521   [request
522       setMainDocumentURL:[NSURL URLWithString:@"http://www.google.com/bar"]];
523   SimulateLoadRequest(request);
524   // Verify the behavior.
525   [webController_ setJsMessageQueueThrottled:NO];
526   EXPECT_EQ(1, [observer messageCount]);
528   [webController_ removeObserver:observer];
531 TEST_F(CRWUIWebViewWebControllerTest, CrWebInvokeWithDifferentOrigin) {
532   // Pre-define test window id.
533   [webController_ setWindowId:@"12345678901234567890123456789012"];
534   NSString* valid_key = [webController_ windowId];
535   [[[mockWebView_ stub] andReturn:@"http://www.google.com/"]
536       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
538   // Install an observer to handle custom command messages.
539   base::scoped_nsobject<CountingObserver> observer(
540       [[CountingObserver alloc] init]);
541   [webController_ addObserver:observer];
543   // Queue message.
544   [webController_ setJsMessageQueueThrottled:YES];
545   NSMutableURLRequest* request =
546       requestForCrWebInvokeCommandAndKey(@"wctest.difforigin", valid_key);
547   // Make originURL have a different scheme from currentURL so that the origin
548   // is different.
549   [request setMainDocumentURL:[NSURL URLWithString:@"https://www.google.com/"]];
550   SimulateLoadRequest(request);
551   // Verify the behavior.
552   [webController_ setJsMessageQueueThrottled:NO];
553   EXPECT_EQ(0, [observer messageCount]);
555   [webController_ removeObserver:observer];
558 TEST_F(CRWUIWebViewWebControllerTest, EmptyMessageQueue) {
559   [[[mockWebView_ stub] andReturn:@"[]"]
560       stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
562   NSURLRequest* request =
563       [NSURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
565   SimulateLoadRequest(request);
568 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenBlankURL) {
569   NSString* messageQueueJSON = @"[{"
570                                 "\"command\" : \"window.open\", "
571                                 "\"target\" : \"newtab\", "
572                                 "\"url\" : \"\", "
573                                 "\"referrerPolicy\" : \"default\" }]";
575   SEL selector =
576       @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
577   [mockDelegate_ onSelector:selector
578        callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
579                               NSString* windowName, BOOL inBackground) {
580          EXPECT_EQ(url, GURL("about:blank"));
581          EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
582          EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
583          EXPECT_FALSE(inBackground);
584        }];
585   [[[mockWebView_ stub] andReturn:messageQueueJSON]
586       stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
587   [[[mockWebView_ stub] andReturn:kTestURLString]
588       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
590   NSMutableURLRequest* request =
591       [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
592   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
593   [webController_ touched:YES];
594   SimulateLoadRequest(request);
596   LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
597                YES);
600 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithInteraction) {
601   NSString* messageQueueJSON = @"[{"
602                                 "\"command\" : \"window.open\", "
603                                 "\"target\" : \"newtab\", "
604                                 "\"url\" : \"http://chromium.org\", "
605                                 "\"referrerPolicy\" : \"default\" }]";
607   SEL selector =
608       @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
609   [mockDelegate_ onSelector:selector
610        callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
611                               NSString* windowName, BOOL inBackground) {
612          EXPECT_EQ(url, GURL("http://chromium.org"));
613          EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
614          EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
615          EXPECT_FALSE(inBackground);
616        }];
617   [[[mockWebView_ stub] andReturn:messageQueueJSON]
618       stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
619   [[[mockWebView_ stub] andReturn:kTestURLString]
620       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
622   NSMutableURLRequest* request =
623       [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
624   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
625   [webController_ touched:YES];
626   SimulateLoadRequest(request);
628   LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
629                YES);
632 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithFinishingInteraction) {
633   NSString* messageQueueJSON = @"[{"
634                                 "\"command\" : \"window.open\", "
635                                 "\"target\" : \"newtab\", "
636                                 "\"url\" : \"http://chromium.org\", "
637                                 "\"referrerPolicy\" : \"default\" }]";
639   SEL selector =
640       @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
641   [mockDelegate_ onSelector:selector
642        callBlockExpectation:^(const GURL& url, const web::Referrer& referrer,
643                               NSString* windowName, BOOL inBackground) {
644          EXPECT_EQ(url, GURL("http://chromium.org"));
645          EXPECT_EQ(referrer.url, GURL("http://www.google.com?q=foo#bar"));
646          EXPECT_NSEQ(windowName, @"http://www.google.com/#newtab");
647          EXPECT_FALSE(inBackground);
648        }];
649   [[[mockWebView_ stub] andReturn:kTestURLString]
650       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
652   NSMutableURLRequest* request =
653       [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
654   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
655   [webController_ touched:YES];
656   [webController_ touched:NO];
657   SimulateLoadRequest(request);
659   LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
660                YES);
663 TEST_F(CRWUIWebViewWebControllerTest, WindowOpenWithoutInteraction) {
664   NSString* messageQueueJSON = @"[{"
665                                 "\"command\" : \"window.open\", "
666                                 "\"target\" : \"newtab\", "
667                                 "\"url\" : \"http://chromium.org\", "
668                                 "\"referrerPolicy\" : \"default\" }]";
669   [[[mockWebView_ stub] andReturn:kTestURLString]
670       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
672   NSMutableURLRequest* request =
673       [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
674   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
675   SimulateLoadRequest(request);
677   LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
678                NO);
680   MockInteractionLoader* delegate = (MockInteractionLoader*)mockDelegate_;
681   EXPECT_EQ("http://www.google.com/?q=foo#bar", [delegate sourceURL].spec());
682   EXPECT_EQ("http://chromium.org/", [delegate popupURL].spec());
684   EXPECT_TRUE([delegate blockedPopupInfo]);
687 TEST_F(CRWUIWebViewWebControllerTest, WindowClose) {
688   NSString* messageQueueJSON = @"[{"
689                                 "\"command\" : \"window.close\", "
690                                 "\"target\" : \"newtab\" }]";
692   [[mockChildWebController_ expect] orderClose];
694   NSMutableURLRequest* request =
695       [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
696   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
697   [[[mockWebView_ stub] andReturn:kTestURLString]
698       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
700   SimulateLoadRequest(request);
702   LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
703                YES);
706 TEST_F(CRWUIWebViewWebControllerTest, WindowStop) {
707   NSString* messageQueueJSON = @"[{"
708                                 "\"command\" : \"window.stop\", "
709                                 "\"target\" : \"newtab\" }]";
711   [[mockChildWebController_ expect] stopLoading];
713   NSMutableURLRequest* request =
714       [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
715   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
716   [[[mockWebView_ stub] andReturn:kTestURLString]
717       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
719   SimulateLoadRequest(request);
721   LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
722                YES);
725 TEST_F(CRWUIWebViewWebControllerTest, DocumentWrite) {
726   NSString* messageQueueJSON = @"[{"
727                                 "\"command\" : \"window.document.write\", "
728                                 "\"target\" : \"newtab\", "
729                                 "\"html\" : \"<html></html>\" }]";
731   [[mockChildWebController_ expect] loadHTML:@"<html></html>"];
732   [[[mockWebView_ stub] andReturn:kTestURLString]
733       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
735   NSMutableURLRequest* request =
736       [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome:"]];
737   [request setMainDocumentURL:[NSURL URLWithString:kTestURLString]];
738   SimulateLoadRequest(request);
740   LoadCommands(messageQueueJSON, net::GURLWithNSURL(request.mainDocumentURL),
741                YES);
744 TEST_F(CRWUIWebViewWebControllerTest, UnicodeEncoding) {
745   base::scoped_nsobject<UIWebView> testWebView(
746       [[UIWebView alloc] initWithFrame:CGRectZero]);
747   NSArray* unicodeArray = @[
748     @"ascii",
749     @"unicode £´∑∂∆˚√˜ß∂",
750     @"˜ǯ˜Â‚·´ÎÔ´„ÅÒ",
751     @"adª™£nÎÍlansdn",
752     @"אבדלמצש",
753     @"صسخبئغفىي",
754     @"ऒतॲहड़६ॼ",
755   ];
756   for (NSString* unicodeString in unicodeArray) {
757     NSString* encodeJS =
758         [NSString stringWithFormat:@"encodeURIComponent('%@');", unicodeString];
759     NSString* encodedString =
760         [testWebView stringByEvaluatingJavaScriptFromString:encodeJS];
761     NSString* decodedString = [encodedString
762         stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
763     EXPECT_NSEQ(unicodeString, decodedString);
764   }
767 // Tests the removal of document.loaded and document.present commands in the
768 // CRWJSInvokeParameterQueue. Only applicable for UIWebView.
769 TEST_F(CRWUIWebViewWebControllerTest, PageLoadCommandRemoval) {
770   [[[mockWebView_ stub] andReturn:@"[]"]
771       stringByEvaluatingJavaScriptFromString:kGetMessageQueueJavaScript];
772   [[[mockWebView_ stub] andReturn:kTestURLString]
773       stringByEvaluatingJavaScriptFromString:kCheckURLJavaScript];
775   // Pre-define test window id.
776   [webController_ setWindowId:@"12345678901234567890123456789012"];
777   NSString* valid_key = [webController_ windowId];
779   // Add some commands to the queue.
780   [webController_ setJsMessageQueueThrottled:YES];
781   NSURLRequest* request =
782       requestForCrWebInvokeCommandAndKey(@"document.present", valid_key);
783   SimulateLoadRequest(request);
784   request = requestForCrWebInvokeCommandAndKey(@"document.loaded", valid_key);
785   SimulateLoadRequest(request);
786   request =
787       requestForCrWebInvokeCommandAndKey(@"window.history.forward", valid_key);
788   SimulateLoadRequest(request);
790   // Check the queue size before and after removing document load commands.
791   NSUInteger expectedBeforeCount = 3;
792   NSUInteger expectedAfterCount = 1;
793   ASSERT_EQ(expectedBeforeCount,
794             [[static_cast<CRWUIWebViewWebController*>(webController_)
795                     jsInvokeParameterQueue] queueLength]);
796   [webController_ removeDocumentLoadCommandsFromQueue];
797   ASSERT_EQ(expectedAfterCount,
798             [[static_cast<CRWUIWebViewWebController*>(webController_)
799                     jsInvokeParameterQueue] queueLength]);
800   [webController_ setJsMessageQueueThrottled:NO];
803 #define MAKE_URL(url_string) GURL([url_string UTF8String])
805 WEB_TEST_F(CRWUIWebViewWebControllerTest,
806            CRWWKWebViewWebControllerTest,
807            URLForHistoryNavigation) {
808   NSArray* urlsNoFragments = @[
809     @"http://one.com",
810     @"http://two.com/",
811     @"http://three.com/bar",
812     @"http://four.com/bar/",
813     @"five",
814     @"/six",
815     @"/seven/",
816     @""
817   ];
819   NSArray* fragments = @[ @"#", @"#bar" ];
820   NSMutableArray* urlsWithFragments = [NSMutableArray array];
821   for (NSString* url in urlsNoFragments) {
822     for (NSString* fragment in fragments) {
823       [urlsWithFragments addObject:[url stringByAppendingString:fragment]];
824     }
825   }
827   // No start fragment: the end url is never changed.
828   for (NSString* start in urlsNoFragments) {
829     for (NSString* end in urlsWithFragments) {
830       EXPECT_EQ(MAKE_URL(end),
831                 [this->webController_
832                     updateURLForHistoryNavigationFromURL:MAKE_URL(start)
833                                                    toURL:MAKE_URL(end)]);
834     }
835   }
836   // Both contain fragments: the end url is never changed.
837   for (NSString* start in urlsWithFragments) {
838     for (NSString* end in urlsWithFragments) {
839       EXPECT_EQ(MAKE_URL(end),
840                 [this->webController_
841                     updateURLForHistoryNavigationFromURL:MAKE_URL(start)
842                                                    toURL:MAKE_URL(end)]);
843     }
844   }
845   for (unsigned start_index = 0; start_index < [urlsWithFragments count];
846        ++start_index) {
847     NSString* start = urlsWithFragments[start_index];
848     for (unsigned end_index = 0; end_index < [urlsNoFragments count];
849          ++end_index) {
850       NSString* end = urlsNoFragments[end_index];
851       if (start_index / 2 != end_index) {
852         // The URLs have nothing in common, they are left untouched.
853         EXPECT_EQ(MAKE_URL(end),
854                   [this->webController_
855                       updateURLForHistoryNavigationFromURL:MAKE_URL(start)
856                                                      toURL:MAKE_URL(end)]);
857       } else {
858         // Start contains a fragment and matches end: An empty fragment is
859         // added.
860         EXPECT_EQ(MAKE_URL([end stringByAppendingString:@"#"]),
861                   [this->webController_
862                       updateURLForHistoryNavigationFromURL:MAKE_URL(start)
863                                                      toURL:MAKE_URL(end)]);
864       }
865     }
866   }
869 // This test requires iOS net stack.
870 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
871 // Tests that presentSSLError:forSSLStatus:recoverable:callback: is called with
872 // correct arguments if WKWebView fails to load a page with bad SSL cert.
873 TEST_F(CRWWKWebViewWebControllerTest, SSLError) {
874   CR_TEST_REQUIRES_WK_WEB_VIEW();
876   ASSERT_FALSE([mockDelegate_ SSLInfo].is_valid());
878   NSError* error =
879       [NSError errorWithDomain:NSURLErrorDomain
880                           code:NSURLErrorServerCertificateHasUnknownRoot
881                       userInfo:nil];
882   WKWebView* webView = static_cast<WKWebView*>([webController_ webView]);
883   [static_cast<id<WKNavigationDelegate>>(webController_.get()) webView:webView
884                                           didFailProvisionalNavigation:nil
885                                                              withError:error];
887   // Verify correctness of delegate's method arguments.
888   EXPECT_TRUE([mockDelegate_ SSLInfo].is_valid());
889   EXPECT_EQ(net::CERT_STATUS_INVALID, [mockDelegate_ SSLInfo].cert_status);
890   EXPECT_EQ(net::CERT_STATUS_INVALID, [mockDelegate_ SSLStatus].cert_status);
891   EXPECT_EQ(web::SECURITY_STYLE_AUTHENTICATION_BROKEN,
892             [mockDelegate_ SSLStatus].security_style);
893   EXPECT_FALSE([mockDelegate_ recoverable]);
894   EXPECT_FALSE([mockDelegate_ shouldContinueCallback]);
896 #endif  // !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
898 // None of the |CRWUIWebViewWebControllerTest| setup is needed;
899 typedef web::WebTestWithUIWebViewWebController
900     CRWUIWebControllerPageDialogsOpenPolicyTest;
902 // None of the |CRWWKWebViewWebControllerTest| setup is needed;
903 typedef web::WebTestWithWKWebViewWebController
904     CRWWKWebControllerPageDialogsOpenPolicyTest;
906 WEB_TEST_F(CRWUIWebControllerPageDialogsOpenPolicyTest,
907            CRWWKWebControllerPageDialogsOpenPolicyTest,
908            SuppressPolicy) {
909   this->LoadHtml(@"<html><body></body></html>");
910   id delegate = [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
911   [[delegate expect] webControllerDidSuppressDialog:this->webController_];
913   [this->webController_ setDelegate:delegate];
914   [this->webController_ setPageDialogOpenPolicy:web::DIALOG_POLICY_SUPPRESS];
915   this->RunJavaScript(@"alert('')");
917   this->WaitForBackgroundTasks();
918   EXPECT_OCMOCK_VERIFY(delegate);
919   [this->webController_ setDelegate:nil];
922 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
923 // is needed;
924 class CRWUIWebControllerPageScrollStateTest
925     : public web::WebTestWithUIWebViewWebController {
926  protected:
927   // Returns a web::PageDisplayState that will scroll a UIWebView to
928   // |scrollOffset| and zoom the content by |relativeZoomScale|.
929   inline web::PageDisplayState CreateTestPageDisplayState(
930       CGPoint scroll_offset,
931       CGFloat relative_zoom_scale,
932       CGFloat original_minimum_zoom_scale,
933       CGFloat original_maximum_zoom_scale,
934       CGFloat original_zoom_scale) const {
935     return web::PageDisplayState(
936         scroll_offset.x, scroll_offset.y,
937         original_minimum_zoom_scale / relative_zoom_scale,
938         original_maximum_zoom_scale / relative_zoom_scale, 1.0);
939   }
942 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
943 // is needed;
944 class CRWWKWebControllerPageScrollStateTest
945     : public web::WebTestWithWKWebViewWebController {
946  protected:
947   // Returns a web::PageDisplayState that will scroll a WKWebView to
948   // |scrollOffset| and zoom the content by |relativeZoomScale|.
949   inline web::PageDisplayState CreateTestPageDisplayState(
950       CGPoint scroll_offset,
951       CGFloat relative_zoom_scale,
952       CGFloat original_minimum_zoom_scale,
953       CGFloat original_maximum_zoom_scale,
954       CGFloat original_zoom_scale) const {
955     return web::PageDisplayState(
956         scroll_offset.x, scroll_offset.y, original_minimum_zoom_scale,
957         original_maximum_zoom_scale,
958         relative_zoom_scale * original_minimum_zoom_scale);
959   }
962 // TODO(iOS): Flaky on the bots. crbug/493427
963 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
964            CRWWKWebControllerPageScrollStateTest,
965            FLAKY_SetPageDisplayStateWithUserScalableDisabled) {
966 #if !TARGET_IPHONE_SIMULATOR
967   // This test fails flakily on device with WKWebView, so skip it there.
968   // crbug.com/453530
969   if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE)
970     return;
971 #endif
972   web::PageZoomState zoom_state(1.0, 5.0, 1.0);
973   this->LoadHtml(GetHTMLForZoomState(zoom_state, PAGE_SCALABILITY_DISABLED));
974   CRWWebController* web_controller = this->webController_.get();
975   WaitForZoomRendering(web_controller, zoom_state);
976   web::PageZoomState original_zoom_state =
977       web_controller.pageDisplayState.zoom_state();
979   web::NavigationManager* nagivation_manager =
980       web_controller.webState->GetNavigationManager();
981   nagivation_manager->GetLastCommittedItem()->SetPageDisplayState(
982       this->CreateTestPageDisplayState(CGPointMake(1.0, 1.0),  // scroll offset
983                                        3.0,    // relative zoom scale
984                                        1.0,    // original minimum zoom scale
985                                        5.0,    // original maximum zoom scale
986                                        1.0));  // original zoom scale
987   [web_controller restoreStateFromHistory];
989   // |-restoreStateFromHistory| is async; wait for its completion.
990   base::test::ios::WaitUntilCondition(^bool() {
991     return web_controller.pageDisplayState.scroll_state().offset_x() == 1.0;
992   });
994   ASSERT_EQ(original_zoom_state, web_controller.pageDisplayState.zoom_state());
997 // TODO(iOS): Flaky on the bots. crbug/493427
998 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
999            CRWWKWebControllerPageScrollStateTest,
1000            FLAKY_SetPageDisplayStateWithUserScalableEnabled) {
1001   web::PageZoomState zoom_state(1.0, 10.0, 1.0);
1002   this->LoadHtml(GetHTMLForZoomState(zoom_state, PAGE_SCALABILITY_ENABLED));
1003   CRWWebController* web_controller = this->webController_.get();
1004   WaitForZoomRendering(web_controller, zoom_state);
1006   web::NavigationManager* nagivation_manager =
1007       web_controller.webState->GetNavigationManager();
1008   nagivation_manager->GetLastCommittedItem()->SetPageDisplayState(
1009       this->CreateTestPageDisplayState(CGPointMake(1.0, 1.0),  // scroll offset
1010                                        3.0,    // relative zoom scale
1011                                        1.0,    // original minimum zoom scale
1012                                        10.0,   // original maximum zoom scale
1013                                        1.0));  // original zoom scale
1014   [web_controller restoreStateFromHistory];
1016   // |-restoreStateFromHistory| is async; wait for its completion.
1017   base::test::ios::WaitUntilCondition(^bool() {
1018     return web_controller.pageDisplayState.scroll_state().offset_x() == 1.0;
1019   });
1021   web::PageZoomState final_zoom_state =
1022       web_controller.pageDisplayState.zoom_state();
1023   EXPECT_FLOAT_EQ(3, final_zoom_state.zoom_scale() /
1024                         final_zoom_state.minimum_zoom_scale());
1027 // TODO(iOS): Flaky on the bots. crbug/493427
1028 WEB_TEST_F(CRWUIWebControllerPageScrollStateTest,
1029            CRWWKWebControllerPageScrollStateTest,
1030            FLAKY_AtTop) {
1031   // This test fails on iPhone 6/6+ with WKWebView; skip until it's fixed.
1032   // crbug.com/453105
1033   if ([this->webController_ webViewType] == web::WK_WEB_VIEW_TYPE &&
1034       IsIPhone6Or6Plus())
1035     return;
1037   web::PageZoomState zoom_state = web::PageZoomState(1.0, 5.0, 1.0);
1038   this->LoadHtml(GetHTMLForZoomState(zoom_state, PAGE_SCALABILITY_ENABLED));
1039   CRWWebController* web_controller = this->webController_.get();
1040   WaitForZoomRendering(web_controller, zoom_state);
1041   ASSERT_TRUE(web_controller.atTop);
1043   web::NavigationManager* nagivation_manager =
1044       web_controller.webState->GetNavigationManager();
1045   nagivation_manager->GetLastCommittedItem()->SetPageDisplayState(
1046       this->CreateTestPageDisplayState(CGPointMake(0.0, 30.0),  // scroll offset
1047                                        5.0,    // relative zoom scale
1048                                        1.0,    // original minimum zoom scale
1049                                        5.0,    // original maximum zoom scale
1050                                        1.0));  // original zoom scale
1051   [web_controller restoreStateFromHistory];
1053   // |-restoreStateFromHistory| is async; wait for its completion.
1054   base::test::ios::WaitUntilCondition(^bool() {
1055     return web_controller.pageDisplayState.scroll_state().offset_y() == 30.0;
1056   });
1058   ASSERT_FALSE(web_controller.atTop);
1061 // Tests that evaluateJavaScript:completionHandler: properly forwards the
1062 // call to UIWebView.
1063 TEST_F(CRWUIWebViewWebControllerTest, JavaScriptEvaluation) {
1064   NSString* kTestScript = @"script";
1065   NSString* kTestResult = @"result";
1066   NSString* scriptWithIDCheck =
1067       [webController_ scriptByAddingWindowIDCheckForScript:kTestScript];
1068   [[[mockWebView_ stub] andReturn:kTestResult]
1069       stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1071   __block BOOL completionBlockWasCalled = NO;
1072   [webController_ evaluateJavaScript:kTestScript
1073                  stringResultHandler:^(NSString* string, NSError* error) {
1074                    completionBlockWasCalled = YES;
1075                    EXPECT_NSEQ(kTestResult, string);
1076                    EXPECT_EQ(nil, error);
1077                  }];
1079   // Wait until JavaScript is evaluated.
1080   base::test::ios::WaitUntilCondition(^bool() {
1081     return completionBlockWasCalled;
1082   });
1083   EXPECT_TRUE(completionBlockWasCalled);
1086 TEST_F(CRWUIWebViewWebControllerTest, POSTRequestCache) {
1087   GURL url("http://www.google.fr/");
1088   NSString* mixedCaseCookieHeaderName = @"cOoKiE";
1089   NSString* otherHeaderName = @"Myheader";
1090   NSString* otherHeaderValue = @"A";
1091   NSString* otherHeaderIncorrectValue = @"C";
1093   scoped_ptr<web::NavigationItemImpl> item(new web::NavigationItemImpl());
1094   item->SetURL(url);
1095   item->SetTransitionType(ui::PAGE_TRANSITION_FORM_SUBMIT);
1096   item->set_is_renderer_initiated(true);
1097   base::scoped_nsobject<CRWSessionEntry> currentEntry(
1098       [[CRWSessionEntry alloc] initWithNavigationItem:item.Pass()]);
1099   base::scoped_nsobject<NSMutableURLRequest> request(
1100       [[NSMutableURLRequest alloc] initWithURL:net::NSURLWithGURL(url)]);
1101   [request setHTTPMethod:@"POST"];
1102   [request setValue:otherHeaderValue forHTTPHeaderField:otherHeaderName];
1103   [request setValue:@"B" forHTTPHeaderField:mixedCaseCookieHeaderName];
1104   // No data is cached initially.
1105   EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1106   EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1107   // Streams are not cached.
1108   [request setHTTPBodyStream:[NSInputStream inputStreamWithData:[NSData data]]];
1109   [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1110   EXPECT_EQ(nil, [currentEntry navigationItemImpl]->GetPostData());
1111   EXPECT_EQ(nil, [currentEntry navigationItem]->GetHttpRequestHeaders());
1112   [request setHTTPBodyStream:nil];
1113   // POST Data is cached. Cookie headers are stripped, no matter their case.
1114   base::scoped_nsobject<NSData> body([[NSData alloc] init]);
1115   [request setHTTPBody:body];
1116   [webController_ cachePOSTDataForRequest:request inSessionEntry:currentEntry];
1117   EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1118   EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1119               @{otherHeaderName : otherHeaderValue});
1120   // A new request will not change the cached version.
1121   base::scoped_nsobject<NSData> body2([[NSData alloc] init]);
1122   [request setValue:otherHeaderIncorrectValue
1123       forHTTPHeaderField:otherHeaderName];
1124   [request setHTTPBody:body2];
1125   EXPECT_EQ(body.get(), [currentEntry navigationItemImpl]->GetPostData());
1126   EXPECT_NSEQ([currentEntry navigationItem]->GetHttpRequestHeaders(),
1127               @{otherHeaderName : otherHeaderValue});
1130 class WebControllerJSEvaluationTest : public CRWUIWebViewWebControllerTest {
1131  protected:
1132   void SetUp() override {
1133     WebControllerTest::SetUp();
1135     NSString* kTestResult = @"result";
1136     completionBlockWasCalled_ = NO;
1137     NSString* scriptWithIDCheck =
1138         [webController_ scriptByAddingWindowIDCheckForScript:GetTestScript()];
1139     [[[mockWebView_ stub] andReturn:kTestResult]
1140         stringByEvaluatingJavaScriptFromString:scriptWithIDCheck];
1141     completionHandler_.reset([^(NSString* string, NSError* error) {
1142       completionBlockWasCalled_ = YES;
1143       EXPECT_NSEQ(kTestResult, string);
1144       EXPECT_EQ(nil, error);
1145     } copy]);
1146   }
1147   NSString* GetTestScript() const { return @"script"; }
1148   base::scoped_nsprotocol<web::JavaScriptCompletion> completionHandler_;
1149   BOOL completionBlockWasCalled_;
1152 // Tests that -evaluateJavaScript:stringResultHandler: properly forwards
1153 // the call to the UIWebView.
1154 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluation) {
1155   [webController_ evaluateJavaScript:GetTestScript()
1156                  stringResultHandler:completionHandler_];
1157   // Wait until JavaScript is evaluated.
1158   base::test::ios::WaitUntilCondition(^bool() {
1159     return this->completionBlockWasCalled_;
1160   });
1161   EXPECT_TRUE(completionBlockWasCalled_);
1164 // Tests that -evaluateUserJavaScript:stringResultHandler: properly
1165 // forwards the call to the UIWebView.
1166 TEST_F(WebControllerJSEvaluationTest, UserJavaScriptEvaluation) {
1167   __block BOOL method_called = NO;
1168   [[[mockWebView_ stub] andDo:^(NSInvocation*) {
1169     method_called = YES;
1170   }]
1171       performSelectorOnMainThread:@selector(
1172                                       stringByEvaluatingJavaScriptFromString:)
1173                        withObject:GetTestScript()
1174                     waitUntilDone:NO];
1175   [webController_ evaluateUserJavaScript:GetTestScript()];
1176   EXPECT_TRUE(method_called);
1179 // Tests that -evaluateJavaScript:stringResultHandler: does not crash
1180 // on a nil completionHandler.
1181 TEST_F(WebControllerJSEvaluationTest, JavaScriptEvaluationNilHandler) {
1182   [webController_ evaluateJavaScript:GetTestScript() stringResultHandler:nil];
1185 // Real UIWebView is required for JSEvaluationTest.
1186 typedef web::WebTestWithUIWebViewWebController
1187     CRWUIWebControllerJSEvaluationTest;
1189 // Real WKWebView is required for JSEvaluationTest.
1190 typedef web::WebTestWithWKWebViewWebController
1191     CRWWKWebControllerJSEvaluationTest;
1193 // Tests that a script correctly evaluates to string.
1194 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1195            CRWWKWebControllerJSEvaluationTest,
1196            Evaluation) {
1197   this->LoadHtml(@"<p></p>");
1198   EXPECT_NSEQ(@"true", this->EvaluateJavaScriptAsString(@"true"));
1199   EXPECT_NSEQ(@"false", this->EvaluateJavaScriptAsString(@"false"));
1202 // Tests that a script is not evaluated on windowID mismatch.
1203 WEB_TEST_F(CRWUIWebControllerJSEvaluationTest,
1204            CRWWKWebControllerJSEvaluationTest,
1205            WindowIDMissmatch) {
1206   this->LoadHtml(@"<p></p>");
1207   // Script is evaluated since windowID is matched.
1208   this->EvaluateJavaScriptAsString(@"window.test1 = '1';");
1209   EXPECT_NSEQ(@"1", this->EvaluateJavaScriptAsString(@"window.test1"));
1211   // Change windowID.
1212   this->EvaluateJavaScriptAsString(@"__gCrWeb['windowId'] = '';");
1214   // Script is not evaluated because of windowID mismatch.
1215   this->EvaluateJavaScriptAsString(@"window.test2 = '2';");
1216   EXPECT_NSEQ(@"", this->EvaluateJavaScriptAsString(@"window.test2"));
1219 // A separate test class is used for testing the keyboard dismissal, as none of
1220 // the setup in |CRWUIWebViewWebControllerTest| is needed; keyboard appearance
1221 // is tracked by the KeyboardAppearanceListener.
1222 class WebControllerKeyboardTest
1223     : public web::WebTestWithUIWebViewWebController {
1224  protected:
1225   void SetUp() override {
1226     web::WebTestWithUIWebViewWebController::SetUp();
1227     // Close any outstanding alert boxes.
1228     ui::test::uiview_utils::CancelAlerts();
1230     // Sets up the listener for keyboard activation/deactivation notifications.
1231     keyboardListener_.reset([[KeyboardAppearanceListener alloc] init]);
1232   }
1234   base::scoped_nsobject<KeyboardAppearanceListener> keyboardListener_;
1237 TEST_F(WebControllerKeyboardTest, DismissKeyboard) {
1238   LoadHtml(@"<html><head></head><body><form><input id=\"textField\" /></form>"
1239            @"</body></html>");
1241   // Get the webview.
1242   UIWebView* webView = static_cast<UIWebView*>(
1243       [webController_ containerView].webViewContentView.webView);
1244   EXPECT_TRUE(webView);
1246   // Create the window and add the webview.
1247   base::scoped_nsobject<UIWindow> window(
1248       [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]);
1249   [window makeKeyAndVisible];
1250   [window addSubview:webView];
1252   // Set the flag to allow focus() in code in UIWebView.
1253   EXPECT_TRUE([webView keyboardDisplayRequiresUserAction]);
1254   [webView setKeyboardDisplayRequiresUserAction:NO];
1255   EXPECT_FALSE([webView keyboardDisplayRequiresUserAction]);
1257   // Set the focus on the textField.
1258   EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1259   [webView stringByEvaluatingJavaScriptFromString:
1260                @"document.getElementById(\"textField\").focus();"];
1261   base::test::ios::WaitUntilCondition(^bool() {
1262     return [keyboardListener_ isKeyboardVisible];
1263   });
1264   EXPECT_TRUE([keyboardListener_ isKeyboardVisible]);
1266   // Method to test.
1267   [webController_ dismissKeyboard];
1269   base::test::ios::WaitUntilCondition(^bool() {
1270     return ![keyboardListener_ isKeyboardVisible];
1271   });
1272   EXPECT_FALSE([keyboardListener_ isKeyboardVisible]);
1275 TEST_F(CRWWKWebViewWebControllerTest, WebURLWithTrustLevel) {
1276   CR_TEST_REQUIRES_WK_WEB_VIEW();
1278   [[[mockWebView_ stub] andReturn:[NSURL URLWithString:kTestURLString]] URL];
1279 #if !defined(ENABLE_CHROME_NET_STACK_FOR_WKWEBVIEW)
1280   [[[mockWebView_ stub] andReturnBool:NO] hasOnlySecureContent];
1281 #endif
1283   // Stub out the injection process.
1284   [[mockWebView_ stub] evaluateJavaScript:OCMOCK_ANY
1285                         completionHandler:OCMOCK_ANY];
1287   // Simulate registering load request to avoid failing page load simulation.
1288   [webController_ simulateLoadRequestWithURL:GURL([kTestURLString UTF8String])];
1289   // Simulate a page load to trigger a URL update.
1290   [static_cast<id<WKNavigationDelegate>>(webController_.get())
1291                   webView:mockWebView_
1292       didCommitNavigation:nil];
1294   web::URLVerificationTrustLevel trust_level = web::kNone;
1295   GURL gurl = [webController_ currentURLWithTrustLevel:&trust_level];
1297   EXPECT_EQ(gurl, GURL(base::SysNSStringToUTF8(kTestURLString)));
1298   EXPECT_EQ(web::kAbsolute, trust_level);
1301 // A separate test class, as none of the |CRWUIWebViewWebControllerTest| setup
1302 // is needed;
1303 typedef web::WebTestWithUIWebViewWebController CRWUIWebControllerObserversTest;
1304 typedef web::WebTestWithWKWebViewWebController CRWWKWebControllerObserversTest;
1306 // Tests that CRWWebControllerObservers are called.
1307 WEB_TEST_F(CRWUIWebControllerObserversTest,
1308            CRWWKWebControllerObserversTest,
1309            Observers) {
1310   base::scoped_nsobject<CountingObserver> observer(
1311       [[CountingObserver alloc] init]);
1312   CRWWebController* web_controller = this->webController_;
1313   EXPECT_EQ(0u, [web_controller observerCount]);
1314   [web_controller addObserver:observer];
1315   EXPECT_EQ(1u, [web_controller observerCount]);
1317   EXPECT_EQ(0, [observer pageLoadedCount]);
1318   [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), false);
1319   EXPECT_EQ(0, [observer pageLoadedCount]);
1320   [web_controller webStateImpl]->OnPageLoaded(GURL("http://test"), true);
1321   EXPECT_EQ(1, [observer pageLoadedCount]);
1323   EXPECT_EQ(0, [observer messageCount]);
1324   // Non-matching prefix.
1325   EXPECT_FALSE([web_controller webStateImpl]->OnScriptCommandReceived(
1326       "a", base::DictionaryValue(), GURL("http://test"), true));
1327   EXPECT_EQ(0, [observer messageCount]);
1328   // Matching prefix.
1329   EXPECT_TRUE([web_controller webStateImpl]->OnScriptCommandReceived(
1330       base::SysNSStringToUTF8([observer commandPrefix]) + ".foo",
1331       base::DictionaryValue(), GURL("http://test"), true));
1332   EXPECT_EQ(1, [observer messageCount]);
1334   [web_controller removeObserver:observer];
1335   EXPECT_EQ(0u, [web_controller observerCount]);
1338 // Test fixture for window.open tests.
1339 class CRWWKWebControllerWindowOpenTest
1340     : public web::WebTestWithWKWebViewWebController {
1341  protected:
1342   void SetUp() override {
1343     CR_TEST_REQUIRES_WK_WEB_VIEW();
1344     web::WebTestWithWKWebViewWebController::SetUp();
1346     // Configure web delegate.
1347     delegate_.reset([[MockInteractionLoader alloc]
1348         initWithRepresentedObject:
1349             [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)]]);
1350     ASSERT_TRUE([delegate_ blockPopups]);
1351     [webController_ setDelegate:delegate_];
1353     // Configure child web controller.
1354     child_.reset(CreateWebController());
1355     [child_ setWebUsageEnabled:YES];
1356     [delegate_ setChildWebController:child_];
1358     // Configure child web controller's session controller mock.
1359     id sessionController =
1360         [OCMockObject niceMockForClass:[CRWSessionController class]];
1361     BOOL yes = YES;
1362     [[[sessionController stub] andReturnValue:OCMOCK_VALUE(yes)] isOpenedByDOM];
1363     [child_ webStateImpl]->GetNavigationManagerImpl().SetSessionController(
1364         sessionController);
1366     LoadHtml(@"<html><body></body></html>");
1367   }
1368   void TearDown() override {
1369     EXPECT_OCMOCK_VERIFY(delegate_);
1370     [webController_ setDelegate:nil];
1371     [child_ close];
1373     web::WebTestWithWKWebViewWebController::TearDown();
1374   }
1375   // Executes JavaScript that opens a new window and returns evaluation result
1376   // as a string.
1377   NSString* OpenWindowByDOM() {
1378     NSString* const kOpenWindowScript =
1379         @"var w = window.open('javascript:void(0);', target='_blank');"
1380          "w.toString();";
1381     NSString* windowJSObject = EvaluateJavaScriptAsString(kOpenWindowScript);
1382     WaitForBackgroundTasks();
1383     return windowJSObject;
1384   }
1385   // A CRWWebDelegate mock used for testing.
1386   base::scoped_nsobject<id> delegate_;
1387   // A child CRWWebController used for testing.
1388   base::scoped_nsobject<CRWWebController> child_;
1391 // Tests that absence of web delegate is handled gracefully.
1392 TEST_F(CRWWKWebControllerWindowOpenTest, NoDelegate) {
1393   CR_TEST_REQUIRES_WK_WEB_VIEW();
1395   [webController_ setDelegate:nil];
1397   EXPECT_NSEQ(@"", OpenWindowByDOM());
1399   EXPECT_FALSE([delegate_ blockedPopupInfo]);
1402 // Tests that window.open triggered by user gesture opens a new non-popup
1403 // window.
1404 TEST_F(CRWWKWebControllerWindowOpenTest, OpenWithUserGesture) {
1405   CR_TEST_REQUIRES_WK_WEB_VIEW();
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   [webController_ touched:YES];
1416   EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1417   EXPECT_FALSE([delegate_ blockedPopupInfo]);
1420 // Tests that window.open executed w/o user gesture does not open a new window.
1421 // Once the blocked popup is allowed a new window is opened.
1422 TEST_F(CRWWKWebControllerWindowOpenTest, AllowPopup) {
1423   CR_TEST_REQUIRES_WK_WEB_VIEW();
1425   SEL selector =
1426       @selector(webPageOrderedOpen:referrer:windowName:inBackground:);
1427   [delegate_ onSelector:selector
1428       callBlockExpectation:^(const GURL& new_window_url,
1429                              const web::Referrer& referrer,
1430                              NSString* obsoleted_window_name,
1431                              BOOL in_background) {
1432         EXPECT_EQ("javascript:void(0);", new_window_url.spec());
1433         EXPECT_EQ("", referrer.url.spec());
1434         EXPECT_FALSE(in_background);
1435       }];
1437   ASSERT_FALSE([webController_ userIsInteracting]);
1438   EXPECT_NSEQ(@"", OpenWindowByDOM());
1439   base::test::ios::WaitUntilCondition(^bool() {
1440     return [delegate_ blockedPopupInfo];
1441   });
1443   if ([delegate_ blockedPopupInfo])
1444     [delegate_ blockedPopupInfo]->ShowPopup();
1446   EXPECT_EQ("", [delegate_ sourceURL].spec());
1447   EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1450 // Tests that window.open executed w/o user gesture opens a new window, assuming
1451 // that delegate allows popups.
1452 TEST_F(CRWWKWebControllerWindowOpenTest, DontBlockPopup) {
1453   CR_TEST_REQUIRES_WK_WEB_VIEW();
1455   [delegate_ setBlockPopups:NO];
1456   SEL selector = @selector(webPageOrderedOpenBlankWithReferrer:inBackground:);
1457   [delegate_ onSelector:selector
1458       callBlockExpectation:^(const web::Referrer& referrer,
1459                              BOOL in_background) {
1460         EXPECT_EQ("", referrer.url.spec());
1461         EXPECT_FALSE(in_background);
1462       }];
1464   EXPECT_NSEQ(@"[object Window]", OpenWindowByDOM());
1465   EXPECT_FALSE([delegate_ blockedPopupInfo]);
1467   EXPECT_EQ("", [delegate_ sourceURL].spec());
1468   EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1471 // Tests that window.open executed w/o user gesture does not open a new window.
1472 TEST_F(CRWWKWebControllerWindowOpenTest, BlockPopup) {
1473   CR_TEST_REQUIRES_WK_WEB_VIEW();
1475   ASSERT_FALSE([webController_ userIsInteracting]);
1476   EXPECT_NSEQ(@"", OpenWindowByDOM());
1477   base::test::ios::WaitUntilCondition(^bool() {
1478     return [delegate_ blockedPopupInfo];
1479   });
1481   EXPECT_EQ("", [delegate_ sourceURL].spec());
1482   EXPECT_EQ("javascript:void(0);", [delegate_ popupURL].spec());
1485 // Fixture class to test WKWebView crashes.
1486 class CRWWKWebControllerWebProcessTest
1487     : public web::WebTestWithWKWebViewWebController {
1488  protected:
1489   void SetUp() override {
1490     CR_TEST_REQUIRES_WK_WEB_VIEW();
1491     web::WebTestWithWKWebViewWebController::SetUp();
1492     webView_.reset(web::CreateTerminatedWKWebView());
1493     base::scoped_nsobject<TestWebViewContentView> webViewContentView(
1494         [[TestWebViewContentView alloc]
1495             initWithMockWebView:webView_
1496                      scrollView:[webView_ scrollView]]);
1497     [webController_ injectWebViewContentView:webViewContentView];
1498   }
1499   base::scoped_nsobject<WKWebView> webView_;
1502 // Tests that -[CRWWebDelegate webControllerWebProcessDidCrash:] is called
1503 // when WKWebView web process has crashed.
1504 TEST_F(CRWWKWebControllerWebProcessTest, Crash) {
1505   CR_TEST_REQUIRES_WK_WEB_VIEW();
1507   id delegate = [OCMockObject niceMockForProtocol:@protocol(CRWWebDelegate)];
1508   [[delegate expect] webControllerWebProcessDidCrash:this->webController_];
1510   [this->webController_ setDelegate:delegate];
1511   web::SimulateWKWebViewCrash(webView_);
1513   EXPECT_OCMOCK_VERIFY(delegate);
1514   [this->webController_ setDelegate:nil];
1517 // Tests that WKWebView does not crash if deallocation happens during JavaScript
1518 // evaluation.
1519 typedef web::WebTestWithWKWebViewWebController DeallocationDuringJSEvaluation;
1520 TEST_F(DeallocationDuringJSEvaluation, NoCrash) {
1521   CR_TEST_REQUIRES_WK_WEB_VIEW();
1523   this->LoadHtml(@"<html><body></body></html>");
1524   [webController_ evaluateJavaScript:@"null"
1525                  stringResultHandler:^(NSString* value, NSError*) {
1526                    // Block can not be empty in order to reproduce the crash:
1527                    // https://bugs.webkit.org/show_bug.cgi?id=140203
1528                    VLOG(1) << "Script has been flushed.";
1529                  }];
1530   // -evaluateJavaScript:stringResultHandler: is asynchronous so JavaScript
1531   // evaluation will not happen until TearDown, which deallocates
1532   // CRWWebController, which in its turn will deallocate WKWebView to create a
1533   // crashy condition.
1536 }  // namespace