Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ios / web / test / web_test.mm
blob9c518c561280ac34ff707c4369bbd4e5e924e2b9
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 #include "ios/web/test/web_test.h"
7 #include "base/base64.h"
8 #include "base/strings/stringprintf.h"
9 #import "base/test/ios/wait_util.h"
10 #import "ios/testing/ocmock_complex_type_helper.h"
11 #import "ios/web/navigation/crw_session_controller.h"
12 #import "ios/web/net/crw_url_verifying_protocol_handler.h"
13 #include "ios/web/public/active_state_manager.h"
14 #include "ios/web/public/referrer.h"
15 #import "ios/web/public/web_state/ui/crw_web_delegate.h"
16 #import "ios/web/web_state/js/crw_js_invoke_parameter_queue.h"
17 #import "ios/web/web_state/ui/crw_wk_web_view_web_controller.h"
18 #import "ios/web/web_state/web_state_impl.h"
19 #include "third_party/ocmock/OCMock/OCMock.h"
21 // Helper Mock to stub out API with C++ objects in arguments.
22 @interface WebDelegateMock : OCMockComplexTypeHelper
23 @end
25 @implementation WebDelegateMock
26 // Stub implementation always returns YES.
27 - (BOOL)webController:(CRWWebController*)webController
28         shouldOpenURL:(const GURL&)url
29       mainDocumentURL:(const GURL&)mainDocumentURL
30           linkClicked:(BOOL)linkClicked {
31   return YES;
33 @end
35 namespace web {
37 #pragma mark -
39 WebTest::WebTest() {}
40 WebTest::~WebTest() {}
42 void WebTest::SetUp() {
43   PlatformTest::SetUp();
44   web::SetWebClient(&client_);
45   BrowserState::GetActiveStateManager(&browser_state_)->SetActive(true);
48 void WebTest::TearDown() {
49   BrowserState::GetActiveStateManager(&browser_state_)->SetActive(false);
50   web::SetWebClient(nullptr);
51   PlatformTest::TearDown();
54 #pragma mark -
56 WebTestWithWebController::WebTestWithWebController() {}
58 WebTestWithWebController::~WebTestWithWebController() {}
60 static int s_html_load_count;
62 void WebTestWithWebController::SetUp() {
63   WebTest::SetUp();
64   BOOL success =
65       [NSURLProtocol registerClass:[CRWURLVerifyingProtocolHandler class]];
66   DCHECK(success);
67   webController_.reset(this->CreateWebController());
69   [webController_ setWebUsageEnabled:YES];
70   // Force generation of child views; necessary for some tests.
71   [webController_ triggerPendingLoad];
72   s_html_load_count = 0;
75 void WebTestWithWebController::TearDown() {
76   [webController_ close];
77   [NSURLProtocol unregisterClass:[CRWURLVerifyingProtocolHandler class]];
78   WebTest::TearDown();
81 void WebTestWithWebController::LoadHtml(NSString* html) {
82   LoadHtml([html UTF8String]);
85 void WebTestWithWebController::LoadHtml(const std::string& html) {
86   NSString* load_check = [NSString stringWithFormat:
87       @"<p style=\"display: none;\">%d</p>", s_html_load_count++];
89   std::string marked_html = html + [load_check UTF8String];
90   std::string encoded_html;
91   base::Base64Encode(marked_html, &encoded_html);
92   GURL url("data:text/html;base64," + encoded_html);
93   LoadURL(url);
95   // Data URLs sometimes lock up navigation, so if the loaded page is not the
96   // one expected, reset the web view. In some cases, document or document.body
97   // does not exist either; also reset in those cases.
98   NSString* inner_html = RunJavaScript(
99       @"(document && document.body && document.body.innerHTML) || 'undefined'");
100   if ([inner_html rangeOfString:load_check].location == NSNotFound) {
101     [webController_ setWebUsageEnabled:NO];
102     [webController_ setWebUsageEnabled:YES];
103     [webController_ triggerPendingLoad];
104     LoadHtml(html);
105   }
108 void WebTestWithWebController::LoadURL(const GURL& url) {
109   // First step is to ensure that the web controller has finished any previous
110   // page loads so the new load is not confused.
111   while ([webController_ loadPhase] != PAGE_LOADED)
112     WaitForBackgroundTasks();
113   id originalMockDelegate = [OCMockObject
114       niceMockForProtocol:@protocol(CRWWebDelegate)];
115   id mockDelegate = [[WebDelegateMock alloc]
116       initWithRepresentedObject:originalMockDelegate];
117   id existingDelegate = webController_.get().delegate;
118   webController_.get().delegate = mockDelegate;
120   web::NavigationManagerImpl& navManager =
121       [webController_ webStateImpl]->GetNavigationManagerImpl();
122   navManager.InitializeSession(@"name", nil, NO, 0);
123   [navManager.GetSessionController()
124         addPendingEntry:url
125                referrer:web::Referrer()
126              transition:ui::PAGE_TRANSITION_TYPED
127       rendererInitiated:NO];
129   [webController_ loadCurrentURL];
130   while ([webController_ loadPhase] != PAGE_LOADED)
131     WaitForBackgroundTasks();
132   webController_.get().delegate = existingDelegate;
133   [[webController_ view] layoutIfNeeded];
136 void WebTestWithWebController::WaitForBackgroundTasks() {
137   // Because tasks can add new tasks to either queue, the loop continues until
138   // the first pass where no activity is seen from either queue.
139   bool activitySeen = false;
140   base::MessageLoop* messageLoop = base::MessageLoop::current();
141   messageLoop->AddTaskObserver(this);
142   do {
143     activitySeen = false;
145     // Yield to the iOS message queue, e.g. [NSObject performSelector:] events.
146     if (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) ==
147         kCFRunLoopRunHandledSource)
148       activitySeen = true;
150     // Yield to the Chromium message queue, e.g. WebThread::PostTask()
151     // events.
152     processed_a_task_ = false;
153     messageLoop->RunUntilIdle();
154     if (processed_a_task_)  // Set in TaskObserver method.
155       activitySeen = true;
157   } while (activitySeen || !MessageQueueIsEmpty());
158   messageLoop->RemoveTaskObserver(this);
161 void WebTestWithWebController::WaitForCondition(ConditionBlock condition) {
162   base::MessageLoop* messageLoop = base::MessageLoop::current();
163   DCHECK(messageLoop);
164   base::test::ios::WaitUntilCondition(condition, messageLoop,
165                                       base::TimeDelta::FromSeconds(10));
168 bool WebTestWithWebController::MessageQueueIsEmpty() const {
169   // Using this check rather than polymorphism because polymorphising
170   // Chrome*WebViewWebTest would be overengineering. Chrome*WebViewWebTest
171   // inherits from WebTestWithWebController.
172   return [webController_ webViewType] == web::WK_WEB_VIEW_TYPE ||
173       [static_cast<CRWUIWebViewWebController*>(webController_)
174           jsInvokeParameterQueue].isEmpty;
177 NSString* WebTestWithWebController::EvaluateJavaScriptAsString(
178     NSString* script) const {
179   __block base::scoped_nsobject<NSString> evaluationResult;
180   [webController_ evaluateJavaScript:script
181                  stringResultHandler:^(NSString* result, NSError* error) {
182                      DCHECK([result isKindOfClass:[NSString class]]);
183                      evaluationResult.reset([result copy]);
184                  }];
185   base::test::ios::WaitUntilCondition(^bool() {
186     return evaluationResult;
187   });
188   return [[evaluationResult retain] autorelease];
191 NSString* WebTestWithWebController::RunJavaScript(NSString* script) {
192   // The platform JSON serializer is used to safely escape the |script| and
193   // decode the result while preserving unicode encoding that can be lost when
194   // converting to Chromium string types.
195   NSError* error = nil;
196   NSData* data = [NSJSONSerialization dataWithJSONObject:@[ script ]
197                                                  options:0
198                                                    error:&error];
199   DCHECK(data && !error);
200   base::scoped_nsobject<NSString> jsonString(
201       [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
202   // 'eval' is used because it is the only way to stay 100% compatible with
203   // stringByEvaluatingJavaScriptFromString in the event that the script is a
204   // statement.
205   NSString* wrappedScript = [NSString stringWithFormat:
206       @"try {"
207       @"  JSON.stringify({"  // Expression for the success case.
208       @"    result: '' + eval(%@[0]),"  // '' + converts result to string.
209       @"    toJSON: null"  // Use default JSON stringifier.
210       @"  });"
211       @"} catch(e) {"
212       @"  JSON.stringify({"  // Expression for the exception case.
213       @"    exception: e.toString(),"
214       @"    toJSON: null"  // Use default JSON stringifier.
215       @"  });"
216       @"}", jsonString.get()];
218   // Run asyncronious JavaScript evaluation and wait for its completion.
219   __block base::scoped_nsobject<NSData> evaluationData;
220   [webController_ evaluateJavaScript:wrappedScript
221                  stringResultHandler:^(NSString* result, NSError* error) {
222                    DCHECK([result length]);
223                    evaluationData.reset([[result dataUsingEncoding:
224                        NSUTF8StringEncoding] retain]);
225                  }];
226   base::test::ios::WaitUntilCondition(^bool() {
227     return evaluationData;
228   });
230   // The output is wrapped in a JSON dictionary to distinguish between an
231   // exception string and a result string.
232   NSDictionary* dictionary = [NSJSONSerialization
233       JSONObjectWithData:evaluationData
234                  options:0
235                    error:&error];
236   DCHECK(dictionary && !error);
237   NSString* exception = [dictionary objectForKey:@"exception"];
238   CHECK(!exception) << "Script error: " << [exception UTF8String];
239   return [dictionary objectForKey:@"result"];
242 void WebTestWithWebController::WillProcessTask(
243     const base::PendingTask& pending_task) {
244   // Nothing to do.
247 void WebTestWithWebController::DidProcessTask(
248     const base::PendingTask& pending_task) {
249   processed_a_task_ = true;
252 #pragma mark -
254 CRWWebController* WebTestWithUIWebViewWebController::CreateWebController() {
255   scoped_ptr<WebStateImpl> web_state_impl(new WebStateImpl(GetBrowserState()));
256   return [[TestWebController alloc] initWithWebState:web_state_impl.Pass()];
259 void WebTestWithUIWebViewWebController::LoadCommands(NSString* commands,
260                                                      const GURL& origin_url,
261                                                      BOOL user_is_interacting) {
262   [static_cast<CRWUIWebViewWebController*>(webController_)
263       respondToMessageQueue:commands
264           userIsInteracting:user_is_interacting
265                   originURL:origin_url];
268 #pragma mark -
270 CRWWebController* WebTestWithWKWebViewWebController::CreateWebController() {
271   scoped_ptr<WebStateImpl> web_state_impl(new WebStateImpl(GetBrowserState()));
272   return [[CRWWKWebViewWebController alloc] initWithWebState:
273       web_state_impl.Pass()];
276 }  // namespace web
278 #pragma mark -
280 // Declare CRWUIWebViewWebController's (private) implementation of
281 // UIWebViewDelegate.
282 @interface CRWUIWebViewWebController(TestProtocolDeclaration)<UIWebViewDelegate>
283 @end
285 @implementation TestWebController {
286   BOOL _interceptRequest;
287   BOOL _requestIntercepted;
288   BOOL _invokeShouldStartLoadWithRequestNavigationTypeDone;
291 @synthesize interceptRequest = _interceptRequest;
292 @synthesize requestIntercepted = _requestIntercepted;
293 @synthesize invokeShouldStartLoadWithRequestNavigationTypeDone =
294     _invokeShouldStartLoadWithRequestNavigationTypeDone;
296 - (BOOL)webView:(UIWebView*)webView
297     shouldStartLoadWithRequest:(NSURLRequest*)request
298                 navigationType:(UIWebViewNavigationType)navigationType {
299   _invokeShouldStartLoadWithRequestNavigationTypeDone = false;
300   // Conditionally block the request to open a webpage.
301   if (_interceptRequest) {
302     _requestIntercepted = true;
303     return false;
304   }
305   BOOL result = [super webView:webView
306       shouldStartLoadWithRequest:request
307                   navigationType:navigationType];
308   _invokeShouldStartLoadWithRequestNavigationTypeDone = true;
309   return result;
311 @end