Commit 523243f1 authored by Mounir Lamouri's avatar Mounir Lamouri Committed by Commit Bot

Media Controls: keyboard interaction for the overflow menu.

This is allowing the user to use Tab and up/down keys to navigation
throughout the menu. It can also be closed using the Escape key.

This change also trap the focus inside the menu while it's visible and
make it so that hovenig an item will move the focus to it, like most
menus will do.

This is mostly for accessibility so users can interact with the
controls using only a keyboard. The change applies to both the overflow
menu and the text track list.

Bug: 821131
Change-Id: I22db341d0d55a8e3297b3c7a18c92331e296cd9e
Reviewed-on: https://chromium-review.googlesource.com/1013478Reviewed-by: default avatarBecca Hughes <beccahughes@chromium.org>
Commit-Queue: Mounir Lamouri <mlamouri@chromium.org>
Cr-Commit-Position: refs/heads/master@{#551353}
parent ca449870
<!DOCTYPE html>
<title>Media Controls: overflow menu keyboard navigation</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../media-controls.js"></script>
<body>
</body>
<script>
function testNavigation(root, elements, next, previous) {
// Internal copy of the array. They are otherwise passed by reference.
const list = elements.slice(0);
assert_equals(root.activeElement, list[0]);
list.forEach(element => {
if (element == list[0])
return;
next();
assert_equals(root.activeElement, element);
assert_equals(
window.getComputedStyle(element).getPropertyValue('background-color'),
'rgb(224, 224, 224)');
});
// Next action after reaching the end is a no-op.
next();
assert_equals(root.activeElement, list.pop());
// pop() will remove the last element which will allow the next iteration to
// start from n-1.
list.reverse().forEach(element => {
previous();
assert_equals(root.activeElement, element);
assert_equals(
window.getComputedStyle(element).getPropertyValue('background-color'),
'rgb(224, 224, 224)');
});
// Previous element after reaching the beginning is a no-op.
previous();
assert_equals(root.activeElement, list[list.length - 1]);
}
async_test(t => {
assert_true('internals' in window);
assert_true('eventSender' in window);
assert_true(internals.runtimeFlags.modernMediaControlsEnabled);
const video = document.createElement('video');
video.controls = true;
video.src = '../content/test.ogv';
internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);
[ '../track/captions-webvtt/captions-fast.vtt',
'../track/captions-webvtt/captions-rtl.vtt' ].forEach(source => {
const track = document.createElement('track');
track.src = source;
track.kind = 'captions';
video.appendChild(track);
});
assert_equals(video.textTracks.length, 2);
document.body.appendChild(video);
video.addEventListener('loadedmetadata', t.step_func(() => {
assert_true(isVisible(overflowButton(video)));
singleTapOnControl(overflowButton(video), t.step_func_done(() => {
const menu = overflowMenu(video);
assert_true(isVisible(menu));
assert_equals(internals.shadowRoot(video).activeElement,
menu.lastElementChild);
const elements = [ menu.lastElementChild,
menu.lastElementChild.previousSibling ];
testNavigation(internals.shadowRoot(video), elements,
() => { eventSender.keyDown('ArrowUp'); },
() => { eventSender.keyDown('ArrowDown'); });
testNavigation(internals.shadowRoot(video), elements,
() => { eventSender.keyDown('Tab', [ 'shiftKey' ]); },
() => { eventSender.keyDown('Tab'); });
// Navigating with Shift + Arrow Keys should also work.
testNavigation(internals.shadowRoot(video), elements,
() => { eventSender.keyDown('ArrowUp', [ 'shiftKey' ]); },
() => { eventSender.keyDown('ArrowDown', [ 'shiftKey' ]); });
// Closing.
eventSender.keyDown('Escape');
assert_false(isVisible(menu));
assert_equals(internals.shadowRoot(video).activeElement,
overflowButton(video));
}));
}), { once: true });
});
</script>
<!DOCTYPE html>
<title>Media Controls: overflow menu pointer selection</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../media-controls.js"></script>
<body>
</body>
<script>
function moveMouseTo(x, y) {
return new Promise((resolve, reject) => {
chrome.gpuBenchmarking.pointerActionSequence([{
source: 'mouse',
actions: [ { name: 'pointerMove', x: x, y: y } ],
}], resolve);
});
}
async function testMouseMoveInsideElement(root, element) {
const box = element.getBoundingClientRect();
const y = box.top + (box.height / 2);
// Move the mouse from the left (middle-height) of the element to the right.
// The element is expected to stay focused while this is happening.
for (let x = box.left + 10; x < box.right; x += 10) {
await moveMouseTo(x, y);
assert_equals(root.activeElement, element);
}
}
async function testMouseEventForElements(root, elements) {
for (let i = 0; i < elements.length; i++)
await testMouseMoveInsideElement(root, elements[i]);
}
async_test(t => {
assert_true('internals' in window);
assert_true('eventSender' in window);
assert_true('chrome' in window);
assert_true(internals.runtimeFlags.modernMediaControlsEnabled);
const video = document.createElement('video');
video.controls = true;
video.src = '../content/test.ogv';
internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);
[ '../track/captions-webvtt/captions-fast.vtt',
'../track/captions-webvtt/captions-rtl.vtt' ].forEach(source => {
const track = document.createElement('track');
track.src = source;
track.kind = 'captions';
video.appendChild(track);
});
assert_equals(video.textTracks.length, 2);
document.body.appendChild(video);
video.addEventListener('loadedmetadata', t.step_func(() => {
assert_true(isVisible(overflowButton(video)));
singleTapOnControl(overflowButton(video), t.step_func(() => {
const menu = overflowMenu(video);
assert_true(isVisible(menu));
const root = internals.shadowRoot(video);
const elements = [ menu.lastElementChild,
menu.lastElementChild.previousSibling ];
testMouseEventForElements(root, elements).then(t.step_func(() => {
// Move focus to bottom element via keyboard then move back to top one
// via mouse.
elements.forEach(_ => eventSender.keyDown('ArrowDown'));
assert_equals(root.activeElement, menu.lastElementChild);
const target = elements[0];
const coord = elementCoordinates(target);
chrome.gpuBenchmarking.pointerActionSequence([{
source: 'mouse',
actions: [ { name: 'pointerMove', x: coord[0], y: coord[1] } ],
}], t.step_func_done(() => {
assert_equals(root.activeElement, target);
}));
}));
}));
}), { once: true });
});
</script>
<!DOCTYPE html>
<title>Media Controls: text track menu keyboard navigation</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../media-controls.js"></script>
<body>
</body>
<script>
function testNavigation(root, elements, next, previous) {
// Internal copy of the array. They are otherwise passed by reference.
const list = elements.slice(0);
assert_equals(root.activeElement, list[0]);
list.forEach(element => {
if (element == list[0])
return;
next();
assert_equals(root.activeElement, element);
assert_equals(
window.getComputedStyle(element).getPropertyValue('background-color'),
'rgb(224, 224, 224)');
});
// Next action after reaching the end is a no-op.
next();
assert_equals(root.activeElement, list.pop());
// pop() will remove the last element which will allow the next iteration to
// start from n-1.
list.reverse().forEach(element => {
previous();
assert_equals(root.activeElement, element);
assert_equals(
window.getComputedStyle(element).getPropertyValue('background-color'),
'rgb(224, 224, 224)');
});
// Previous element after reaching the beginning is a no-op.
previous();
assert_equals(root.activeElement, list[list.length - 1]);
}
async_test(t => {
assert_true('internals' in window);
assert_true('eventSender' in window);
assert_true(internals.runtimeFlags.modernMediaControlsEnabled);
const video = document.createElement('video');
video.controls = true;
video.src = '../content/test.ogv';
internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);
[ '../track/captions-webvtt/captions-fast.vtt',
'../track/captions-webvtt/captions-rtl.vtt' ].forEach(source => {
const track = document.createElement('track');
track.src = source;
track.kind = 'captions';
video.appendChild(track);
});
assert_equals(video.textTracks.length, 2);
document.body.appendChild(video);
video.addEventListener('loadedmetadata', t.step_func(() => {
assert_true(isVisible(overflowButton(video)));
openOverflowAndClickButton(video, captionsOverflowItem(video), t.step_func_done(() => {
const menu = textTrackMenu(video);
assert_true(isVisible(menu));
assert_equals(internals.shadowRoot(video).activeElement,
menu.lastElementChild);
const elements = [];
for (let i = 0; i < menu.children.length; i++)
elements.unshift(menu.children[i]);
testNavigation(internals.shadowRoot(video), elements,
() => { eventSender.keyDown('ArrowUp'); },
() => { eventSender.keyDown('ArrowDown'); });
testNavigation(internals.shadowRoot(video), elements,
() => { eventSender.keyDown('Tab', [ 'shiftKey' ]); },
() => { eventSender.keyDown('Tab'); });
// Navigating with Shift + Arrow Keys should also work.
testNavigation(internals.shadowRoot(video), elements,
() => { eventSender.keyDown('ArrowUp', [ 'shiftKey' ]); },
() => { eventSender.keyDown('ArrowDown', [ 'shiftKey' ]); });
// Closing.
eventSender.keyDown('Escape');
assert_false(isVisible(menu));
assert_equals(internals.shadowRoot(video).activeElement,
overflowButton(video));
}));
}), { once: true });
});
</script>
<!DOCTYPE html>
<title>Media Controls: text track menu pointer selection</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../media-controls.js"></script>
<body>
</body>
<script>
function moveMouseTo(x, y) {
return new Promise((resolve, reject) => {
chrome.gpuBenchmarking.pointerActionSequence([{
source: 'mouse',
actions: [ { name: 'pointerMove', x: x, y: y } ],
}], resolve);
});
}
async function testMouseMoveInsideElement(root, element) {
const box = element.getBoundingClientRect();
const y = box.top + (box.height / 2);
// Move the mouse from the left (middle-height) of the element to the right.
// The element is expected to stay focused while this is happening.
for (let x = box.left + 10; x < box.right; x += 10) {
await moveMouseTo(x, y);
assert_equals(root.activeElement, element);
}
}
async function testMouseEventForElements(root, elements) {
for (let i = 0; i < elements.length; i++)
await testMouseMoveInsideElement(root, elements[i]);
}
async_test(t => {
assert_true('internals' in window);
assert_true('eventSender' in window);
assert_true('chrome' in window);
assert_true(internals.runtimeFlags.modernMediaControlsEnabled);
const video = document.createElement('video');
video.controls = true;
video.src = '../content/test.ogv';
internals.mediaPlayerRemoteRouteAvailabilityChanged(video, true);
[ '../track/captions-webvtt/captions-fast.vtt',
'../track/captions-webvtt/captions-rtl.vtt' ].forEach(source => {
const track = document.createElement('track');
track.src = source;
track.kind = 'captions';
video.appendChild(track);
});
assert_equals(video.textTracks.length, 2);
document.body.appendChild(video);
video.addEventListener('loadedmetadata', t.step_func(() => {
assert_true(isVisible(overflowButton(video)));
openOverflowAndClickButton(video, captionsOverflowItem(video), t.step_func(() => {
const menu = textTrackMenu(video);
assert_true(isVisible(menu));
const root = internals.shadowRoot(video);
const elements = [];
for (let i = 0; i < menu.children.length; i++)
elements.unshift(menu.children[i]);
testMouseEventForElements(root, elements).then(t.step_func(() => {
// Move focus to bottom element via keyboard then move back to top one
// via mouse.
elements.forEach(_ => eventSender.keyDown('ArrowDown'));
assert_equals(root.activeElement, menu.lastElementChild);
const target = elements[0];
const coord = elementCoordinates(target);
chrome.gpuBenchmarking.pointerActionSequence([{
source: 'mouse',
actions: [ { name: 'pointerMove', x: coord[0], y: coord[1] } ],
}], t.step_func_done(() => {
assert_equals(root.activeElement, target);
}));
}));
}));
}), { once: true });
});
</script>
...@@ -62,7 +62,7 @@ function overflowButton(videoElement) ...@@ -62,7 +62,7 @@ function overflowButton(videoElement)
var controlID = '-internal-media-controls-overflow-button'; var controlID = '-internal-media-controls-overflow-button';
var button = mediaControlsElement(window.internals.shadowRoot(videoElement).firstChild, controlID); var button = mediaControlsElement(window.internals.shadowRoot(videoElement).firstChild, controlID);
if (!button) if (!button)
throw 'Failed to find cast button'; throw 'Failed to find overflow button';
return button; return button;
} }
...@@ -270,8 +270,7 @@ function scrubbingMessageElement(videoElement) { ...@@ -270,8 +270,7 @@ function scrubbingMessageElement(videoElement) {
return button; return button;
} }
function clickAtCoordinates(x, y) function clickAtCoordinates(x, y) {
{
eventSender.mouseMoveTo(x, y); eventSender.mouseMoveTo(x, y);
eventSender.mouseDown(); eventSender.mouseDown();
eventSender.mouseUp(); eventSender.mouseUp();
......
...@@ -68,6 +68,10 @@ HTMLElement* MediaControlInputElement::CreateOverflowElement( ...@@ -68,6 +68,10 @@ HTMLElement* MediaControlInputElement::CreateOverflowElement(
// are passed down to the button, performing the action we'd expect. // are passed down to the button, performing the action we'd expect.
element->ParserAppendChild(button); element->ParserAppendChild(button);
// Allows to focus the list entry instead of the button.
element->setTabIndex(0);
button->setTabIndex(-1);
if (MediaControlsImpl::IsModern()) { if (MediaControlsImpl::IsModern()) {
overflow_menu_container_ = HTMLDivElement::Create(GetDocument()); overflow_menu_container_ = HTMLDivElement::Create(GetDocument());
overflow_menu_container_->ParserAppendChild(overflow_menu_text_); overflow_menu_container_->ParserAppendChild(overflow_menu_text_);
......
...@@ -40,7 +40,7 @@ void MediaControlOverflowMenuListElement::DefaultEventHandler(Event* event) { ...@@ -40,7 +40,7 @@ void MediaControlOverflowMenuListElement::DefaultEventHandler(Event* event) {
if (event->type() == EventTypeNames::click) if (event->type() == EventTypeNames::click)
event->SetDefaultHandled(); event->SetDefaultHandled();
MediaControlDivElement::DefaultEventHandler(event); MediaControlPopupMenuElement::DefaultEventHandler(event);
} }
void MediaControlOverflowMenuListElement::SetIsWanted(bool wanted) { void MediaControlOverflowMenuListElement::SetIsWanted(bool wanted) {
......
...@@ -4,22 +4,128 @@ ...@@ -4,22 +4,128 @@
#include "third_party/blink/renderer/modules/media_controls/elements/media_control_popup_menu_element.h" #include "third_party/blink/renderer/modules/media_controls/elements/media_control_popup_menu_element.h"
#include "third_party/blink/renderer/core/css/css_property_value_set.h"
#include "third_party/blink/renderer/core/css/css_style_declaration.h" #include "third_party/blink/renderer/core/css/css_style_declaration.h"
#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/events/event_listener.h"
#include "third_party/blink/renderer/core/events/keyboard_event.h"
#include "third_party/blink/renderer/core/frame/dom_visual_viewport.h" #include "third_party/blink/renderer/core/frame/dom_visual_viewport.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/geometry/dom_rect.h" #include "third_party/blink/renderer/core/geometry/dom_rect.h"
#include "third_party/blink/renderer/core/html/media/html_media_element.h" #include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "third_party/blink/renderer/modules/media_controls/elements/media_control_overflow_menu_button_element.h" #include "third_party/blink/renderer/modules/media_controls/elements/media_control_overflow_menu_button_element.h"
#include "third_party/blink/renderer/modules/media_controls/media_controls_impl.h" #include "third_party/blink/renderer/modules/media_controls/media_controls_impl.h"
#include "third_party/blink/renderer/platform/keyboard_codes.h"
namespace blink { namespace blink {
namespace {
// Focus the given item in the list if it is displayed. Returns whether it was
// focused.
bool FocusListItemIfDisplayed(Node* node) {
Element* element = ToElement(node);
if (!element->InlineStyle() ||
!element->InlineStyle()->HasProperty(CSSPropertyDisplay)) {
element->focus();
return true;
}
return false;
}
} // anonymous namespace
class MediaControlPopupMenuElement::KeyboardEventListener final
: public EventListener {
public:
explicit KeyboardEventListener(MediaControlPopupMenuElement* popup_menu)
: EventListener(kCPPEventListenerType), popup_menu_(popup_menu) {}
~KeyboardEventListener() final = default;
bool operator==(const EventListener& other) const final {
return &other == this;
}
void Trace(blink::Visitor* visitor) final {
EventListener::Trace(visitor);
visitor->Trace(popup_menu_);
}
private:
void handleEvent(ExecutionContext*, Event* event) final {
if (event->type() == EventTypeNames::keydown && event->IsKeyboardEvent()) {
KeyboardEvent* keyboard_event = ToKeyboardEvent(event);
switch (keyboard_event->keyCode()) {
case VKEY_TAB:
keyboard_event->shiftKey() ? popup_menu_->SelectNextItem()
: popup_menu_->SelectPreviousitem();
break;
case VKEY_UP:
popup_menu_->SelectNextItem();
break;
case VKEY_DOWN:
popup_menu_->SelectPreviousitem();
break;
case VKEY_ESCAPE:
popup_menu_->CloseFromKeyboard();
break;
case VKEY_RETURN:
case VKEY_SPACE:
ToElement(event->target()->ToNode())->DispatchSimulatedClick(event);
break;
}
event->stopPropagation();
event->SetDefaultHandled();
}
}
Member<MediaControlPopupMenuElement> popup_menu_;
};
MediaControlPopupMenuElement::~MediaControlPopupMenuElement() = default;
void MediaControlPopupMenuElement::SetIsWanted(bool wanted) { void MediaControlPopupMenuElement::SetIsWanted(bool wanted) {
MediaControlDivElement::SetIsWanted(wanted); MediaControlDivElement::SetIsWanted(wanted);
if (wanted) if (wanted) {
SetPosition(); SetPosition();
SelectFirstItem();
if (!keyboard_event_listener_) {
keyboard_event_listener_ = new KeyboardEventListener(this);
addEventListener(EventTypeNames::keydown, keyboard_event_listener_,
false);
}
}
}
void MediaControlPopupMenuElement::DefaultEventHandler(Event* event) {
if (event->type() == EventTypeNames::pointermove)
ToElement(event->target()->ToNode())->focus();
MediaControlDivElement::DefaultEventHandler(event);
}
void MediaControlPopupMenuElement::RemovedFrom(ContainerNode* container) {
if (keyboard_event_listener_) {
removeEventListener(EventTypeNames::keydown, keyboard_event_listener_,
false);
keyboard_event_listener_ = nullptr;
}
MediaControlDivElement::RemovedFrom(container);
}
void MediaControlPopupMenuElement::Trace(blink::Visitor* visitor) {
MediaControlDivElement::Trace(visitor);
visitor->Trace(keyboard_event_listener_);
} }
MediaControlPopupMenuElement::MediaControlPopupMenuElement( MediaControlPopupMenuElement::MediaControlPopupMenuElement(
...@@ -39,10 +145,8 @@ void MediaControlPopupMenuElement::SetPosition() { ...@@ -39,10 +145,8 @@ void MediaControlPopupMenuElement::SetPosition() {
DCHECK(GetDocument().domWindow()); DCHECK(GetDocument().domWindow());
DCHECK(GetDocument().domWindow()->visualViewport()); DCHECK(GetDocument().domWindow()->visualViewport());
Element* anchor_element = MediaControlsImpl::IsModern() DOMRect* bounding_client_rect =
? &GetMediaControls().OverflowButton() EffectivePopupAnchor()->getBoundingClientRect();
: PopupAnchor();
DOMRect* bounding_client_rect = anchor_element->getBoundingClientRect();
DOMVisualViewport* viewport = GetDocument().domWindow()->visualViewport(); DOMVisualViewport* viewport = GetDocument().domWindow()->visualViewport();
WTF::String bottom_str_value = WTF::String::Number( WTF::String bottom_str_value = WTF::String::Number(
...@@ -59,4 +163,45 @@ void MediaControlPopupMenuElement::SetPosition() { ...@@ -59,4 +163,45 @@ void MediaControlPopupMenuElement::SetPosition() {
ASSERT_NO_EXCEPTION); ASSERT_NO_EXCEPTION);
} }
Element* MediaControlPopupMenuElement::EffectivePopupAnchor() const {
return MediaControlsImpl::IsModern() ? &GetMediaControls().OverflowButton()
: PopupAnchor();
}
void MediaControlPopupMenuElement::SelectFirstItem() {
for (Node* target = lastChild(); target; target = target->previousSibling()) {
if (FocusListItemIfDisplayed(target))
break;
}
}
void MediaControlPopupMenuElement::SelectNextItem() {
Element* focused_element = GetDocument().FocusedElement();
if (!focused_element || focused_element->parentElement() != this)
return;
for (Node* target = focused_element->previousSibling(); target;
target = target->previousSibling()) {
if (FocusListItemIfDisplayed(target))
break;
}
}
void MediaControlPopupMenuElement::SelectPreviousitem() {
Element* focused_element = GetDocument().FocusedElement();
if (!focused_element || focused_element->parentElement() != this)
return;
for (Node* target = focused_element->nextSibling(); target;
target = target->nextSibling()) {
if (FocusListItemIfDisplayed(target))
break;
}
}
void MediaControlPopupMenuElement::CloseFromKeyboard() {
SetIsWanted(false);
EffectivePopupAnchor()->focus();
}
} // namespace blink } // namespace blink
...@@ -13,16 +13,39 @@ class MediaControlsImpl; ...@@ -13,16 +13,39 @@ class MediaControlsImpl;
class MediaControlPopupMenuElement : public MediaControlDivElement { class MediaControlPopupMenuElement : public MediaControlDivElement {
public: public:
~MediaControlPopupMenuElement() override;
void SetIsWanted(bool) override; void SetIsWanted(bool) override;
// Only use when !IsModern to get the element that should be used an as anchor // Only use when !IsModern to get the element that should be used an as anchor
// for the popup. // for the popup.
virtual Element* PopupAnchor() const = 0; virtual Element* PopupAnchor() const = 0;
// Node override.
void DefaultEventHandler(Event*) override;
void RemovedFrom(ContainerNode*) override;
void Trace(blink::Visitor*) override;
protected: protected:
MediaControlPopupMenuElement(MediaControlsImpl&, MediaControlElementType); MediaControlPopupMenuElement(MediaControlsImpl&, MediaControlElementType);
void SetPosition(); void SetPosition();
private:
class KeyboardEventListener;
Element* EffectivePopupAnchor() const;
void SelectFirstItem();
// Actions called by the KeyboardEventListener object when specific evenst are
// received.
void SelectNextItem();
void SelectPreviousitem();
void CloseFromKeyboard();
Member<EventListener> keyboard_event_listener_;
}; };
} // namespace blink } // namespace blink
......
...@@ -59,10 +59,10 @@ bool MediaControlTextTrackListElement::WillRespondToMouseClickEvents() { ...@@ -59,10 +59,10 @@ bool MediaControlTextTrackListElement::WillRespondToMouseClickEvents() {
} }
void MediaControlTextTrackListElement::SetIsWanted(bool wanted) { void MediaControlTextTrackListElement::SetIsWanted(bool wanted) {
MediaControlPopupMenuElement::SetIsWanted(wanted);
if (wanted) if (wanted)
RefreshTextTrackListMenu(); RefreshTextTrackListMenu();
MediaControlPopupMenuElement::SetIsWanted(wanted);
} }
Element* MediaControlTextTrackListElement::PopupAnchor() const { Element* MediaControlTextTrackListElement::PopupAnchor() const {
...@@ -92,7 +92,7 @@ void MediaControlTextTrackListElement::DefaultEventHandler(Event* event) { ...@@ -92,7 +92,7 @@ void MediaControlTextTrackListElement::DefaultEventHandler(Event* event) {
event->SetDefaultHandled(); event->SetDefaultHandled();
} }
MediaControlDivElement::DefaultEventHandler(event); MediaControlPopupMenuElement::DefaultEventHandler(event);
} }
// TextTrack parameter when passed in as a nullptr, creates the "Off" list item // TextTrack parameter when passed in as a nullptr, creates the "Off" list item
...@@ -119,6 +119,10 @@ Element* MediaControlTextTrackListElement::CreateTextTrackListItem( ...@@ -119,6 +119,10 @@ Element* MediaControlTextTrackListElement::CreateTextTrackListItem(
track_item_input->setChecked(true); track_item_input->setChecked(true);
} }
// Allows to focus the list entry instead of the button.
track_item->setTabIndex(0);
track_item_input->setTabIndex(-1);
// Modern media controls should have the checkbox after the text instead of // Modern media controls should have the checkbox after the text instead of
// the other way around. // the other way around.
if (!MediaControlsImpl::IsModern()) if (!MediaControlsImpl::IsModern())
...@@ -153,6 +157,7 @@ Element* MediaControlTextTrackListElement::CreateTextTrackHeaderItem() { ...@@ -153,6 +157,7 @@ Element* MediaControlTextTrackListElement::CreateTextTrackHeaderItem() {
Text::Create(GetDocument(), Text::Create(GetDocument(),
GetLocale().QueryString( GetLocale().QueryString(
WebLocalizedString::kOverflowMenuCaptionsSubmenuTitle))); WebLocalizedString::kOverflowMenuCaptionsSubmenuTitle)));
header_item->setTabIndex(0);
return header_item; return header_item;
} }
......
...@@ -503,6 +503,7 @@ video::-internal-media-controls-overflow-menu-list-item { ...@@ -503,6 +503,7 @@ video::-internal-media-controls-overflow-menu-list-item {
label[pseudo="-internal-media-controls-overflow-menu-list-item"] input { label[pseudo="-internal-media-controls-overflow-menu-list-item"] input {
margin-left: -9px; margin-left: -9px;
margin-right: 6px; margin-right: 6px;
pointer-events: none;
} }
label[pseudo="-internal-media-controls-overflow-menu-list-item"] div { label[pseudo="-internal-media-controls-overflow-menu-list-item"] div {
...@@ -522,12 +523,12 @@ label[pseudo="-internal-media-controls-overflow-menu-list-item"] div span.subtit ...@@ -522,12 +523,12 @@ label[pseudo="-internal-media-controls-overflow-menu-list-item"] div span.subtit
color: rgba(0,0,0,0.54); color: rgba(0,0,0,0.54);
} }
audio::-internal-media-controls-text-track-list-header:hover, audio::-internal-media-controls-text-track-list-header:focus,
video::-internal-media-controls-text-track-list-header:hover, video::-internal-media-controls-text-track-list-header:focus,
audio::-internal-media-controls-overflow-menu-list-item:hover, audio::-internal-media-controls-overflow-menu-list-item:focus,
video::-internal-media-controls-overflow-menu-list-item:hover, video::-internal-media-controls-overflow-menu-list-item:focus,
audio::-internal-media-controls-text-track-list-item:hover, audio::-internal-media-controls-text-track-list-item:focus,
video::-internal-media-controls-text-track-list-item:hover { video::-internal-media-controls-text-track-list-item:focus {
background-color: #e0e0e0; background-color: #e0e0e0;
} }
...@@ -553,6 +554,7 @@ label[pseudo="-internal-media-controls-text-track-list-item"] input { ...@@ -553,6 +554,7 @@ label[pseudo="-internal-media-controls-text-track-list-item"] input {
height: 18px; height: 18px;
margin: 15px; margin: 15px;
float: right; float: right;
pointer-events: none;
} }
label[pseudo="-internal-media-controls-text-track-list-item"] input:checked { label[pseudo="-internal-media-controls-text-track-list-item"] input:checked {
......
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