OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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/automation/automation_proxy.h" | |
6 | |
7 #include <sstream> | |
8 | |
9 #include "base/basictypes.h" | |
10 #include "base/file_util.h" | |
11 #include "base/logging.h" | |
12 #include "base/memory/ref_counted.h" | |
13 #include "base/synchronization/waitable_event.h" | |
14 #include "base/threading/platform_thread.h" | |
15 #include "chrome/common/automation_constants.h" | |
16 #include "chrome/common/automation_messages.h" | |
17 #include "chrome/common/chrome_version_info.h" | |
18 #include "chrome/test/automation/browser_proxy.h" | |
19 #include "chrome/test/automation/tab_proxy.h" | |
20 #include "ipc/ipc_descriptors.h" | |
21 #if defined(OS_WIN) | |
22 // TODO(port): Enable when dialog_delegate is ported. | |
23 #include "ui/views/window/dialog_delegate.h" | |
24 #endif | |
25 | |
26 using base::TimeDelta; | |
27 using base::TimeTicks; | |
28 | |
29 namespace { | |
30 | |
31 const char kChannelErrorVersionString[] = "***CHANNEL_ERROR***"; | |
32 | |
33 // This object allows messages received on the background thread to be | |
34 // properly triaged. | |
35 class AutomationMessageFilter : public IPC::ChannelProxy::MessageFilter { | |
36 public: | |
37 explicit AutomationMessageFilter(AutomationProxy* server) : server_(server) {} | |
38 | |
39 // Return true to indicate that the message was handled, or false to let | |
40 // the message be handled in the default way. | |
41 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE { | |
42 bool handled = true; | |
43 IPC_BEGIN_MESSAGE_MAP(AutomationMessageFilter, message) | |
44 IPC_MESSAGE_HANDLER_GENERIC(AutomationMsg_Hello, | |
45 OnAutomationHello(message)) | |
46 IPC_MESSAGE_HANDLER_GENERIC( | |
47 AutomationMsg_InitialLoadsComplete, server_->SignalInitialLoads()) | |
48 IPC_MESSAGE_HANDLER(AutomationMsg_InitialNewTabUILoadComplete, | |
49 NewTabLoaded) | |
50 IPC_MESSAGE_HANDLER_GENERIC( | |
51 AutomationMsg_InvalidateHandle, server_->InvalidateHandle(message)) | |
52 IPC_MESSAGE_UNHANDLED(handled = false) | |
53 IPC_END_MESSAGE_MAP() | |
54 | |
55 return handled; | |
56 } | |
57 | |
58 virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE { | |
59 server_->SetChannel(channel); | |
60 } | |
61 | |
62 virtual void OnFilterRemoved() OVERRIDE { | |
63 server_->ResetChannel(); | |
64 } | |
65 | |
66 virtual void OnChannelError() OVERRIDE { | |
67 server_->SignalAppLaunch(kChannelErrorVersionString); | |
68 server_->SignalNewTabUITab(-1); | |
69 } | |
70 | |
71 private: | |
72 void NewTabLoaded(int load_time) { | |
73 server_->SignalNewTabUITab(load_time); | |
74 } | |
75 | |
76 void OnAutomationHello(const IPC::Message& hello_message) { | |
77 std::string server_version; | |
78 PickleIterator iter(hello_message); | |
79 if (!hello_message.ReadString(&iter, &server_version)) { | |
80 // We got an AutomationMsg_Hello from an old automation provider | |
81 // that doesn't send version info. Leave server_version as an empty | |
82 // string to signal a version mismatch. | |
83 LOG(ERROR) << "Pre-versioning protocol detected in automation provider."; | |
84 } | |
85 | |
86 server_->SignalAppLaunch(server_version); | |
87 } | |
88 | |
89 AutomationProxy* server_; | |
90 | |
91 DISALLOW_COPY_AND_ASSIGN(AutomationMessageFilter); | |
92 }; | |
93 | |
94 } // anonymous namespace | |
95 | |
96 | |
97 AutomationProxy::AutomationProxy(base::TimeDelta action_timeout, | |
98 bool disconnect_on_failure) | |
99 : app_launched_(true, false), | |
100 initial_loads_complete_(true, false), | |
101 new_tab_ui_load_complete_(true, false), | |
102 shutdown_event_(new base::WaitableEvent(true, false)), | |
103 perform_version_check_(false), | |
104 disconnect_on_failure_(disconnect_on_failure), | |
105 channel_disconnected_on_failure_(false), | |
106 action_timeout_(action_timeout), | |
107 listener_thread_id_(0) { | |
108 // base::WaitableEvent::TimedWait() will choke if we give it a negative value. | |
109 // Zero also seems unreasonable, since we need to wait for IPC, but at | |
110 // least it is legal... ;-) | |
111 DCHECK_GE(action_timeout.InMilliseconds(), 0); | |
112 listener_thread_id_ = base::PlatformThread::CurrentId(); | |
113 InitializeHandleTracker(); | |
114 InitializeThread(); | |
115 } | |
116 | |
117 AutomationProxy::~AutomationProxy() { | |
118 // Destruction order is important. Thread has to outlive the channel and | |
119 // tracker has to outlive the thread since we access the tracker inside | |
120 // AutomationMessageFilter::OnMessageReceived. | |
121 Disconnect(); | |
122 thread_.reset(); | |
123 tracker_.reset(); | |
124 } | |
125 | |
126 std::string AutomationProxy::GenerateChannelID() { | |
127 // The channel counter keeps us out of trouble if we create and destroy | |
128 // several AutomationProxies sequentially over the course of a test run. | |
129 // (Creating the channel sometimes failed before when running a lot of | |
130 // tests in sequence, and our theory is that sometimes the channel ID | |
131 // wasn't getting freed up in time for the next test.) | |
132 static int channel_counter = 0; | |
133 | |
134 std::ostringstream buf; | |
135 buf << "ChromeTestingInterface:" << base::GetCurrentProcId() << | |
136 "." << ++channel_counter; | |
137 return buf.str(); | |
138 } | |
139 | |
140 void AutomationProxy::InitializeThread() { | |
141 scoped_ptr<base::Thread> thread( | |
142 new base::Thread("AutomationProxy_BackgroundThread")); | |
143 base::Thread::Options options; | |
144 options.message_loop_type = base::MessageLoop::TYPE_IO; | |
145 bool thread_result = thread->StartWithOptions(options); | |
146 DCHECK(thread_result); | |
147 thread_.swap(thread); | |
148 } | |
149 | |
150 void AutomationProxy::InitializeChannel(const std::string& channel_id, | |
151 bool use_named_interface) { | |
152 DCHECK(shutdown_event_.get() != NULL); | |
153 | |
154 // TODO(iyengar) | |
155 // The shutdown event could be global on the same lines as the automation | |
156 // provider, where we use the shutdown event provided by the chrome browser | |
157 // process. | |
158 channel_.reset(new IPC::SyncChannel(this, // we are the listener | |
159 thread_->message_loop_proxy().get(), | |
160 shutdown_event_.get())); | |
161 channel_->AddFilter(new AutomationMessageFilter(this)); | |
162 | |
163 // Create the pipe synchronously so that Chrome doesn't try to connect to an | |
164 // unready server. Note this is done after adding a message filter to | |
165 // guarantee that it doesn't miss any messages when we are the client. | |
166 // See crbug.com/102894. | |
167 channel_->Init( | |
168 channel_id, | |
169 use_named_interface ? IPC::Channel::MODE_NAMED_CLIENT | |
170 : IPC::Channel::MODE_SERVER, | |
171 true /* create_pipe_now */); | |
172 } | |
173 | |
174 void AutomationProxy::InitializeHandleTracker() { | |
175 tracker_.reset(new AutomationHandleTracker()); | |
176 } | |
177 | |
178 AutomationLaunchResult AutomationProxy::WaitForAppLaunch() { | |
179 AutomationLaunchResult result = AUTOMATION_SUCCESS; | |
180 if (app_launched_.TimedWait(action_timeout_)) { | |
181 if (server_version_ == kChannelErrorVersionString) { | |
182 result = AUTOMATION_CHANNEL_ERROR; | |
183 } else if (perform_version_check_) { | |
184 // Obtain our own version number and compare it to what the automation | |
185 // provider sent. | |
186 chrome::VersionInfo version_info; | |
187 DCHECK(version_info.is_valid()); | |
188 | |
189 // Note that we use a simple string comparison since we expect the version | |
190 // to be a punctuated numeric string. Consider using base/Version if we | |
191 // ever need something more complicated here. | |
192 if (server_version_ != version_info.Version()) { | |
193 result = AUTOMATION_VERSION_MISMATCH; | |
194 } | |
195 } | |
196 } else { | |
197 result = AUTOMATION_TIMEOUT; | |
198 } | |
199 return result; | |
200 } | |
201 | |
202 void AutomationProxy::SignalAppLaunch(const std::string& version_string) { | |
203 server_version_ = version_string; | |
204 app_launched_.Signal(); | |
205 } | |
206 | |
207 bool AutomationProxy::WaitForProcessLauncherThreadToGoIdle() { | |
208 return Send(new AutomationMsg_WaitForProcessLauncherThreadToGoIdle()); | |
209 } | |
210 | |
211 bool AutomationProxy::WaitForInitialLoads() { | |
212 return initial_loads_complete_.TimedWait(action_timeout_); | |
213 } | |
214 | |
215 bool AutomationProxy::WaitForInitialNewTabUILoad(int* load_time) { | |
216 if (new_tab_ui_load_complete_.TimedWait(action_timeout_)) { | |
217 *load_time = new_tab_ui_load_time_; | |
218 new_tab_ui_load_complete_.Reset(); | |
219 return true; | |
220 } | |
221 return false; | |
222 } | |
223 | |
224 void AutomationProxy::SignalInitialLoads() { | |
225 initial_loads_complete_.Signal(); | |
226 } | |
227 | |
228 void AutomationProxy::SignalNewTabUITab(int load_time) { | |
229 new_tab_ui_load_time_ = load_time; | |
230 new_tab_ui_load_complete_.Signal(); | |
231 } | |
232 | |
233 bool AutomationProxy::GetBrowserWindowCount(int* num_windows) { | |
234 if (!num_windows) { | |
235 NOTREACHED(); | |
236 return false; | |
237 } | |
238 | |
239 return Send(new AutomationMsg_BrowserWindowCount(num_windows)); | |
240 } | |
241 | |
242 bool AutomationProxy::GetNormalBrowserWindowCount(int* num_windows) { | |
243 if (!num_windows) { | |
244 NOTREACHED(); | |
245 return false; | |
246 } | |
247 | |
248 return Send(new AutomationMsg_NormalBrowserWindowCount(num_windows)); | |
249 } | |
250 | |
251 bool AutomationProxy::WaitForWindowCountToBecome(int count) { | |
252 bool wait_success = false; | |
253 if (!Send(new AutomationMsg_WaitForBrowserWindowCountToBecome( | |
254 count, &wait_success))) { | |
255 return false; | |
256 } | |
257 return wait_success; | |
258 } | |
259 | |
260 bool AutomationProxy::IsURLDisplayed(GURL url) { | |
261 int window_count; | |
262 if (!GetBrowserWindowCount(&window_count)) | |
263 return false; | |
264 | |
265 for (int i = 0; i < window_count; i++) { | |
266 scoped_refptr<BrowserProxy> window = GetBrowserWindow(i); | |
267 if (!window.get()) | |
268 break; | |
269 | |
270 int tab_count; | |
271 if (!window->GetTabCount(&tab_count)) | |
272 continue; | |
273 | |
274 for (int j = 0; j < tab_count; j++) { | |
275 scoped_refptr<TabProxy> tab = window->GetTab(j); | |
276 if (!tab.get()) | |
277 break; | |
278 | |
279 GURL tab_url; | |
280 if (!tab->GetCurrentURL(&tab_url)) | |
281 continue; | |
282 | |
283 if (tab_url == url) | |
284 return true; | |
285 } | |
286 } | |
287 | |
288 return false; | |
289 } | |
290 | |
291 bool AutomationProxy::GetMetricEventDuration(const std::string& event_name, | |
292 int* duration_ms) { | |
293 return Send(new AutomationMsg_GetMetricEventDuration(event_name, | |
294 duration_ms)); | |
295 } | |
296 | |
297 void AutomationProxy::Disconnect() { | |
298 DCHECK(shutdown_event_.get() != NULL); | |
299 shutdown_event_->Signal(); | |
300 channel_.reset(); | |
301 } | |
302 | |
303 bool AutomationProxy::OnMessageReceived(const IPC::Message& msg) { | |
304 // This won't get called unless AutomationProxy is run from | |
305 // inside a message loop. | |
306 NOTREACHED(); | |
307 return false; | |
308 } | |
309 | |
310 void AutomationProxy::OnChannelError() { | |
311 LOG(ERROR) << "Channel error in AutomationProxy."; | |
312 if (disconnect_on_failure_) | |
313 Disconnect(); | |
314 } | |
315 | |
316 scoped_refptr<BrowserProxy> AutomationProxy::GetBrowserWindow( | |
317 int window_index) { | |
318 int handle = 0; | |
319 if (!Send(new AutomationMsg_BrowserWindow(window_index, &handle))) | |
320 return NULL; | |
321 | |
322 return ProxyObjectFromHandle<BrowserProxy>(handle); | |
323 } | |
324 | |
325 IPC::SyncChannel* AutomationProxy::channel() { | |
326 return channel_.get(); | |
327 } | |
328 | |
329 bool AutomationProxy::Send(IPC::Message* message) { | |
330 return Send(message, | |
331 static_cast<int>(action_timeout_.InMilliseconds())); | |
332 } | |
333 | |
334 bool AutomationProxy::Send(IPC::Message* message, int timeout_ms) { | |
335 if (!channel_.get()) { | |
336 LOG(ERROR) << "Automation channel has been closed; dropping message!"; | |
337 delete message; | |
338 return false; | |
339 } | |
340 | |
341 bool success = channel_->SendWithTimeout(message, timeout_ms); | |
342 | |
343 if (!success && disconnect_on_failure_) { | |
344 // Send failed (possibly due to a timeout). Browser is likely in a weird | |
345 // state, and further IPC requests are extremely likely to fail (possibly | |
346 // timeout, which would make tests slower). Disconnect the channel now | |
347 // to avoid the slowness. | |
348 channel_disconnected_on_failure_ = true; | |
349 LOG(ERROR) << "Disconnecting channel after error!"; | |
350 Disconnect(); | |
351 } | |
352 | |
353 return success; | |
354 } | |
355 | |
356 void AutomationProxy::InvalidateHandle(const IPC::Message& message) { | |
357 PickleIterator iter(message); | |
358 int handle; | |
359 | |
360 if (message.ReadInt(&iter, &handle)) { | |
361 tracker_->InvalidateHandle(handle); | |
362 } | |
363 } | |
364 | |
365 bool AutomationProxy::OpenNewBrowserWindow(Browser::Type type, bool show) { | |
366 return Send( | |
367 new AutomationMsg_OpenNewBrowserWindowOfType(static_cast<int>(type), | |
368 show)); | |
369 } | |
370 | |
371 template <class T> scoped_refptr<T> AutomationProxy::ProxyObjectFromHandle( | |
372 int handle) { | |
373 if (!handle) | |
374 return NULL; | |
375 | |
376 // Get AddRef-ed pointer to the object if handle is already seen. | |
377 T* p = static_cast<T*>(tracker_->GetResource(handle)); | |
378 if (!p) { | |
379 p = new T(this, tracker_.get(), handle); | |
380 p->AddRef(); | |
381 } | |
382 | |
383 // Since there is no scoped_refptr::attach. | |
384 scoped_refptr<T> result; | |
385 result.swap(&p); | |
386 return result; | |
387 } | |
388 | |
389 void AutomationProxy::SetChannel(IPC::Channel* channel) { | |
390 if (tracker_.get()) | |
391 tracker_->put_channel(channel); | |
392 } | |
393 | |
394 void AutomationProxy::ResetChannel() { | |
395 if (tracker_.get()) | |
396 tracker_->put_channel(NULL); | |
397 } | |
398 | |
399 bool AutomationProxy::BeginTracing(const std::string& category_patterns) { | |
400 bool result = false; | |
401 bool send_success = Send(new AutomationMsg_BeginTracing(category_patterns, | |
402 &result)); | |
403 return send_success && result; | |
404 } | |
405 | |
406 bool AutomationProxy::EndTracing(std::string* json_trace_output) { | |
407 bool success = false; | |
408 base::FilePath path; | |
409 if (!Send(new AutomationMsg_EndTracing(&path, &success)) || !success) | |
410 return false; | |
411 | |
412 bool ok = base::ReadFileToString(path, json_trace_output); | |
413 DCHECK(ok); | |
414 base::DeleteFile(path, false); | |
415 return true; | |
416 } | |
417 | |
418 bool AutomationProxy::SendJSONRequest(const std::string& request, | |
419 int timeout_ms, | |
420 std::string* response) { | |
421 bool result = false; | |
422 if (!Send(new AutomationMsg_SendJSONRequest(-1, request, response, &result), | |
423 timeout_ms)) | |
424 return false; | |
425 return result; | |
426 } | |
OLD | NEW |