blob: d7f465e791d2b7b23fafb39155c32cc9dfe07269 [file] [log] [blame]
// Copyright 2017 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/vr/content_input_delegate.h"
#include "base/callback_helpers.h"
#include "base/time/time.h"
#include "chrome/browser/vr/platform_controller.h"
#include "third_party/WebKit/public/platform/WebGestureEvent.h"
#include "third_party/WebKit/public/platform/WebMouseEvent.h"
namespace vr {
namespace {
static constexpr gfx::PointF kOutOfBoundsPoint = {-0.5f, -0.5f};
} // namespace
ContentInputDelegate::ContentInputDelegate() {}
ContentInputDelegate::ContentInputDelegate(ContentInputForwarder* content)
: content_(content) {}
ContentInputDelegate::~ContentInputDelegate() = default;
void ContentInputDelegate::OnContentEnter(
const gfx::PointF& normalized_hit_point) {
SendGestureToContent(
MakeMouseEvent(blink::WebInputEvent::kMouseEnter, normalized_hit_point));
}
void ContentInputDelegate::OnContentLeave() {
// Note that we send an out of bounds mouse leave event. With blink feature
// UpdateHoverPostLayout turned on, a MouseMove event will dispatched post a
// Layout. Sending a mouse leave event at 0,0 will result continuous
// MouseMove events sent to the content if the content keeps relayout itself.
// See crbug.com/762573 for details.
SendGestureToContent(
MakeMouseEvent(blink::WebInputEvent::kMouseLeave, kOutOfBoundsPoint));
}
void ContentInputDelegate::OnContentMove(
const gfx::PointF& normalized_hit_point) {
SendGestureToContent(
MakeMouseEvent(blink::WebInputEvent::kMouseMove, normalized_hit_point));
}
void ContentInputDelegate::OnContentDown(
const gfx::PointF& normalized_hit_point) {
SendGestureToContent(
MakeMouseEvent(blink::WebInputEvent::kMouseDown, normalized_hit_point));
}
void ContentInputDelegate::OnContentUp(
const gfx::PointF& normalized_hit_point) {
SendGestureToContent(
MakeMouseEvent(blink::WebInputEvent::kMouseUp, normalized_hit_point));
}
void ContentInputDelegate::OnFocusChanged(bool focused) {
// The call below tells the renderer to clear the focused element. Note that
// we don't need to do anything when focused is true because the renderer
// already knows about the focused element.
if (!focused)
content_->ClearFocusedElement();
}
void ContentInputDelegate::OnWebInputEdited(const EditedText& info,
bool commit) {
if (!content_)
return;
last_keyboard_edit_ = info;
if (commit) {
content_->SubmitWebInput();
return;
}
content_->OnWebInputEdited(info.GetDiff());
}
void ContentInputDelegate::OnContentFlingCancel(
std::unique_ptr<blink::WebGestureEvent> gesture,
const gfx::PointF& normalized_hit_point) {
UpdateGesture(normalized_hit_point, *gesture);
SendGestureToContent(std::move(gesture));
}
void ContentInputDelegate::OnContentScrollBegin(
std::unique_ptr<blink::WebGestureEvent> gesture,
const gfx::PointF& normalized_hit_point) {
UpdateGesture(normalized_hit_point, *gesture);
SendGestureToContent(std::move(gesture));
}
void ContentInputDelegate::OnContentScrollUpdate(
std::unique_ptr<blink::WebGestureEvent> gesture,
const gfx::PointF& normalized_hit_point) {
UpdateGesture(normalized_hit_point, *gesture);
SendGestureToContent(std::move(gesture));
}
void ContentInputDelegate::OnContentScrollEnd(
std::unique_ptr<blink::WebGestureEvent> gesture,
const gfx::PointF& normalized_hit_point) {
UpdateGesture(normalized_hit_point, *gesture);
SendGestureToContent(std::move(gesture));
}
void ContentInputDelegate::OnSwapContents(int new_content_id) {
content_id_ = new_content_id;
}
void ContentInputDelegate::UpdateGesture(
const gfx::PointF& normalized_content_hit_point,
blink::WebGestureEvent& gesture) {
gesture.x = content_tex_css_width_ * normalized_content_hit_point.x();
gesture.y = content_tex_css_height_ * normalized_content_hit_point.y();
}
void ContentInputDelegate::SendGestureToContent(
std::unique_ptr<blink::WebInputEvent> event) {
if (!event || !content_ || ContentGestureIsLocked(event->GetType()))
return;
content_->ForwardEvent(std::move(event), content_id_);
}
bool ContentInputDelegate::ContentGestureIsLocked(
blink::WebInputEvent::Type type) {
// TODO (asimjour) create a new MouseEnter event when we swap webcontents and
// pointer is on the content quad.
if (type == blink::WebInputEvent::kGestureScrollBegin ||
type == blink::WebInputEvent::kMouseMove ||
type == blink::WebInputEvent::kMouseDown ||
type == blink::WebInputEvent::kMouseEnter)
locked_content_id_ = content_id_;
if (locked_content_id_ != content_id_)
return true;
return false;
}
void ContentInputDelegate::OnContentBoundsChanged(int width, int height) {
content_tex_css_width_ = width;
content_tex_css_height_ = height;
}
void ContentInputDelegate::OnWebInputIndicesChanged(
int selection_start,
int selection_end,
int composition_start,
int composition_end,
base::OnceCallback<void(const TextInputInfo&)> callback) {
// The purpose of this method is to determine if we need to query content for
// the text surrounding the currently focused web input field.
// If the changed indices match with that from the last keyboard edit, then
// this is called in response to the user entering text using the keyboard, so
// we already know the text and don't need to ask content for it.
TextInputInfo i = last_keyboard_edit_.current;
if (i.selection_start == selection_start &&
i.selection_end == selection_end &&
i.composition_start == composition_start &&
i.composition_end == composition_end) {
base::ResetAndReturn(&callback).Run(i);
return;
}
// If the changed indices are the same as the previous ones, this is probably
// called as a side-effect of us requesting the text state below, so it's safe
// to ignore this update. If this is not called as a side-effect of us
// requesting the text state below, and the indices just happen to match the
// previous state, it's still okay to ignore this update. Consider the
// following scenario: 1) State after last request for web input state:
// This is a test|
// 2) JS on the page changed the web input state to:
// This blah test|
// In this case, 2) will trigger this function and we'll ignore it. The next
// time user types something, we'll calculate the diff from our stale version
// of the web input state, but because we're committing the delta between the
// previous and the current keyboard state, the update to content will still
// be correct. That is, even if the keyboard works with the incorrect version
// of the text state, the end result is still correct from the user's point of
// view. This is also how android IME works (it only requests text state when
// the indices actually change).
i = pending_text_input_info_;
if (i.selection_start == selection_start &&
i.selection_end == selection_end &&
i.composition_start == composition_start &&
i.composition_end == composition_end) {
return;
}
// The indices changed and we need to query the update state.
pending_text_input_info_.selection_start = selection_start;
pending_text_input_info_.selection_end = selection_end;
pending_text_input_info_.composition_start = composition_start;
pending_text_input_info_.composition_end = composition_end;
update_state_callback_ = std::move(callback);
content_->RequestWebInputText(base::BindOnce(
&ContentInputDelegate::OnWebInputTextChanged, base::Unretained(this)));
}
void ContentInputDelegate::OnWebInputTextChanged(const base::string16& text) {
pending_text_input_info_.text = text;
DCHECK(!update_state_callback_.is_null());
base::ResetAndReturn(&update_state_callback_).Run(pending_text_input_info_);
}
std::unique_ptr<blink::WebMouseEvent> ContentInputDelegate::MakeMouseEvent(
blink::WebInputEvent::Type type,
const gfx::PointF& normalized_web_content_location) {
if (!controller_)
return nullptr;
gfx::Point location(
content_tex_css_width_ * normalized_web_content_location.x(),
content_tex_css_height_ * normalized_web_content_location.y());
blink::WebInputEvent::Modifiers modifiers =
controller_->IsButtonDown(PlatformController::kButtonSelect)
? blink::WebInputEvent::kLeftButtonDown
: blink::WebInputEvent::kNoModifiers;
base::TimeTicks timestamp;
switch (type) {
case blink::WebInputEvent::kMouseUp:
case blink::WebInputEvent::kMouseDown:
timestamp = controller_->GetLastButtonTimestamp();
break;
case blink::WebInputEvent::kMouseMove:
case blink::WebInputEvent::kMouseEnter:
case blink::WebInputEvent::kMouseLeave:
timestamp = controller_->GetLastOrientationTimestamp();
break;
default:
NOTREACHED();
}
auto mouse_event = std::make_unique<blink::WebMouseEvent>(
type, modifiers, (timestamp - base::TimeTicks()).InSecondsF());
mouse_event->pointer_type = blink::WebPointerProperties::PointerType::kMouse;
mouse_event->button = blink::WebPointerProperties::Button::kLeft;
mouse_event->SetPositionInWidget(location.x(), location.y());
// TODO(mthiesse): Should we support double-clicks for input? What should the
// timeout be?
mouse_event->click_count = 1;
return mouse_event;
}
} // namespace vr