Add ICU message format support
[chromium-blink-merge.git] / ios / web / test / web_test.mm
blobc6a04e8e815c92e4216241c22eec2ea27d85b55f
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/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 WebTestBase::WebTestBase() {}
58 WebTestBase::~WebTestBase() {}
60 static int s_html_load_count;
62 void WebTestBase::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 WebTestBase::TearDown() {
76   [webController_ close];
77   [NSURLProtocol unregisterClass:[CRWURLVerifyingProtocolHandler class]];
78   WebTest::TearDown();
81 void WebTestBase::LoadHtml(NSString* html) {
82   LoadHtml([html UTF8String]);
85 void WebTestBase::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 WebTestBase::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 WebTestBase::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 WebTestBase::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 WebTestBase::MessageQueueIsEmpty() const {
169   // Using this check rather than polymorphism because polymorphising
170   // Chrome*WebViewWebTest would be overengineering. Chrome*WebViewWebTest
171   // inherits from WebTestBase.
172   return [webController_ webViewType] == web::WK_WEB_VIEW_TYPE ||
173       [static_cast<CRWUIWebViewWebController*>(webController_)
174           jsInvokeParameterQueue].isEmpty;
177 NSString* WebTestBase::EvaluateJavaScriptAsString(NSString* script) const {
178   __block base::scoped_nsobject<NSString> evaluationResult;
179   [webController_ evaluateJavaScript:script
180                  stringResultHandler:^(NSString* result, NSError* error) {
181                      DCHECK([result isKindOfClass:[NSString class]]);
182                      evaluationResult.reset([result copy]);
183                  }];
184   base::test::ios::WaitUntilCondition(^bool() {
185     return evaluationResult;
186   });
187   return [[evaluationResult retain] autorelease];
190 NSString* WebTestBase::RunJavaScript(NSString* script) {
191   // The platform JSON serializer is used to safely escape the |script| and
192   // decode the result while preserving unicode encoding that can be lost when
193   // converting to Chromium string types.
194   NSError* error = nil;
195   NSData* data = [NSJSONSerialization dataWithJSONObject:@[ script ]
196                                                  options:0
197                                                    error:&error];
198   DCHECK(data && !error);
199   base::scoped_nsobject<NSString> jsonString(
200       [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
201   // 'eval' is used because it is the only way to stay 100% compatible with
202   // stringByEvaluatingJavaScriptFromString in the event that the script is a
203   // statement.
204   NSString* wrappedScript = [NSString stringWithFormat:
205       @"try {"
206       @"  JSON.stringify({"  // Expression for the success case.
207       @"    result: '' + eval(%@[0]),"  // '' + converts result to string.
208       @"    toJSON: null"  // Use default JSON stringifier.
209       @"  });"
210       @"} catch(e) {"
211       @"  JSON.stringify({"  // Expression for the exception case.
212       @"    exception: e.toString(),"
213       @"    toJSON: null"  // Use default JSON stringifier.
214       @"  });"
215       @"}", jsonString.get()];
217   // Run asyncronious JavaScript evaluation and wait for its completion.
218   __block base::scoped_nsobject<NSData> evaluationData;
219   [webController_ evaluateJavaScript:wrappedScript
220                  stringResultHandler:^(NSString* result, NSError* error) {
221                    DCHECK([result length]);
222                    evaluationData.reset([[result dataUsingEncoding:
223                        NSUTF8StringEncoding] retain]);
224                  }];
225   base::test::ios::WaitUntilCondition(^bool() {
226     return evaluationData;
227   });
229   // The output is wrapped in a JSON dictionary to distinguish between an
230   // exception string and a result string.
231   NSDictionary* dictionary = [NSJSONSerialization
232       JSONObjectWithData:evaluationData
233                  options:0
234                    error:&error];
235   DCHECK(dictionary && !error);
236   NSString* exception = [dictionary objectForKey:@"exception"];
237   CHECK(!exception) << "Script error: " << [exception UTF8String];
238   return [dictionary objectForKey:@"result"];
241 void WebTestBase::WillProcessTask(const base::PendingTask& pending_task) {
242   // Nothing to do.
245 void WebTestBase::DidProcessTask(const base::PendingTask& pending_task) {
246   processed_a_task_ = true;
249 #pragma mark -
251 CRWWebController* UIWebViewWebTest::CreateWebController() {
252   scoped_ptr<WebStateImpl> web_state_impl(new WebStateImpl(GetBrowserState()));
253   return [[TestWebController alloc] initWithWebState:web_state_impl.Pass()];
256 void UIWebViewWebTest::LoadCommands(NSString* commands,
257                                     const GURL& origin_url,
258                                     BOOL user_is_interacting) {
259   [static_cast<CRWUIWebViewWebController*>(webController_)
260       respondToMessageQueue:commands
261           userIsInteracting:user_is_interacting
262                   originURL:origin_url];
265 #pragma mark -
267 CRWWebController* WKWebViewWebTest::CreateWebController() {
268   scoped_ptr<WebStateImpl> web_state_impl(new WebStateImpl(GetBrowserState()));
269   return [[CRWWKWebViewWebController alloc] initWithWebState:
270       web_state_impl.Pass()];
273 }  // namespace web
275 #pragma mark -
277 // Declare CRWUIWebViewWebController's (private) implementation of
278 // UIWebViewDelegate.
279 @interface CRWUIWebViewWebController(TestProtocolDeclaration)<UIWebViewDelegate>
280 @end
282 @implementation TestWebController {
283   BOOL _interceptRequest;
284   BOOL _requestIntercepted;
285   BOOL _invokeShouldStartLoadWithRequestNavigationTypeDone;
288 @synthesize interceptRequest = _interceptRequest;
289 @synthesize requestIntercepted = _requestIntercepted;
290 @synthesize invokeShouldStartLoadWithRequestNavigationTypeDone =
291     _invokeShouldStartLoadWithRequestNavigationTypeDone;
293 - (BOOL)webView:(UIWebView*)webView
294     shouldStartLoadWithRequest:(NSURLRequest*)request
295                 navigationType:(UIWebViewNavigationType)navigationType {
296   _invokeShouldStartLoadWithRequestNavigationTypeDone = false;
297   // Conditionally block the request to open a webpage.
298   if (_interceptRequest) {
299     _requestIntercepted = true;
300     return false;
301   }
302   BOOL result = [super webView:webView
303       shouldStartLoadWithRequest:request
304                   navigationType:navigationType];
305   _invokeShouldStartLoadWithRequestNavigationTypeDone = true;
306   return result;
308 @end