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: 35 explicit InvokeTaskLaterTask(Task* task) : task_(task) {} 36 virtual ~InvokeTaskLaterTask() {} 37 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 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 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: 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) {} 84 virtual ~MouseEventTask() {} 85 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: 130 WindowDragResponseTask(AutomationProvider* provider, 131 IPC::Message* reply_message) 132 : provider_(provider), reply_message_(reply_message) {} 133 virtual ~WindowDragResponseTask() {} 134 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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