1// Copyright (c) 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.
4
5#include "chrome/test/base/ui_test_utils.h"
6
7#if defined(OS_WIN)
8#include <windows.h>
9#endif
10
11#include "base/bind.h"
12#include "base/bind_helpers.h"
13#include "base/callback.h"
14#include "base/command_line.h"
15#include "base/files/file_path.h"
16#include "base/files/file_util.h"
17#include "base/json/json_reader.h"
18#include "base/memory/ref_counted.h"
19#include "base/memory/scoped_ptr.h"
20#include "base/path_service.h"
21#include "base/prefs/pref_service.h"
22#include "base/strings/stringprintf.h"
23#include "base/strings/utf_string_conversions.h"
24#include "base/test/test_timeouts.h"
25#include "base/time/time.h"
26#include "base/values.h"
27#include "chrome/browser/autocomplete/autocomplete_controller.h"
28#include "chrome/browser/bookmarks/bookmark_model_factory.h"
29#include "chrome/browser/browser_process.h"
30#include "chrome/browser/chrome_notification_types.h"
31#include "chrome/browser/extensions/extension_action.h"
32#include "chrome/browser/history/history_service_factory.h"
33#include "chrome/browser/profiles/profile.h"
34#include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog.h"
35#include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h"
36#include "chrome/browser/ui/browser.h"
37#include "chrome/browser/ui/browser_commands.h"
38#include "chrome/browser/ui/browser_finder.h"
39#include "chrome/browser/ui/browser_iterator.h"
40#include "chrome/browser/ui/browser_list.h"
41#include "chrome/browser/ui/browser_navigator.h"
42#include "chrome/browser/ui/browser_window.h"
43#include "chrome/browser/ui/find_bar/find_notification_details.h"
44#include "chrome/browser/ui/find_bar/find_tab_helper.h"
45#include "chrome/browser/ui/host_desktop.h"
46#include "chrome/browser/ui/location_bar/location_bar.h"
47#include "chrome/browser/ui/omnibox/omnibox_view.h"
48#include "chrome/browser/ui/tabs/tab_strip_model.h"
49#include "chrome/common/chrome_paths.h"
50#include "chrome/common/pref_names.h"
51#include "chrome/test/base/find_in_page_observer.h"
52#include "components/bookmarks/browser/bookmark_model.h"
53#include "components/search_engines/template_url_service.h"
54#include "content/public/browser/dom_operation_notification_details.h"
55#include "content/public/browser/download_item.h"
56#include "content/public/browser/download_manager.h"
57#include "content/public/browser/geolocation_provider.h"
58#include "content/public/browser/navigation_controller.h"
59#include "content/public/browser/navigation_entry.h"
60#include "content/public/browser/notification_service.h"
61#include "content/public/browser/render_process_host.h"
62#include "content/public/browser/render_view_host.h"
63#include "content/public/browser/web_contents.h"
64#include "content/public/browser/web_contents_observer.h"
65#include "content/public/common/geoposition.h"
66#include "content/public/test/browser_test_utils.h"
67#include "content/public/test/download_test_observer.h"
68#include "content/public/test/test_navigation_observer.h"
69#include "content/public/test/test_utils.h"
70#include "net/base/filename_util.h"
71#include "net/cookies/cookie_constants.h"
72#include "net/cookies/cookie_monster.h"
73#include "net/cookies/cookie_store.h"
74#include "net/test/python_utils.h"
75#include "net/url_request/url_request_context.h"
76#include "net/url_request/url_request_context_getter.h"
77#include "third_party/skia/include/core/SkBitmap.h"
78#include "third_party/skia/include/core/SkColor.h"
79#include "ui/gfx/size.h"
80#include "ui/snapshot/test/snapshot_desktop.h"
81
82#if defined(USE_AURA)
83#include "ash/shell.h"
84#include "ui/aura/window_event_dispatcher.h"
85#endif
86
87using content::DomOperationNotificationDetails;
88using content::NativeWebKeyboardEvent;
89using content::NavigationController;
90using content::NavigationEntry;
91using content::OpenURLParams;
92using content::RenderViewHost;
93using content::RenderWidgetHost;
94using content::Referrer;
95using content::WebContents;
96
97namespace ui_test_utils {
98
99namespace {
100
101#if defined(OS_WIN)
102const char kSnapshotBaseName[] = "ChromiumSnapshot";
103const char kSnapshotExtension[] = ".png";
104
105base::FilePath GetSnapshotFileName(const base::FilePath& snapshot_directory) {
106  base::Time::Exploded the_time;
107
108  base::Time::Now().LocalExplode(&the_time);
109  std::string filename(base::StringPrintf("%s%04d%02d%02d%02d%02d%02d%s",
110      kSnapshotBaseName, the_time.year, the_time.month, the_time.day_of_month,
111      the_time.hour, the_time.minute, the_time.second, kSnapshotExtension));
112
113  base::FilePath snapshot_file = snapshot_directory.AppendASCII(filename);
114  if (base::PathExists(snapshot_file)) {
115    int index = 0;
116    std::string suffix;
117    base::FilePath trial_file;
118    do {
119      suffix = base::StringPrintf(" (%d)", ++index);
120      trial_file = snapshot_file.InsertBeforeExtensionASCII(suffix);
121    } while (base::PathExists(trial_file));
122    snapshot_file = trial_file;
123  }
124  return snapshot_file;
125}
126#endif  // defined(OS_WIN)
127
128Browser* WaitForBrowserNotInSet(std::set<Browser*> excluded_browsers) {
129  Browser* new_browser = GetBrowserNotInSet(excluded_browsers);
130  if (new_browser == NULL) {
131    BrowserAddedObserver observer;
132    new_browser = observer.WaitForSingleNewBrowser();
133    // The new browser should never be in |excluded_browsers|.
134    DCHECK(!ContainsKey(excluded_browsers, new_browser));
135  }
136  return new_browser;
137}
138
139}  // namespace
140
141bool GetCurrentTabTitle(const Browser* browser, base::string16* title) {
142  WebContents* web_contents =
143      browser->tab_strip_model()->GetActiveWebContents();
144  if (!web_contents)
145    return false;
146  NavigationEntry* last_entry = web_contents->GetController().GetActiveEntry();
147  if (!last_entry)
148    return false;
149  title->assign(last_entry->GetTitleForDisplay(std::string()));
150  return true;
151}
152
153Browser* OpenURLOffTheRecord(Profile* profile, const GURL& url) {
154  chrome::HostDesktopType active_desktop = chrome::GetActiveDesktop();
155  chrome::OpenURLOffTheRecord(profile, url, active_desktop);
156  Browser* browser = chrome::FindTabbedBrowser(
157      profile->GetOffTheRecordProfile(), false, active_desktop);
158  content::TestNavigationObserver observer(
159      browser->tab_strip_model()->GetActiveWebContents());
160  observer.Wait();
161  return browser;
162}
163
164void NavigateToURL(chrome::NavigateParams* params) {
165  chrome::Navigate(params);
166  content::WaitForLoadStop(params->target_contents);
167}
168
169
170void NavigateToURLWithPost(Browser* browser, const GURL& url) {
171  chrome::NavigateParams params(browser, url,
172                                ui::PAGE_TRANSITION_FORM_SUBMIT);
173  params.uses_post = true;
174  NavigateToURL(&params);
175}
176
177void NavigateToURL(Browser* browser, const GURL& url) {
178  NavigateToURLWithDisposition(browser, url, CURRENT_TAB,
179                               BROWSER_TEST_WAIT_FOR_NAVIGATION);
180}
181
182// Navigates the specified tab (via |disposition|) of |browser| to |url|,
183// blocking until the |number_of_navigations| specified complete.
184// |disposition| indicates what tab the download occurs in, and
185// |browser_test_flags| controls what to wait for before continuing.
186static void NavigateToURLWithDispositionBlockUntilNavigationsComplete(
187    Browser* browser,
188    const GURL& url,
189    int number_of_navigations,
190    WindowOpenDisposition disposition,
191    int browser_test_flags) {
192  TabStripModel* tab_strip = browser->tab_strip_model();
193  if (disposition == CURRENT_TAB && tab_strip->GetActiveWebContents())
194      content::WaitForLoadStop(tab_strip->GetActiveWebContents());
195  content::TestNavigationObserver same_tab_observer(
196      tab_strip->GetActiveWebContents(),
197      number_of_navigations);
198
199  std::set<Browser*> initial_browsers;
200  for (chrome::BrowserIterator it; !it.done(); it.Next())
201    initial_browsers.insert(*it);
202
203  content::WindowedNotificationObserver tab_added_observer(
204      chrome::NOTIFICATION_TAB_ADDED,
205      content::NotificationService::AllSources());
206
207  browser->OpenURL(OpenURLParams(
208      url, Referrer(), disposition, ui::PAGE_TRANSITION_TYPED, false));
209  if (browser_test_flags & BROWSER_TEST_WAIT_FOR_BROWSER)
210    browser = WaitForBrowserNotInSet(initial_browsers);
211  if (browser_test_flags & BROWSER_TEST_WAIT_FOR_TAB)
212    tab_added_observer.Wait();
213  if (!(browser_test_flags & BROWSER_TEST_WAIT_FOR_NAVIGATION)) {
214    // Some other flag caused the wait prior to this.
215    return;
216  }
217  WebContents* web_contents = NULL;
218  if (disposition == NEW_BACKGROUND_TAB) {
219    // We've opened up a new tab, but not selected it.
220    TabStripModel* tab_strip = browser->tab_strip_model();
221    web_contents = tab_strip->GetWebContentsAt(tab_strip->active_index() + 1);
222    EXPECT_TRUE(web_contents != NULL)
223        << " Unable to wait for navigation to \"" << url.spec()
224        << "\" because the new tab is not available yet";
225    if (!web_contents)
226      return;
227  } else if ((disposition == CURRENT_TAB) ||
228      (disposition == NEW_FOREGROUND_TAB) ||
229      (disposition == SINGLETON_TAB)) {
230    // The currently selected tab is the right one.
231    web_contents = browser->tab_strip_model()->GetActiveWebContents();
232  }
233  if (disposition == CURRENT_TAB) {
234    same_tab_observer.Wait();
235    return;
236  } else if (web_contents) {
237    content::TestNavigationObserver observer(web_contents,
238                                             number_of_navigations);
239    observer.Wait();
240    return;
241  }
242  EXPECT_TRUE(NULL != web_contents) << " Unable to wait for navigation to \""
243                                    << url.spec() << "\""
244                                    << " because we can't get the tab contents";
245}
246
247void NavigateToURLWithDisposition(Browser* browser,
248                                  const GURL& url,
249                                  WindowOpenDisposition disposition,
250                                  int browser_test_flags) {
251  NavigateToURLWithDispositionBlockUntilNavigationsComplete(
252      browser,
253      url,
254      1,
255      disposition,
256      browser_test_flags);
257}
258
259void NavigateToURLBlockUntilNavigationsComplete(Browser* browser,
260                                                const GURL& url,
261                                                int number_of_navigations) {
262  NavigateToURLWithDispositionBlockUntilNavigationsComplete(
263      browser,
264      url,
265      number_of_navigations,
266      CURRENT_TAB,
267      BROWSER_TEST_WAIT_FOR_NAVIGATION);
268}
269
270base::FilePath GetTestFilePath(const base::FilePath& dir,
271                               const base::FilePath& file) {
272  base::FilePath path;
273  PathService::Get(chrome::DIR_TEST_DATA, &path);
274  return path.Append(dir).Append(file);
275}
276
277GURL GetTestUrl(const base::FilePath& dir, const base::FilePath& file) {
278  return net::FilePathToFileURL(GetTestFilePath(dir, file));
279}
280
281bool GetRelativeBuildDirectory(base::FilePath* build_dir) {
282  // This function is used to find the build directory so TestServer can serve
283  // built files (nexes, etc).  TestServer expects a path relative to the source
284  // root.
285  base::FilePath exe_dir =
286      CommandLine::ForCurrentProcess()->GetProgram().DirName();
287  base::FilePath src_dir;
288  if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir))
289    return false;
290
291  // We must first generate absolute paths to SRC and EXE and from there
292  // generate a relative path.
293  if (!exe_dir.IsAbsolute())
294    exe_dir = base::MakeAbsoluteFilePath(exe_dir);
295  if (!src_dir.IsAbsolute())
296    src_dir = base::MakeAbsoluteFilePath(src_dir);
297  if (!exe_dir.IsAbsolute())
298    return false;
299  if (!src_dir.IsAbsolute())
300    return false;
301
302  size_t match, exe_size, src_size;
303  std::vector<base::FilePath::StringType> src_parts, exe_parts;
304
305  // Determine point at which src and exe diverge.
306  exe_dir.GetComponents(&exe_parts);
307  src_dir.GetComponents(&src_parts);
308  exe_size = exe_parts.size();
309  src_size = src_parts.size();
310  for (match = 0; match < exe_size && match < src_size; ++match) {
311    if (exe_parts[match] != src_parts[match])
312      break;
313  }
314
315  // Create a relative path.
316  *build_dir = base::FilePath();
317  for (size_t tmp_itr = match; tmp_itr < src_size; ++tmp_itr)
318    *build_dir = build_dir->Append(FILE_PATH_LITERAL(".."));
319  for (; match < exe_size; ++match)
320    *build_dir = build_dir->Append(exe_parts[match]);
321  return true;
322}
323
324AppModalDialog* WaitForAppModalDialog() {
325  AppModalDialogQueue* dialog_queue = AppModalDialogQueue::GetInstance();
326  if (dialog_queue->HasActiveDialog())
327    return dialog_queue->active_dialog();
328
329  content::WindowedNotificationObserver observer(
330      chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN,
331      content::NotificationService::AllSources());
332  observer.Wait();
333  return content::Source<AppModalDialog>(observer.source()).ptr();
334}
335
336int FindInPage(WebContents* tab,
337               const base::string16& search_string,
338               bool forward,
339               bool match_case,
340               int* ordinal,
341               gfx::Rect* selection_rect) {
342  FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(tab);
343  find_tab_helper->StartFinding(search_string, forward, match_case);
344  FindInPageNotificationObserver observer(tab);
345  observer.Wait();
346  if (ordinal)
347    *ordinal = observer.active_match_ordinal();
348  if (selection_rect)
349    *selection_rect = observer.selection_rect();
350  return observer.number_of_matches();
351}
352
353void WaitForTemplateURLServiceToLoad(TemplateURLService* service) {
354  if (service->loaded())
355    return;
356  scoped_refptr<content::MessageLoopRunner> message_loop_runner =
357      new content::MessageLoopRunner;
358  scoped_ptr<TemplateURLService::Subscription> subscription =
359      service->RegisterOnLoadedCallback(
360          message_loop_runner->QuitClosure());
361  service->Load();
362  message_loop_runner->Run();
363
364  ASSERT_TRUE(service->loaded());
365}
366
367void WaitForHistoryToLoad(HistoryService* history_service) {
368  content::WindowedNotificationObserver history_loaded_observer(
369      chrome::NOTIFICATION_HISTORY_LOADED,
370      content::NotificationService::AllSources());
371  if (!history_service->BackendLoaded())
372    history_loaded_observer.Wait();
373}
374
375void DownloadURL(Browser* browser, const GURL& download_url) {
376  base::ScopedTempDir downloads_directory;
377  ASSERT_TRUE(downloads_directory.CreateUniqueTempDir());
378  browser->profile()->GetPrefs()->SetFilePath(
379      prefs::kDownloadDefaultDirectory, downloads_directory.path());
380
381  content::DownloadManager* download_manager =
382      content::BrowserContext::GetDownloadManager(browser->profile());
383  scoped_ptr<content::DownloadTestObserver> observer(
384      new content::DownloadTestObserverTerminal(
385          download_manager, 1,
386          content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_ACCEPT));
387
388  ui_test_utils::NavigateToURL(browser, download_url);
389  observer->WaitForFinished();
390}
391
392void SendToOmniboxAndSubmit(LocationBar* location_bar,
393                            const std::string& input) {
394  OmniboxView* omnibox = location_bar->GetOmniboxView();
395  omnibox->model()->OnSetFocus(false);
396  omnibox->SetUserText(base::ASCIIToUTF16(input));
397  location_bar->AcceptInput();
398  while (!omnibox->model()->autocomplete_controller()->done()) {
399    content::WindowedNotificationObserver observer(
400        chrome::NOTIFICATION_AUTOCOMPLETE_CONTROLLER_RESULT_READY,
401        content::NotificationService::AllSources());
402    observer.Wait();
403  }
404}
405
406Browser* GetBrowserNotInSet(std::set<Browser*> excluded_browsers) {
407  for (chrome::BrowserIterator it; !it.done(); it.Next()) {
408    if (excluded_browsers.find(*it) == excluded_browsers.end())
409      return *it;
410  }
411  return NULL;
412}
413
414namespace {
415
416void GetCookiesCallback(base::WaitableEvent* event,
417                        std::string* cookies,
418                        const std::string& cookie_line) {
419  *cookies = cookie_line;
420  event->Signal();
421}
422
423void GetCookiesOnIOThread(
424    const GURL& url,
425    const scoped_refptr<net::URLRequestContextGetter>& context_getter,
426    base::WaitableEvent* event,
427    std::string* cookies) {
428  context_getter->GetURLRequestContext()->cookie_store()->
429      GetCookiesWithOptionsAsync(
430          url, net::CookieOptions(),
431          base::Bind(&GetCookiesCallback, event, cookies));
432}
433
434}  // namespace
435
436void GetCookies(const GURL& url,
437                WebContents* contents,
438                int* value_size,
439                std::string* value) {
440  *value_size = -1;
441  if (url.is_valid() && contents) {
442    scoped_refptr<net::URLRequestContextGetter> context_getter =
443        contents->GetBrowserContext()->GetRequestContextForRenderProcess(
444            contents->GetRenderProcessHost()->GetID());
445    base::WaitableEvent event(true /* manual reset */,
446                              false /* not initially signaled */);
447    CHECK(content::BrowserThread::PostTask(
448        content::BrowserThread::IO, FROM_HERE,
449        base::Bind(&GetCookiesOnIOThread, url, context_getter, &event, value)));
450    event.Wait();
451
452    *value_size = static_cast<int>(value->size());
453  }
454}
455
456WindowedTabAddedNotificationObserver::WindowedTabAddedNotificationObserver(
457    const content::NotificationSource& source)
458    : WindowedNotificationObserver(chrome::NOTIFICATION_TAB_ADDED, source),
459      added_tab_(NULL) {
460}
461
462void WindowedTabAddedNotificationObserver::Observe(
463    int type,
464    const content::NotificationSource& source,
465    const content::NotificationDetails& details) {
466  added_tab_ = content::Details<WebContents>(details).ptr();
467  content::WindowedNotificationObserver::Observe(type, source, details);
468}
469
470UrlLoadObserver::UrlLoadObserver(const GURL& url,
471                                 const content::NotificationSource& source)
472    : WindowedNotificationObserver(content::NOTIFICATION_LOAD_STOP, source),
473      url_(url) {
474}
475
476UrlLoadObserver::~UrlLoadObserver() {}
477
478void UrlLoadObserver::Observe(
479    int type,
480    const content::NotificationSource& source,
481    const content::NotificationDetails& details) {
482  NavigationController* controller =
483      content::Source<NavigationController>(source).ptr();
484  if (controller->GetWebContents()->GetURL() != url_)
485    return;
486
487  WindowedNotificationObserver::Observe(type, source, details);
488}
489
490BrowserAddedObserver::BrowserAddedObserver()
491    : notification_observer_(
492          chrome::NOTIFICATION_BROWSER_OPENED,
493          content::NotificationService::AllSources()) {
494  for (chrome::BrowserIterator it; !it.done(); it.Next())
495    original_browsers_.insert(*it);
496}
497
498BrowserAddedObserver::~BrowserAddedObserver() {
499}
500
501Browser* BrowserAddedObserver::WaitForSingleNewBrowser() {
502  notification_observer_.Wait();
503  // Ensure that only a single new browser has appeared.
504  EXPECT_EQ(original_browsers_.size() + 1, chrome::GetTotalBrowserCount());
505  return GetBrowserNotInSet(original_browsers_);
506}
507
508#if defined(OS_WIN)
509
510bool SaveScreenSnapshotToDirectory(const base::FilePath& directory,
511                                   base::FilePath* screenshot_path) {
512  bool succeeded = false;
513  base::FilePath out_path(GetSnapshotFileName(directory));
514
515  MONITORINFO monitor_info = {};
516  monitor_info.cbSize = sizeof(monitor_info);
517  HMONITOR main_monitor = MonitorFromWindow(NULL, MONITOR_DEFAULTTOPRIMARY);
518  if (GetMonitorInfo(main_monitor, &monitor_info)) {
519    RECT& rect = monitor_info.rcMonitor;
520
521    std::vector<unsigned char> png_data;
522    gfx::Rect bounds(
523        gfx::Size(rect.right - rect.left, rect.bottom - rect.top));
524    if (ui::GrabDesktopSnapshot(bounds, &png_data) &&
525        png_data.size() <= INT_MAX) {
526      int bytes = static_cast<int>(png_data.size());
527      int written = base::WriteFile(
528          out_path, reinterpret_cast<char*>(&png_data[0]), bytes);
529      succeeded = (written == bytes);
530    }
531  }
532
533  if (succeeded && screenshot_path != NULL)
534    *screenshot_path = out_path;
535
536  return succeeded;
537}
538
539bool SaveScreenSnapshotToDesktop(base::FilePath* screenshot_path) {
540  base::FilePath desktop;
541
542  return PathService::Get(base::DIR_USER_DESKTOP, &desktop) &&
543      SaveScreenSnapshotToDirectory(desktop, screenshot_path);
544}
545
546#endif  // defined(OS_WIN)
547
548void OverrideGeolocation(double latitude, double longitude) {
549  content::Geoposition position;
550  position.latitude = latitude;
551  position.longitude = longitude;
552  position.altitude = 0.;
553  position.accuracy = 0.;
554  position.timestamp = base::Time::Now();
555  content::GeolocationProvider::GetInstance()->OverrideLocationForTesting(
556      position);
557}
558
559HistoryEnumerator::HistoryEnumerator(Profile* profile) {
560  scoped_refptr<content::MessageLoopRunner> message_loop_runner =
561      new content::MessageLoopRunner;
562
563  HistoryService* hs = HistoryServiceFactory::GetForProfile(
564      profile, Profile::EXPLICIT_ACCESS);
565  hs->QueryHistory(base::string16(),
566                   history::QueryOptions(),
567                   base::Bind(&HistoryEnumerator::HistoryQueryComplete,
568                              base::Unretained(this),
569                              message_loop_runner->QuitClosure()),
570                   &tracker_);
571  message_loop_runner->Run();
572}
573
574HistoryEnumerator::~HistoryEnumerator() {}
575
576void HistoryEnumerator::HistoryQueryComplete(
577    const base::Closure& quit_task,
578    history::QueryResults* results) {
579  for (size_t i = 0; i < results->size(); ++i)
580    urls_.push_back((*results)[i].url());
581  quit_task.Run();
582}
583
584}  // namespace ui_test_utils
585