[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:

committed by
Chromium LUCI CQ

parent
0a1a3b5ebb
commit
15d4924b21
third_party/blink
renderer
web_tests
@ -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.
|
||||
|
14
third_party/blink/web_tests/VirtualTestSuites
vendored
14
third_party/blink/web_tests/VirtualTestSuites
vendored
@ -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"],
|
||||
|
44
third_party/blink/web_tests/external/wpt/shadow-dom/focus/text-selection-with-delegatesFocus-on-slotted-content.html
vendored
Normal file
44
third_party/blink/web_tests/external/wpt/shadow-dom/focus/text-selection-with-delegatesFocus-on-slotted-content.html
vendored
Normal file
@ -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>
|
||||
|
5
third_party/blink/web_tests/virtual/selection-shadow-delegates-focus-disabled/README.md
vendored
Normal file
5
third_party/blink/web_tests/virtual/selection-shadow-delegates-focus-disabled/README.md
vendored
Normal file
@ -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`.
|
5
third_party/blink/web_tests/virtual/selection-shadow-delegates-focus-disabled/external/wpt/shadow-dom/focus/text-selection-with-delegatesFocus-on-slotted-content-expected.txt
vendored
Normal file
5
third_party/blink/web_tests/virtual/selection-shadow-delegates-focus-disabled/external/wpt/shadow-dom/focus/text-selection-with-delegatesFocus-on-slotted-content-expected.txt
vendored
Normal file
@ -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.
|
||||
|
5
third_party/blink/web_tests/virtual/selection-shadow-delegates-focus-disabled/shadow-dom/focus-slide-on-shadow-host-expected.txt
vendored
Normal file
5
third_party/blink/web_tests/virtual/selection-shadow-delegates-focus-disabled/shadow-dom/focus-slide-on-shadow-host-expected.txt
vendored
Normal file
@ -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.
|
||||
|
Reference in New Issue
Block a user