// Copyright (c) 2009 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/ui_test_utils.h" #include #include "base/json_reader.h" #include "base/message_loop.h" #include "base/path_service.h" #include "base/process_util.h" #include "base/values.h" #include "chrome/browser/browser.h" #include "chrome/browser/dom_operation_notification_details.h" #include "chrome/browser/download/download_manager.h" #include "chrome/browser/renderer_host/render_process_host.h" #include "chrome/browser/tab_contents/navigation_controller.h" #include "chrome/browser/tab_contents/navigation_entry.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/notification_registrar.h" #include "chrome/common/notification_service.h" #if defined(TOOLKIT_VIEWS) #include "views/focus/accelerator_handler.h" #endif #include "googleurl/src/gurl.h" #include "net/base/net_util.h" namespace ui_test_utils { namespace { // Used to block until a navigation completes. class NavigationNotificationObserver : public NotificationObserver { public: NavigationNotificationObserver(NavigationController* controller, int number_of_navigations) : navigation_started_(false), navigations_completed_(0), number_of_navigations_(number_of_navigations) { registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, Source(controller)); registrar_.Add(this, NotificationType::LOAD_START, Source(controller)); registrar_.Add(this, NotificationType::LOAD_STOP, Source(controller)); RunMessageLoop(); } virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::NAV_ENTRY_COMMITTED || type == NotificationType::LOAD_START) { navigation_started_ = true; } else if (type == NotificationType::LOAD_STOP) { if (navigation_started_ && ++navigations_completed_ == number_of_navigations_) { navigation_started_ = false; MessageLoopForUI::current()->Quit(); } } } private: NotificationRegistrar registrar_; // If true the navigation has started. bool navigation_started_; // The number of navigations that have been completed. int navigations_completed_; // The number of navigations to wait for. int number_of_navigations_; DISALLOW_COPY_AND_ASSIGN(NavigationNotificationObserver); }; class DOMOperationObserver : public NotificationObserver { public: explicit DOMOperationObserver(RenderViewHost* render_view_host) { registrar_.Add(this, NotificationType::DOM_OPERATION_RESPONSE, Source(render_view_host)); RunMessageLoop(); } virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { DCHECK(type == NotificationType::DOM_OPERATION_RESPONSE); Details dom_op_details(details); response_ = dom_op_details->json(); MessageLoopForUI::current()->Quit(); } std::string response() const { return response_; } private: NotificationRegistrar registrar_; std::string response_; DISALLOW_COPY_AND_ASSIGN(DOMOperationObserver); }; // DownloadsCompleteObserver waits for a given number of downloads to complete. // Example usage: // // ui_test_utils::NavigateToURL(browser(), zip_url); // DownloadsCompleteObserver wait_on_download( // browser()->profile()->GetDownloadManager(), 1); // /* |zip_url| download will be complete by this line. */ // class DownloadsCompleteObserver : public DownloadManager::Observer, public DownloadItem::Observer { public: explicit DownloadsCompleteObserver(DownloadManager* download_manager, size_t wait_count) : download_manager_(download_manager), wait_count_(wait_count), waiting_(false) { download_manager_->AddObserver(this); } // CheckAllDownloadsComplete will be called when the DownloadManager // fires it's ModelChanged() call, and also when incomplete downloads // fire their OnDownloadUpdated(). bool CheckAllDownloadsComplete() { if (downloads_.size() < wait_count_) return false; bool still_waiting = false; std::vector::iterator it = downloads_.begin(); for (; it != downloads_.end(); ++it) { // We always remove ourselves as an observer, then re-add if the download // isn't complete. This is to avoid having to track which downloads we // are currently observing. Removing has no effect if we are not currently // an observer. (*it)->RemoveObserver(this); if ((*it)->state() != DownloadItem::COMPLETE) { (*it)->AddObserver(this); still_waiting = true; } } if (still_waiting) return false; download_manager_->RemoveObserver(this); // waiting_ will have been set if not all downloads were complete on first // pass below in SetDownloads(). if (waiting_) MessageLoopForUI::current()->Quit(); return true; } // DownloadItem::Observer virtual void OnDownloadUpdated(DownloadItem* download) { if (download->state() == DownloadItem::COMPLETE) { CheckAllDownloadsComplete(); } } virtual void OnDownloadOpened(DownloadItem* download) {} // DownloadManager::Observer virtual void ModelChanged() { download_manager_->GetDownloads(this, L""); } virtual void SetDownloads(std::vector& downloads) { downloads_ = downloads; if (CheckAllDownloadsComplete()) return; if (!waiting_) { waiting_ = true; ui_test_utils::RunMessageLoop(); } } private: // The observed download manager. DownloadManager* download_manager_; // The current downloads being tracked. std::vector downloads_; // The number of downloads to wait on completing. size_t wait_count_; // Whether an internal message loop has been started and must be quit upon // all downloads completing. bool waiting_; DISALLOW_COPY_AND_ASSIGN(DownloadsCompleteObserver); }; // Used to block until an application modal dialog is shown. class AppModalDialogObserver : public NotificationObserver { public: AppModalDialogObserver() : dialog_(NULL) {} AppModalDialog* WaitForAppModalDialog() { registrar_.Add(this, NotificationType::APP_MODAL_DIALOG_SHOWN, NotificationService::AllSources()); dialog_ = NULL; ui_test_utils::RunMessageLoop(); DCHECK(dialog_); return dialog_; } virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::APP_MODAL_DIALOG_SHOWN) { registrar_.Remove(this, NotificationType::APP_MODAL_DIALOG_SHOWN, NotificationService::AllSources()); dialog_ = Source(source).ptr(); MessageLoopForUI::current()->Quit(); } else { NOTREACHED(); } } private: NotificationRegistrar registrar_; AppModalDialog* dialog_; DISALLOW_COPY_AND_ASSIGN(AppModalDialogObserver); }; template class SimpleNotificationObserver : public NotificationObserver { public: SimpleNotificationObserver(NotificationType notification_type, T* source) { registrar_.Add(this, notification_type, Source(source)); ui_test_utils::RunMessageLoop(); } virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { MessageLoopForUI::current()->Quit(); } private: NotificationRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(SimpleNotificationObserver); }; class FindInPageNotificationObserver : public NotificationObserver { public: explicit FindInPageNotificationObserver(TabContents* parent_tab) : parent_tab_(parent_tab), active_match_ordinal_(-1), number_of_matches_(0) { current_find_request_id_ = parent_tab->current_find_request_id(); registrar_.Add(this, NotificationType::FIND_RESULT_AVAILABLE, Source(parent_tab_)); 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(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { if (type == NotificationType::FIND_RESULT_AVAILABLE) { Details 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 { DLOG(INFO) << "Ignoring, since we only care about the final message"; } } } else { NOTREACHED(); } } private: NotificationRegistrar registrar_; TabContents* 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); }; } // namespace void RunMessageLoop() { MessageLoopForUI* loop = MessageLoopForUI::current(); bool did_allow_task_nesting = loop->NestableTasksAllowed(); loop->SetNestableTasksAllowed(true); #if defined(TOOLKIT_VIEWS) views::AcceleratorHandler handler; loop->Run(&handler); #elif defined(OS_LINUX) loop->Run(NULL); #else loop->Run(); #endif loop->SetNestableTasksAllowed(did_allow_task_nesting); } bool GetCurrentTabTitle(const Browser* browser, string16* title) { TabContents* tab_contents = browser->GetSelectedTabContents(); if (!tab_contents) return false; NavigationEntry* last_entry = tab_contents->controller().GetActiveEntry(); if (!last_entry) return false; title->assign(last_entry->title()); return true; } bool WaitForNavigationInCurrentTab(Browser* browser) { TabContents* tab_contents = browser->GetSelectedTabContents(); if (!tab_contents) return false; WaitForNavigation(&tab_contents->controller()); return true; } bool WaitForNavigationsInCurrentTab(Browser* browser, int number_of_navigations) { TabContents* tab_contents = browser->GetSelectedTabContents(); if (!tab_contents) return false; WaitForNavigations(&tab_contents->controller(), number_of_navigations); return true; } void WaitForNavigation(NavigationController* controller) { WaitForNavigations(controller, 1); } void WaitForNavigations(NavigationController* controller, int number_of_navigations) { NavigationNotificationObserver observer(controller, number_of_navigations); } void WaitForNewTab(Browser* browser) { SimpleNotificationObserver new_tab_observer(NotificationType::TAB_ADDED, browser); } void WaitForLoadStop(NavigationController* controller) { SimpleNotificationObserver new_tab_observer(NotificationType::LOAD_STOP, controller); } void NavigateToURL(Browser* browser, const GURL& url) { NavigateToURLBlockUntilNavigationsComplete(browser, url, 1); } void NavigateToURLBlockUntilNavigationsComplete(Browser* browser, const GURL& url, int number_of_navigations) { NavigationController* controller = &browser->GetSelectedTabContents()->controller(); browser->OpenURL(url, GURL(), CURRENT_TAB, PageTransition::TYPED); WaitForNavigations(controller, number_of_navigations); } Value* ExecuteJavaScript(RenderViewHost* render_view_host, const std::wstring& frame_xpath, const std::wstring& original_script) { // 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(frame_xpath, script); DOMOperationObserver dom_op_observer(render_view_host); std::string json = dom_op_observer.response(); // 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 root_val(JSONReader::Read(json, true)); if (!root_val->IsType(Value::TYPE_LIST)) return NULL; ListValue* list = static_cast(root_val.get()); Value* result; if (!list || !list->GetSize() || !list->Remove(0, &result)) // Remove gives us ownership of the value. return NULL; return result; } bool ExecuteJavaScriptAndExtractInt(RenderViewHost* render_view_host, const std::wstring& frame_xpath, const std::wstring& script, int* result) { DCHECK(result); scoped_ptr value(ExecuteJavaScript(render_view_host, frame_xpath, script)); if (!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(ExecuteJavaScript(render_view_host, frame_xpath, script)); if (!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(ExecuteJavaScript(render_view_host, frame_xpath, script)); if (!value.get()) return false; return value->GetAsString(result); } GURL GetTestUrl(const std::wstring& dir, const std::wstring file) { FilePath path; PathService::Get(chrome::DIR_TEST_DATA, &path); path = path.Append(FilePath::FromWStringHack(dir)); path = path.Append(FilePath::FromWStringHack(file)); return net::FilePathToFileURL(path); } void WaitForDownloadCount(DownloadManager* download_manager, size_t count) { DownloadsCompleteObserver download_observer(download_manager, count); } AppModalDialog* WaitForAppModalDialog() { AppModalDialogObserver observer; return observer.WaitForAppModalDialog(); } void CrashTab(TabContents* tab) { RenderProcessHost* rph = tab->render_view_host()->process(); base::KillProcess(rph->process().handle(), 0, false); SimpleNotificationObserver crash_observer(NotificationType::RENDERER_PROCESS_CLOSED, rph); } void WaitForFocusChange(RenderViewHost* rvh) { SimpleNotificationObserver focus_observer(NotificationType::FOCUS_CHANGED_IN_PAGE, rvh); } void WaitForFocusInBrowser(Browser* browser) { SimpleNotificationObserver focus_observer(NotificationType::FOCUS_RETURNED_TO_BROWSER, browser); } int FindInPage(TabContents* tab_contents, const string16& search_string, bool forward, bool match_case, int* ordinal) { tab_contents->StartFinding(search_string, forward, match_case); FindInPageNotificationObserver observer(tab_contents); if (ordinal) *ordinal = observer.active_match_ordinal(); return observer.number_of_matches(); } void RegisterAndWait(NotificationType::Type type, NotificationObserver* observer, int64 timeout_ms) { NotificationRegistrar registrar; registrar.Add(observer, type, NotificationService::AllSources()); MessageLoop::current()->PostDelayedTask( FROM_HERE, new MessageLoop::QuitTask, timeout_ms); RunMessageLoop(); } } // namespace ui_test_utils