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
KillProcess(base::ProcessHandle process_id)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(5);
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
ChromeDesktopImpl(scoped_ptr<DevToolsHttpClient> client,ScopedVector<DevToolsEventListener> & devtools_event_listeners,scoped_ptr<PortReservation> port_reservation,base::ProcessHandle process,const CommandLine & command,base::ScopedTempDir * user_data_dir,base::ScopedTempDir * extension_dir)64 ChromeDesktopImpl::ChromeDesktopImpl(
65 scoped_ptr<DevToolsHttpClient> client,
66 ScopedVector<DevToolsEventListener>& devtools_event_listeners,
67 scoped_ptr<PortReservation> port_reservation,
68 base::ProcessHandle process,
69 const CommandLine& command,
70 base::ScopedTempDir* user_data_dir,
71 base::ScopedTempDir* extension_dir)
72 : ChromeImpl(client.Pass(),
73 devtools_event_listeners,
74 port_reservation.Pass()),
75 process_(process),
76 command_(command) {
77 if (user_data_dir->IsValid())
78 CHECK(user_data_dir_.Set(user_data_dir->Take()));
79 if (extension_dir->IsValid())
80 CHECK(extension_dir_.Set(extension_dir->Take()));
81 }
82
~ChromeDesktopImpl()83 ChromeDesktopImpl::~ChromeDesktopImpl() {
84 if (!quit_) {
85 base::FilePath user_data_dir = user_data_dir_.Take();
86 base::FilePath extension_dir = extension_dir_.Take();
87 LOG(WARNING) << "chrome detaches, user should take care of directory:"
88 << user_data_dir.value() << " and " << extension_dir.value();
89 }
90 base::CloseProcessHandle(process_);
91 }
92
WaitForPageToLoad(const std::string & url,const base::TimeDelta & timeout,scoped_ptr<WebView> * web_view)93 Status ChromeDesktopImpl::WaitForPageToLoad(const std::string& url,
94 const base::TimeDelta& timeout,
95 scoped_ptr<WebView>* web_view) {
96 base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
97 std::string id;
98 while (base::TimeTicks::Now() < deadline) {
99 WebViewsInfo views_info;
100 Status status = devtools_http_client_->GetWebViewsInfo(&views_info);
101 if (status.IsError())
102 return status;
103
104 for (size_t i = 0; i < views_info.GetSize(); ++i) {
105 if (views_info.Get(i).url.find(url) == 0) {
106 id = views_info.Get(i).id;
107 break;
108 }
109 }
110 if (!id.empty())
111 break;
112 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
113 }
114 if (id.empty())
115 return Status(kUnknownError, "page could not be found: " + url);
116
117 scoped_ptr<WebView> web_view_tmp(new WebViewImpl(
118 id, GetBuildNo(), devtools_http_client_->CreateClient(id)));
119 Status status = web_view_tmp->ConnectIfNecessary();
120 if (status.IsError())
121 return status;
122
123 status = web_view_tmp->WaitForPendingNavigations(
124 std::string(), deadline - base::TimeTicks::Now(), false);
125 if (status.IsOk())
126 *web_view = web_view_tmp.Pass();
127 return status;
128 }
129
GetAutomationExtension(AutomationExtension ** extension)130 Status ChromeDesktopImpl::GetAutomationExtension(
131 AutomationExtension** extension) {
132 if (!automation_extension_) {
133 scoped_ptr<WebView> web_view;
134 Status status = WaitForPageToLoad(
135 "chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb/"
136 "_generated_background_page.html",
137 base::TimeDelta::FromSeconds(10),
138 &web_view);
139 if (status.IsError())
140 return Status(kUnknownError, "cannot get automation extension", status);
141
142 automation_extension_.reset(new AutomationExtension(web_view.Pass()));
143 }
144 *extension = automation_extension_.get();
145 return Status(kOk);
146 }
147
GetAsDesktop()148 ChromeDesktopImpl* ChromeDesktopImpl::GetAsDesktop() {
149 return this;
150 }
151
GetOperatingSystemName()152 std::string ChromeDesktopImpl::GetOperatingSystemName() {
153 return base::SysInfo::OperatingSystemName();
154 }
155
QuitImpl()156 Status ChromeDesktopImpl::QuitImpl() {
157 if (!KillProcess(process_))
158 return Status(kUnknownError, "cannot kill Chrome");
159 return Status(kOk);
160 }
161
command() const162 const CommandLine& ChromeDesktopImpl::command() const {
163 return command_;
164 }
165