Commit 93823ec8 authored by Adithya Srinivasan's avatar Adithya Srinivasan Committed by Commit Bot

Portals: Make HTMLPortalElement keyboard focusable

Also simulates a click when clicking enter/space when a portal is
focused (similar to buttons).

Bug: 1043127
Change-Id: I0b1a35cd978c191a958dea0ed47bf737cac7292f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1995940
Commit-Queue: Adithya Srinivasan <adithyas@chromium.org>
Reviewed-by: default avatarJeremy Roman <jbroman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#732989}
parent 4d0eaac8
...@@ -120,33 +120,8 @@ void HTMLButtonElement::DefaultEventHandlerInternal(Event& event) { ...@@ -120,33 +120,8 @@ void HTMLButtonElement::DefaultEventHandlerInternal(Event& event) {
} }
} }
if (event.IsKeyboardEvent()) { if (HandleKeyboardActivation(event))
if (event.type() == event_type_names::kKeydown && return;
ToKeyboardEvent(event).key() == " ") {
SetActive(true);
// No setDefaultHandled() - IE dispatches a keypress in this case.
return;
}
if (event.type() == event_type_names::kKeypress) {
switch (ToKeyboardEvent(event).charCode()) {
case '\r':
DispatchSimulatedClick(&event);
event.SetDefaultHandled();
return;
case ' ':
// Prevent scrolling down the page.
event.SetDefaultHandled();
return;
}
}
if (event.type() == event_type_names::kKeyup &&
ToKeyboardEvent(event).key() == " ") {
if (IsActive())
DispatchSimulatedClick(&event);
event.SetDefaultHandled();
return;
}
}
HTMLFormControlElement::DefaultEventHandler(event); HTMLFormControlElement::DefaultEventHandler(event);
} }
......
...@@ -1391,6 +1391,37 @@ void HTMLElement::DefaultEventHandler(Event& event) { ...@@ -1391,6 +1391,37 @@ void HTMLElement::DefaultEventHandler(Event& event) {
Element::DefaultEventHandler(event); Element::DefaultEventHandler(event);
} }
bool HTMLElement::HandleKeyboardActivation(Event& event) {
if (event.IsKeyboardEvent()) {
if (event.type() == event_type_names::kKeydown &&
ToKeyboardEvent(event).key() == " ") {
SetActive(true);
// No setDefaultHandled() - IE dispatches a keypress in this case.
return true;
}
if (event.type() == event_type_names::kKeypress) {
switch (ToKeyboardEvent(event).charCode()) {
case '\r':
DispatchSimulatedClick(&event);
event.SetDefaultHandled();
return true;
case ' ':
// Prevent scrolling down the page.
event.SetDefaultHandled();
return true;
}
}
if (event.type() == event_type_names::kKeyup &&
ToKeyboardEvent(event).key() == " ") {
if (IsActive())
DispatchSimulatedClick(&event);
event.SetDefaultHandled();
return true;
}
}
return false;
}
bool HTMLElement::MatchesReadOnlyPseudoClass() const { bool HTMLElement::MatchesReadOnlyPseudoClass() const {
return !MatchesReadWritePseudoClass(); return !MatchesReadWritePseudoClass();
} }
......
...@@ -119,6 +119,10 @@ class CORE_EXPORT HTMLElement : public Element { ...@@ -119,6 +119,10 @@ class CORE_EXPORT HTMLElement : public Element {
virtual bool IsInteractiveContent() const; virtual bool IsInteractiveContent() const;
void DefaultEventHandler(Event&) override; void DefaultEventHandler(Event&) override;
// Used to handle return/space key events and simulate clicks. Returns true
// if the event is handled.
bool HandleKeyboardActivation(Event& event);
static const AtomicString& EventNameForAttributeName( static const AtomicString& EventNameForAttributeName(
const QualifiedName& attr_name); const QualifiedName& attr_name);
......
...@@ -342,6 +342,12 @@ void HTMLPortalElement::RemovedFrom(ContainerNode& node) { ...@@ -342,6 +342,12 @@ void HTMLPortalElement::RemovedFrom(ContainerNode& node) {
HTMLFrameOwnerElement::RemovedFrom(node); HTMLFrameOwnerElement::RemovedFrom(node);
} }
void HTMLPortalElement::DefaultEventHandler(Event& event) {
if (HandleKeyboardActivation(event))
return;
HTMLFrameOwnerElement::DefaultEventHandler(event);
}
bool HTMLPortalElement::IsURLAttribute(const Attribute& attribute) const { bool HTMLPortalElement::IsURLAttribute(const Attribute& attribute) const {
return attribute.GetName() == html_names::kSrcAttr || return attribute.GetName() == html_names::kSrcAttr ||
HTMLFrameOwnerElement::IsURLAttribute(attribute); HTMLFrameOwnerElement::IsURLAttribute(attribute);
...@@ -388,6 +394,10 @@ LayoutObject* HTMLPortalElement::CreateLayoutObject(const ComputedStyle& style, ...@@ -388,6 +394,10 @@ LayoutObject* HTMLPortalElement::CreateLayoutObject(const ComputedStyle& style,
return new LayoutIFrame(this); return new LayoutIFrame(this);
} }
bool HTMLPortalElement::SupportsFocus() const {
return true;
}
void HTMLPortalElement::DisconnectContentFrame() { void HTMLPortalElement::DisconnectContentFrame() {
HTMLFrameOwnerElement::DisconnectContentFrame(); HTMLFrameOwnerElement::DisconnectContentFrame();
ConsumePortal(); ConsumePortal();
......
...@@ -101,11 +101,13 @@ class CORE_EXPORT HTMLPortalElement : public HTMLFrameOwnerElement { ...@@ -101,11 +101,13 @@ class CORE_EXPORT HTMLPortalElement : public HTMLFrameOwnerElement {
// Node overrides // Node overrides
InsertionNotificationRequest InsertedInto(ContainerNode&) override; InsertionNotificationRequest InsertedInto(ContainerNode&) override;
void RemovedFrom(ContainerNode&) override; void RemovedFrom(ContainerNode&) override;
void DefaultEventHandler(Event&) override;
// Element overrides // Element overrides
bool IsURLAttribute(const Attribute&) const override; bool IsURLAttribute(const Attribute&) const override;
void ParseAttribute(const AttributeModificationParams&) override; void ParseAttribute(const AttributeModificationParams&) override;
LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override; LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override;
bool SupportsFocus() const override;
// HTMLFrameOwnerElement overrides // HTMLFrameOwnerElement overrides
void DisconnectContentFrame() override; void DisconnectContentFrame() override;
......
...@@ -86,19 +86,47 @@ ...@@ -86,19 +86,47 @@
}, "test that a x-origin iframe inside an adopted portal cannot steal focus"); }, "test that a x-origin iframe inside an adopted portal cannot steal focus");
const TAB = "\ue004"; // https://w3c.github.io/webdriver/#keyboard-actions const TAB = "\ue004"; // https://w3c.github.io/webdriver/#keyboard-actions
const SPACE = " "
const RETURN = "\r";
promise_test(async t => { promise_test(async t => {
let portal = await createPortal(document, "resources/focus-page-with-button.html"); let portal = await createPortal(document, "resources/focus-page-with-button.html");
try { try {
portal.tabIndex = 0;
await test_driver.send_keys(document.body, TAB); await test_driver.send_keys(document.body, TAB);
portal.onmessage = t.step_func(e => { portal.onmessage = t.unreached_func("button inside portal should not be focused");
assert_unreached("button inside portal should not be focused"); await new Promise(r => t.step_timeout(r, 500));
}); } finally {
document.body.removeChild(portal);
}
}, "test that a portal is keyboard focusable");
promise_test(async t => {
let portal = await createPortal(document, "resources/focus-page-with-button.html");
try {
let portalFocusPromise = new Promise(r => portal.onfocus = r);
portal.onmessage = t.unreached_func("button inside portal should not be focused");
await test_driver.send_keys(document.body, TAB);
await portalFocusPromise;
await test_driver.send_keys(document.body, TAB);
await new Promise(r => t.step_timeout(r, 500)); await new Promise(r => t.step_timeout(r, 500));
} finally { } finally {
document.body.removeChild(portal); document.body.removeChild(portal);
} }
}, "test that we cannot tab into a portal's contents"); }, "test that we cannot tab into a portal's contents");
promise_test(async t => {
let portal = await createPortal(document, "resources/simple-portal.html");
try {
portal.focus();
for (let key of [SPACE, RETURN]) {
let clickPromise = new Promise(r => portal.onclick = r);
await test_driver.send_keys(document.body, key);
await clickPromise;
}
} finally {
document.body.removeChild(portal);
}
}, "test that a portal is keyboard activatable");
</script> </script>
</body> </body>
...@@ -18,7 +18,12 @@ ...@@ -18,7 +18,12 @@
let portal = e.adoptPredecessor(); let portal = e.adoptPredecessor();
document.body.appendChild(portal); document.body.appendChild(portal);
portal.onmessage = handleMessage; portal.onmessage = handleMessage;
} };
window.onfocus = () => {
if (window.portalHost)
window.portalHost.postMessage("window focused", "*");
};
</script> </script>
<button>A</button> <button>A</button>
</body> </body>
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