1 // Copyright (c) 2013 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/chromedriver/chrome/chrome_desktop_impl.h" 6 7 #include "base/files/file_path.h" 8 #include "base/logging.h" 9 #include "base/posix/eintr_wrapper.h" 10 #include "base/process/kill.h" 11 #include "base/sys_info.h" 12 #include "base/threading/platform_thread.h" 13 #include "base/time/time.h" 14 #include "chrome/test/chromedriver/chrome/automation_extension.h" 15 #include "chrome/test/chromedriver/chrome/devtools_client.h" 16 #include "chrome/test/chromedriver/chrome/devtools_http_client.h" 17 #include "chrome/test/chromedriver/chrome/status.h" 18 #include "chrome/test/chromedriver/chrome/web_view_impl.h" 19 #include "chrome/test/chromedriver/net/port_server.h" 20 21 #if defined(OS_POSIX) 22 #include <errno.h> 23 #include <signal.h> 24 #include <sys/wait.h> 25 #include <unistd.h> 26 #endif 27 28 namespace { 29 30 bool KillProcess(base::ProcessHandle process_id) { 31 #if defined(OS_POSIX) 32 kill(process_id, SIGKILL); 33 base::TimeTicks deadline = 34 base::TimeTicks::Now() + base::TimeDelta::FromSeconds(30); 35 while (base::TimeTicks::Now() < deadline) { 36 pid_t pid = HANDLE_EINTR(waitpid(process_id, NULL, WNOHANG)); 37 if (pid == process_id) 38 return true; 39 if (pid == -1) { 40 if (errno == ECHILD) { 41 // The wait may fail with ECHILD if another process also waited for 42 // the same pid, causing the process state to get cleaned up. 43 return true; 44 } 45 LOG(WARNING) << "Error waiting for process " << process_id; 46 } 47 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(50)); 48 } 49 return false; 50 #endif 51 52 #if defined(OS_WIN) 53 if (!base::KillProcess(process_id, 0, true)) { 54 int exit_code; 55 return base::GetTerminationStatus(process_id, &exit_code) != 56 base::TERMINATION_STATUS_STILL_RUNNING; 57 } 58 return true; 59 #endif 60 } 61 62 } // namespace 63 64 ChromeDesktopImpl::ChromeDesktopImpl( 65 scoped_ptr<DevToolsHttpClient> http_client, 66 scoped_ptr<DevToolsClient> websocket_client, 67 ScopedVector<DevToolsEventListener>& devtools_event_listeners, 68 scoped_ptr<PortReservation> port_reservation, 69 base::ProcessHandle process, 70 const CommandLine& command, 71 base::ScopedTempDir* user_data_dir, 72 base::ScopedTempDir* extension_dir) 73 : ChromeImpl(http_client.Pass(), 74 websocket_client.Pass(), 75 devtools_event_listeners, 76 port_reservation.Pass()), 77 process_(process), 78 command_(command) { 79 if (user_data_dir->IsValid()) 80 CHECK(user_data_dir_.Set(user_data_dir->Take())); 81 if (extension_dir->IsValid()) 82 CHECK(extension_dir_.Set(extension_dir->Take())); 83 } 84 85 ChromeDesktopImpl::~ChromeDesktopImpl() { 86 if (!quit_) { 87 base::FilePath user_data_dir = user_data_dir_.Take(); 88 base::FilePath extension_dir = extension_dir_.Take(); 89 LOG(WARNING) << "chrome detaches, user should take care of directory:" 90 << user_data_dir.value() << " and " << extension_dir.value(); 91 } 92 base::CloseProcessHandle(process_); 93 } 94 95 Status ChromeDesktopImpl::WaitForPageToLoad(const std::string& url, 96 const base::TimeDelta& timeout, 97 scoped_ptr<WebView>* web_view) { 98 base::TimeTicks deadline = base::TimeTicks::Now() + timeout; 99 std::string id; 100 while (base::TimeTicks::Now() < deadline) { 101 WebViewsInfo views_info; 102 Status status = devtools_http_client_->GetWebViewsInfo(&views_info); 103 if (status.IsError()) 104 return status; 105 106 for (size_t i = 0; i < views_info.GetSize(); ++i) { 107 if (views_info.Get(i).url.find(url) == 0) { 108 id = views_info.Get(i).id; 109 break; 110 } 111 } 112 if (!id.empty()) 113 break; 114 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); 115 } 116 if (id.empty()) 117 return Status(kUnknownError, "page could not be found: " + url); 118 119 scoped_ptr<WebView> web_view_tmp( 120 new WebViewImpl(id, 121 devtools_http_client_->browser_info(), 122 devtools_http_client_->CreateClient(id), 123 devtools_http_client_->device_metrics())); 124 Status status = web_view_tmp->ConnectIfNecessary(); 125 if (status.IsError()) 126 return status; 127 128 status = web_view_tmp->WaitForPendingNavigations( 129 std::string(), deadline - base::TimeTicks::Now(), false); 130 if (status.IsOk()) 131 *web_view = web_view_tmp.Pass(); 132 return status; 133 } 134 135 Status ChromeDesktopImpl::GetAutomationExtension( 136 AutomationExtension** extension) { 137 if (!automation_extension_) { 138 scoped_ptr<WebView> web_view; 139 Status status = WaitForPageToLoad( 140 "chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb/" 141 "_generated_background_page.html", 142 base::TimeDelta::FromSeconds(10), 143 &web_view); 144 if (status.IsError()) 145 return Status(kUnknownError, "cannot get automation extension", status); 146 147 automation_extension_.reset(new AutomationExtension(web_view.Pass())); 148 } 149 *extension = automation_extension_.get(); 150 return Status(kOk); 151 } 152 153 ChromeDesktopImpl* ChromeDesktopImpl::GetAsDesktop() { 154 return this; 155 } 156 157 std::string ChromeDesktopImpl::GetOperatingSystemName() { 158 return base::SysInfo::OperatingSystemName(); 159 } 160 161 bool ChromeDesktopImpl::IsMobileEmulationEnabled() const { 162 return devtools_http_client_->device_metrics() != NULL; 163 } 164 165 Status ChromeDesktopImpl::QuitImpl() { 166 if (!KillProcess(process_)) 167 return Status(kUnknownError, "cannot kill Chrome"); 168 return Status(kOk); 169 } 170 171 const CommandLine& ChromeDesktopImpl::command() const { 172 return command_; 173 } 174