blob: 6be2253ddab6198dd9a7d3e7f6dd700fb70f2b25 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/media_controls/elements/media_control_text_track_list_element.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/forms/html_label_element.h"
#include "third_party/blink/renderer/core/html/html_span_element.h"
#include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "third_party/blink/renderer/core/html/track/text_track.h"
#include "third_party/blink/renderer/core/html/track/text_track_list.h"
#include "third_party/blink/renderer/core/input_type_names.h"
#include "third_party/blink/renderer/modules/media_controls/elements/media_control_toggle_closed_captions_button_element.h"
#include "third_party/blink/renderer/modules/media_controls/media_controls_impl.h"
#include "third_party/blink/renderer/platform/event_dispatch_forbidden_scope.h"
#include "third_party/blink/renderer/platform/text/platform_locale.h"
namespace blink {
namespace {
// When specified as trackIndex, disable text tracks.
constexpr int kTrackIndexOffValue = -1;
const QualifiedName& TrackIndexAttrName() {
// Save the track index in an attribute to avoid holding a pointer to the text
// track.
DEFINE_STATIC_LOCAL(QualifiedName, track_index_attr,
(g_null_atom, "data-track-index", g_null_atom));
return track_index_attr;
}
bool HasDuplicateLabel(TextTrack* current_track) {
DCHECK(current_track);
TextTrackList* track_list = current_track->TrackList();
// The runtime of this method is quadratic but since there are usually very
// few text tracks it won't affect the performance much.
String current_track_label = current_track->label();
for (unsigned i = 0; i < track_list->length(); i++) {
TextTrack* track = track_list->AnonymousIndexedGetter(i);
if (current_track != track && current_track_label == track->label())
return true;
}
return false;
}
} // anonymous namespace
MediaControlTextTrackListElement::MediaControlTextTrackListElement(
MediaControlsImpl& media_controls)
: MediaControlPopupMenuElement(media_controls, kMediaTextTrackList) {
SetShadowPseudoId(AtomicString("-internal-media-controls-text-track-list"));
}
bool MediaControlTextTrackListElement::WillRespondToMouseClickEvents() {
return true;
}
void MediaControlTextTrackListElement::SetIsWanted(bool wanted) {
if (wanted)
RefreshTextTrackListMenu();
MediaControlPopupMenuElement::SetIsWanted(wanted);
}
Element* MediaControlTextTrackListElement::PopupAnchor() const {
return &GetMediaControls().ToggleClosedCaptions();
}
void MediaControlTextTrackListElement::DefaultEventHandler(Event* event) {
if (event->type() == EventTypeNames::click) {
// This handles the back button click. Clicking on a menu item triggers the
// change event instead.
GetMediaControls().ToggleOverflowMenu();
event->SetDefaultHandled();
} else if (event->type() == EventTypeNames::change) {
// Identify which input element was selected and set track to showing
Node* target = event->target()->ToNode();
if (!target || !target->IsElementNode())
return;
GetMediaControls().DisableShowingTextTracks();
int track_index =
ToElement(target)->GetIntegralAttribute(TrackIndexAttrName());
if (track_index != kTrackIndexOffValue) {
DCHECK_GE(track_index, 0);
GetMediaControls().ShowTextTrackAtIndex(track_index);
MediaElement().DisableAutomaticTextTrackSelection();
}
event->SetDefaultHandled();
}
MediaControlPopupMenuElement::DefaultEventHandler(event);
}
// TextTrack parameter when passed in as a nullptr, creates the "Off" list item
// in the track list.
Element* MediaControlTextTrackListElement::CreateTextTrackListItem(
TextTrack* track) {
int track_index = track ? track->TrackIndex() : kTrackIndexOffValue;
HTMLLabelElement* track_item = HTMLLabelElement::Create(GetDocument());
track_item->SetShadowPseudoId(
AtomicString("-internal-media-controls-text-track-list-item"));
auto* track_item_input =
HTMLInputElement::Create(GetDocument(), CreateElementFlags());
track_item_input->SetShadowPseudoId(
AtomicString("-internal-media-controls-text-track-list-item-input"));
track_item_input->setType(InputTypeNames::checkbox);
track_item_input->SetIntegralAttribute(TrackIndexAttrName(), track_index);
if (!MediaElement().TextTracksVisible()) {
if (!track)
track_item_input->setChecked(true);
} else {
// If there are multiple text tracks set to showing, they must all have
// checkmarks displayed.
if (track && track->mode() == TextTrack::ShowingKeyword())
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
// the other way around.
if (!MediaControlsImpl::IsModern())
track_item->ParserAppendChild(track_item_input);
String track_label = GetMediaControls().GetTextTrackLabel(track);
track_item->ParserAppendChild(Text::Create(GetDocument(), track_label));
if (MediaControlsImpl::IsModern())
track_item->ParserAppendChild(track_item_input);
// Add a track kind marker icon if there are multiple tracks with the same
// label or if the track has no label.
if (track && (track->label().IsEmpty() || HasDuplicateLabel(track))) {
HTMLSpanElement* track_kind_marker = HTMLSpanElement::Create(GetDocument());
if (track->kind() == track->CaptionsKeyword()) {
track_kind_marker->SetShadowPseudoId(AtomicString(
"-internal-media-controls-text-track-list-kind-captions"));
} else {
DCHECK_EQ(track->kind(), track->SubtitlesKeyword());
track_kind_marker->SetShadowPseudoId(AtomicString(
"-internal-media-controls-text-track-list-kind-subtitles"));
}
track_item->ParserAppendChild(track_kind_marker);
}
return track_item;
}
Element* MediaControlTextTrackListElement::CreateTextTrackHeaderItem() {
HTMLLabelElement* header_item = HTMLLabelElement::Create(GetDocument());
header_item->SetShadowPseudoId(
"-internal-media-controls-text-track-list-header");
header_item->ParserAppendChild(
Text::Create(GetDocument(),
GetLocale().QueryString(
WebLocalizedString::kOverflowMenuCaptionsSubmenuTitle)));
header_item->setTabIndex(0);
return header_item;
}
void MediaControlTextTrackListElement::RefreshTextTrackListMenu() {
if (!MediaElement().HasClosedCaptions() ||
!MediaElement().TextTracksAreReady()) {
return;
}
EventDispatchForbiddenScope::AllowUserAgentEvents allow_events;
RemoveChildren(kOmitSubtreeModifiedEvent);
if (MediaControlsImpl::IsModern())
ParserAppendChild(CreateTextTrackHeaderItem());
// Construct a menu for subtitles and captions. Pass in a nullptr to
// createTextTrackListItem to create the "Off" track item.
ParserAppendChild(CreateTextTrackListItem(nullptr));
TextTrackList* track_list = MediaElement().textTracks();
for (unsigned i = 0; i < track_list->length(); i++) {
TextTrack* track = track_list->AnonymousIndexedGetter(i);
if (!track->CanBeRendered())
continue;
ParserAppendChild(CreateTextTrackListItem(track));
}
}
} // namespace blink