blob: c78309429506e055438d27bf7d4029627199cdd6 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/automation/automation_provider.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/debug/trace_event.h"
#include "base/json/json_reader.h"
#include "base/run_loop.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/automation/automation_browser_tracker.h"
#include "chrome/browser/automation/automation_tab_tracker.h"
#include "chrome/browser/automation/automation_window_tracker.h"
#include "chrome/browser/external_tab/external_tab_container.h"
#include "chrome/browser/printing/print_view_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/common/automation_messages.h"
#include "chrome/common/render_messages.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/page_zoom.h"
#include "ui/base/keycodes/keyboard_codes.h"
#include "ui/ui_controls/ui_controls.h"
#include "ui/views/focus/accelerator_handler.h"
#include "ui/views/widget/root_view.h"
using content::NavigationController;
using content::RenderViewHost;
using content::WebContents;
namespace {
// Allow some pending tasks up to |num_deferrals| generations to complete.
void DeferredQuitRunLoop(const base::Closure& quit_task,
int num_quit_deferrals) {
if (num_quit_deferrals <= 0) {
quit_task.Run();
} else {
MessageLoop::current()->PostTask(FROM_HERE,
base::Bind(&DeferredQuitRunLoop, quit_task, num_quit_deferrals - 1));
}
}
// This callback just adds another callback to the event queue. This is useful
// if you want to ensure that any callbacks added to the event queue after this
// one have already been processed by the time |callback| is run.
void InvokeCallbackLater(const base::Closure& callback) {
MessageLoop::current()->PostTask(FROM_HERE, callback);
}
void MoveMouse(const POINT& point) {
SetCursorPos(point.x, point.y);
// Now, make sure that GetMessagePos returns the values we just set by
// simulating a mouse move. The value returned by GetMessagePos is updated
// when a mouse move event is removed from the event queue.
PostMessage(NULL, WM_MOUSEMOVE, 0, MAKELPARAM(point.x, point.y));
MSG msg;
while (PeekMessage(&msg, NULL, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) {
}
// Verify
#ifndef NDEBUG
DWORD pos = GetMessagePos();
gfx::Point cursor_point(pos);
DCHECK_EQ(point.x, cursor_point.x());
DCHECK_EQ(point.y, cursor_point.y());
#endif
}
BOOL CALLBACK EnumThreadWndProc(HWND hwnd, LPARAM l_param) {
if (hwnd == reinterpret_cast<HWND>(l_param)) {
return FALSE;
}
return TRUE;
}
// This callback sends a WindowDragResponse message with the appropriate routing
// ID to the automation proxy. This is implemented as a task so that we know
// that the mouse events (and any tasks that they spawn on the message loop)
// have been processed by the time this is sent.
void WindowDragResponseCallback(AutomationProvider* provider,
IPC::Message* reply_message) {
DCHECK(reply_message != NULL);
AutomationMsg_WindowDrag::WriteReplyParams(reply_message, true);
provider->Send(reply_message);
}
} // namespace
void AutomationProvider::WindowSimulateDrag(
int handle,
const std::vector<gfx::Point>& drag_path,
int flags,
bool press_escape_en_route,
IPC::Message* reply_message) {
if (browser_tracker_->ContainsHandle(handle) && (drag_path.size() > 1)) {
gfx::NativeWindow window =
browser_tracker_->GetResource(handle)->window()->GetNativeWindow();
UINT down_message = 0;
UINT up_message = 0;
WPARAM wparam_flags = 0;
if (flags & ui::EF_SHIFT_DOWN)
wparam_flags |= MK_SHIFT;
if (flags & ui::EF_CONTROL_DOWN)
wparam_flags |= MK_CONTROL;
if (flags & ui::EF_LEFT_MOUSE_BUTTON) {
wparam_flags |= MK_LBUTTON;
down_message = WM_LBUTTONDOWN;
up_message = WM_LBUTTONUP;
}
if (flags & ui::EF_MIDDLE_MOUSE_BUTTON) {
wparam_flags |= MK_MBUTTON;
down_message = WM_MBUTTONDOWN;
up_message = WM_MBUTTONUP;
}
if (flags & ui::EF_RIGHT_MOUSE_BUTTON) {
wparam_flags |= MK_RBUTTON;
down_message = WM_LBUTTONDOWN;
up_message = WM_LBUTTONUP;
}
Browser* browser = browser_tracker_->GetResource(handle);
DCHECK(browser);
HWND top_level_hwnd =
reinterpret_cast<HWND>(browser->window()->GetNativeWindow());
POINT temp = drag_path[0].ToPOINT();
MapWindowPoints(top_level_hwnd, HWND_DESKTOP, &temp, 1);
MoveMouse(temp);
SendMessage(top_level_hwnd, down_message, wparam_flags,
MAKELPARAM(drag_path[0].x(), drag_path[0].y()));
for (int i = 1; i < static_cast<int>(drag_path.size()); ++i) {
temp = drag_path[i].ToPOINT();
MapWindowPoints(top_level_hwnd, HWND_DESKTOP, &temp, 1);
MoveMouse(temp);
SendMessage(top_level_hwnd, WM_MOUSEMOVE, wparam_flags,
MAKELPARAM(drag_path[i].x(), drag_path[i].y()));
}
POINT end = drag_path[drag_path.size() - 1].ToPOINT();
MapWindowPoints(top_level_hwnd, HWND_DESKTOP, &end, 1);
MoveMouse(end);
if (press_escape_en_route) {
// Press Escape, making sure we wait until chrome processes the escape.
// TODO(phajdan.jr): make this use ui_test_utils::SendKeyPressSync.
views::AcceleratorHandler handler;
base::RunLoop run_loop(&handler);
// Number of times to repost Quit task to allow pending tasks to complete.
// See kNumQuitDeferrals in ui_test_utils.cc for explanation.
int num_quit_deferrals = 10;
ui_controls::SendKeyPressNotifyWhenDone(
window, ui::VKEY_ESCAPE,
((flags & ui::EF_CONTROL_DOWN) ==
ui::EF_CONTROL_DOWN),
((flags & ui::EF_SHIFT_DOWN) ==
ui::EF_SHIFT_DOWN),
((flags & ui::EF_ALT_DOWN) == ui::EF_ALT_DOWN),
false,
base::Bind(&DeferredQuitRunLoop, run_loop.QuitClosure(),
num_quit_deferrals));
MessageLoopForUI* loop = MessageLoopForUI::current();
MessageLoop::ScopedNestableTaskAllower allow(loop);
run_loop.Run();
}
SendMessage(top_level_hwnd, up_message, wparam_flags,
MAKELPARAM(end.x, end.y));
MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(
&InvokeCallbackLater,
base::Bind(&WindowDragResponseCallback, this, reply_message)));
} else {
AutomationMsg_WindowDrag::WriteReplyParams(reply_message, false);
Send(reply_message);
}
}
void AutomationProvider::CreateExternalTab(
const ExternalTabSettings& settings,
gfx::NativeWindow* tab_container_window, gfx::NativeWindow* tab_window,
int* tab_handle, int* session_id) {
TRACE_EVENT_BEGIN_ETW("AutomationProvider::CreateExternalTab", 0, "");
*tab_handle = 0;
*tab_container_window = NULL;
*tab_window = NULL;
*session_id = -1;
scoped_refptr<ExternalTabContainer> external_tab_container =
ExternalTabContainer::Create(this, automation_resource_message_filter_);
Profile* profile = settings.is_incognito ?
profile_->GetOffTheRecordProfile() : profile_;
// When the ExternalTabContainer window is created we grab a reference on it
// which is released when the window is destroyed.
external_tab_container->Init(profile, settings.parent, settings.dimensions,
settings.style, settings.load_requests_via_automation,
settings.handle_top_level_requests, NULL, settings.initial_url,
settings.referrer, settings.infobars_enabled,
settings.route_all_top_level_navigations);
if (AddExternalTab(external_tab_container)) {
WebContents* web_contents = external_tab_container->GetWebContents();
SessionTabHelper* session_tab_helper =
SessionTabHelper::FromWebContents(web_contents);
*tab_handle = external_tab_container->GetTabHandle();
*tab_container_window = external_tab_container->GetExternalTabNativeView();
*tab_window = web_contents->GetNativeView();
*session_id = session_tab_helper->session_id().id();
} else {
external_tab_container->Uninitialize();
}
TRACE_EVENT_END_ETW("AutomationProvider::CreateExternalTab", 0, "");
}
bool AutomationProvider::AddExternalTab(ExternalTabContainer* external_tab) {
DCHECK(external_tab != NULL);
WebContents* web_contents = external_tab->GetWebContents();
if (web_contents) {
int tab_handle = tab_tracker_->Add(&web_contents->GetController());
external_tab->SetTabHandle(tab_handle);
return true;
}
return false;
}
void AutomationProvider::ProcessUnhandledAccelerator(
const IPC::Message& message, int handle, const MSG& msg) {
ExternalTabContainer* external_tab = GetExternalTabForHandle(handle);
if (external_tab) {
external_tab->ProcessUnhandledAccelerator(msg);
}
// This message expects no response.
}
void AutomationProvider::SetInitialFocus(const IPC::Message& message,
int handle, bool reverse,
bool restore_focus_to_view) {
ExternalTabContainer* external_tab = GetExternalTabForHandle(handle);
if (external_tab) {
external_tab->FocusThroughTabTraversal(reverse, restore_focus_to_view);
}
// This message expects no response.
}
void AutomationProvider::PrintAsync(int tab_handle) {
WebContents* web_contents = GetWebContentsForHandle(tab_handle, NULL);
if (!web_contents)
return;
printing::PrintViewManager* print_view_manager =
printing::PrintViewManager::FromWebContents(web_contents);
print_view_manager->PrintNow();
}
ExternalTabContainer* AutomationProvider::GetExternalTabForHandle(int handle) {
if (tab_tracker_->ContainsHandle(handle)) {
NavigationController* tab = tab_tracker_->GetResource(handle);
return ExternalTabContainer::GetContainerForTab(
tab->GetWebContents()->GetNativeView());
}
return NULL;
}
void AutomationProvider::OnTabReposition(
int tab_handle, const Reposition_Params& params) {
if (!tab_tracker_->ContainsHandle(tab_handle))
return;
if (!IsWindow(params.window))
return;
unsigned long process_id = 0;
unsigned long thread_id = 0;
thread_id = GetWindowThreadProcessId(params.window, &process_id);
if (thread_id != GetCurrentThreadId()) {
DCHECK_EQ(thread_id, GetCurrentThreadId());
return;
}
SetWindowPos(params.window, params.window_insert_after, params.left,
params.top, params.width, params.height, params.flags);
if (params.set_parent) {
if (IsWindow(params.parent_window)) {
if (!SetParent(params.window, params.parent_window))
DLOG(WARNING) << "SetParent failed. Error 0x%x" << GetLastError();
}
}
}
void AutomationProvider::OnForwardContextMenuCommandToChrome(int tab_handle,
int command) {
if (!tab_tracker_->ContainsHandle(tab_handle))
return;
NavigationController* tab = tab_tracker_->GetResource(tab_handle);
if (!tab) {
NOTREACHED();
return;
}
WebContents* web_contents = tab->GetWebContents();
if (!web_contents || !web_contents->GetDelegate()) {
NOTREACHED();
return;
}
web_contents->GetDelegate()->ExecuteContextMenuCommand(command);
}
void AutomationProvider::ConnectExternalTab(
uint64 cookie,
bool allow,
gfx::NativeWindow parent_window,
gfx::NativeWindow* tab_container_window,
gfx::NativeWindow* tab_window,
int* tab_handle,
int* session_id) {
TRACE_EVENT_BEGIN_ETW("AutomationProvider::ConnectExternalTab", 0, "");
*tab_handle = 0;
*tab_container_window = NULL;
*tab_window = NULL;
*session_id = -1;
scoped_refptr<ExternalTabContainer> external_tab_container =
ExternalTabContainer::RemovePendingTab(static_cast<uintptr_t>(cookie));
if (!external_tab_container.get()) {
NOTREACHED();
return;
}
if (allow && AddExternalTab(external_tab_container)) {
external_tab_container->Reinitialize(this,
automation_resource_message_filter_,
parent_window);
WebContents* web_contents = external_tab_container->GetWebContents();
SessionTabHelper* session_tab_helper =
SessionTabHelper::FromWebContents(web_contents);
*tab_handle = external_tab_container->GetTabHandle();
*tab_container_window = external_tab_container->GetExternalTabNativeView();
*tab_window = web_contents->GetNativeView();
*session_id = session_tab_helper->session_id().id();
} else {
external_tab_container->Uninitialize();
}
TRACE_EVENT_END_ETW("AutomationProvider::ConnectExternalTab", 0, "");
}
void AutomationProvider::OnBrowserMoved(int tab_handle) {
ExternalTabContainer* external_tab = GetExternalTabForHandle(tab_handle);
if (!external_tab) {
DLOG(WARNING) <<
"AutomationProvider::OnBrowserMoved called with invalid tab handle.";
}
}
void AutomationProvider::OnMessageFromExternalHost(int handle,
const std::string& message,
const std::string& origin,
const std::string& target) {
RenderViewHost* view_host = GetViewForTab(handle);
if (!view_host)
return;
view_host->Send(new ChromeViewMsg_HandleMessageFromExternalHost(
view_host->GetRoutingID(), message, origin, target));
}
void AutomationProvider::NavigateInExternalTab(
int handle, const GURL& url, const GURL& referrer,
AutomationMsg_NavigationResponseValues* status) {
*status = AUTOMATION_MSG_NAVIGATION_ERROR;
if (tab_tracker_->ContainsHandle(handle)) {
NavigationController* tab = tab_tracker_->GetResource(handle);
tab->LoadURL(
url,
content::Referrer(referrer, WebKit::WebReferrerPolicyDefault),
content::PAGE_TRANSITION_TYPED, std::string());
*status = AUTOMATION_MSG_NAVIGATION_SUCCESS;
}
}
void AutomationProvider::NavigateExternalTabAtIndex(
int handle, int navigation_index,
AutomationMsg_NavigationResponseValues* status) {
*status = AUTOMATION_MSG_NAVIGATION_ERROR;
if (tab_tracker_->ContainsHandle(handle)) {
NavigationController* tab = tab_tracker_->GetResource(handle);
tab->GoToIndex(navigation_index);
*status = AUTOMATION_MSG_NAVIGATION_SUCCESS;
}
}
void AutomationProvider::OnRunUnloadHandlers(
int handle, IPC::Message* reply_message) {
ExternalTabContainer* external_tab = GetExternalTabForHandle(handle);
if (external_tab) {
external_tab->RunUnloadHandlers(reply_message);
}
}
void AutomationProvider::OnSetZoomLevel(int handle, int zoom_level) {
if (tab_tracker_->ContainsHandle(handle)) {
NavigationController* tab = tab_tracker_->GetResource(handle);
if (tab->GetWebContents() && tab->GetWebContents()->GetRenderViewHost()) {
RenderViewHost* host = tab->GetWebContents()->GetRenderViewHost();
content::PageZoom zoom = static_cast<content::PageZoom>(zoom_level);
host->Zoom(zoom);
}
}
}