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. 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: 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 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 87 WebUIBrowserTest::~WebUIBrowserTest() { 88 } 89 90 bool WebUIBrowserTest::RunJavascriptFunction(const std::string& function_name) { 91 ConstValueVector empty_args; 92 return RunJavascriptFunction(function_name, empty_args); 93 } 94 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 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 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 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 131 bool WebUIBrowserTest::RunJavascriptTest(const std::string& test_name) { 132 ConstValueVector empty_args; 133 return RunJavascriptTest(test_name, empty_args); 134 } 135 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 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 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 159 bool WebUIBrowserTest::RunJavascriptAsyncTest(const std::string& test_name) { 160 ConstValueVector empty_args; 161 return RunJavascriptAsyncTest(test_name, empty_args); 162 } 163 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 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 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 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 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 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: 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 238 void Wait() { 239 message_loop_runner_->Run(); 240 content::WaitForLoadStop(preview_dialog_); 241 } 242 243 private: 244 // ChromeContentBrowserClient implementation: 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 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 293 WebUIBrowserTest::WebUIBrowserTest() 294 : test_handler_(new WebUITestHandler()), 295 libraries_preloaded_(false), 296 override_selected_web_ui_(NULL) { 297 } 298 299 void WebUIBrowserTest::set_preload_test_fixture( 300 const std::string& preload_test_fixture) { 301 preload_test_fixture_ = preload_test_fixture; 302 } 303 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: 317 MockWebUIDataSource() {} 318 319 private: 320 virtual ~MockWebUIDataSource() {} 321 322 virtual std::string GetSource() const OVERRIDE { return "dummyurl"; } 323 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 335 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: 347 MockWebUIProvider() {} 348 349 // Returns a new WebUI 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 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 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 400 void WebUIBrowserTest::SetWebUIInstance(content::WebUI* web_ui) { 401 override_selected_web_ui_ = web_ui; 402 } 403 404 WebUIMessageHandler* WebUIBrowserTest::GetMockMessageHandler() { 405 return NULL; 406 } 407 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 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 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