blob: 42cffce8c10216954b617c01e7172311e4c5d3c3 [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 "modules/media_controls/elements/MediaControlInputElement.h"
#include "core/dom/DOMTokenList.h"
#include "core/dom/events/Event.h"
#include "core/html/forms/HTMLLabelElement.h"
#include "core/html/media/HTMLMediaElement.h"
#include "modules/media_controls/MediaControlsImpl.h"
#include "modules/media_controls/elements/MediaControlElementsHelper.h"
#include "platform/Histogram.h"
#include "platform/runtime_enabled_features.h"
#include "platform/text/PlatformLocale.h"
#include "public/platform/WebSize.h"
namespace {
// The default size of an overflow button in pixels.
constexpr int kDefaultButtonSize = 36;
} // namespace
namespace blink {
// static
bool MediaControlInputElement::ShouldRecordDisplayStates(
const HTMLMediaElement& media_element) {
// Only record when the metadat are available so that the display state of the
// buttons are fairly stable. For example, before metadata are available, the
// size of the element might differ, it's unknown if the file has an audio
// track, etc.
if (media_element.getReadyState() >= HTMLMediaElement::kHaveMetadata)
return true;
// When metadata are not available, only record the display state if the
// element will require a user gesture in order to load.
if (media_element.EffectivePreloadType() ==
WebMediaPlayer::Preload::kPreloadNone) {
return true;
}
return false;
}
HTMLElement* MediaControlInputElement::CreateOverflowElement(
MediaControlInputElement* button) {
if (!button)
return nullptr;
// We don't want the button visible within the overflow menu.
button->SetInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
overflow_menu_text_ =
Text::Create(GetDocument(), button->GetOverflowMenuString());
HTMLLabelElement* element = HTMLLabelElement::Create(GetDocument());
element->SetShadowPseudoId(
AtomicString("-internal-media-controls-overflow-menu-list-item"));
// Appending a button to a label element ensures that clicks on the label
// are passed down to the button, performing the action we'd expect.
element->AppendChild(button);
element->AppendChild(overflow_menu_text_);
// Initialize the internal states of the main element and the overflow one.
button->is_overflow_element_ = true;
overflow_element_ = button;
// Keeping the element hidden by default. This is setting the style in
// addition of calling ShouldShowButtonInOverflowMenu() to guarantee that the
// internal state matches the CSS state.
element->SetInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
SetOverflowElementIsWanted(false);
return element;
}
void MediaControlInputElement::SetOverflowElementIsWanted(bool wanted) {
if (!overflow_element_)
return;
overflow_element_->SetIsWanted(wanted);
}
void MediaControlInputElement::MaybeRecordDisplayed() {
// Display is defined as wanted and fitting. Overflow elements will only be
// displayed if their inline counterpart isn't displayed.
if (!IsWanted() || !DoesFit()) {
if (IsWanted() && overflow_element_)
overflow_element_->MaybeRecordDisplayed();
return;
}
// Keep this check after the block above because `display_recorded_` might be
// true for the inline element but not for the overflow one.
if (display_recorded_)
return;
RecordCTREvent(CTREvent::kDisplayed);
display_recorded_ = true;
}
void MediaControlInputElement::UpdateOverflowString() {
if (!overflow_menu_text_)
return;
DCHECK(overflow_element_);
overflow_menu_text_->ReplaceWholeText(GetOverflowMenuString());
}
MediaControlInputElement::MediaControlInputElement(
MediaControlsImpl& media_controls,
MediaControlElementType display_type)
: HTMLInputElement(media_controls.GetDocument(), false),
MediaControlElementBase(media_controls, display_type, this) {
CreateUserAgentShadowRoot();
CreateShadowSubtree();
}
WebLocalizedString::Name MediaControlInputElement::GetOverflowStringName()
const {
NOTREACHED();
return WebLocalizedString::kAXAMPMFieldText;
}
void MediaControlInputElement::UpdateShownState() {
if (is_overflow_element_) {
Element* parent = parentElement();
DCHECK(parent);
DCHECK(IsHTMLLabelElement(parent));
if (IsWanted() && DoesFit())
parent->RemoveInlineStyleProperty(CSSPropertyDisplay);
else
parent->SetInlineStyleProperty(CSSPropertyDisplay, CSSValueNone);
// Don't update the shown state of the element if we want to hide
// icons on the overflow menu.
if (!RuntimeEnabledFeatures::OverflowIconsForMediaControlsEnabled())
return;
}
MediaControlElementBase::UpdateShownState();
}
void MediaControlInputElement::DefaultEventHandler(Event* event) {
if (event->type() == EventTypeNames::click) {
if (IsOverflowElement())
GetMediaControls().MaybeRecordOverflowTimeToAction();
MaybeRecordInteracted();
}
HTMLInputElement::DefaultEventHandler(event);
}
void MediaControlInputElement::MaybeRecordInteracted() {
if (interaction_recorded_)
return;
if (!display_recorded_) {
// The only valid reason to not have the display recorded at this point is
// if it wasn't allowed. Regardless, the display will now be recorded.
DCHECK(!ShouldRecordDisplayStates(MediaElement()));
RecordCTREvent(CTREvent::kDisplayed);
}
RecordCTREvent(CTREvent::kInteracted);
interaction_recorded_ = true;
}
bool MediaControlInputElement::IsOverflowElement() const {
return is_overflow_element_;
}
bool MediaControlInputElement::IsMouseFocusable() const {
return false;
}
bool MediaControlInputElement::IsMediaControlElement() const {
return true;
}
String MediaControlInputElement::GetOverflowMenuString() const {
return MediaElement().GetLocale().QueryString(GetOverflowStringName());
}
void MediaControlInputElement::RecordCTREvent(CTREvent event) {
String histogram_name("Media.Controls.CTR.");
histogram_name.append(GetNameForHistograms());
EnumerationHistogram ctr_histogram(histogram_name.Ascii().data(),
static_cast<int>(CTREvent::kCount));
ctr_histogram.Count(static_cast<int>(event));
}
void MediaControlInputElement::SetClass(const AtomicString& class_name,
bool should_have_class) {
if (should_have_class)
classList().Add(class_name);
else
classList().Remove(class_name);
}
void MediaControlInputElement::UpdateDisplayType() {
if (overflow_element_)
overflow_element_->UpdateDisplayType();
}
WebSize MediaControlInputElement::GetSizeOrDefault() const {
if (HasOverflowButton()) {
// If this has an overflow button then it is a button control and therefore
// has a default size of kDefaultButtonSize.
return MediaControlElementsHelper::GetSizeOrDefault(
*this, WebSize(kDefaultButtonSize, kDefaultButtonSize));
}
return MediaControlElementsHelper::GetSizeOrDefault(*this, WebSize(0, 0));
}
void MediaControlInputElement::Trace(blink::Visitor* visitor) {
HTMLInputElement::Trace(visitor);
MediaControlElementBase::Trace(visitor);
visitor->Trace(overflow_element_);
visitor->Trace(overflow_menu_text_);
}
} // namespace blink