// Copyright 2014 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 "content/browser/devtools/protocol/input_handler.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "cc/output/compositor_frame_metadata.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/common/input/synthetic_pinch_gesture_params.h" #include "content/common/input/synthetic_smooth_scroll_gesture_params.h" #include "content/common/input/synthetic_tap_gesture_params.h" #include "third_party/WebKit/public/web/WebInputEvent.h" #include "ui/events/keycodes/dom4/keycode_converter.h" #include "ui/gfx/geometry/point.h" namespace content { namespace devtools { namespace input { namespace { gfx::Point CssPixelsToPoint(int x, int y, float page_scale_factor) { return gfx::Point(x * page_scale_factor, y * page_scale_factor); } gfx::Vector2d CssPixelsToVector2d(int x, int y, float page_scale_factor) { return gfx::Vector2d(x * page_scale_factor, y * page_scale_factor); } bool StringToGestureSourceType(const std::string& in, SyntheticGestureParams::GestureSourceType& out) { if (in == kGestureSourceTypeDefault) { out = SyntheticGestureParams::GestureSourceType::DEFAULT_INPUT; return true; } else if (in == kGestureSourceTypeTouch) { out = SyntheticGestureParams::GestureSourceType::TOUCH_INPUT; return true; } else if (in == kGestureSourceTypeMouse) { out = SyntheticGestureParams::GestureSourceType::MOUSE_INPUT; return true; } else { return false; } } } typedef DevToolsProtocolClient::Response Response; namespace { void SetEventModifiers(blink::WebInputEvent* event, const int* modifiers) { if (!modifiers) return; if (*modifiers & 1) event->modifiers |= blink::WebInputEvent::AltKey; if (*modifiers & 2) event->modifiers |= blink::WebInputEvent::ControlKey; if (*modifiers & 4) event->modifiers |= blink::WebInputEvent::MetaKey; if (*modifiers & 8) event->modifiers |= blink::WebInputEvent::ShiftKey; } void SetEventTimestamp(blink::WebInputEvent* event, const double* timestamp) { event->timeStampSeconds = timestamp ? *timestamp : base::Time::Now().ToDoubleT(); } bool SetKeyboardEventText(blink::WebUChar* to, const std::string* from) { if (!from) return true; base::string16 text16 = base::UTF8ToUTF16(*from); if (text16.size() > blink::WebKeyboardEvent::textLengthCap) return false; for (size_t i = 0; i < text16.size(); ++i) to[i] = text16[i]; return true; } bool SetMouseEventButton(blink::WebMouseEvent* event, const std::string* button) { if (!button) return true; if (*button == dispatch_mouse_event::kButtonNone) { event->button = blink::WebMouseEvent::ButtonNone; } else if (*button == dispatch_mouse_event::kButtonLeft) { event->button = blink::WebMouseEvent::ButtonLeft; event->modifiers |= blink::WebInputEvent::LeftButtonDown; } else if (*button == dispatch_mouse_event::kButtonMiddle) { event->button = blink::WebMouseEvent::ButtonMiddle; event->modifiers |= blink::WebInputEvent::MiddleButtonDown; } else if (*button == dispatch_mouse_event::kButtonRight) { event->button = blink::WebMouseEvent::ButtonRight; event->modifiers |= blink::WebInputEvent::RightButtonDown; } else { return false; } return true; } bool SetMouseEventType(blink::WebMouseEvent* event, const std::string& type) { if (type == dispatch_mouse_event::kTypeMousePressed) { event->type = blink::WebInputEvent::MouseDown; } else if (type == dispatch_mouse_event::kTypeMouseReleased) { event->type = blink::WebInputEvent::MouseUp; } else if (type == dispatch_mouse_event::kTypeMouseMoved) { event->type = blink::WebInputEvent::MouseMove; } else { return false; } return true; } } // namespace InputHandler::InputHandler() : host_(NULL), page_scale_factor_(1.0), weak_factory_(this) { } InputHandler::~InputHandler() { } void InputHandler::SetRenderWidgetHost(RenderWidgetHostImpl* host) { host_ = host; } void InputHandler::SetClient(scoped_ptr client) { client_.swap(client); } void InputHandler::OnSwapCompositorFrame( const cc::CompositorFrameMetadata& frame_metadata) { page_scale_factor_ = frame_metadata.page_scale_factor; scrollable_viewport_size_ = frame_metadata.scrollable_viewport_size; } Response InputHandler::DispatchKeyEvent( const std::string& type, const int* modifiers, const double* timestamp, const std::string* text, const std::string* unmodified_text, const std::string* key_identifier, const std::string* code, const int* windows_virtual_key_code, const int* native_virtual_key_code, const bool* auto_repeat, const bool* is_keypad, const bool* is_system_key) { NativeWebKeyboardEvent event; if (type == dispatch_key_event::kTypeKeyDown) { event.type = blink::WebInputEvent::KeyDown; } else if (type == dispatch_key_event::kTypeKeyUp) { event.type = blink::WebInputEvent::KeyUp; } else if (type == dispatch_key_event::kTypeChar) { event.type = blink::WebInputEvent::Char; } else if (type == dispatch_key_event::kTypeRawKeyDown) { event.type = blink::WebInputEvent::RawKeyDown; } else { return Response::InvalidParams( base::StringPrintf("Unexpected event type '%s'", type.c_str())); } SetEventModifiers(&event, modifiers); SetEventTimestamp(&event, timestamp); if (!SetKeyboardEventText(event.text, text)) return Response::InvalidParams("Invalid 'text' parameter"); if (!SetKeyboardEventText(event.unmodifiedText, unmodified_text)) return Response::InvalidParams("Invalid 'unmodifiedText' parameter"); if (key_identifier) { if (key_identifier->size() > blink::WebKeyboardEvent::keyIdentifierLengthCap) { return Response::InvalidParams("Invalid 'keyIdentifier' parameter"); } for (size_t i = 0; i < key_identifier->size(); ++i) event.keyIdentifier[i] = (*key_identifier)[i]; } if (code) { event.domCode = static_cast( ui::KeycodeConverter::CodeStringToDomCode(code->c_str())); } if (windows_virtual_key_code) event.windowsKeyCode = *windows_virtual_key_code; if (native_virtual_key_code) event.nativeKeyCode = *native_virtual_key_code; if (auto_repeat && *auto_repeat) event.modifiers |= blink::WebInputEvent::IsAutoRepeat; if (is_keypad && *is_keypad) event.modifiers |= blink::WebInputEvent::IsKeyPad; if (is_system_key) event.isSystemKey = *is_system_key; if (!host_) return Response::ServerError("Could not connect to view"); host_->Focus(); host_->ForwardKeyboardEvent(event); return Response::OK(); } Response InputHandler::DispatchMouseEvent( const std::string& type, int x, int y, const int* modifiers, const double* timestamp, const std::string* button, const int* click_count) { blink::WebMouseEvent event; if (!SetMouseEventType(&event, type)) { return Response::InvalidParams( base::StringPrintf("Unexpected event type '%s'", type.c_str())); } SetEventModifiers(&event, modifiers); SetEventTimestamp(&event, timestamp); if (!SetMouseEventButton(&event, button)) return Response::InvalidParams("Invalid mouse button"); event.x = x; event.y = y; event.windowX = x; event.windowY = y; event.globalX = x; event.globalY = y; event.clickCount = click_count ? *click_count : 0; if (!host_) return Response::ServerError("Could not connect to view"); host_->Focus(); host_->ForwardMouseEvent(event); return Response::OK(); } Response InputHandler::EmulateTouchFromMouseEvent(const std::string& type, int x, int y, double timestamp, const std::string& button, double* delta_x, double* delta_y, int* modifiers, int* click_count) { blink::WebMouseWheelEvent wheel_event; blink::WebMouseEvent mouse_event; blink::WebMouseEvent* event = &mouse_event; if (type == emulate_touch_from_mouse_event::kTypeMouseWheel) { if (!delta_x || !delta_y) { return Response::InvalidParams( "'deltaX' and 'deltaY' are expected for mouseWheel event"); } wheel_event.deltaX = static_cast(*delta_x); wheel_event.deltaY = static_cast(*delta_y); event = &wheel_event; event->type = blink::WebInputEvent::MouseWheel; } else if (!SetMouseEventType(event, type)) { return Response::InvalidParams( base::StringPrintf("Unexpected event type '%s'", type.c_str())); } SetEventModifiers(event, modifiers); SetEventTimestamp(event, ×tamp); if (!SetMouseEventButton(event, &button)) return Response::InvalidParams("Invalid mouse button"); event->x = x; event->y = y; event->windowX = x; event->windowY = y; event->globalX = x; event->globalY = y; event->clickCount = click_count ? *click_count : 0; if (!host_) return Response::ServerError("Could not connect to view"); if (event->type == blink::WebInputEvent::MouseWheel) host_->ForwardWheelEvent(wheel_event); else host_->ForwardMouseEvent(mouse_event); return Response::OK(); } Response InputHandler::SynthesizePinchGesture( DevToolsCommandId command_id, int x, int y, double scale_factor, const int* relative_speed, const std::string* gesture_source_type) { if (!host_) return Response::ServerError("Could not connect to view"); SyntheticPinchGestureParams gesture_params; const int kDefaultRelativeSpeed = 800; gesture_params.scale_factor = scale_factor; gesture_params.anchor = CssPixelsToPoint(x, y, page_scale_factor_); gesture_params.relative_pointer_speed_in_pixels_s = relative_speed ? *relative_speed : kDefaultRelativeSpeed; if (!StringToGestureSourceType( gesture_source_type ? *gesture_source_type : kGestureSourceTypeDefault, gesture_params.gesture_source_type)) { return Response::InvalidParams("gestureSourceType"); } host_->QueueSyntheticGesture( SyntheticGesture::Create(gesture_params), base::Bind(&InputHandler::SendSynthesizePinchGestureResponse, weak_factory_.GetWeakPtr(), command_id)); return Response::OK(); } Response InputHandler::SynthesizeScrollGesture( DevToolsCommandId command_id, int x, int y, const int* x_distance, const int* y_distance, const int* x_overscroll, const int* y_overscroll, const bool* prevent_fling, const int* speed, const std::string* gesture_source_type) { if (!host_) return Response::ServerError("Could not connect to view"); SyntheticSmoothScrollGestureParams gesture_params; const bool kDefaultPreventFling = true; const int kDefaultSpeed = 800; gesture_params.anchor = CssPixelsToPoint(x, y, page_scale_factor_); gesture_params.prevent_fling = prevent_fling ? *prevent_fling : kDefaultPreventFling; gesture_params.speed_in_pixels_s = speed ? *speed : kDefaultSpeed; if (x_distance || y_distance) { gesture_params.distances.push_back( CssPixelsToVector2d(x_distance ? *x_distance : 0, y_distance ? *y_distance : 0, page_scale_factor_)); } if (x_overscroll || y_overscroll) { gesture_params.distances.push_back( CssPixelsToVector2d(x_overscroll ? -*x_overscroll : 0, y_overscroll ? -*y_overscroll : 0, page_scale_factor_)); } if (!StringToGestureSourceType( gesture_source_type ? *gesture_source_type : kGestureSourceTypeDefault, gesture_params.gesture_source_type)) { return Response::InvalidParams("gestureSourceType"); } host_->QueueSyntheticGesture( SyntheticGesture::Create(gesture_params), base::Bind(&InputHandler::SendSynthesizeScrollGestureResponse, weak_factory_.GetWeakPtr(), command_id)); return Response::OK(); } Response InputHandler::SynthesizeTapGesture( DevToolsCommandId command_id, int x, int y, const int* duration, const int* tap_count, const std::string* gesture_source_type) { if (!host_) return Response::ServerError("Could not connect to view"); SyntheticTapGestureParams gesture_params; const int kDefaultDuration = 50; const int kDefaultTapCount = 1; gesture_params.position = CssPixelsToPoint(x, y, page_scale_factor_); gesture_params.duration_ms = duration ? *duration : kDefaultDuration; if (!StringToGestureSourceType( gesture_source_type ? *gesture_source_type : kGestureSourceTypeDefault, gesture_params.gesture_source_type)) { return Response::InvalidParams("gestureSourceType"); } if (!tap_count) tap_count = &kDefaultTapCount; for (int i = 0; i < *tap_count; i++) { // If we're doing more than one tap, don't send the response to the client // until we've completed the last tap. bool is_last_tap = i == *tap_count - 1; host_->QueueSyntheticGesture( SyntheticGesture::Create(gesture_params), base::Bind(&InputHandler::SendSynthesizeTapGestureResponse, weak_factory_.GetWeakPtr(), command_id, is_last_tap)); } return Response::OK(); } void InputHandler::SendSynthesizePinchGestureResponse( DevToolsCommandId command_id, SyntheticGesture::Result result) { if (result == SyntheticGesture::Result::GESTURE_FINISHED) { client_->SendSynthesizePinchGestureResponse( command_id, SynthesizePinchGestureResponse::Create()); } else { client_->SendError(command_id, Response::InternalError(base::StringPrintf( "Synthetic pinch failed, result was %d", result))); } } void InputHandler::SendSynthesizeScrollGestureResponse( DevToolsCommandId command_id, SyntheticGesture::Result result) { if (result == SyntheticGesture::Result::GESTURE_FINISHED) { client_->SendSynthesizeScrollGestureResponse( command_id, SynthesizeScrollGestureResponse::Create()); } else { client_->SendError(command_id, Response::InternalError(base::StringPrintf( "Synthetic scroll failed, result was %d", result))); } } void InputHandler::SendSynthesizeTapGestureResponse( DevToolsCommandId command_id, bool send_success, SyntheticGesture::Result result) { if (result == SyntheticGesture::Result::GESTURE_FINISHED) { if (send_success) { client_->SendSynthesizeTapGestureResponse( command_id, SynthesizeTapGestureResponse::Create()); } } else { client_->SendError(command_id, Response::InternalError(base::StringPrintf( "Synthetic tap failed, result was %d", result))); } } } // namespace input } // namespace devtools } // namespace content