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
39using content::RenderViewHost;
40using content::WebContents;
41using content::WebUIController;
42using content::WebUIMessageHandler;
43
44namespace {
45
46base::LazyInstance<std::vector<std::string> > error_messages_ =
47    LAZY_INSTANCE_INITIALIZER;
48
49// Intercepts all log messages.
50bool 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
63class 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
87WebUIBrowserTest::~WebUIBrowserTest() {
88}
89
90bool WebUIBrowserTest::RunJavascriptFunction(const std::string& function_name) {
91  ConstValueVector empty_args;
92  return RunJavascriptFunction(function_name, empty_args);
93}
94
95bool 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
102bool 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
111bool 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
118bool 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
131bool WebUIBrowserTest::RunJavascriptTest(const std::string& test_name) {
132  ConstValueVector empty_args;
133  return RunJavascriptTest(test_name, empty_args);
134}
135
136bool 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
143bool 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
152bool 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
159bool WebUIBrowserTest::RunJavascriptAsyncTest(const std::string& test_name) {
160  ConstValueVector empty_args;
161  return RunJavascriptAsyncTest(test_name, empty_args);
162}
163
164bool 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
171bool 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
180bool 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
191bool 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
197void 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
210void 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(&params);
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.
227class 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
265void 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
291const char WebUIBrowserTest::kDummyURL[] = "chrome://DummyURL";
292
293WebUIBrowserTest::WebUIBrowserTest()
294    : test_handler_(new WebUITestHandler()),
295      libraries_preloaded_(false),
296      override_selected_web_ui_(NULL) {
297}
298
299void WebUIBrowserTest::set_preload_test_fixture(
300    const std::string& preload_test_fixture) {
301  preload_test_fixture_ = preload_test_fixture;
302}
303
304void WebUIBrowserTest::set_preload_test_name(
305    const std::string& preload_test_name) {
306  preload_test_name_ = preload_test_name;
307}
308
309namespace {
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).
315class 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.
344class 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
362base::LazyInstance<MockWebUIProvider> mock_provider_ =
363    LAZY_INSTANCE_INITIALIZER;
364
365}  // namespace
366
367void 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
385void 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
400void WebUIBrowserTest::SetWebUIInstance(content::WebUI* web_ui) {
401  override_selected_web_ui_ = web_ui;
402}
403
404WebUIMessageHandler* WebUIBrowserTest::GetMockMessageHandler() {
405  return NULL;
406}
407
408bool 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
462void 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
478GURL 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