Commit fe282048 authored by Jinsong Fan's avatar Jinsong Fan Committed by Commit Bot

Fix the wrong position for ShowNonLocatedContextMenu

In code path of ShowNonLocatedContextMenu, if can show the context
menu at the selection, here always use the first rect of the selection
range to compute the position for hittest. So if there is a lot of
selected text in the contenteditable box, the begin of the selection is
likely to be invisible, and the value of the first rect will be negative
and invalid. The CL uses one of corner point or center of the selection
rect fully between the handles.

Bug: 1002344
Change-Id: Iba5110cad4d8d7d2dd36ece2454c415d7dcdd419
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1792426Reviewed-by: default avatarYoshifumi Inoue <yosin@chromium.org>
Reviewed-by: default avatarDave Tapuska <dtapuska@chromium.org>
Commit-Queue: Jinsong Fan <fanjinsong@sogou-inc.com>
Cr-Commit-Position: refs/heads/master@{#706471}
parent 95811819
......@@ -47,7 +47,12 @@
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/local_caret_rect.h"
#include "third_party/blink/renderer/core/editing/selection_controller.h"
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include "third_party/blink/renderer/core/editing/text_affinity.h"
#include "third_party/blink/renderer/core/editing/visible_position.h"
#include "third_party/blink/renderer/core/editing/visible_selection.h"
#include "third_party/blink/renderer/core/events/gesture_event.h"
#include "third_party/blink/renderer/core/events/keyboard_event.h"
#include "third_party/blink/renderer/core/events/mouse_event.h"
......@@ -93,6 +98,8 @@
#include "third_party/blink/renderer/core/style/cursor_data.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/geometry/float_point.h"
#include "third_party/blink/renderer/platform/geometry/int_point.h"
#include "third_party/blink/renderer/platform/geometry/int_rect.h"
#include "third_party/blink/renderer/platform/graphics/image.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
......@@ -122,6 +129,37 @@ bool ShouldRefetchEventTarget(const MouseEventWithHitTestResults& mev) {
return false;
}
IntPoint GetSelectionStartpoint(const PositionWithAffinity& position) {
const LocalCaretRect& local_caret_rect = LocalCaretRectOfPosition(position);
const IntRect rect = AbsoluteCaretBoundsOf(position);
// In a multiline edit, rect.MaxY() would end up on the next line, so
// take the midpoint in order to use this corner point directly.
if (local_caret_rect.layout_object->IsHorizontalWritingMode())
return {rect.X(), (rect.Y() + rect.MaxY()) / 2};
// When text is vertical, rect.MaxX() would end up on the next line, so
// take the midpoint in order to use this corner point directly.
return {(rect.X() + rect.MaxX()) / 2, rect.Y()};
}
IntPoint GetSelectionEndpoint(const PositionWithAffinity& position) {
const LocalCaretRect& local_caret_rect = LocalCaretRectOfPosition(position);
const IntRect rect = AbsoluteCaretBoundsOf(position);
// In a multiline edit, rect.MaxY() would end up on the next line, so
// take the midpoint in order to use this corner point directly.
if (local_caret_rect.layout_object->IsHorizontalWritingMode())
return {rect.X(), (rect.Y() + rect.MaxY()) / 2};
// When text is vertical, rect.MaxX() would end up on the next line, so
// take the midpoint in order to use this corner point directly.
return {(rect.X() + rect.MaxX()) / 2, rect.Y()};
}
bool ContainsEvenAtEdge(const IntRect& rect, const IntPoint& point) {
return point.X() >= rect.X() && point.X() <= rect.MaxX() &&
point.Y() >= rect.Y() && point.Y() <= rect.MaxY();
}
} // namespace
// The amount of time to wait for a cursor update on style and layout changes
......@@ -2029,15 +2067,41 @@ WebInputEventResult EventHandler::ShowNonLocatedContextMenu(
if (!override_target_element && ShouldShowContextMenuAtSelection(selection)) {
DCHECK(!doc->NeedsLayoutTreeUpdate());
IntRect first_rect =
FirstRectForRange(selection.ComputeVisibleSelectionInDOMTree()
.ToNormalizedEphemeralRange());
// Enclose the selection rect fully between the handles. If the handles are
// on the same line, the selection rect is empty.
const SelectionInDOMTree& visible_selection =
selection.ComputeVisibleSelectionInDOMTree().AsSelection();
const PositionWithAffinity start_position(
visible_selection.ComputeStartPosition(), visible_selection.Affinity());
const IntPoint start_point = GetSelectionStartpoint(start_position);
const PositionWithAffinity end_position(
visible_selection.ComputeEndPosition(), visible_selection.Affinity());
const IntPoint end_point = GetSelectionEndpoint(end_position);
int left = std::min(start_point.X(), end_point.X());
int top = std::min(start_point.Y(), end_point.Y());
int right = std::max(start_point.X(), end_point.X());
int bottom = std::max(start_point.Y(), end_point.Y());
// Intersect the selection rect and the visible bounds of focused_element.
if (focused_element) {
IntRect clipped_rect = view->ViewportToFrame(
focused_element->VisibleBoundsInVisualViewport());
left = std::max(clipped_rect.X(), left);
top = std::max(clipped_rect.Y(), top);
right = std::min(clipped_rect.MaxX(), right);
bottom = std::min(clipped_rect.MaxY(), bottom);
}
IntRect selection_rect = IntRect(left, top, right - left, bottom - top);
int x = first_rect.X();
// In a multiline edit, firstRect.maxY() would end up on the next line, so
// take the midpoint.
int y = (first_rect.MaxY() + first_rect.Y()) / 2;
location_in_root_frame = view->ConvertToRootFrame(IntPoint(x, y));
if (ContainsEvenAtEdge(selection_rect, start_point)) {
location_in_root_frame = view->ConvertToRootFrame(start_point);
} else if (ContainsEvenAtEdge(selection_rect, end_point)) {
location_in_root_frame = view->ConvertToRootFrame(end_point);
} else {
location_in_root_frame =
view->ConvertToRootFrame(selection_rect.Center());
}
} else if (focused_element) {
IntRect clipped_rect = focused_element->BoundsInViewport();
location_in_root_frame =
......
......@@ -94,6 +94,7 @@ class ContextMenuControllerTest : public testing::Test {
web_view_helper_.LocalMainFrame()->GetDocument());
}
WebView* GetWebView() { return web_view_helper_.GetWebView(); }
Page* GetPage() { return web_view_helper_.GetWebView()->GetPage(); }
WebLocalFrameImpl* LocalMainFrame() {
return web_view_helper_.LocalMainFrame();
......@@ -543,4 +544,48 @@ TEST_F(ContextMenuControllerTest, EditingActionsEnabledInXMLDocument) {
EXPECT_EQ(context_menu_data.selected_text, "Blue text");
}
TEST_F(ContextMenuControllerTest, ShowNonLocatedContextMenuEvent) {
GetDocument()->documentElement()->SetInnerHTMLFromString(
"<input id='sample' type='text' size='5' value='Sample Input Text'>");
Document* document = GetDocument();
Element* input_element = document->getElementById("sample");
document->UpdateStyleAndLayout();
// Select the 'Sample' of |input|.
DOMRect* rect = input_element->getBoundingClientRect();
WebGestureEvent gesture_event(
WebInputEvent::kGestureLongPress, WebInputEvent::kNoModifiers,
base::TimeTicks::Now(), WebGestureDevice::kTouchscreen);
gesture_event.SetPositionInWidget(WebFloatPoint(rect->left(), rect->top()));
GetWebView()->MainFrameWidget()->HandleInputEvent(
WebCoalescedInputEvent(gesture_event));
WebContextMenuData context_menu_data =
GetWebFrameClient().GetContextMenuData();
EXPECT_EQ(context_menu_data.selected_text, "Sample");
// Adjust the selection from the start of |input| to the middle.
LayoutPoint middle_point((rect->left() + rect->right()) / 2,
(rect->top() + rect->bottom()) / 2);
LocalMainFrame()->MoveRangeSelectionExtent(
WebPoint(middle_point.X().ToInt(), middle_point.Y().ToInt()));
GetWebView()->MainFrameWidget()->ShowContextMenu(kMenuSourceTouchHandle);
context_menu_data = GetWebFrameClient().GetContextMenuData();
EXPECT_NE(context_menu_data.selected_text, "");
// Scroll the value of |input| to end.
input_element->setScrollLeft(input_element->scrollWidth());
// Select all the value of |input| to ensure the start of selection is
// invisible.
LocalMainFrame()->MoveRangeSelectionExtent(
WebPoint(rect->right(), rect->bottom()));
GetWebView()->MainFrameWidget()->ShowContextMenu(kMenuSourceTouchHandle);
context_menu_data = GetWebFrameClient().GetContextMenuData();
EXPECT_EQ(context_menu_data.selected_text, "Sample Input Text");
}
} // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment