Home | History | Annotate | Download | only in automation
      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