cef/libcef/browser/osr/touch_selection_controller_...

551 lines
18 KiB
C++

// Copyright 2022 The Chromium Embedded Framework Authors.
// Portions copyright 2015 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 "libcef/browser/osr/touch_selection_controller_client_osr.h"
#include <cmath>
#include <set>
#include "libcef/browser/osr/render_widget_host_view_osr.h"
#include "libcef/browser/osr/touch_handle_drawable_osr.h"
#include "base/functional/bind.h"
#include "content/browser/renderer_host/render_widget_host_delegate.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/public/browser/context_menu_params.h"
#include "content/public/browser/render_view_host.h"
#include "ui/base/clipboard/clipboard.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.h"
#include "ui/base/pointer/touch_editing_controller.h"
#include "ui/gfx/geometry/point_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
namespace {
// Delay before showing the quick menu, in milliseconds.
constexpr int kQuickMenuDelayInMs = 100;
constexpr cef_quick_menu_edit_state_flags_t kMenuCommands[] = {
QM_EDITFLAG_CAN_ELLIPSIS, QM_EDITFLAG_CAN_CUT, QM_EDITFLAG_CAN_COPY,
QM_EDITFLAG_CAN_PASTE};
constexpr int kInvalidCommandId = -1;
constexpr cef_event_flags_t kEmptyEventFlags =
static_cast<cef_event_flags_t>(0);
class CefRunQuickMenuCallbackImpl : public CefRunQuickMenuCallback {
public:
using Callback = base::OnceCallback<void(int, int)>;
explicit CefRunQuickMenuCallbackImpl(Callback callback)
: callback_(std::move(callback)) {}
CefRunQuickMenuCallbackImpl(const CefRunQuickMenuCallbackImpl&) = delete;
CefRunQuickMenuCallbackImpl& operator=(const CefRunQuickMenuCallbackImpl&) =
delete;
~CefRunQuickMenuCallbackImpl() override {
if (!callback_.is_null()) {
// The callback is still pending. Cancel it now.
if (CEF_CURRENTLY_ON_UIT()) {
RunNow(std::move(callback_), kInvalidCommandId, kEmptyEventFlags);
} else {
CEF_POST_TASK(CEF_UIT,
base::BindOnce(&CefRunQuickMenuCallbackImpl::RunNow,
std::move(callback_), kInvalidCommandId,
kEmptyEventFlags));
}
}
}
void Continue(int command_id, cef_event_flags_t event_flags) override {
if (CEF_CURRENTLY_ON_UIT()) {
if (!callback_.is_null()) {
RunNow(std::move(callback_), command_id, event_flags);
}
} else {
CEF_POST_TASK(CEF_UIT,
base::BindOnce(&CefRunQuickMenuCallbackImpl::Continue, this,
command_id, event_flags));
}
}
void Cancel() override { Continue(kInvalidCommandId, kEmptyEventFlags); }
void Disconnect() { callback_.Reset(); }
private:
static void RunNow(Callback callback,
int command_id,
cef_event_flags_t event_flags) {
CEF_REQUIRE_UIT();
std::move(callback).Run(command_id, event_flags);
}
Callback callback_;
IMPLEMENT_REFCOUNTING(CefRunQuickMenuCallbackImpl);
};
} // namespace
CefTouchSelectionControllerClientOSR::CefTouchSelectionControllerClientOSR(
CefRenderWidgetHostViewOSR* rwhv)
: rwhv_(rwhv),
internal_client_(rwhv),
active_client_(&internal_client_),
active_menu_client_(this),
quick_menu_timer_(
FROM_HERE,
base::Milliseconds(kQuickMenuDelayInMs),
base::BindRepeating(
&CefTouchSelectionControllerClientOSR::ShowQuickMenu,
base::Unretained(this))),
weak_ptr_factory_(this) {
DCHECK(rwhv_);
}
CefTouchSelectionControllerClientOSR::~CefTouchSelectionControllerClientOSR() {
for (auto& observer : observers_) {
observer.OnManagerWillDestroy(this);
}
}
void CefTouchSelectionControllerClientOSR::CloseQuickMenuAndHideHandles() {
CloseQuickMenu();
rwhv_->selection_controller()->HideAndDisallowShowingAutomatically();
}
void CefTouchSelectionControllerClientOSR::OnWindowMoved() {
UpdateQuickMenu();
}
void CefTouchSelectionControllerClientOSR::OnTouchDown() {
touch_down_ = true;
UpdateQuickMenu();
}
void CefTouchSelectionControllerClientOSR::OnTouchUp() {
touch_down_ = false;
UpdateQuickMenu();
}
void CefTouchSelectionControllerClientOSR::OnScrollStarted() {
scroll_in_progress_ = true;
rwhv_->selection_controller()->SetTemporarilyHidden(true);
UpdateQuickMenu();
}
void CefTouchSelectionControllerClientOSR::OnScrollCompleted() {
scroll_in_progress_ = false;
active_client_->DidScroll();
rwhv_->selection_controller()->SetTemporarilyHidden(false);
UpdateQuickMenu();
}
bool CefTouchSelectionControllerClientOSR::HandleContextMenu(
const content::ContextMenuParams& params) {
if ((params.source_type == ui::MENU_SOURCE_LONG_PRESS ||
params.source_type == ui::MENU_SOURCE_LONG_TAP) &&
params.is_editable && params.selection_text.empty() &&
IsQuickMenuAvailable()) {
quick_menu_requested_ = true;
UpdateQuickMenu();
return true;
}
const bool from_touch = params.source_type == ui::MENU_SOURCE_LONG_PRESS ||
params.source_type == ui::MENU_SOURCE_LONG_TAP ||
params.source_type == ui::MENU_SOURCE_TOUCH;
if (from_touch && !params.selection_text.empty()) {
return true;
}
rwhv_->selection_controller()->HideAndDisallowShowingAutomatically();
return false;
}
void CefTouchSelectionControllerClientOSR::DidStopFlinging() {
OnScrollCompleted();
}
void CefTouchSelectionControllerClientOSR::OnSwipeToMoveCursorBegin() {
rwhv_->selection_controller()->OnSwipeToMoveCursorBegin();
OnSelectionEvent(ui::INSERTION_HANDLE_DRAG_STARTED);
}
void CefTouchSelectionControllerClientOSR::OnSwipeToMoveCursorEnd() {
rwhv_->selection_controller()->OnSwipeToMoveCursorEnd();
OnSelectionEvent(ui::INSERTION_HANDLE_DRAG_STOPPED);
}
void CefTouchSelectionControllerClientOSR::OnClientHitTestRegionUpdated(
ui::TouchSelectionControllerClient* client) {
if (client != active_client_ || !rwhv_->selection_controller() ||
rwhv_->selection_controller()->active_status() ==
ui::TouchSelectionController::INACTIVE) {
return;
}
active_client_->DidScroll();
}
void CefTouchSelectionControllerClientOSR::UpdateClientSelectionBounds(
const gfx::SelectionBound& start,
const gfx::SelectionBound& end) {
UpdateClientSelectionBounds(start, end, &internal_client_, this);
}
void CefTouchSelectionControllerClientOSR::UpdateClientSelectionBounds(
const gfx::SelectionBound& start,
const gfx::SelectionBound& end,
ui::TouchSelectionControllerClient* client,
ui::TouchSelectionMenuClient* menu_client) {
if (client != active_client_ &&
(start.type() == gfx::SelectionBound::EMPTY || !start.visible()) &&
(end.type() == gfx::SelectionBound::EMPTY || !end.visible()) &&
(manager_selection_start_.type() != gfx::SelectionBound::EMPTY ||
manager_selection_end_.type() != gfx::SelectionBound::EMPTY)) {
return;
}
active_client_ = client;
active_menu_client_ = menu_client;
manager_selection_start_ = start;
manager_selection_end_ = end;
// Notify TouchSelectionController if anything should change here. Only
// update if the client is different and not making a change to empty, or
// is the same client.
GetTouchSelectionController()->OnSelectionBoundsChanged(start, end);
}
void CefTouchSelectionControllerClientOSR::InvalidateClient(
ui::TouchSelectionControllerClient* client) {
DCHECK(client != &internal_client_);
if (client == active_client_) {
active_client_ = &internal_client_;
active_menu_client_ = this;
}
}
ui::TouchSelectionController*
CefTouchSelectionControllerClientOSR::GetTouchSelectionController() {
return rwhv_->selection_controller();
}
void CefTouchSelectionControllerClientOSR::AddObserver(
TouchSelectionControllerClientManager::Observer* observer) {
observers_.AddObserver(observer);
}
void CefTouchSelectionControllerClientOSR::RemoveObserver(
TouchSelectionControllerClientManager::Observer* observer) {
observers_.RemoveObserver(observer);
}
bool CefTouchSelectionControllerClientOSR::IsQuickMenuAvailable() const {
DCHECK(active_menu_client_);
const auto is_enabled = [this](cef_quick_menu_edit_state_flags_t command) {
return active_menu_client_->IsCommandIdEnabled(command);
};
return std::any_of(std::cbegin(kMenuCommands), std::cend(kMenuCommands),
is_enabled);
}
void CefTouchSelectionControllerClientOSR::CloseQuickMenu() {
if (!quick_menu_running_) {
return;
}
quick_menu_running_ = false;
auto browser = rwhv_->browser_impl();
if (auto handler = browser->client()->GetContextMenuHandler()) {
handler->OnQuickMenuDismissed(browser.get(), browser->GetFocusedFrame());
}
}
void CefTouchSelectionControllerClientOSR::ShowQuickMenu() {
auto browser = rwhv_->browser_impl();
if (auto handler = browser->client()->GetContextMenuHandler()) {
gfx::RectF rect =
rwhv_->selection_controller()->GetVisibleRectBetweenBounds();
gfx::PointF origin = rect.origin();
gfx::PointF bottom_right = rect.bottom_right();
auto client_bounds = gfx::RectF(rwhv_->GetViewBounds());
origin.SetToMax(client_bounds.origin());
bottom_right.SetToMin(client_bounds.bottom_right());
if (origin.x() > bottom_right.x() || origin.y() > bottom_right.y()) {
return;
}
gfx::Vector2dF diagonal = bottom_right - origin;
gfx::SizeF size(diagonal.x(), diagonal.y());
int quickmenuflags = 0;
for (const auto& command : kMenuCommands) {
if (active_menu_client_->IsCommandIdEnabled(command)) {
quickmenuflags |= command;
}
}
CefRefPtr<CefRunQuickMenuCallbackImpl> callbackImpl(
new CefRunQuickMenuCallbackImpl(base::BindOnce(
&CefTouchSelectionControllerClientOSR::ExecuteCommand,
weak_ptr_factory_.GetWeakPtr())));
quick_menu_running_ = true;
if (!handler->RunQuickMenu(
browser, browser->GetFocusedFrame(),
{static_cast<int>(std::round(origin.x())),
static_cast<int>(std::round(origin.y()))},
{static_cast<int>(std::round(size.width())),
static_cast<int>(std::round(size.height()))},
static_cast<CefContextMenuHandler::QuickMenuEditStateFlags>(
quickmenuflags),
callbackImpl)) {
callbackImpl->Disconnect();
CloseQuickMenu();
}
}
}
void CefTouchSelectionControllerClientOSR::UpdateQuickMenu() {
// Hide the quick menu if there is any. This should happen even if the menu
// should be shown again, in order to update its location or content.
if (quick_menu_running_) {
CloseQuickMenu();
} else {
quick_menu_timer_.Stop();
}
// Start timer to show quick menu if necessary.
if (ShouldShowQuickMenu()) {
quick_menu_timer_.Reset();
}
}
bool CefTouchSelectionControllerClientOSR::SupportsAnimation() const {
return false;
}
bool CefTouchSelectionControllerClientOSR::InternalClient::SupportsAnimation()
const {
DCHECK(false);
return false;
}
void CefTouchSelectionControllerClientOSR::SetNeedsAnimate() {
DCHECK(false);
}
void CefTouchSelectionControllerClientOSR::InternalClient::SetNeedsAnimate() {
DCHECK(false);
}
void CefTouchSelectionControllerClientOSR::MoveCaret(
const gfx::PointF& position) {
active_client_->MoveCaret(position);
}
void CefTouchSelectionControllerClientOSR::InternalClient::MoveCaret(
const gfx::PointF& position) {
if (auto host_delegate = rwhv_->host()->delegate()) {
host_delegate->MoveCaret(gfx::ToRoundedPoint(position));
}
}
void CefTouchSelectionControllerClientOSR::MoveRangeSelectionExtent(
const gfx::PointF& extent) {
active_client_->MoveRangeSelectionExtent(extent);
}
void CefTouchSelectionControllerClientOSR::InternalClient::
MoveRangeSelectionExtent(const gfx::PointF& extent) {
if (auto host_delegate = rwhv_->host()->delegate()) {
host_delegate->MoveRangeSelectionExtent(gfx::ToRoundedPoint(extent));
}
}
void CefTouchSelectionControllerClientOSR::SelectBetweenCoordinates(
const gfx::PointF& base,
const gfx::PointF& extent) {
active_client_->SelectBetweenCoordinates(base, extent);
}
void CefTouchSelectionControllerClientOSR::InternalClient::
SelectBetweenCoordinates(const gfx::PointF& base,
const gfx::PointF& extent) {
if (auto host_delegate = rwhv_->host()->delegate()) {
host_delegate->SelectRange(gfx::ToRoundedPoint(base),
gfx::ToRoundedPoint(extent));
}
}
void CefTouchSelectionControllerClientOSR::OnSelectionEvent(
ui::SelectionEventType event) {
// This function (implicitly) uses active_menu_client_, so we don't go to the
// active view for this.
switch (event) {
case ui::SELECTION_HANDLES_SHOWN:
quick_menu_requested_ = true;
[[fallthrough]];
case ui::INSERTION_HANDLE_SHOWN:
UpdateQuickMenu();
break;
case ui::SELECTION_HANDLES_CLEARED:
case ui::INSERTION_HANDLE_CLEARED:
quick_menu_requested_ = false;
UpdateQuickMenu();
break;
case ui::SELECTION_HANDLE_DRAG_STARTED:
case ui::INSERTION_HANDLE_DRAG_STARTED:
handle_drag_in_progress_ = true;
UpdateQuickMenu();
break;
case ui::SELECTION_HANDLE_DRAG_STOPPED:
case ui::INSERTION_HANDLE_DRAG_STOPPED:
handle_drag_in_progress_ = false;
UpdateQuickMenu();
break;
case ui::SELECTION_HANDLES_MOVED:
case ui::INSERTION_HANDLE_MOVED:
UpdateQuickMenu();
break;
case ui::INSERTION_HANDLE_TAPPED:
quick_menu_requested_ = !quick_menu_requested_;
UpdateQuickMenu();
break;
}
}
void CefTouchSelectionControllerClientOSR::InternalClient::OnSelectionEvent(
ui::SelectionEventType event) {
DCHECK(false);
}
void CefTouchSelectionControllerClientOSR::OnDragUpdate(
const ui::TouchSelectionDraggable::Type type,
const gfx::PointF& position) {}
void CefTouchSelectionControllerClientOSR::InternalClient::OnDragUpdate(
const ui::TouchSelectionDraggable::Type type,
const gfx::PointF& position) {
DCHECK(false);
}
std::unique_ptr<ui::TouchHandleDrawable>
CefTouchSelectionControllerClientOSR::CreateDrawable() {
return std::make_unique<CefTouchHandleDrawableOSR>(rwhv_);
}
void CefTouchSelectionControllerClientOSR::DidScroll() {}
std::unique_ptr<ui::TouchHandleDrawable>
CefTouchSelectionControllerClientOSR::InternalClient::CreateDrawable() {
DCHECK(false);
return nullptr;
}
void CefTouchSelectionControllerClientOSR::InternalClient::DidScroll() {
DCHECK(false);
}
bool CefTouchSelectionControllerClientOSR::IsCommandIdEnabled(
int command_id) const {
bool editable = rwhv_->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE;
bool readable = rwhv_->GetTextInputType() != ui::TEXT_INPUT_TYPE_PASSWORD;
bool has_selection = !rwhv_->GetSelectedText().empty();
switch (command_id) {
case QM_EDITFLAG_CAN_ELLIPSIS:
return true; // Always allowed to show the ellipsis button.
case QM_EDITFLAG_CAN_CUT:
return editable && readable && has_selection;
case QM_EDITFLAG_CAN_COPY:
return readable && has_selection;
case QM_EDITFLAG_CAN_PASTE: {
std::u16string result;
ui::DataTransferEndpoint data_dst = ui::DataTransferEndpoint(
ui::EndpointType::kDefault, /*notify_if_restricted=*/false);
ui::Clipboard::GetForCurrentThread()->ReadText(
ui::ClipboardBuffer::kCopyPaste, &data_dst, &result);
return editable && !result.empty();
}
default:
return false;
}
}
void CefTouchSelectionControllerClientOSR::ExecuteCommand(int command_id,
int event_flags) {
if (command_id == kInvalidCommandId) {
return;
}
if (command_id != QM_EDITFLAG_CAN_ELLIPSIS) {
rwhv_->selection_controller()->HideAndDisallowShowingAutomatically();
}
content::RenderWidgetHostDelegate* host_delegate = rwhv_->host()->delegate();
if (!host_delegate) {
return;
}
auto browser = rwhv_->browser_impl();
if (auto handler = browser->client()->GetContextMenuHandler()) {
if (handler->OnQuickMenuCommand(
browser.get(), browser->GetFocusedFrame(), command_id,
static_cast<cef_event_flags_t>(event_flags))) {
return;
}
}
switch (command_id) {
case QM_EDITFLAG_CAN_CUT:
host_delegate->Cut();
break;
case QM_EDITFLAG_CAN_COPY:
host_delegate->Copy();
break;
case QM_EDITFLAG_CAN_PASTE:
host_delegate->Paste();
break;
case QM_EDITFLAG_CAN_ELLIPSIS:
CloseQuickMenu();
RunContextMenu();
break;
default:
// Invalid command, do nothing.
// Also reached when callback is destroyed/cancelled.
break;
}
}
void CefTouchSelectionControllerClientOSR::RunContextMenu() {
const gfx::RectF anchor_rect =
rwhv_->selection_controller()->GetVisibleRectBetweenBounds();
const gfx::PointF anchor_point =
gfx::PointF(anchor_rect.CenterPoint().x(), anchor_rect.y());
rwhv_->host()->ShowContextMenuAtPoint(gfx::ToRoundedPoint(anchor_point),
ui::MENU_SOURCE_TOUCH_EDIT_MENU);
// Hide selection handles after getting rect-between-bounds from touch
// selection controller; otherwise, rect would be empty and the above
// calculations would be invalid.
rwhv_->selection_controller()->HideAndDisallowShowingAutomatically();
}
bool CefTouchSelectionControllerClientOSR::ShouldShowQuickMenu() {
return quick_menu_requested_ && !touch_down_ && !scroll_in_progress_ &&
!handle_drag_in_progress_ && IsQuickMenuAvailable();
}
std::u16string CefTouchSelectionControllerClientOSR::GetSelectedText() {
return rwhv_->GetSelectedText();
}