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