1 // Copyright (c) 2011 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/browser/automation/automation_provider.h"
6
7 #include "base/debug/trace_event.h"
8 #include "base/json/json_reader.h"
9 #include "base/utf_string_conversions.h"
10 #include "chrome/browser/automation/automation_browser_tracker.h"
11 #include "chrome/browser/automation/automation_tab_tracker.h"
12 #include "chrome/browser/automation/automation_window_tracker.h"
13 #include "chrome/browser/automation/ui_controls.h"
14 #include "chrome/browser/external_tab_container_win.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/ui/browser.h"
17 #include "chrome/browser/ui/browser_window.h"
18 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
19 #include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
20 #include "chrome/common/automation_messages.h"
21 #include "content/browser/renderer_host/render_view_host.h"
22 #include "content/browser/tab_contents/tab_contents.h"
23 #include "content/common/page_zoom.h"
24 #include "ui/base/keycodes/keyboard_codes.h"
25 #include "views/focus/accelerator_handler.h"
26 #include "views/widget/root_view.h"
27 #include "views/widget/widget_win.h"
28 #include "views/window/window.h"
29
30 // This task just adds another task to the event queue. This is useful if
31 // you want to ensure that any tasks added to the event queue after this one
32 // have already been processed by the time |task| is run.
33 class InvokeTaskLaterTask : public Task {
34 public:
InvokeTaskLaterTask(Task * task)35 explicit InvokeTaskLaterTask(Task* task) : task_(task) {}
~InvokeTaskLaterTask()36 virtual ~InvokeTaskLaterTask() {}
37
Run()38 virtual void Run() {
39 MessageLoop::current()->PostTask(FROM_HERE, task_);
40 }
41
42 private:
43 Task* task_;
44
45 DISALLOW_COPY_AND_ASSIGN(InvokeTaskLaterTask);
46 };
47
MoveMouse(const POINT & point)48 static void MoveMouse(const POINT& point) {
49 SetCursorPos(point.x, point.y);
50
51 // Now, make sure that GetMessagePos returns the values we just set by
52 // simulating a mouse move. The value returned by GetMessagePos is updated
53 // when a mouse move event is removed from the event queue.
54 PostMessage(NULL, WM_MOUSEMOVE, 0, MAKELPARAM(point.x, point.y));
55 MSG msg;
56 while (PeekMessage(&msg, NULL, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) {
57 }
58
59 // Verify
60 #ifndef NDEBUG
61 DWORD pos = GetMessagePos();
62 gfx::Point cursor_point(pos);
63 DCHECK_EQ(point.x, cursor_point.x());
64 DCHECK_EQ(point.y, cursor_point.y());
65 #endif
66 }
67
EnumThreadWndProc(HWND hwnd,LPARAM l_param)68 BOOL CALLBACK EnumThreadWndProc(HWND hwnd, LPARAM l_param) {
69 if (hwnd == reinterpret_cast<HWND>(l_param)) {
70 return FALSE;
71 }
72 return TRUE;
73 }
74
75 // This task enqueues a mouse event on the event loop, so that the view
76 // that it's being sent to can do the requisite post-processing.
77 class MouseEventTask : public Task {
78 public:
MouseEventTask(views::View * view,ui::EventType type,const gfx::Point & point,int flags)79 MouseEventTask(views::View* view,
80 ui::EventType type,
81 const gfx::Point& point,
82 int flags)
83 : view_(view), type_(type), point_(point), flags_(flags) {}
~MouseEventTask()84 virtual ~MouseEventTask() {}
85
Run()86 virtual void Run() {
87 views::MouseEvent event(type_, point_.x(), point_.y(), flags_);
88 // We need to set the cursor position before we process the event because
89 // some code (tab dragging, for instance) queries the actual cursor location
90 // rather than the location of the mouse event. Note that the reason why
91 // the drag code moved away from using mouse event locations was because
92 // our conversion to screen location doesn't work well with multiple
93 // monitors, so this only works reliably in a single monitor setup.
94 gfx::Point screen_location(point_.x(), point_.y());
95 view_->ConvertPointToScreen(view_, &screen_location);
96 MoveMouse(screen_location.ToPOINT());
97 switch (type_) {
98 case ui::ET_MOUSE_PRESSED:
99 view_->OnMousePressed(event);
100 break;
101
102 case ui::ET_MOUSE_DRAGGED:
103 view_->OnMouseDragged(event);
104 break;
105
106 case ui::ET_MOUSE_RELEASED:
107 view_->OnMouseReleased(event);
108 break;
109
110 default:
111 NOTREACHED();
112 }
113 }
114
115 private:
116 views::View* view_;
117 ui::EventType type_;
118 gfx::Point point_;
119 int flags_;
120
121 DISALLOW_COPY_AND_ASSIGN(MouseEventTask);
122 };
123
124 // This task sends a WindowDragResponse message with the appropriate
125 // routing ID to the automation proxy. This is implemented as a task so that
126 // we know that the mouse events (and any tasks that they spawn on the message
127 // loop) have been processed by the time this is sent.
128 class WindowDragResponseTask : public Task {
129 public:
WindowDragResponseTask(AutomationProvider * provider,IPC::Message * reply_message)130 WindowDragResponseTask(AutomationProvider* provider,
131 IPC::Message* reply_message)
132 : provider_(provider), reply_message_(reply_message) {}
~WindowDragResponseTask()133 virtual ~WindowDragResponseTask() {}
134
Run()135 virtual void Run() {
136 DCHECK(reply_message_ != NULL);
137 AutomationMsg_WindowDrag::WriteReplyParams(reply_message_, true);
138 provider_->Send(reply_message_);
139 }
140
141 private:
142 AutomationProvider* provider_;
143 IPC::Message* reply_message_;
144
145 DISALLOW_COPY_AND_ASSIGN(WindowDragResponseTask);
146 };
147
WindowSimulateDrag(int handle,const std::vector<gfx::Point> & drag_path,int flags,bool press_escape_en_route,IPC::Message * reply_message)148 void AutomationProvider::WindowSimulateDrag(
149 int handle,
150 const std::vector<gfx::Point>& drag_path,
151 int flags,
152 bool press_escape_en_route,
153 IPC::Message* reply_message) {
154 if (browser_tracker_->ContainsHandle(handle) && (drag_path.size() > 1)) {
155 gfx::NativeWindow window =
156 browser_tracker_->GetResource(handle)->window()->GetNativeHandle();
157
158 UINT down_message = 0;
159 UINT up_message = 0;
160 WPARAM wparam_flags = 0;
161 if (flags & ui::EF_SHIFT_DOWN)
162 wparam_flags |= MK_SHIFT;
163 if (flags & ui::EF_CONTROL_DOWN)
164 wparam_flags |= MK_CONTROL;
165 if (flags & ui::EF_LEFT_BUTTON_DOWN) {
166 wparam_flags |= MK_LBUTTON;
167 down_message = WM_LBUTTONDOWN;
168 up_message = WM_LBUTTONUP;
169 }
170 if (flags & ui::EF_MIDDLE_BUTTON_DOWN) {
171 wparam_flags |= MK_MBUTTON;
172 down_message = WM_MBUTTONDOWN;
173 up_message = WM_MBUTTONUP;
174 }
175 if (flags & ui::EF_RIGHT_BUTTON_DOWN) {
176 wparam_flags |= MK_RBUTTON;
177 down_message = WM_LBUTTONDOWN;
178 up_message = WM_LBUTTONUP;
179 }
180
181 Browser* browser = browser_tracker_->GetResource(handle);
182 DCHECK(browser);
183 HWND top_level_hwnd =
184 reinterpret_cast<HWND>(browser->window()->GetNativeHandle());
185 POINT temp = drag_path[0].ToPOINT();
186 MapWindowPoints(top_level_hwnd, HWND_DESKTOP, &temp, 1);
187 MoveMouse(temp);
188 SendMessage(top_level_hwnd, down_message, wparam_flags,
189 MAKELPARAM(drag_path[0].x(), drag_path[0].y()));
190 for (int i = 1; i < static_cast<int>(drag_path.size()); ++i) {
191 temp = drag_path[i].ToPOINT();
192 MapWindowPoints(top_level_hwnd, HWND_DESKTOP, &temp, 1);
193 MoveMouse(temp);
194 SendMessage(top_level_hwnd, WM_MOUSEMOVE, wparam_flags,
195 MAKELPARAM(drag_path[i].x(), drag_path[i].y()));
196 }
197 POINT end = drag_path[drag_path.size() - 1].ToPOINT();
198 MapWindowPoints(top_level_hwnd, HWND_DESKTOP, &end, 1);
199 MoveMouse(end);
200
201 if (press_escape_en_route) {
202 // Press Escape, making sure we wait until chrome processes the escape.
203 // TODO(phajdan.jr): make this use ui_test_utils::SendKeyPressSync.
204 ui_controls::SendKeyPressNotifyWhenDone(
205 window, ui::VKEY_ESCAPE,
206 ((flags & ui::EF_CONTROL_DOWN) ==
207 ui::EF_CONTROL_DOWN),
208 ((flags & ui::EF_SHIFT_DOWN) ==
209 ui::EF_SHIFT_DOWN),
210 ((flags & ui::EF_ALT_DOWN) == ui::EF_ALT_DOWN),
211 false,
212 new MessageLoop::QuitTask());
213 MessageLoopForUI* loop = MessageLoopForUI::current();
214 bool did_allow_task_nesting = loop->NestableTasksAllowed();
215 loop->SetNestableTasksAllowed(true);
216 views::AcceleratorHandler handler;
217 loop->Run(&handler);
218 loop->SetNestableTasksAllowed(did_allow_task_nesting);
219 }
220 SendMessage(top_level_hwnd, up_message, wparam_flags,
221 MAKELPARAM(end.x, end.y));
222
223 MessageLoop::current()->PostTask(FROM_HERE, new InvokeTaskLaterTask(
224 new WindowDragResponseTask(this, reply_message)));
225 } else {
226 AutomationMsg_WindowDrag::WriteReplyParams(reply_message, false);
227 Send(reply_message);
228 }
229 }
230
CreateExternalTab(const ExternalTabSettings & settings,gfx::NativeWindow * tab_container_window,gfx::NativeWindow * tab_window,int * tab_handle,int * session_id)231 void AutomationProvider::CreateExternalTab(
232 const ExternalTabSettings& settings,
233 gfx::NativeWindow* tab_container_window, gfx::NativeWindow* tab_window,
234 int* tab_handle, int* session_id) {
235 TRACE_EVENT_BEGIN("AutomationProvider::CreateExternalTab", 0, "");
236
237 *tab_handle = 0;
238 *tab_container_window = NULL;
239 *tab_window = NULL;
240 *session_id = -1;
241 scoped_refptr<ExternalTabContainer> external_tab_container =
242 new ExternalTabContainer(this, automation_resource_message_filter_);
243
244 Profile* profile = settings.is_incognito ?
245 profile_->GetOffTheRecordProfile() : profile_;
246
247 // When the ExternalTabContainer window is created we grab a reference on it
248 // which is released when the window is destroyed.
249 external_tab_container->Init(profile, settings.parent, settings.dimensions,
250 settings.style, settings.load_requests_via_automation,
251 settings.handle_top_level_requests, NULL, settings.initial_url,
252 settings.referrer, settings.infobars_enabled,
253 settings.route_all_top_level_navigations);
254
255 if (AddExternalTab(external_tab_container)) {
256 TabContents* tab_contents = external_tab_container->tab_contents();
257 *tab_handle = external_tab_container->tab_handle();
258 *tab_container_window = external_tab_container->GetNativeView();
259 *tab_window = tab_contents->GetNativeView();
260 *session_id = tab_contents->controller().session_id().id();
261 } else {
262 external_tab_container->Uninitialize();
263 }
264
265 TRACE_EVENT_END("AutomationProvider::CreateExternalTab", 0, "");
266 }
267
AddExternalTab(ExternalTabContainer * external_tab)268 bool AutomationProvider::AddExternalTab(ExternalTabContainer* external_tab) {
269 DCHECK(external_tab != NULL);
270
271 TabContents* tab_contents = external_tab->tab_contents();
272 if (tab_contents) {
273 int tab_handle = tab_tracker_->Add(&tab_contents->controller());
274 external_tab->SetTabHandle(tab_handle);
275 return true;
276 }
277
278 return false;
279 }
280
ProcessUnhandledAccelerator(const IPC::Message & message,int handle,const MSG & msg)281 void AutomationProvider::ProcessUnhandledAccelerator(
282 const IPC::Message& message, int handle, const MSG& msg) {
283 ExternalTabContainer* external_tab = GetExternalTabForHandle(handle);
284 if (external_tab) {
285 external_tab->ProcessUnhandledAccelerator(msg);
286 }
287 // This message expects no response.
288 }
289
SetInitialFocus(const IPC::Message & message,int handle,bool reverse,bool restore_focus_to_view)290 void AutomationProvider::SetInitialFocus(const IPC::Message& message,
291 int handle, bool reverse,
292 bool restore_focus_to_view) {
293 ExternalTabContainer* external_tab = GetExternalTabForHandle(handle);
294 if (external_tab) {
295 external_tab->FocusThroughTabTraversal(reverse, restore_focus_to_view);
296 }
297 // This message expects no response.
298 }
299
PrintAsync(int tab_handle)300 void AutomationProvider::PrintAsync(int tab_handle) {
301 TabContents* tab_contents = GetTabContentsForHandle(tab_handle, NULL);
302 if (!tab_contents)
303 return;
304
305 TabContentsWrapper* wrapper =
306 TabContentsWrapper::GetCurrentWrapperForContents(tab_contents);
307 wrapper->print_view_manager()->PrintNow();
308 }
309
GetExternalTabForHandle(int handle)310 ExternalTabContainer* AutomationProvider::GetExternalTabForHandle(int handle) {
311 if (tab_tracker_->ContainsHandle(handle)) {
312 NavigationController* tab = tab_tracker_->GetResource(handle);
313 return ExternalTabContainer::GetContainerForTab(
314 tab->tab_contents()->GetNativeView());
315 }
316
317 return NULL;
318 }
319
OnTabReposition(int tab_handle,const Reposition_Params & params)320 void AutomationProvider::OnTabReposition(
321 int tab_handle, const Reposition_Params& params) {
322 if (!tab_tracker_->ContainsHandle(tab_handle))
323 return;
324
325 if (!IsWindow(params.window))
326 return;
327
328 unsigned long process_id = 0;
329 unsigned long thread_id = 0;
330
331 thread_id = GetWindowThreadProcessId(params.window, &process_id);
332
333 if (thread_id != GetCurrentThreadId()) {
334 DCHECK_EQ(thread_id, GetCurrentThreadId());
335 return;
336 }
337
338 SetWindowPos(params.window, params.window_insert_after, params.left,
339 params.top, params.width, params.height, params.flags);
340
341 if (params.set_parent) {
342 if (IsWindow(params.parent_window)) {
343 if (!SetParent(params.window, params.parent_window))
344 DLOG(WARNING) << "SetParent failed. Error 0x%x" << GetLastError();
345 }
346 }
347 }
348
OnForwardContextMenuCommandToChrome(int tab_handle,int command)349 void AutomationProvider::OnForwardContextMenuCommandToChrome(int tab_handle,
350 int command) {
351 if (tab_tracker_->ContainsHandle(tab_handle)) {
352 NavigationController* tab = tab_tracker_->GetResource(tab_handle);
353 if (!tab) {
354 NOTREACHED();
355 return;
356 }
357
358 TabContents* tab_contents = tab->tab_contents();
359 if (!tab_contents || !tab_contents->delegate()) {
360 NOTREACHED();
361 return;
362 }
363
364 tab_contents->delegate()->ExecuteContextMenuCommand(command);
365 }
366 }
367
ConnectExternalTab(uint64 cookie,bool allow,gfx::NativeWindow parent_window,gfx::NativeWindow * tab_container_window,gfx::NativeWindow * tab_window,int * tab_handle,int * session_id)368 void AutomationProvider::ConnectExternalTab(
369 uint64 cookie,
370 bool allow,
371 gfx::NativeWindow parent_window,
372 gfx::NativeWindow* tab_container_window,
373 gfx::NativeWindow* tab_window,
374 int* tab_handle,
375 int* session_id) {
376 TRACE_EVENT_BEGIN("AutomationProvider::ConnectExternalTab", 0, "");
377
378 *tab_handle = 0;
379 *tab_container_window = NULL;
380 *tab_window = NULL;
381 *session_id = -1;
382
383 scoped_refptr<ExternalTabContainer> external_tab_container =
384 ExternalTabContainer::RemovePendingTab(static_cast<uintptr_t>(cookie));
385 if (!external_tab_container.get()) {
386 NOTREACHED();
387 return;
388 }
389
390 if (allow && AddExternalTab(external_tab_container)) {
391 external_tab_container->Reinitialize(this,
392 automation_resource_message_filter_,
393 parent_window);
394 TabContents* tab_contents = external_tab_container->tab_contents();
395 *tab_handle = external_tab_container->tab_handle();
396 *tab_container_window = external_tab_container->GetNativeView();
397 *tab_window = tab_contents->GetNativeView();
398 *session_id = tab_contents->controller().session_id().id();
399 } else {
400 external_tab_container->Uninitialize();
401 }
402
403 TRACE_EVENT_END("AutomationProvider::ConnectExternalTab", 0, "");
404 }
405
OnBrowserMoved(int tab_handle)406 void AutomationProvider::OnBrowserMoved(int tab_handle) {
407 ExternalTabContainer* external_tab = GetExternalTabForHandle(tab_handle);
408 if (external_tab) {
409 external_tab->WindowMoved();
410 } else {
411 DLOG(WARNING) <<
412 "AutomationProvider::OnBrowserMoved called with invalid tab handle.";
413 }
414 }
415
OnMessageFromExternalHost(int handle,const std::string & message,const std::string & origin,const std::string & target)416 void AutomationProvider::OnMessageFromExternalHost(int handle,
417 const std::string& message,
418 const std::string& origin,
419 const std::string& target) {
420 RenderViewHost* view_host = GetViewForTab(handle);
421 if (!view_host)
422 return;
423
424 view_host->ForwardMessageFromExternalHost(message, origin, target);
425 }
426
NavigateInExternalTab(int handle,const GURL & url,const GURL & referrer,AutomationMsg_NavigationResponseValues * status)427 void AutomationProvider::NavigateInExternalTab(
428 int handle, const GURL& url, const GURL& referrer,
429 AutomationMsg_NavigationResponseValues* status) {
430 *status = AUTOMATION_MSG_NAVIGATION_ERROR;
431
432 if (tab_tracker_->ContainsHandle(handle)) {
433 NavigationController* tab = tab_tracker_->GetResource(handle);
434 tab->LoadURL(url, referrer, PageTransition::TYPED);
435 *status = AUTOMATION_MSG_NAVIGATION_SUCCESS;
436 }
437 }
438
NavigateExternalTabAtIndex(int handle,int navigation_index,AutomationMsg_NavigationResponseValues * status)439 void AutomationProvider::NavigateExternalTabAtIndex(
440 int handle, int navigation_index,
441 AutomationMsg_NavigationResponseValues* status) {
442 *status = AUTOMATION_MSG_NAVIGATION_ERROR;
443
444 if (tab_tracker_->ContainsHandle(handle)) {
445 NavigationController* tab = tab_tracker_->GetResource(handle);
446 tab->GoToIndex(navigation_index);
447 *status = AUTOMATION_MSG_NAVIGATION_SUCCESS;
448 }
449 }
450
OnRunUnloadHandlers(int handle,IPC::Message * reply_message)451 void AutomationProvider::OnRunUnloadHandlers(
452 int handle, IPC::Message* reply_message) {
453 ExternalTabContainer* external_tab = GetExternalTabForHandle(handle);
454 if (external_tab) {
455 external_tab->RunUnloadHandlers(reply_message);
456 }
457 }
458
OnSetZoomLevel(int handle,int zoom_level)459 void AutomationProvider::OnSetZoomLevel(int handle, int zoom_level) {
460 if (tab_tracker_->ContainsHandle(handle)) {
461 NavigationController* tab = tab_tracker_->GetResource(handle);
462 if (tab->tab_contents() && tab->tab_contents()->render_view_host()) {
463 tab->tab_contents()->render_view_host()->Zoom(
464 static_cast<PageZoom::Function>(zoom_level));
465 }
466 }
467 }
468