// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/test/base/ui_test_utils.h"
#if defined(OS_WIN)
#include <windows.h>
#endif
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/prefs/pref_service.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_timeouts.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/autocomplete/autocomplete_controller.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/devtools/devtools_window.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service.h"
#include "chrome/browser/search_engines/template_url_service_test_util.h"
#include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog.h"
#include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_iterator.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/find_bar/find_notification_details.h"
#include "chrome/browser/ui/find_bar/find_tab_helper.h"
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/browser/ui/omnibox/location_bar.h"
#include "chrome/browser/ui/omnibox/omnibox_view.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/find_in_page_observer.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "content/public/browser/dom_operation_notification_details.h"
#include "content/public/browser/download_item.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/geolocation_provider.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/geoposition.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/base/filename_util.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_monster.h"
#include "net/cookies/cookie_store.h"
#include "net/test/python_utils.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/size.h"
#include "ui/snapshot/test/snapshot_desktop.h"
#if defined(USE_AURA)
#include "ash/shell.h"
#include "ui/aura/window_event_dispatcher.h"
#endif
using content::DomOperationNotificationDetails;
using content::NativeWebKeyboardEvent;
using content::NavigationController;
using content::NavigationEntry;
using content::OpenURLParams;
using content::RenderViewHost;
using content::RenderWidgetHost;
using content::Referrer;
using content::WebContents;
namespace ui_test_utils {
namespace {
#if defined(OS_WIN)
const char kSnapshotBaseName[] = "ChromiumSnapshot";
const char kSnapshotExtension[] = ".png";
base::FilePath GetSnapshotFileName(const base::FilePath& snapshot_directory) {
base::Time::Exploded the_time;
base::Time::Now().LocalExplode(&the_time);
std::string filename(base::StringPrintf("%s%04d%02d%02d%02d%02d%02d%s",
kSnapshotBaseName, the_time.year, the_time.month, the_time.day_of_month,
the_time.hour, the_time.minute, the_time.second, kSnapshotExtension));
base::FilePath snapshot_file = snapshot_directory.AppendASCII(filename);
if (base::PathExists(snapshot_file)) {
int index = 0;
std::string suffix;
base::FilePath trial_file;
do {
suffix = base::StringPrintf(" (%d)", ++index);
trial_file = snapshot_file.InsertBeforeExtensionASCII(suffix);
} while (base::PathExists(trial_file));
snapshot_file = trial_file;
}
return snapshot_file;
}
#endif // defined(OS_WIN)
Browser* WaitForBrowserNotInSet(std::set<Browser*> excluded_browsers) {
Browser* new_browser = GetBrowserNotInSet(excluded_browsers);
if (new_browser == NULL) {
BrowserAddedObserver observer;
new_browser = observer.WaitForSingleNewBrowser();
// The new browser should never be in |excluded_browsers|.
DCHECK(!ContainsKey(excluded_browsers, new_browser));
}
return new_browser;
}
} // namespace
bool GetCurrentTabTitle(const Browser* browser, base::string16* title) {
WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents();
if (!web_contents)
return false;
NavigationEntry* last_entry = web_contents->GetController().GetActiveEntry();
if (!last_entry)
return false;
title->assign(last_entry->GetTitleForDisplay(std::string()));
return true;
}
Browser* OpenURLOffTheRecord(Profile* profile, const GURL& url) {
chrome::HostDesktopType active_desktop = chrome::GetActiveDesktop();
chrome::OpenURLOffTheRecord(profile, url, active_desktop);
Browser* browser = chrome::FindTabbedBrowser(
profile->GetOffTheRecordProfile(), false, active_desktop);
content::TestNavigationObserver observer(
browser->tab_strip_model()->GetActiveWebContents());
observer.Wait();
return browser;
}
void NavigateToURL(chrome::NavigateParams* params) {
chrome::Navigate(params);
content::WaitForLoadStop(params->target_contents);
}
void NavigateToURLWithPost(Browser* browser, const GURL& url) {
chrome::NavigateParams params(browser, url,
content::PAGE_TRANSITION_FORM_SUBMIT);
params.uses_post = true;
NavigateToURL(¶ms);
}
void NavigateToURL(Browser* browser, const GURL& url) {
NavigateToURLWithDisposition(browser, url, CURRENT_TAB,
BROWSER_TEST_WAIT_FOR_NAVIGATION);
}
// Navigates the specified tab (via |disposition|) of |browser| to |url|,
// blocking until the |number_of_navigations| specified complete.
// |disposition| indicates what tab the download occurs in, and
// |browser_test_flags| controls what to wait for before continuing.
static void NavigateToURLWithDispositionBlockUntilNavigationsComplete(
Browser* browser,
const GURL& url,
int number_of_navigations,
WindowOpenDisposition disposition,
int browser_test_flags) {
TabStripModel* tab_strip = browser->tab_strip_model();
if (disposition == CURRENT_TAB && tab_strip->GetActiveWebContents())
content::WaitForLoadStop(tab_strip->GetActiveWebContents());
content::TestNavigationObserver same_tab_observer(
tab_strip->GetActiveWebContents(),
number_of_navigations);
std::set<Browser*> initial_browsers;
for (chrome::BrowserIterator it; !it.done(); it.Next())
initial_browsers.insert(*it);
content::WindowedNotificationObserver tab_added_observer(
chrome::NOTIFICATION_TAB_ADDED,
content::NotificationService::AllSources());
browser->OpenURL(OpenURLParams(
url, Referrer(), disposition, content::PAGE_TRANSITION_TYPED, false));
if (browser_test_flags & BROWSER_TEST_WAIT_FOR_BROWSER)
browser = WaitForBrowserNotInSet(initial_browsers);
if (browser_test_flags & BROWSER_TEST_WAIT_FOR_TAB)
tab_added_observer.Wait();
if (!(browser_test_flags & BROWSER_TEST_WAIT_FOR_NAVIGATION)) {
// Some other flag caused the wait prior to this.
return;
}
WebContents* web_contents = NULL;
if (disposition == NEW_BACKGROUND_TAB) {
// We've opened up a new tab, but not selected it.
TabStripModel* tab_strip = browser->tab_strip_model();
web_contents = tab_strip->GetWebContentsAt(tab_strip->active_index() + 1);
EXPECT_TRUE(web_contents != NULL)
<< " Unable to wait for navigation to \"" << url.spec()
<< "\" because the new tab is not available yet";
if (!web_contents)
return;
} else if ((disposition == CURRENT_TAB) ||
(disposition == NEW_FOREGROUND_TAB) ||
(disposition == SINGLETON_TAB)) {
// The currently selected tab is the right one.
web_contents = browser->tab_strip_model()->GetActiveWebContents();
}
if (disposition == CURRENT_TAB) {
same_tab_observer.Wait();
return;
} else if (web_contents) {
content::TestNavigationObserver observer(web_contents,
number_of_navigations);
observer.Wait();
return;
}
EXPECT_TRUE(NULL != web_contents) << " Unable to wait for navigation to \""
<< url.spec() << "\""
<< " because we can't get the tab contents";
}
void NavigateToURLWithDisposition(Browser* browser,
const GURL& url,
WindowOpenDisposition disposition,
int browser_test_flags) {
NavigateToURLWithDispositionBlockUntilNavigationsComplete(
browser,
url,
1,
disposition,
browser_test_flags);
}
void NavigateToURLBlockUntilNavigationsComplete(Browser* browser,
const GURL& url,
int number_of_navigations) {
NavigateToURLWithDispositionBlockUntilNavigationsComplete(
browser,
url,
number_of_navigations,
CURRENT_TAB,
BROWSER_TEST_WAIT_FOR_NAVIGATION);
}
void WaitUntilDevToolsWindowLoaded(DevToolsWindow* window) {
scoped_refptr<content::MessageLoopRunner> runner =
new content::MessageLoopRunner;
window->SetLoadCompletedCallback(runner->QuitClosure());
runner->Run();
}
base::FilePath GetTestFilePath(const base::FilePath& dir,
const base::FilePath& file) {
base::FilePath path;
PathService::Get(chrome::DIR_TEST_DATA, &path);
return path.Append(dir).Append(file);
}
GURL GetTestUrl(const base::FilePath& dir, const base::FilePath& file) {
return net::FilePathToFileURL(GetTestFilePath(dir, file));
}
bool GetRelativeBuildDirectory(base::FilePath* build_dir) {
// This function is used to find the build directory so TestServer can serve
// built files (nexes, etc). TestServer expects a path relative to the source
// root.
base::FilePath exe_dir =
CommandLine::ForCurrentProcess()->GetProgram().DirName();
base::FilePath src_dir;
if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir))
return false;
// We must first generate absolute paths to SRC and EXE and from there
// generate a relative path.
if (!exe_dir.IsAbsolute())
exe_dir = base::MakeAbsoluteFilePath(exe_dir);
if (!src_dir.IsAbsolute())
src_dir = base::MakeAbsoluteFilePath(src_dir);
if (!exe_dir.IsAbsolute())
return false;
if (!src_dir.IsAbsolute())
return false;
size_t match, exe_size, src_size;
std::vector<base::FilePath::StringType> src_parts, exe_parts;
// Determine point at which src and exe diverge.
exe_dir.GetComponents(&exe_parts);
src_dir.GetComponents(&src_parts);
exe_size = exe_parts.size();
src_size = src_parts.size();
for (match = 0; match < exe_size && match < src_size; ++match) {
if (exe_parts[match] != src_parts[match])
break;
}
// Create a relative path.
*build_dir = base::FilePath();
for (size_t tmp_itr = match; tmp_itr < src_size; ++tmp_itr)
*build_dir = build_dir->Append(FILE_PATH_LITERAL(".."));
for (; match < exe_size; ++match)
*build_dir = build_dir->Append(exe_parts[match]);
return true;
}
AppModalDialog* WaitForAppModalDialog() {
AppModalDialogQueue* dialog_queue = AppModalDialogQueue::GetInstance();
if (dialog_queue->HasActiveDialog())
return dialog_queue->active_dialog();
content::WindowedNotificationObserver observer(
chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN,
content::NotificationService::AllSources());
observer.Wait();
return content::Source<AppModalDialog>(observer.source()).ptr();
}
int FindInPage(WebContents* tab,
const base::string16& search_string,
bool forward,
bool match_case,
int* ordinal,
gfx::Rect* selection_rect) {
FindTabHelper* find_tab_helper = FindTabHelper::FromWebContents(tab);
find_tab_helper->StartFinding(search_string, forward, match_case);
FindInPageNotificationObserver observer(tab);
observer.Wait();
if (ordinal)
*ordinal = observer.active_match_ordinal();
if (selection_rect)
*selection_rect = observer.selection_rect();
return observer.number_of_matches();
}
void WaitForTemplateURLServiceToLoad(TemplateURLService* service) {
if (service->loaded())
return;
scoped_refptr<content::MessageLoopRunner> message_loop_runner =
new content::MessageLoopRunner;
scoped_ptr<TemplateURLService::Subscription> subscription =
service->RegisterOnLoadedCallback(
message_loop_runner->QuitClosure());
service->Load();
message_loop_runner->Run();
ASSERT_TRUE(service->loaded());
}
void WaitForHistoryToLoad(HistoryService* history_service) {
content::WindowedNotificationObserver history_loaded_observer(
chrome::NOTIFICATION_HISTORY_LOADED,
content::NotificationService::AllSources());
if (!history_service->BackendLoaded())
history_loaded_observer.Wait();
}
void DownloadURL(Browser* browser, const GURL& download_url) {
base::ScopedTempDir downloads_directory;
ASSERT_TRUE(downloads_directory.CreateUniqueTempDir());
browser->profile()->GetPrefs()->SetFilePath(
prefs::kDownloadDefaultDirectory, downloads_directory.path());
content::DownloadManager* download_manager =
content::BrowserContext::GetDownloadManager(browser->profile());
scoped_ptr<content::DownloadTestObserver> observer(
new content::DownloadTestObserverTerminal(
download_manager, 1,
content::DownloadTestObserver::ON_DANGEROUS_DOWNLOAD_ACCEPT));
ui_test_utils::NavigateToURL(browser, download_url);
observer->WaitForFinished();
}
void SendToOmniboxAndSubmit(LocationBar* location_bar,
const std::string& input) {
OmniboxView* omnibox = location_bar->GetOmniboxView();
omnibox->model()->OnSetFocus(false);
omnibox->SetUserText(base::ASCIIToUTF16(input));
location_bar->AcceptInput();
while (!omnibox->model()->autocomplete_controller()->done()) {
content::WindowedNotificationObserver observer(
chrome::NOTIFICATION_AUTOCOMPLETE_CONTROLLER_RESULT_READY,
content::NotificationService::AllSources());
observer.Wait();
}
}
Browser* GetBrowserNotInSet(std::set<Browser*> excluded_browsers) {
for (chrome::BrowserIterator it; !it.done(); it.Next()) {
if (excluded_browsers.find(*it) == excluded_browsers.end())
return *it;
}
return NULL;
}
namespace {
void GetCookiesCallback(base::WaitableEvent* event,
std::string* cookies,
const std::string& cookie_line) {
*cookies = cookie_line;
event->Signal();
}
void GetCookiesOnIOThread(
const GURL& url,
const scoped_refptr<net::URLRequestContextGetter>& context_getter,
base::WaitableEvent* event,
std::string* cookies) {
context_getter->GetURLRequestContext()->cookie_store()->
GetCookiesWithOptionsAsync(
url, net::CookieOptions(),
base::Bind(&GetCookiesCallback, event, cookies));
}
} // namespace
void GetCookies(const GURL& url,
WebContents* contents,
int* value_size,
std::string* value) {
*value_size = -1;
if (url.is_valid() && contents) {
scoped_refptr<net::URLRequestContextGetter> context_getter =
contents->GetBrowserContext()->GetRequestContextForRenderProcess(
contents->GetRenderProcessHost()->GetID());
base::WaitableEvent event(true /* manual reset */,
false /* not initially signaled */);
CHECK(content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&GetCookiesOnIOThread, url, context_getter, &event, value)));
event.Wait();
*value_size = static_cast<int>(value->size());
}
}
WindowedTabAddedNotificationObserver::WindowedTabAddedNotificationObserver(
const content::NotificationSource& source)
: WindowedNotificationObserver(chrome::NOTIFICATION_TAB_ADDED, source),
added_tab_(NULL) {
}
void WindowedTabAddedNotificationObserver::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
added_tab_ = content::Details<WebContents>(details).ptr();
content::WindowedNotificationObserver::Observe(type, source, details);
}
UrlLoadObserver::UrlLoadObserver(const GURL& url,
const content::NotificationSource& source)
: WindowedNotificationObserver(content::NOTIFICATION_LOAD_STOP, source),
url_(url) {
}
UrlLoadObserver::~UrlLoadObserver() {}
void UrlLoadObserver::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
NavigationController* controller =
content::Source<NavigationController>(source).ptr();
if (controller->GetWebContents()->GetURL() != url_)
return;
WindowedNotificationObserver::Observe(type, source, details);
}
BrowserAddedObserver::BrowserAddedObserver()
: notification_observer_(
chrome::NOTIFICATION_BROWSER_OPENED,
content::NotificationService::AllSources()) {
for (chrome::BrowserIterator it; !it.done(); it.Next())
original_browsers_.insert(*it);
}
BrowserAddedObserver::~BrowserAddedObserver() {
}
Browser* BrowserAddedObserver::WaitForSingleNewBrowser() {
notification_observer_.Wait();
// Ensure that only a single new browser has appeared.
EXPECT_EQ(original_browsers_.size() + 1, chrome::GetTotalBrowserCount());
return GetBrowserNotInSet(original_browsers_);
}
#if defined(OS_WIN)
bool SaveScreenSnapshotToDirectory(const base::FilePath& directory,
base::FilePath* screenshot_path) {
bool succeeded = false;
base::FilePath out_path(GetSnapshotFileName(directory));
MONITORINFO monitor_info = {};
monitor_info.cbSize = sizeof(monitor_info);
HMONITOR main_monitor = MonitorFromWindow(NULL, MONITOR_DEFAULTTOPRIMARY);
if (GetMonitorInfo(main_monitor, &monitor_info)) {
RECT& rect = monitor_info.rcMonitor;
std::vector<unsigned char> png_data;
gfx::Rect bounds(
gfx::Size(rect.right - rect.left, rect.bottom - rect.top));
if (ui::GrabDesktopSnapshot(bounds, &png_data) &&
png_data.size() <= INT_MAX) {
int bytes = static_cast<int>(png_data.size());
int written = base::WriteFile(
out_path, reinterpret_cast<char*>(&png_data[0]), bytes);
succeeded = (written == bytes);
}
}
if (succeeded && screenshot_path != NULL)
*screenshot_path = out_path;
return succeeded;
}
bool SaveScreenSnapshotToDesktop(base::FilePath* screenshot_path) {
base::FilePath desktop;
return PathService::Get(base::DIR_USER_DESKTOP, &desktop) &&
SaveScreenSnapshotToDirectory(desktop, screenshot_path);
}
#endif // defined(OS_WIN)
void OverrideGeolocation(double latitude, double longitude) {
content::Geoposition position;
position.latitude = latitude;
position.longitude = longitude;
position.altitude = 0.;
position.accuracy = 0.;
position.timestamp = base::Time::Now();
content::GeolocationProvider::GetInstance()->OverrideLocationForTesting(
position);
}
HistoryEnumerator::HistoryEnumerator(Profile* profile) {
scoped_refptr<content::MessageLoopRunner> message_loop_runner =
new content::MessageLoopRunner;
HistoryService* hs = HistoryServiceFactory::GetForProfile(
profile, Profile::EXPLICIT_ACCESS);
hs->QueryHistory(
base::string16(),
history::QueryOptions(),
&consumer_,
base::Bind(&HistoryEnumerator::HistoryQueryComplete,
base::Unretained(this), message_loop_runner->QuitClosure()));
message_loop_runner->Run();
}
HistoryEnumerator::~HistoryEnumerator() {}
void HistoryEnumerator::HistoryQueryComplete(
const base::Closure& quit_task,
HistoryService::Handle request_handle,
history::QueryResults* results) {
for (size_t i = 0; i < results->size(); ++i)
urls_.push_back((*results)[i].url());
quit_task.Run();
}
} // namespace ui_test_utils