1 // Copyright 2014 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.
4
5 #include "chrome/test/base/web_ui_browser_test.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/lazy_instance.h"
11 #include "base/memory/ref_counted_memory.h"
12 #include "base/path_service.h"
13 #include "base/strings/string_util.h"
14 #include "base/values.h"
15 #include "chrome/browser/chrome_content_browser_client.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/ui/browser.h"
18 #include "chrome/browser/ui/browser_commands.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
20 #include "chrome/browser/ui/webui/web_ui_test_handler.h"
21 #include "chrome/common/chrome_paths.h"
22 #include "chrome/common/url_constants.h"
23 #include "chrome/test/base/test_chrome_web_ui_controller_factory.h"
24 #include "chrome/test/base/ui_test_utils.h"
25 #include "content/public/browser/url_data_source.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/browser/web_contents_observer.h"
28 #include "content/public/browser/web_ui_controller.h"
29 #include "content/public/browser/web_ui_message_handler.h"
30 #include "content/public/test/browser_test_utils.h"
31 #include "content/public/test/test_navigation_observer.h"
32 #include "net/base/filename_util.h"
33 #include "ui/base/resource/resource_handle.h"
34
35 #if defined(ENABLE_FULL_PRINTING)
36 #include "chrome/browser/printing/print_preview_dialog_controller.h"
37 #endif
38
39 using content::RenderViewHost;
40 using content::WebContents;
41 using content::WebUIController;
42 using content::WebUIMessageHandler;
43
44 namespace {
45
46 base::LazyInstance<std::vector<std::string> > error_messages_ =
47 LAZY_INSTANCE_INITIALIZER;
48
49 // Intercepts all log messages.
LogHandler(int severity,const char * file,int line,size_t message_start,const std::string & str)50 bool LogHandler(int severity,
51 const char* file,
52 int line,
53 size_t message_start,
54 const std::string& str) {
55 if (severity == logging::LOG_ERROR && file &&
56 std::string("CONSOLE") == file) {
57 error_messages_.Get().push_back(str);
58 }
59
60 return false;
61 }
62
63 class WebUIJsInjectionReadyObserver : public content::WebContentsObserver {
64 public:
WebUIJsInjectionReadyObserver(content::WebContents * web_contents,WebUIBrowserTest * browser_test,const std::string & preload_test_fixture,const std::string & preload_test_name)65 WebUIJsInjectionReadyObserver(content::WebContents* web_contents,
66 WebUIBrowserTest* browser_test,
67 const std::string& preload_test_fixture,
68 const std::string& preload_test_name)
69 : content::WebContentsObserver(web_contents),
70 browser_test_(browser_test),
71 preload_test_fixture_(preload_test_fixture),
72 preload_test_name_(preload_test_name) {}
73
RenderViewCreated(content::RenderViewHost * rvh)74 virtual void RenderViewCreated(content::RenderViewHost* rvh) OVERRIDE {
75 browser_test_->PreLoadJavascriptLibraries(
76 preload_test_fixture_, preload_test_name_, rvh);
77 }
78
79 private:
80 WebUIBrowserTest* browser_test_;
81 std::string preload_test_fixture_;
82 std::string preload_test_name_;
83 };
84
85 } // namespace
86
~WebUIBrowserTest()87 WebUIBrowserTest::~WebUIBrowserTest() {
88 }
89
RunJavascriptFunction(const std::string & function_name)90 bool WebUIBrowserTest::RunJavascriptFunction(const std::string& function_name) {
91 ConstValueVector empty_args;
92 return RunJavascriptFunction(function_name, empty_args);
93 }
94
RunJavascriptFunction(const std::string & function_name,base::Value * arg)95 bool WebUIBrowserTest::RunJavascriptFunction(const std::string& function_name,
96 base::Value* arg) {
97 ConstValueVector args;
98 args.push_back(arg);
99 return RunJavascriptFunction(function_name, args);
100 }
101
RunJavascriptFunction(const std::string & function_name,base::Value * arg1,base::Value * arg2)102 bool WebUIBrowserTest::RunJavascriptFunction(const std::string& function_name,
103 base::Value* arg1,
104 base::Value* arg2) {
105 ConstValueVector args;
106 args.push_back(arg1);
107 args.push_back(arg2);
108 return RunJavascriptFunction(function_name, args);
109 }
110
RunJavascriptFunction(const std::string & function_name,const ConstValueVector & function_arguments)111 bool WebUIBrowserTest::RunJavascriptFunction(
112 const std::string& function_name,
113 const ConstValueVector& function_arguments) {
114 return RunJavascriptUsingHandler(
115 function_name, function_arguments, false, false, NULL);
116 }
117
RunJavascriptTestF(bool is_async,const std::string & test_fixture,const std::string & test_name)118 bool WebUIBrowserTest::RunJavascriptTestF(bool is_async,
119 const std::string& test_fixture,
120 const std::string& test_name) {
121 ConstValueVector args;
122 args.push_back(new base::StringValue(test_fixture));
123 args.push_back(new base::StringValue(test_name));
124
125 if (is_async)
126 return RunJavascriptAsyncTest("RUN_TEST_F", args);
127 else
128 return RunJavascriptTest("RUN_TEST_F", args);
129 }
130
RunJavascriptTest(const std::string & test_name)131 bool WebUIBrowserTest::RunJavascriptTest(const std::string& test_name) {
132 ConstValueVector empty_args;
133 return RunJavascriptTest(test_name, empty_args);
134 }
135
RunJavascriptTest(const std::string & test_name,base::Value * arg)136 bool WebUIBrowserTest::RunJavascriptTest(const std::string& test_name,
137 base::Value* arg) {
138 ConstValueVector args;
139 args.push_back(arg);
140 return RunJavascriptTest(test_name, args);
141 }
142
RunJavascriptTest(const std::string & test_name,base::Value * arg1,base::Value * arg2)143 bool WebUIBrowserTest::RunJavascriptTest(const std::string& test_name,
144 base::Value* arg1,
145 base::Value* arg2) {
146 ConstValueVector args;
147 args.push_back(arg1);
148 args.push_back(arg2);
149 return RunJavascriptTest(test_name, args);
150 }
151
RunJavascriptTest(const std::string & test_name,const ConstValueVector & test_arguments)152 bool WebUIBrowserTest::RunJavascriptTest(
153 const std::string& test_name,
154 const ConstValueVector& test_arguments) {
155 return RunJavascriptUsingHandler(
156 test_name, test_arguments, true, false, NULL);
157 }
158
RunJavascriptAsyncTest(const std::string & test_name)159 bool WebUIBrowserTest::RunJavascriptAsyncTest(const std::string& test_name) {
160 ConstValueVector empty_args;
161 return RunJavascriptAsyncTest(test_name, empty_args);
162 }
163
RunJavascriptAsyncTest(const std::string & test_name,base::Value * arg)164 bool WebUIBrowserTest::RunJavascriptAsyncTest(const std::string& test_name,
165 base::Value* arg) {
166 ConstValueVector args;
167 args.push_back(arg);
168 return RunJavascriptAsyncTest(test_name, args);
169 }
170
RunJavascriptAsyncTest(const std::string & test_name,base::Value * arg1,base::Value * arg2)171 bool WebUIBrowserTest::RunJavascriptAsyncTest(const std::string& test_name,
172 base::Value* arg1,
173 base::Value* arg2) {
174 ConstValueVector args;
175 args.push_back(arg1);
176 args.push_back(arg2);
177 return RunJavascriptAsyncTest(test_name, args);
178 }
179
RunJavascriptAsyncTest(const std::string & test_name,base::Value * arg1,base::Value * arg2,base::Value * arg3)180 bool WebUIBrowserTest::RunJavascriptAsyncTest(const std::string& test_name,
181 base::Value* arg1,
182 base::Value* arg2,
183 base::Value* arg3) {
184 ConstValueVector args;
185 args.push_back(arg1);
186 args.push_back(arg2);
187 args.push_back(arg3);
188 return RunJavascriptAsyncTest(test_name, args);
189 }
190
RunJavascriptAsyncTest(const std::string & test_name,const ConstValueVector & test_arguments)191 bool WebUIBrowserTest::RunJavascriptAsyncTest(
192 const std::string& test_name,
193 const ConstValueVector& test_arguments) {
194 return RunJavascriptUsingHandler(test_name, test_arguments, true, true, NULL);
195 }
196
PreLoadJavascriptLibraries(const std::string & preload_test_fixture,const std::string & preload_test_name,RenderViewHost * preload_host)197 void WebUIBrowserTest::PreLoadJavascriptLibraries(
198 const std::string& preload_test_fixture,
199 const std::string& preload_test_name,
200 RenderViewHost* preload_host) {
201 ASSERT_FALSE(libraries_preloaded_);
202 ConstValueVector args;
203 args.push_back(new base::StringValue(preload_test_fixture));
204 args.push_back(new base::StringValue(preload_test_name));
205 RunJavascriptUsingHandler(
206 "preloadJavascriptLibraries", args, false, false, preload_host);
207 libraries_preloaded_ = true;
208 }
209
BrowsePreload(const GURL & browse_to)210 void WebUIBrowserTest::BrowsePreload(const GURL& browse_to) {
211 content::WebContents* web_contents =
212 browser()->tab_strip_model()->GetActiveWebContents();
213 WebUIJsInjectionReadyObserver injection_observer(
214 web_contents, this, preload_test_fixture_, preload_test_name_);
215 content::TestNavigationObserver navigation_observer(web_contents);
216 chrome::NavigateParams params(
217 browser(), GURL(browse_to), ui::PAGE_TRANSITION_TYPED);
218 params.disposition = CURRENT_TAB;
219 chrome::Navigate(¶ms);
220 navigation_observer.Wait();
221 }
222
223 #if defined(ENABLE_FULL_PRINTING)
224
225 // This custom ContentBrowserClient is used to get notified when a WebContents
226 // for the print preview dialog gets created.
227 class PrintContentBrowserClient : public chrome::ChromeContentBrowserClient {
228 public:
PrintContentBrowserClient(WebUIBrowserTest * browser_test,const std::string & preload_test_fixture,const std::string & preload_test_name)229 PrintContentBrowserClient(WebUIBrowserTest* browser_test,
230 const std::string& preload_test_fixture,
231 const std::string& preload_test_name)
232 : browser_test_(browser_test),
233 preload_test_fixture_(preload_test_fixture),
234 preload_test_name_(preload_test_name),
235 preview_dialog_(NULL),
236 message_loop_runner_(new content::MessageLoopRunner) {}
237
Wait()238 void Wait() {
239 message_loop_runner_->Run();
240 content::WaitForLoadStop(preview_dialog_);
241 }
242
243 private:
244 // ChromeContentBrowserClient implementation:
GetWebContentsViewDelegate(content::WebContents * web_contents)245 virtual content::WebContentsViewDelegate* GetWebContentsViewDelegate(
246 content::WebContents* web_contents) OVERRIDE {
247 preview_dialog_ = web_contents;
248 observer_.reset(new WebUIJsInjectionReadyObserver(preview_dialog_,
249 browser_test_,
250 preload_test_fixture_,
251 preload_test_name_));
252 message_loop_runner_->Quit();
253 return NULL;
254 }
255
256 WebUIBrowserTest* browser_test_;
257 scoped_ptr<WebUIJsInjectionReadyObserver> observer_;
258 std::string preload_test_fixture_;
259 std::string preload_test_name_;
260 content::WebContents* preview_dialog_;
261 scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
262 };
263 #endif
264
BrowsePrintPreload(const GURL & browse_to)265 void WebUIBrowserTest::BrowsePrintPreload(const GURL& browse_to) {
266 #if defined(ENABLE_FULL_PRINTING)
267 ui_test_utils::NavigateToURL(browser(), browse_to);
268
269 PrintContentBrowserClient new_client(
270 this, preload_test_fixture_, preload_test_name_);
271 content::ContentBrowserClient* old_client =
272 SetBrowserClientForTesting(&new_client);
273
274 chrome::Print(browser());
275 new_client.Wait();
276
277 SetBrowserClientForTesting(old_client);
278
279 printing::PrintPreviewDialogController* tab_controller =
280 printing::PrintPreviewDialogController::GetInstance();
281 ASSERT_TRUE(tab_controller);
282 WebContents* preview_dialog = tab_controller->GetPrintPreviewForContents(
283 browser()->tab_strip_model()->GetActiveWebContents());
284 ASSERT_TRUE(preview_dialog);
285 SetWebUIInstance(preview_dialog->GetWebUI());
286 #else
287 NOTREACHED();
288 #endif
289 }
290
291 const char WebUIBrowserTest::kDummyURL[] = "chrome://DummyURL";
292
WebUIBrowserTest()293 WebUIBrowserTest::WebUIBrowserTest()
294 : test_handler_(new WebUITestHandler()),
295 libraries_preloaded_(false),
296 override_selected_web_ui_(NULL) {
297 }
298
set_preload_test_fixture(const std::string & preload_test_fixture)299 void WebUIBrowserTest::set_preload_test_fixture(
300 const std::string& preload_test_fixture) {
301 preload_test_fixture_ = preload_test_fixture;
302 }
303
set_preload_test_name(const std::string & preload_test_name)304 void WebUIBrowserTest::set_preload_test_name(
305 const std::string& preload_test_name) {
306 preload_test_name_ = preload_test_name;
307 }
308
309 namespace {
310
311 // DataSource for the dummy URL. If no data source is provided then an error
312 // page is shown. While this doesn't matter for most tests, without it,
313 // navigation to different anchors cannot be listened to (via the hashchange
314 // event).
315 class MockWebUIDataSource : public content::URLDataSource {
316 public:
MockWebUIDataSource()317 MockWebUIDataSource() {}
318
319 private:
~MockWebUIDataSource()320 virtual ~MockWebUIDataSource() {}
321
GetSource() const322 virtual std::string GetSource() const OVERRIDE { return "dummyurl"; }
323
StartDataRequest(const std::string & path,int render_process_id,int render_frame_id,const content::URLDataSource::GotDataCallback & callback)324 virtual void StartDataRequest(
325 const std::string& path,
326 int render_process_id,
327 int render_frame_id,
328 const content::URLDataSource::GotDataCallback& callback) OVERRIDE {
329 std::string dummy_html = "<html><body>Dummy</body></html>";
330 scoped_refptr<base::RefCountedString> response =
331 base::RefCountedString::TakeString(&dummy_html);
332 callback.Run(response.get());
333 }
334
GetMimeType(const std::string & path) const335 virtual std::string GetMimeType(const std::string& path) const OVERRIDE {
336 return "text/html";
337 }
338
339 DISALLOW_COPY_AND_ASSIGN(MockWebUIDataSource);
340 };
341
342 // WebUIProvider to allow attaching the DataSource for the dummy URL when
343 // testing.
344 class MockWebUIProvider
345 : public TestChromeWebUIControllerFactory::WebUIProvider {
346 public:
MockWebUIProvider()347 MockWebUIProvider() {}
348
349 // Returns a new WebUI
NewWebUI(content::WebUI * web_ui,const GURL & url)350 virtual WebUIController* NewWebUI(content::WebUI* web_ui,
351 const GURL& url) OVERRIDE {
352 WebUIController* controller = new content::WebUIController(web_ui);
353 Profile* profile = Profile::FromWebUI(web_ui);
354 content::URLDataSource::Add(profile, new MockWebUIDataSource());
355 return controller;
356 }
357
358 private:
359 DISALLOW_COPY_AND_ASSIGN(MockWebUIProvider);
360 };
361
362 base::LazyInstance<MockWebUIProvider> mock_provider_ =
363 LAZY_INSTANCE_INITIALIZER;
364
365 } // namespace
366
SetUpOnMainThread()367 void WebUIBrowserTest::SetUpOnMainThread() {
368 JavaScriptBrowserTest::SetUpOnMainThread();
369
370 logging::SetLogMessageHandler(&LogHandler);
371
372 AddLibrary(base::FilePath(kA11yAuditLibraryJSPath));
373
374 content::WebUIControllerFactory::UnregisterFactoryForTesting(
375 ChromeWebUIControllerFactory::GetInstance());
376
377 test_factory_.reset(new TestChromeWebUIControllerFactory);
378
379 content::WebUIControllerFactory::RegisterFactory(test_factory_.get());
380
381 test_factory_->AddFactoryOverride(GURL(kDummyURL).host(),
382 mock_provider_.Pointer());
383 }
384
TearDownOnMainThread()385 void WebUIBrowserTest::TearDownOnMainThread() {
386 logging::SetLogMessageHandler(NULL);
387
388 test_factory_->RemoveFactoryOverride(GURL(kDummyURL).host());
389 content::WebUIControllerFactory::UnregisterFactoryForTesting(
390 test_factory_.get());
391
392 // This is needed to avoid a debug assert after the test completes, see stack
393 // trace in http://crrev.com/179347
394 content::WebUIControllerFactory::RegisterFactory(
395 ChromeWebUIControllerFactory::GetInstance());
396
397 test_factory_.reset();
398 }
399
SetWebUIInstance(content::WebUI * web_ui)400 void WebUIBrowserTest::SetWebUIInstance(content::WebUI* web_ui) {
401 override_selected_web_ui_ = web_ui;
402 }
403
GetMockMessageHandler()404 WebUIMessageHandler* WebUIBrowserTest::GetMockMessageHandler() {
405 return NULL;
406 }
407
RunJavascriptUsingHandler(const std::string & function_name,const ConstValueVector & function_arguments,bool is_test,bool is_async,RenderViewHost * preload_host)408 bool WebUIBrowserTest::RunJavascriptUsingHandler(
409 const std::string& function_name,
410 const ConstValueVector& function_arguments,
411 bool is_test,
412 bool is_async,
413 RenderViewHost* preload_host) {
414 // Get the user libraries. Preloading them individually is best, then
415 // we can assign each one a filename for better stack traces. Otherwise
416 // append them all to |content|.
417 base::string16 content;
418 std::vector<base::string16> libraries;
419 if (!libraries_preloaded_) {
420 BuildJavascriptLibraries(&libraries);
421 if (!preload_host) {
422 content = JoinString(libraries, '\n');
423 libraries.clear();
424 }
425 }
426
427 if (!function_name.empty()) {
428 base::string16 called_function;
429 if (is_test) {
430 called_function =
431 BuildRunTestJSCall(is_async, function_name, function_arguments);
432 } else {
433 called_function = content::WebUI::GetJavascriptCall(
434 function_name, function_arguments.get());
435 }
436 content.append(called_function);
437 }
438
439 if (!preload_host)
440 SetupHandlers();
441
442 bool result = true;
443
444 for (size_t i = 0; i < libraries.size(); ++i)
445 test_handler_->PreloadJavaScript(libraries[i], preload_host);
446
447 if (is_test)
448 result = test_handler_->RunJavaScriptTestWithResult(content);
449 else if (preload_host)
450 test_handler_->PreloadJavaScript(content, preload_host);
451 else
452 test_handler_->RunJavaScript(content);
453
454 if (error_messages_.Get().size() > 0) {
455 LOG(ERROR) << "Encountered javascript console error(s)";
456 result = false;
457 error_messages_.Get().clear();
458 }
459 return result;
460 }
461
SetupHandlers()462 void WebUIBrowserTest::SetupHandlers() {
463 content::WebUI* web_ui_instance =
464 override_selected_web_ui_
465 ? override_selected_web_ui_
466 : browser()->tab_strip_model()->GetActiveWebContents()->GetWebUI();
467 ASSERT_TRUE(web_ui_instance != NULL);
468
469 test_handler_->set_web_ui(web_ui_instance);
470 test_handler_->RegisterMessages();
471
472 if (GetMockMessageHandler()) {
473 GetMockMessageHandler()->set_web_ui(web_ui_instance);
474 GetMockMessageHandler()->RegisterMessages();
475 }
476 }
477
WebUITestDataPathToURL(const base::FilePath::StringType & path)478 GURL WebUIBrowserTest::WebUITestDataPathToURL(
479 const base::FilePath::StringType& path) {
480 base::FilePath dir_test_data;
481 EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &dir_test_data));
482 base::FilePath test_path(dir_test_data.Append(kWebUITestFolder).Append(path));
483 EXPECT_TRUE(base::PathExists(test_path));
484 return net::FilePathToFileURL(test_path);
485 }
486