0

[Selection] Fix select text within shadow DOM with delegatesFocus

This patch makes it possible to select text in shadowDOM with
delegatesFocus set.

In MouseEventManager::HandleMouseFocus, when the focus is slided to the
shadow host's delegated target, the event is marked as not
handled so we can reach the selection controller:
> EventHandler::HandleMousePressEvent
-> MouseEventManager::HandleMouseFocus, not handled
-> MouseEventManager::HandleMousePressEvent
--> GetSelectionController().HandleMousePressEvent()

In SelectionFrame::SelectionHasFocus, we update to return true if
selection is in shadow host with delegatesFocus and the focused element
is that shadow host's focusable area.

Further, we add more test cases and put the change behind the flag
SelectionOnShadowDOMWithDelegatesFocus.

Change-Id: I69ccb4b46d4d399cad7a94d2a54524612ad12f4e
Bug: 40622041
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5996772
Reviewed-by: Joey Arhar <jarhar@chromium.org>
Commit-Queue: Di Zhang <dizhangg@chromium.org>
Reviewed-by: Siye Liu <siliu@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1379892}
This commit is contained in:
Di Zhang
2024-11-07 20:24:11 +00:00
committed by Chromium LUCI CQ
parent 0a1a3b5ebb
commit 15d4924b21
10 changed files with 116 additions and 9 deletions

@ -540,9 +540,19 @@ bool FrameSelection::SelectionHasFocus() const {
if (!focused_element || focused_element->IsScrollControlPseudoElement()) {
return false;
}
if (focused_element->IsTextControl())
return focused_element->ContainsIncludingHostElements(*current);
if (RuntimeEnabledFeatures::SelectionOnShadowDOMWithDelegatesFocusEnabled()) {
// If focus is on the delegated target of a shadow host with delegatesFocus,
// selection could be on focus even if focused element does not contain
// current selection start.
if (focused_element->IsTextControl() &&
focused_element->ContainsIncludingHostElements(*current)) {
return true;
}
} else {
if (focused_element->IsTextControl()) {
return focused_element->ContainsIncludingHostElements(*current);
}
}
// Selection has focus if it contains the focused element.
const PositionInFlatTree& focused_position =
@ -561,6 +571,16 @@ bool FrameSelection::SelectionHasFocus() const {
// Selection has focus if its sub tree has focus.
if (current == focused_element)
return true;
if (RuntimeEnabledFeatures::
SelectionOnShadowDOMWithDelegatesFocusEnabled()) {
// If current is a shadow host with delegatesFocus, then it cannot be the
// focused element and we should compare with its focusable area instead.
if (const Element* el = DynamicTo<Element>(current);
el && el->IsShadowHostWithDelegatesFocus() &&
el->GetFocusableArea() == focused_element) {
return true;
}
}
current = current->ParentOrShadowHostNode();
} while (current);

@ -595,7 +595,10 @@ WebInputEventResult MouseEventManager::HandleMouseFocus(
// default behavior).
if (element && !element->IsMouseFocusable() &&
SlideFocusOnShadowHostIfNecessary(*element)) {
return WebInputEventResult::kHandledSystem;
return RuntimeEnabledFeatures::
SelectionOnShadowDOMWithDelegatesFocusEnabled()
? WebInputEventResult::kNotHandled
: WebInputEventResult::kHandledSystem;
}
// We call setFocusedElement even with !element in order to blur

@ -3822,6 +3822,13 @@
name: "SelectionIsCollapsedShadowDOMSupport",
status: "stable",
},
{
// Update HandleMousePressEvent to continue handling event, even after a
// focus slided from shadow host with delegatesFocus.
// See https://crbug.com/40622041
name: "SelectionOnShadowDOMWithDelegatesFocus",
status: "experimental",
},
{
// Sets the minimum target size of <option> in <select> to 24x24 CSS
// pixels to meet Accessibility standards.

@ -3674,6 +3674,20 @@
],
"expires": "May 1, 2025"
},
{
"prefix": "selection-shadow-delegates-focus-disabled",
"owners": ["dizhangg@google.com"],
"platforms": ["Linux", "Mac", "Win"],
"bases": [
"external/wpt/shadow-dom/focus/text-selection-with-delegatesFocus-on-slotted-content.html",
"external/wpt/shadow-dom/focus/text-selection-with-delegatesFocus.html",
"shadow-dom/focus-slide-on-shadow-host.html"
],
"args": [
"--disable-blink-features=SelectionOnShadowDOMWithDelegatesFocus"
],
"expires": "May 1, 2025"
},
{
"prefix": "shadow-realm-enabled",
"platforms": ["Linux", "Mac", "Win"],

@ -0,0 +1,44 @@
<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<link rel="help" href="https://issues.chromium.org/issues/40622041">
<body>
<p>Try to select the text in the dialog...</p>
<div id="host">
<span id="slotted">Slotted content</span>
</div>
</body>
<script>
'use strict';
promise_test(async () => {
host.attachShadow({mode: 'open', delegatesFocus: true});
host.shadowRoot.innerHTML = `
<dialog>
<slot></slot>
<div>Content</div>
</dialog>`;
host.shadowRoot.firstElementChild.show();
const selection = getSelection();
selection.empty();
const slotted = document.getElementById('slotted');
// Mouse down and drag to select text
const actions = new test_driver.Actions();
actions.pointerMove(0, 0, {origin: slotted});
actions.pointerDown();
actions.pointerMove(30, 0, {origin: slotted});
actions.pointerUp();
await actions.send();
assert_greater_than(selection.toString().length, 0);
}, 'select slotted text in shadow root with delegatesFocus.');
</script>

@ -25,6 +25,8 @@ var inputB;
// TODO(https://crbug.com/1350218): Replace this with test_diver.click() and
// move test to WPT
// Note click focus on shadow host changes the text selection differently across
// browsers. This file should be added as tentative to WPT.
function clickOn(el) {
eventSender.mouseMoveTo(el.offsetLeft + 8, el.offsetTop + 8);
eventSender.mouseDown();
@ -32,7 +34,7 @@ function clickOn(el) {
}
function resetFocus() {
document.querySelector('#defaultFocus').focus();
clickOn(document.querySelector('#defaultFocus'));
}
function assert_innermost_active_element(path) {
@ -92,16 +94,18 @@ test(() => {
assert_innermost_active_element('shadowHost/inputA');
inputA.setSelectionRange(1, 1);
clickOn(innerDiv);
// Clicking on non-focusable text moves selection, but should not change the focus state.
assert_innermost_active_element('shadowHost/inputA');
assert_equals(inputA.selectionStart, 1, 'inputA selection start should be preserved.');
assert_equals(inputA.selectionEnd, 1, 'inputA selection end should be preserved.');
assert_equals(inputA.selectionStart, 0);
assert_equals(inputA.selectionEnd, 0);
clickOn(inputB);
assert_innermost_active_element('shadowHost/inputB');
inputA.setSelectionRange(1, 1);
clickOn(innerDiv);
// Clicking on non-focusable text moves selectiont, but should not change the focus state.
assert_innermost_active_element('shadowHost/inputB');
assert_equals(inputB.selectionStart, 1, 'inputB selection start should be preserved.');
assert_equals(inputB.selectionEnd, 1, 'inputB selection end should be preserved.');
assert_equals(inputB.selectionStart, 0);
assert_equals(inputB.selectionEnd, 0);
}, 'click on inner div should focus inner focusable (with delegatesFocus = true).');
</script>

@ -0,0 +1,5 @@
# Overview
This suite tests when text is not selectable inside a shadow DOM with
delegatesFocus=true via
`--disable-blink-features=SelectionOnShadowDOMWithDelegatesFocus`.

@ -0,0 +1,5 @@
This is a testharness.js-based test.
[FAIL] select slotted text in shadow root with delegatesFocus.
assert_greater_than: expected a number greater than 0 but got 0
Harness: the test ran to completion.

@ -0,0 +1,5 @@
This is a testharness.js-based test.
[FAIL] click on inner div should focus inner focusable (with delegatesFocus = true).
assert_equals: expected 0 but got 1
Harness: the test ran to completion.