blob: 2617ea53126b58a6ad67d8d2e94612ec1529793f [file] [log] [blame]
// Copyright (c) 2011 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 <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/file_path.h"
#include "base/json/json_reader.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/process_util.h"
#include "base/string_number_conversions.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/automation/ui_controls.h"
#include "chrome/browser/bookmarks/bookmark_model.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/dom_operation_notification_details.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/tab_contents/thumbnail_generator.h"
#include "chrome/browser/ui/browser.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/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_action.h"
#include "chrome/test/automation/javascript_execution_controller.h"
#include "chrome/test/base/bookmark_load_observer.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/tab_contents/navigation_controller.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/public/browser/download_item.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/test/test_navigation_observer.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/size.h"
#if defined(TOOLKIT_VIEWS)
#include "ui/views/focus/accelerator_handler.h"
#endif
#if defined(USE_AURA)
#include "ui/aura/root_window.h"
#endif
using content::OpenURLParams;
using content::Referrer;
using content::WebContents;
static const int kDefaultWsPort = 8880;
namespace ui_test_utils {
namespace {
class DOMOperationObserver : public content::NotificationObserver,
public content::WebContentsObserver {
public:
explicit DOMOperationObserver(RenderViewHost* render_view_host)
: content::WebContentsObserver(
render_view_host->delegate()->GetAsTabContents()),
did_respond_(false) {
registrar_.Add(this, chrome::NOTIFICATION_DOM_OPERATION_RESPONSE,
content::Source<RenderViewHost>(render_view_host));
ui_test_utils::RunMessageLoop();
}
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE {
DCHECK(type == chrome::NOTIFICATION_DOM_OPERATION_RESPONSE);
content::Details<DomOperationNotificationDetails> dom_op_details(details);
response_ = dom_op_details->json();
did_respond_ = true;
MessageLoopForUI::current()->Quit();
}
// Overridden from content::WebContentsObserver:
virtual void RenderViewGone(base::TerminationStatus status) OVERRIDE {
MessageLoopForUI::current()->Quit();
}
bool GetResponse(std::string* response) WARN_UNUSED_RESULT {
*response = response_;
return did_respond_;
}
private:
content::NotificationRegistrar registrar_;
std::string response_;
bool did_respond_;
DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver);
};
class FindInPageNotificationObserver : public content::NotificationObserver {
public:
explicit FindInPageNotificationObserver(TabContentsWrapper* parent_tab)
: parent_tab_(parent_tab),
active_match_ordinal_(-1),
number_of_matches_(0) {
current_find_request_id_ =
parent_tab->find_tab_helper()->current_find_request_id();
registrar_.Add(this, chrome::NOTIFICATION_FIND_RESULT_AVAILABLE,
content::Source<WebContents>(parent_tab_->tab_contents()));
ui_test_utils::RunMessageLoop();
}
int active_match_ordinal() const { return active_match_ordinal_; }
int number_of_matches() const { return number_of_matches_; }
virtual void Observe(int type, const content::NotificationSource& source,
const content::NotificationDetails& details) {
if (type == chrome::NOTIFICATION_FIND_RESULT_AVAILABLE) {
content::Details<FindNotificationDetails> find_details(details);
if (find_details->request_id() == current_find_request_id_) {
// We get multiple responses and one of those will contain the ordinal.
// This message comes to us before the final update is sent.
if (find_details->active_match_ordinal() > -1)
active_match_ordinal_ = find_details->active_match_ordinal();
if (find_details->final_update()) {
number_of_matches_ = find_details->number_of_matches();
MessageLoopForUI::current()->Quit();
} else {
DVLOG(1) << "Ignoring, since we only care about the final message";
}
}
} else {
NOTREACHED();
}
}
private:
content::NotificationRegistrar registrar_;
TabContentsWrapper* parent_tab_;
// We will at some point (before final update) be notified of the ordinal and
// we need to preserve it so we can send it later.
int active_match_ordinal_;
int number_of_matches_;
// The id of the current find request, obtained from TabContents. Allows us
// to monitor when the search completes.
int current_find_request_id_;
DISALLOW_COPY_AND_ASSIGN(FindInPageNotificationObserver);
};
class InProcessJavaScriptExecutionController
: public base::RefCounted<InProcessJavaScriptExecutionController>,
public JavaScriptExecutionController {
public:
explicit InProcessJavaScriptExecutionController(
RenderViewHost* render_view_host)
: render_view_host_(render_view_host) {}
protected:
// Executes |script| and sets the JSON response |json|.
virtual bool ExecuteJavaScriptAndGetJSON(const std::string& script,
std::string* json) {
render_view_host_->ExecuteJavascriptInWebFrame(string16(),
UTF8ToUTF16(script));
DOMOperationObserver dom_op_observer(render_view_host_);
return dom_op_observer.GetResponse(json);
}
virtual void FirstObjectAdded() {
AddRef();
}
virtual void LastObjectRemoved() {
Release();
}
private:
// Weak pointer to the associated RenderViewHost.
RenderViewHost* render_view_host_;
};
// Specifying a prototype so that we can add the WARN_UNUSED_RESULT attribute.
bool ExecuteJavaScriptHelper(RenderViewHost* render_view_host,
const std::wstring& frame_xpath,
const std::wstring& original_script,
scoped_ptr<Value>* result) WARN_UNUSED_RESULT;
// Executes the passed |original_script| in the frame pointed to by
// |frame_xpath|. If |result| is not NULL, stores the value that the evaluation
// of the script in |result|. Returns true on success.
bool ExecuteJavaScriptHelper(RenderViewHost* render_view_host,
const std::wstring& frame_xpath,
const std::wstring& original_script,
scoped_ptr<Value>* result) {
// TODO(jcampan): we should make the domAutomationController not require an
// automation id.
std::wstring script = L"window.domAutomationController.setAutomationId(0);" +
original_script;
render_view_host->ExecuteJavascriptInWebFrame(WideToUTF16Hack(frame_xpath),
WideToUTF16Hack(script));
DOMOperationObserver dom_op_observer(render_view_host);
std::string json;
if (!dom_op_observer.GetResponse(&json))
return false;
// Nothing more to do for callers that ignore the returned JS value.
if (!result)
return true;
// Wrap |json| in an array before deserializing because valid JSON has an
// array or an object as the root.
json.insert(0, "[");
json.append("]");
scoped_ptr<Value> root_val(base::JSONReader::Read(json, true));
if (!root_val->IsType(Value::TYPE_LIST))
return false;
ListValue* list = static_cast<ListValue*>(root_val.get());
Value* result_val;
if (!list || !list->GetSize() ||
!list->Remove(0, &result_val)) // Remove gives us ownership of the value.
return false;
result->reset(result_val);
return true;
}
void RunAllPendingMessageAndSendQuit(content::BrowserThread::ID thread_id) {
MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
RunMessageLoop();
content::BrowserThread::PostTask(thread_id, FROM_HERE,
MessageLoop::QuitClosure());
}
} // namespace
void RunMessageLoop() {
MessageLoop* loop = MessageLoop::current();
MessageLoopForUI* ui_loop =
content::BrowserThread::CurrentlyOn(content::BrowserThread::UI) ?
MessageLoopForUI::current() : NULL;
bool did_allow_task_nesting = loop->NestableTasksAllowed();
loop->SetNestableTasksAllowed(true);
if (ui_loop) {
#if defined(USE_AURA)
aura::RootWindow::GetInstance()->Run();
#elif defined(TOOLKIT_VIEWS)
views::AcceleratorHandler handler;
ui_loop->RunWithDispatcher(&handler);
#elif defined(OS_POSIX) && !defined(OS_MACOSX)
ui_loop->RunWithDispatcher(NULL);
#else
ui_loop->Run();
#endif
} else {
loop->Run();
}
loop->SetNestableTasksAllowed(did_allow_task_nesting);
}
void RunAllPendingInMessageLoop() {
MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
ui_test_utils::RunMessageLoop();
}
void RunAllPendingInMessageLoop(content::BrowserThread::ID thread_id) {
if (content::BrowserThread::CurrentlyOn(thread_id)) {
RunAllPendingInMessageLoop();
return;
}
content::BrowserThread::ID current_thread_id;
if (!content::BrowserThread::GetCurrentThreadIdentifier(&current_thread_id)) {
NOTREACHED();
return;
}
content::BrowserThread::PostTask(thread_id, FROM_HERE,
base::Bind(&RunAllPendingMessageAndSendQuit, current_thread_id));
ui_test_utils::RunMessageLoop();
}
bool GetCurrentTabTitle(const Browser* browser, string16* title) {
TabContents* tab_contents = browser->GetSelectedTabContents();
if (!tab_contents)
return false;
NavigationEntry* last_entry = tab_contents->GetController().GetActiveEntry();
if (!last_entry)
return false;
title->assign(last_entry->GetTitleForDisplay(""));
return true;
}
void WaitForNavigations(NavigationController* controller,
int number_of_navigations) {
TestNavigationObserver observer(
content::Source<NavigationController>(controller), NULL,
number_of_navigations);
observer.WaitForObservation(
base::Bind(&ui_test_utils::RunMessageLoop),
base::Bind(&MessageLoop::Quit,
base::Unretained(MessageLoopForUI::current())));
}
void WaitForNewTab(Browser* browser) {
TestNotificationObserver observer;
RegisterAndWait(&observer, content::NOTIFICATION_TAB_ADDED,
content::Source<content::WebContentsDelegate>(browser));
}
void WaitForBrowserActionUpdated(ExtensionAction* browser_action) {
TestNotificationObserver observer;
RegisterAndWait(&observer,
chrome::NOTIFICATION_EXTENSION_BROWSER_ACTION_UPDATED,
content::Source<ExtensionAction>(browser_action));
}
void WaitForLoadStop(TabContents* tab) {
WindowedNotificationObserver load_stop_observer(
content::NOTIFICATION_LOAD_STOP,
content::Source<NavigationController>(&tab->GetController()));
// In many cases, the load may have finished before we get here. Only wait if
// the tab still has a pending navigation.
if (!tab->IsLoading())
return;
load_stop_observer.Wait();
}
Browser* WaitForNewBrowser() {
TestNotificationObserver observer;
RegisterAndWait(&observer, chrome::NOTIFICATION_BROWSER_WINDOW_READY,
content::NotificationService::AllSources());
return content::Source<Browser>(observer.source()).ptr();
}
Browser* WaitForBrowserNotInSet(std::set<Browser*> excluded_browsers) {
TestNotificationObserver observer;
Browser* new_browser = GetBrowserNotInSet(excluded_browsers);
if (new_browser == NULL) {
new_browser = WaitForNewBrowser();
// The new browser should never be in |excluded_browsers|.
DCHECK(!ContainsKey(excluded_browsers, new_browser));
}
return new_browser;
}
void OpenURLOffTheRecord(Profile* profile, const GURL& url) {
Browser::OpenURLOffTheRecord(profile, url);
Browser* browser = BrowserList::FindTabbedBrowser(
profile->GetOffTheRecordProfile(), false);
WaitForNavigations(&browser->GetSelectedTabContents()->GetController(), 1);
}
void NavigateToURL(browser::NavigateParams* params) {
TestNavigationObserver observer(
content::NotificationService::AllSources(), NULL, 1);
browser::Navigate(params);
observer.WaitForObservation(
base::Bind(&ui_test_utils::RunMessageLoop),
base::Bind(&MessageLoop::Quit,
base::Unretained(MessageLoopForUI::current())));
}
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) {
if (disposition == CURRENT_TAB && browser->GetSelectedTabContents())
WaitForLoadStop(browser->GetSelectedTabContents());
TestNavigationObserver same_tab_observer(
content::Source<NavigationController>(
&browser->GetSelectedTabContents()->GetController()),
NULL,
number_of_navigations);
std::set<Browser*> initial_browsers;
for (std::vector<Browser*>::const_iterator iter = BrowserList::begin();
iter != BrowserList::end();
++iter) {
initial_browsers.insert(*iter);
}
WindowedNotificationObserver tab_added_observer(
content::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;
}
TabContents* tab_contents = NULL;
if (disposition == NEW_BACKGROUND_TAB) {
// We've opened up a new tab, but not selected it.
tab_contents = browser->GetTabContentsAt(browser->active_index() + 1);
EXPECT_TRUE(tab_contents != NULL)
<< " Unable to wait for navigation to \"" << url.spec()
<< "\" because the new tab is not available yet";
return;
} else if ((disposition == CURRENT_TAB) ||
(disposition == NEW_FOREGROUND_TAB) ||
(disposition == SINGLETON_TAB)) {
// The currently selected tab is the right one.
tab_contents = browser->GetSelectedTabContents();
}
if (disposition == CURRENT_TAB) {
same_tab_observer.WaitForObservation(
base::Bind(&ui_test_utils::RunMessageLoop),
base::Bind(&MessageLoop::Quit,
base::Unretained(MessageLoopForUI::current())));
return;
} else if (tab_contents) {
NavigationController* controller = &tab_contents->GetController();
WaitForNavigations(controller, number_of_navigations);
return;
}
EXPECT_TRUE(NULL != tab_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);
}
DOMElementProxyRef GetActiveDOMDocument(Browser* browser) {
JavaScriptExecutionController* executor =
new InProcessJavaScriptExecutionController(
browser->GetSelectedTabContents()->GetRenderViewHost());
int element_handle;
executor->ExecuteJavaScriptAndGetReturn("document;", &element_handle);
return executor->GetObjectProxy<DOMElementProxy>(element_handle);
}
bool ExecuteJavaScript(RenderViewHost* render_view_host,
const std::wstring& frame_xpath,
const std::wstring& original_script) {
std::wstring script =
original_script + L"window.domAutomationController.send(0);";
return ExecuteJavaScriptHelper(render_view_host, frame_xpath, script, NULL);
}
bool ExecuteJavaScriptAndExtractInt(RenderViewHost* render_view_host,
const std::wstring& frame_xpath,
const std::wstring& script,
int* result) {
DCHECK(result);
scoped_ptr<Value> value;
if (!ExecuteJavaScriptHelper(render_view_host, frame_xpath, script, &value) ||
!value.get())
return false;
return value->GetAsInteger(result);
}
bool ExecuteJavaScriptAndExtractBool(RenderViewHost* render_view_host,
const std::wstring& frame_xpath,
const std::wstring& script,
bool* result) {
DCHECK(result);
scoped_ptr<Value> value;
if (!ExecuteJavaScriptHelper(render_view_host, frame_xpath, script, &value) ||
!value.get())
return false;
return value->GetAsBoolean(result);
}
bool ExecuteJavaScriptAndExtractString(RenderViewHost* render_view_host,
const std::wstring& frame_xpath,
const std::wstring& script,
std::string* result) {
DCHECK(result);
scoped_ptr<Value> value;
if (!ExecuteJavaScriptHelper(render_view_host, frame_xpath, script, &value) ||
!value.get())
return false;
return value->GetAsString(result);
}
FilePath GetTestFilePath(const FilePath& dir, const FilePath& file) {
FilePath path;
PathService::Get(chrome::DIR_TEST_DATA, &path);
return path.Append(dir).Append(file);
}
GURL GetTestUrl(const FilePath& dir, const FilePath& file) {
return net::FilePathToFileURL(GetTestFilePath(dir, file));
}
GURL GetFileUrlWithQuery(const FilePath& path,
const std::string& query_string) {
GURL url = net::FilePathToFileURL(path);
if (!query_string.empty()) {
GURL::Replacements replacements;
replacements.SetQueryStr(query_string);
return url.ReplaceComponents(replacements);
}
return url;
}
AppModalDialog* WaitForAppModalDialog() {
TestNotificationObserver observer;
RegisterAndWait(&observer, chrome::NOTIFICATION_APP_MODAL_DIALOG_SHOWN,
content::NotificationService::AllSources());
return content::Source<AppModalDialog>(observer.source()).ptr();
}
void CrashTab(TabContents* tab) {
content::RenderProcessHost* rph = tab->GetRenderProcessHost();
base::KillProcess(rph->GetHandle(), 0, false);
TestNotificationObserver observer;
RegisterAndWait(&observer, content::NOTIFICATION_RENDERER_PROCESS_CLOSED,
content::Source<content::RenderProcessHost>(rph));
}
int FindInPage(TabContentsWrapper* tab_contents, const string16& search_string,
bool forward, bool match_case, int* ordinal) {
tab_contents->
find_tab_helper()->StartFinding(search_string, forward, match_case);
FindInPageNotificationObserver observer(tab_contents);
if (ordinal)
*ordinal = observer.active_match_ordinal();
return observer.number_of_matches();
}
void RegisterAndWait(content::NotificationObserver* observer,
int type,
const content::NotificationSource& source) {
content::NotificationRegistrar registrar;
registrar.Add(observer, type, source);
RunMessageLoop();
}
void WaitForBookmarkModelToLoad(BookmarkModel* model) {
if (model->IsLoaded())
return;
BookmarkLoadObserver observer;
model->AddObserver(&observer);
RunMessageLoop();
model->RemoveObserver(&observer);
ASSERT_TRUE(model->IsLoaded());
}
void WaitForTemplateURLServiceToLoad(TemplateURLService* service) {
if (service->loaded())
return;
service->Load();
TemplateURLServiceTestUtil::BlockTillServiceProcessesRequests();
ASSERT_TRUE(service->loaded());
}
void WaitForHistoryToLoad(Browser* browser) {
HistoryService* history_service =
browser->profile()->GetHistoryService(Profile::EXPLICIT_ACCESS);
WindowedNotificationObserver history_loaded_observer(
chrome::NOTIFICATION_HISTORY_LOADED,
content::NotificationService::AllSources());
if (!history_service->BackendLoaded())
history_loaded_observer.Wait();
}
bool GetNativeWindow(const Browser* browser, gfx::NativeWindow* native_window) {
BrowserWindow* window = browser->window();
if (!window)
return false;
*native_window = window->GetNativeHandle();
return *native_window;
}
bool BringBrowserWindowToFront(const Browser* browser) {
gfx::NativeWindow window = NULL;
if (!GetNativeWindow(browser, &window))
return false;
return ui_test_utils::ShowAndFocusNativeWindow(window);
}
Browser* GetBrowserNotInSet(std::set<Browser*> excluded_browsers) {
for (BrowserList::const_iterator iter = BrowserList::begin();
iter != BrowserList::end();
++iter) {
if (excluded_browsers.find(*iter) == excluded_browsers.end())
return *iter;
}
return NULL;
}
bool SendKeyPressSync(const Browser* browser,
ui::KeyboardCode key,
bool control,
bool shift,
bool alt,
bool command) {
gfx::NativeWindow window = NULL;
if (!GetNativeWindow(browser, &window))
return false;
if (!ui_controls::SendKeyPressNotifyWhenDone(
window, key, control, shift, alt, command,
MessageLoop::QuitClosure())) {
LOG(ERROR) << "ui_controls::SendKeyPressNotifyWhenDone failed";
return false;
}
// Run the message loop. It'll stop running when either the key was received
// or the test timed out (in which case testing::Test::HasFatalFailure should
// be set).
RunMessageLoop();
return !testing::Test::HasFatalFailure();
}
bool SendKeyPressAndWait(const Browser* browser,
ui::KeyboardCode key,
bool control,
bool shift,
bool alt,
bool command,
int type,
const content::NotificationSource& source) {
WindowedNotificationObserver observer(type, source);
if (!SendKeyPressSync(browser, key, control, shift, alt, command))
return false;
observer.Wait();
return !testing::Test::HasFatalFailure();
}
TimedMessageLoopRunner::TimedMessageLoopRunner()
: loop_(new MessageLoopForUI()),
owned_(true),
quit_loop_invoked_(false) {
}
TimedMessageLoopRunner::~TimedMessageLoopRunner() {
if (owned_)
delete loop_;
}
void TimedMessageLoopRunner::RunFor(int ms) {
QuitAfter(ms);
quit_loop_invoked_ = false;
loop_->Run();
}
void TimedMessageLoopRunner::Quit() {
quit_loop_invoked_ = true;
loop_->PostTask(FROM_HERE, MessageLoop::QuitClosure());
}
void TimedMessageLoopRunner::QuitAfter(int ms) {
quit_loop_invoked_ = true;
loop_->PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(), ms);
}
namespace {
void AppendToPythonPath(const FilePath& dir) {
#if defined(OS_WIN)
const wchar_t kPythonPath[] = L"PYTHONPATH";
// TODO(ukai): handle longer PYTHONPATH variables.
wchar_t oldpath[4096];
if (::GetEnvironmentVariable(kPythonPath, oldpath, arraysize(oldpath)) == 0) {
::SetEnvironmentVariableW(kPythonPath, dir.value().c_str());
} else if (!wcsstr(oldpath, dir.value().c_str())) {
std::wstring newpath(oldpath);
newpath.append(L";");
newpath.append(dir.value());
SetEnvironmentVariableW(kPythonPath, newpath.c_str());
}
#elif defined(OS_POSIX)
const char kPythonPath[] = "PYTHONPATH";
const char* oldpath = getenv(kPythonPath);
if (!oldpath) {
setenv(kPythonPath, dir.value().c_str(), 1);
} else if (!strstr(oldpath, dir.value().c_str())) {
std::string newpath(oldpath);
newpath.append(":");
newpath.append(dir.value());
setenv(kPythonPath, newpath.c_str(), 1);
}
#endif
}
} // anonymous namespace
TestWebSocketServer::TestWebSocketServer()
: started_(false) {
#if defined(OS_POSIX)
process_handle_ = base::kNullProcessHandle;
#endif
}
bool TestWebSocketServer::Start(const FilePath& root_directory) {
if (started_)
return true;
// Append CommandLine arguments after the server script, switches won't work.
scoped_ptr<CommandLine> cmd_line(CreateWebSocketServerCommandLine());
cmd_line->AppendArg("--server=start");
cmd_line->AppendArg("--chromium");
cmd_line->AppendArg("--register_cygwin");
cmd_line->AppendArgNative(FILE_PATH_LITERAL("--root=") +
root_directory.value());
cmd_line->AppendArg("--port=" + base::IntToString(kDefaultWsPort));
if (!temp_dir_.CreateUniqueTempDir()) {
LOG(ERROR) << "Unable to create a temporary directory.";
return false;
}
websocket_pid_file_ = temp_dir_.path().AppendASCII("websocket.pid");
cmd_line->AppendArgNative(FILE_PATH_LITERAL("--pidfile=") +
websocket_pid_file_.value());
SetPythonPath();
base::LaunchOptions options;
base::ProcessHandle* process_handle = NULL;
#if defined(OS_POSIX)
options.new_process_group = true;
process_handle = &process_handle_;
#elif defined(OS_WIN)
job_handle_.Set(CreateJobObject(NULL, NULL));
if (!job_handle_.IsValid()) {
LOG(ERROR) << "Could not create JobObject.";
return false;
}
if (!base::SetJobObjectAsKillOnJobClose(job_handle_.Get())) {
LOG(ERROR) << "Could not SetInformationJobObject.";
return false;
}
options.inherit_handles = true;
options.job_handle = job_handle_.Get();
#endif
// Launch a new WebSocket server process.
options.wait = true;
if (!base::LaunchProcess(*cmd_line.get(), options, process_handle)) {
LOG(ERROR) << "Unable to launch websocket server.";
return false;
}
started_ = true;
return true;
}
CommandLine* TestWebSocketServer::CreatePythonCommandLine() {
// Note: Python's first argument must be the script; do not append CommandLine
// switches, as they would precede the script path and break this CommandLine.
return new CommandLine(FilePath(FILE_PATH_LITERAL("python")));
}
void TestWebSocketServer::SetPythonPath() {
FilePath scripts_path;
PathService::Get(base::DIR_SOURCE_ROOT, &scripts_path);
scripts_path = scripts_path
.Append(FILE_PATH_LITERAL("third_party"))
.Append(FILE_PATH_LITERAL("WebKit"))
.Append(FILE_PATH_LITERAL("Tools"))
.Append(FILE_PATH_LITERAL("Scripts"));
AppendToPythonPath(scripts_path);
}
CommandLine* TestWebSocketServer::CreateWebSocketServerCommandLine() {
FilePath src_path;
// Get to 'src' dir.
PathService::Get(base::DIR_SOURCE_ROOT, &src_path);
FilePath script_path(src_path);
script_path = script_path.AppendASCII("third_party");
script_path = script_path.AppendASCII("WebKit");
script_path = script_path.AppendASCII("Tools");
script_path = script_path.AppendASCII("Scripts");
script_path = script_path.AppendASCII("new-run-webkit-websocketserver");
CommandLine* cmd_line = CreatePythonCommandLine();
cmd_line->AppendArgPath(script_path);
return cmd_line;
}
TestWebSocketServer::~TestWebSocketServer() {
if (!started_)
return;
// Append CommandLine arguments after the server script, switches won't work.
scoped_ptr<CommandLine> cmd_line(CreateWebSocketServerCommandLine());
cmd_line->AppendArg("--server=stop");
cmd_line->AppendArg("--chromium");
cmd_line->AppendArgNative(FILE_PATH_LITERAL("--pidfile=") +
websocket_pid_file_.value());
base::LaunchOptions options;
options.wait = true;
base::LaunchProcess(*cmd_line.get(), options, NULL);
#if defined(OS_POSIX)
// Just to make sure that the server process terminates certainly.
base::KillProcessGroup(process_handle_);
#endif
}
TestNotificationObserver::TestNotificationObserver()
: source_(content::NotificationService::AllSources()) {
}
TestNotificationObserver::~TestNotificationObserver() {}
void TestNotificationObserver::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
source_ = source;
details_ = details;
MessageLoopForUI::current()->Quit();
}
WindowedNotificationObserver::WindowedNotificationObserver(
int notification_type,
const content::NotificationSource& source)
: seen_(false),
running_(false),
waiting_for_(source) {
registrar_.Add(this, notification_type, waiting_for_);
}
WindowedNotificationObserver::~WindowedNotificationObserver() {}
void WindowedNotificationObserver::Wait() {
if (seen_ || (waiting_for_ == content::NotificationService::AllSources() &&
!sources_seen_.empty())) {
return;
}
running_ = true;
ui_test_utils::RunMessageLoop();
}
void WindowedNotificationObserver::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
if (waiting_for_ == source ||
(running_ && waiting_for_ == content::NotificationService::AllSources())) {
seen_ = true;
if (running_)
MessageLoopForUI::current()->Quit();
} else {
sources_seen_.insert(source.map_key());
}
}
TitleWatcher::TitleWatcher(TabContents* tab_contents,
const string16& expected_title)
: tab_contents_(tab_contents),
expected_title_observed_(false),
quit_loop_on_observation_(false) {
EXPECT_TRUE(tab_contents != NULL);
expected_titles_.push_back(expected_title);
notification_registrar_.Add(this,
content::NOTIFICATION_TAB_CONTENTS_TITLE_UPDATED,
content::Source<TabContents>(tab_contents));
// When navigating through the history, the restored NavigationEntry's title
// will be used. If the entry ends up having the same title after we return
// to it, as will usually be the case, the
// NOTIFICATION_TAB_CONTENTS_TITLE_UPDATED will then be suppressed, since the
// NavigationEntry's title hasn't changed.
notification_registrar_.Add(
this,
content::NOTIFICATION_LOAD_STOP,
content::Source<NavigationController>(&tab_contents->GetController()));
}
void TitleWatcher::AlsoWaitForTitle(const string16& expected_title) {
expected_titles_.push_back(expected_title);
}
TitleWatcher::~TitleWatcher() {
}
const string16& TitleWatcher::WaitAndGetTitle() {
if (expected_title_observed_)
return observed_title_;
quit_loop_on_observation_ = true;
ui_test_utils::RunMessageLoop();
return observed_title_;
}
void TitleWatcher::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
if (type == content::NOTIFICATION_TAB_CONTENTS_TITLE_UPDATED) {
TabContents* source_contents = content::Source<TabContents>(source).ptr();
ASSERT_EQ(tab_contents_, source_contents);
} else if (type == content::NOTIFICATION_LOAD_STOP) {
NavigationController* controller =
content::Source<NavigationController>(source).ptr();
ASSERT_EQ(&tab_contents_->GetController(), controller);
} else {
FAIL() << "Unexpected notification received.";
}
std::vector<string16>::const_iterator it =
std::find(expected_titles_.begin(),
expected_titles_.end(),
tab_contents_->GetTitle());
if (it == expected_titles_.end())
return;
observed_title_ = *it;
expected_title_observed_ = true;
if (quit_loop_on_observation_)
MessageLoopForUI::current()->Quit();
}
DOMMessageQueue::DOMMessageQueue() {
registrar_.Add(this, chrome::NOTIFICATION_DOM_OPERATION_RESPONSE,
content::NotificationService::AllSources());
}
DOMMessageQueue::~DOMMessageQueue() {}
void DOMMessageQueue::Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
content::Details<DomOperationNotificationDetails> dom_op_details(details);
content::Source<RenderViewHost> sender(source);
message_queue_.push(dom_op_details->json());
if (waiting_for_message_) {
waiting_for_message_ = false;
MessageLoopForUI::current()->Quit();
}
}
bool DOMMessageQueue::WaitForMessage(std::string* message) {
if (message_queue_.empty()) {
waiting_for_message_ = true;
// This will be quit when a new message comes in.
RunMessageLoop();
}
// The queue should not be empty, unless we were quit because of a timeout.
if (message_queue_.empty())
return false;
if (message)
*message = message_queue_.front();
return true;
}
// Coordinates taking snapshots of a |RenderWidget|.
class SnapshotTaker {
public:
SnapshotTaker() : bitmap_(NULL) {}
bool TakeRenderWidgetSnapshot(RenderWidgetHost* rwh,
const gfx::Size& page_size,
const gfx::Size& desired_size,
SkBitmap* bitmap) WARN_UNUSED_RESULT {
bitmap_ = bitmap;
ThumbnailGenerator* generator = g_browser_process->GetThumbnailGenerator();
generator->MonitorRenderer(rwh, true);
snapshot_taken_ = false;
generator->AskForSnapshot(
rwh,
false, // don't use backing_store
base::Bind(&SnapshotTaker::OnSnapshotTaken, base::Unretained(this)),
page_size,
desired_size);
ui_test_utils::RunMessageLoop();
return snapshot_taken_;
}
bool TakeEntirePageSnapshot(RenderViewHost* rvh,
SkBitmap* bitmap) WARN_UNUSED_RESULT {
const wchar_t* script =
L"window.domAutomationController.send("
L" JSON.stringify([document.width, document.height]))";
std::string json;
if (!ui_test_utils::ExecuteJavaScriptAndExtractString(
rvh, L"", script, &json))
return false;
// Parse the JSON.
std::vector<int> dimensions;
scoped_ptr<Value> value(base::JSONReader::Read(json, true));
if (!value->IsType(Value::TYPE_LIST))
return false;
ListValue* list = static_cast<ListValue*>(value.get());
int width, height;
if (!list->GetInteger(0, &width) || !list->GetInteger(1, &height))
return false;
// Take the snapshot.
gfx::Size page_size(width, height);
return TakeRenderWidgetSnapshot(rvh, page_size, page_size, bitmap);
}
private:
// Called when the ThumbnailGenerator has taken the snapshot.
void OnSnapshotTaken(const SkBitmap& bitmap) {
*bitmap_ = bitmap;
snapshot_taken_ = true;
MessageLoop::current()->Quit();
}
SkBitmap* bitmap_;
// Whether the snapshot was actually taken and received by this SnapshotTaker.
// This will be false if the test times out.
bool snapshot_taken_;
DISALLOW_COPY_AND_ASSIGN(SnapshotTaker);
};
bool TakeRenderWidgetSnapshot(RenderWidgetHost* rwh,
const gfx::Size& page_size,
SkBitmap* bitmap) {
DCHECK(bitmap);
SnapshotTaker taker;
return taker.TakeRenderWidgetSnapshot(rwh, page_size, page_size, bitmap);
}
bool TakeEntirePageSnapshot(RenderViewHost* rvh, SkBitmap* bitmap) {
DCHECK(bitmap);
SnapshotTaker taker;
return taker.TakeEntirePageSnapshot(rvh, bitmap);
}
} // namespace ui_test_utils