1/* 2 * Copyright (C) 2008, 2009, 2010, 2011 Apple Inc. All rights reserved. 3 * Copyright (C) 2012 Google Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30#include "config.h" 31 32#if ENABLE(VIDEO) 33#include "MediaControlElements.h" 34 35#include "DOMTokenList.h" 36#include "EventHandler.h" 37#include "EventNames.h" 38#include "ExceptionCodePlaceholder.h" 39#include "Frame.h" 40#include "GraphicsContext.h" 41#include "HTMLVideoElement.h" 42#include "ImageBuffer.h" 43#include "Language.h" 44#include "LocalizedStrings.h" 45#include "Logging.h" 46#include "MediaControls.h" 47#include "PageGroup.h" 48#include "RenderLayer.h" 49#include "RenderMediaControlElements.h" 50#include "RenderSlider.h" 51#include "RenderVideo.h" 52#include "RenderView.h" 53#include "Settings.h" 54#include "ShadowRoot.h" 55#if ENABLE(VIDEO_TRACK) 56#include "TextTrackList.h" 57#endif 58 59#if ENABLE(WEBVTT_REGIONS) 60#include "VTTRegionList.h" 61#endif 62 63namespace WebCore { 64 65using namespace HTMLNames; 66 67static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId(); 68static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId(); 69 70MediaControlPanelElement::MediaControlPanelElement(Document& document) 71 : MediaControlDivElement(document, MediaControlsPanel) 72 , m_canBeDragged(false) 73 , m_isBeingDragged(false) 74 , m_isDisplayed(false) 75 , m_opaque(true) 76 , m_transitionTimer(this, &MediaControlPanelElement::transitionTimerFired) 77{ 78 setPseudo(shadowPseudoId()); 79} 80 81PassRefPtr<MediaControlPanelElement> MediaControlPanelElement::create(Document& document) 82{ 83 return adoptRef(new MediaControlPanelElement(document)); 84} 85 86const AtomicString& MediaControlPanelElement::shadowPseudoId() const 87{ 88 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-panel", AtomicString::ConstructFromLiteral)); 89 return id; 90} 91 92void MediaControlPanelElement::startDrag(const LayoutPoint& eventLocation) 93{ 94 if (!m_canBeDragged) 95 return; 96 97 if (m_isBeingDragged) 98 return; 99 100 auto renderer = this->renderer(); 101 if (!renderer || !renderer->isBox()) 102 return; 103 104 Frame* frame = document().frame(); 105 if (!frame) 106 return; 107 108 m_lastDragEventLocation = eventLocation; 109 110 frame->eventHandler().setCapturingMouseEventsElement(this); 111 112 m_isBeingDragged = true; 113} 114 115void MediaControlPanelElement::continueDrag(const LayoutPoint& eventLocation) 116{ 117 if (!m_isBeingDragged) 118 return; 119 120 LayoutSize distanceDragged = eventLocation - m_lastDragEventLocation; 121 m_cumulativeDragOffset.move(distanceDragged); 122 m_lastDragEventLocation = eventLocation; 123 setPosition(m_cumulativeDragOffset); 124} 125 126void MediaControlPanelElement::endDrag() 127{ 128 if (!m_isBeingDragged) 129 return; 130 131 m_isBeingDragged = false; 132 133 Frame* frame = document().frame(); 134 if (!frame) 135 return; 136 137 frame->eventHandler().setCapturingMouseEventsElement(nullptr); 138} 139 140void MediaControlPanelElement::startTimer() 141{ 142 stopTimer(); 143 144 // The timer is required to set the property display:'none' on the panel, 145 // such that captions are correctly displayed at the bottom of the video 146 // at the end of the fadeout transition. 147 double duration = document().page() ? document().page()->theme().mediaControlsFadeOutDuration() : 0; 148 m_transitionTimer.startOneShot(duration); 149} 150 151void MediaControlPanelElement::stopTimer() 152{ 153 if (m_transitionTimer.isActive()) 154 m_transitionTimer.stop(); 155} 156 157void MediaControlPanelElement::transitionTimerFired(Timer<MediaControlPanelElement>&) 158{ 159 if (!m_opaque) 160 hide(); 161 162 stopTimer(); 163} 164 165void MediaControlPanelElement::setPosition(const LayoutPoint& position) 166{ 167 double left = position.x(); 168 double top = position.y(); 169 170 // Set the left and top to control the panel's position; this depends on it being absolute positioned. 171 // Set the margin to zero since the position passed in will already include the effect of the margin. 172 setInlineStyleProperty(CSSPropertyLeft, left, CSSPrimitiveValue::CSS_PX); 173 setInlineStyleProperty(CSSPropertyTop, top, CSSPrimitiveValue::CSS_PX); 174 setInlineStyleProperty(CSSPropertyMarginLeft, 0.0, CSSPrimitiveValue::CSS_PX); 175 setInlineStyleProperty(CSSPropertyMarginTop, 0.0, CSSPrimitiveValue::CSS_PX); 176 177 classList()->add("dragged", IGNORE_EXCEPTION); 178} 179 180void MediaControlPanelElement::resetPosition() 181{ 182 removeInlineStyleProperty(CSSPropertyLeft); 183 removeInlineStyleProperty(CSSPropertyTop); 184 removeInlineStyleProperty(CSSPropertyMarginLeft); 185 removeInlineStyleProperty(CSSPropertyMarginTop); 186 187 classList()->remove("dragged", IGNORE_EXCEPTION); 188 189 m_cumulativeDragOffset.setX(0); 190 m_cumulativeDragOffset.setY(0); 191} 192 193void MediaControlPanelElement::makeOpaque() 194{ 195 if (m_opaque) 196 return; 197 198 double duration = document().page() ? document().page()->theme().mediaControlsFadeInDuration() : 0; 199 200 setInlineStyleProperty(CSSPropertyWebkitTransitionProperty, CSSPropertyOpacity); 201 setInlineStyleProperty(CSSPropertyWebkitTransitionDuration, duration, CSSPrimitiveValue::CSS_S); 202 setInlineStyleProperty(CSSPropertyOpacity, 1.0, CSSPrimitiveValue::CSS_NUMBER); 203 204 m_opaque = true; 205 206 if (m_isDisplayed) 207 show(); 208} 209 210void MediaControlPanelElement::makeTransparent() 211{ 212 if (!m_opaque) 213 return; 214 215 double duration = document().page() ? document().page()->theme().mediaControlsFadeOutDuration() : 0; 216 217 setInlineStyleProperty(CSSPropertyWebkitTransitionProperty, CSSPropertyOpacity); 218 setInlineStyleProperty(CSSPropertyWebkitTransitionDuration, duration, CSSPrimitiveValue::CSS_S); 219 setInlineStyleProperty(CSSPropertyOpacity, 0.0, CSSPrimitiveValue::CSS_NUMBER); 220 221 m_opaque = false; 222 startTimer(); 223} 224 225void MediaControlPanelElement::defaultEventHandler(Event* event) 226{ 227 MediaControlDivElement::defaultEventHandler(event); 228 229 if (event->isMouseEvent()) { 230 LayoutPoint location = toMouseEvent(event)->absoluteLocation(); 231 if (event->type() == eventNames().mousedownEvent && event->target() == this) { 232 startDrag(location); 233 event->setDefaultHandled(); 234 } else if (event->type() == eventNames().mousemoveEvent && m_isBeingDragged) 235 continueDrag(location); 236 else if (event->type() == eventNames().mouseupEvent && m_isBeingDragged) { 237 continueDrag(location); 238 endDrag(); 239 event->setDefaultHandled(); 240 } 241 } 242} 243 244void MediaControlPanelElement::setCanBeDragged(bool canBeDragged) 245{ 246 if (m_canBeDragged == canBeDragged) 247 return; 248 249 m_canBeDragged = canBeDragged; 250 251 if (!canBeDragged) 252 endDrag(); 253} 254 255void MediaControlPanelElement::setIsDisplayed(bool isDisplayed) 256{ 257 m_isDisplayed = isDisplayed; 258} 259 260// ---------------------------- 261 262MediaControlPanelEnclosureElement::MediaControlPanelEnclosureElement(Document& document) 263 // Mapping onto same MediaControlElementType as panel element, since it has similar properties. 264 : MediaControlDivElement(document, MediaControlsPanel) 265{ 266 setPseudo(shadowPseudoId()); 267} 268 269PassRefPtr<MediaControlPanelEnclosureElement> MediaControlPanelEnclosureElement::create(Document& document) 270{ 271 return adoptRef(new MediaControlPanelEnclosureElement(document)); 272} 273 274const AtomicString& MediaControlPanelEnclosureElement::shadowPseudoId() const 275{ 276 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-enclosure", AtomicString::ConstructFromLiteral)); 277 return id; 278} 279 280// ---------------------------- 281 282MediaControlOverlayEnclosureElement::MediaControlOverlayEnclosureElement(Document& document) 283 // Mapping onto same MediaControlElementType as panel element, since it has similar properties. 284 : MediaControlDivElement(document, MediaControlsPanel) 285{ 286} 287 288PassRefPtr<MediaControlOverlayEnclosureElement> MediaControlOverlayEnclosureElement::create(Document& document) 289{ 290 return adoptRef(new MediaControlOverlayEnclosureElement(document)); 291} 292 293const AtomicString& MediaControlOverlayEnclosureElement::shadowPseudoId() const 294{ 295 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-overlay-enclosure", AtomicString::ConstructFromLiteral)); 296 return id; 297} 298 299// ---------------------------- 300 301MediaControlTimelineContainerElement::MediaControlTimelineContainerElement(Document& document) 302 : MediaControlDivElement(document, MediaTimelineContainer) 303{ 304 setPseudo(shadowPseudoId()); 305} 306 307PassRefPtr<MediaControlTimelineContainerElement> MediaControlTimelineContainerElement::create(Document& document) 308{ 309 RefPtr<MediaControlTimelineContainerElement> element = adoptRef(new MediaControlTimelineContainerElement(document)); 310 element->hide(); 311 return element.release(); 312} 313 314const AtomicString& MediaControlTimelineContainerElement::shadowPseudoId() const 315{ 316 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-timeline-container", AtomicString::ConstructFromLiteral)); 317 return id; 318} 319 320void MediaControlTimelineContainerElement::setTimeDisplaysHidden(bool hidden) 321{ 322 for (unsigned i = 0; i < childNodeCount(); ++i) { 323 Node* child = childNode(i); 324 if (!child || !child->isElementNode()) 325 continue; 326 Element* element = toElement(child); 327 if (element->shadowPseudoId() != getMediaControlTimeRemainingDisplayElementShadowPseudoId() 328 && element->shadowPseudoId() != getMediaControlCurrentTimeDisplayElementShadowPseudoId()) 329 continue; 330 331 MediaControlTimeDisplayElement* timeDisplay = static_cast<MediaControlTimeDisplayElement*>(element); 332 if (hidden) 333 timeDisplay->hide(); 334 else 335 timeDisplay->show(); 336 } 337} 338 339RenderPtr<RenderElement> MediaControlTimelineContainerElement::createElementRenderer(PassRef<RenderStyle> style) 340{ 341 return createRenderer<RenderMediaControlTimelineContainer>(*this, WTF::move(style)); 342} 343 344// ---------------------------- 345 346MediaControlVolumeSliderContainerElement::MediaControlVolumeSliderContainerElement(Document& document) 347 : MediaControlDivElement(document, MediaVolumeSliderContainer) 348{ 349 setPseudo(shadowPseudoId()); 350} 351 352PassRefPtr<MediaControlVolumeSliderContainerElement> MediaControlVolumeSliderContainerElement::create(Document& document) 353{ 354 RefPtr<MediaControlVolumeSliderContainerElement> element = adoptRef(new MediaControlVolumeSliderContainerElement(document)); 355 element->hide(); 356 return element.release(); 357} 358 359RenderPtr<RenderElement> MediaControlVolumeSliderContainerElement::createElementRenderer(PassRef<RenderStyle> style) 360{ 361 return createRenderer<RenderMediaVolumeSliderContainer>(*this, WTF::move(style)); 362} 363 364void MediaControlVolumeSliderContainerElement::defaultEventHandler(Event* event) 365{ 366 if (!event->isMouseEvent() || event->type() != eventNames().mouseoutEvent) 367 return; 368 369 // Poor man's mouseleave event detection. 370 MouseEvent* mouseEvent = toMouseEvent(event); 371 EventTarget* relatedTarget = mouseEvent->relatedTarget(); 372 if (!relatedTarget || !relatedTarget->toNode()) 373 return; 374 375 if (this->containsIncludingShadowDOM(relatedTarget->toNode())) 376 return; 377 378 hide(); 379} 380 381const AtomicString& MediaControlVolumeSliderContainerElement::shadowPseudoId() const 382{ 383 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-volume-slider-container", AtomicString::ConstructFromLiteral)); 384 return id; 385} 386 387// ---------------------------- 388 389MediaControlStatusDisplayElement::MediaControlStatusDisplayElement(Document& document) 390 : MediaControlDivElement(document, MediaStatusDisplay) 391 , m_stateBeingDisplayed(Nothing) 392{ 393 setPseudo(shadowPseudoId()); 394} 395 396PassRefPtr<MediaControlStatusDisplayElement> MediaControlStatusDisplayElement::create(Document& document) 397{ 398 RefPtr<MediaControlStatusDisplayElement> element = adoptRef(new MediaControlStatusDisplayElement(document)); 399 element->hide(); 400 return element.release(); 401} 402 403void MediaControlStatusDisplayElement::update() 404{ 405 // Get the new state that we'll have to display. 406 StateBeingDisplayed newStateToDisplay = Nothing; 407 408 if (mediaController()->readyState() <= MediaControllerInterface::HAVE_METADATA && mediaController()->hasCurrentSrc()) 409 newStateToDisplay = Loading; 410 else if (mediaController()->isLiveStream()) 411 newStateToDisplay = LiveBroadcast; 412 413 if (newStateToDisplay == m_stateBeingDisplayed) 414 return; 415 416 if (m_stateBeingDisplayed == Nothing) 417 show(); 418 else if (newStateToDisplay == Nothing) 419 hide(); 420 421 m_stateBeingDisplayed = newStateToDisplay; 422 423 switch (m_stateBeingDisplayed) { 424 case Nothing: 425 setInnerText("", IGNORE_EXCEPTION); 426 break; 427 case Loading: 428 setInnerText(mediaElementLoadingStateText(), IGNORE_EXCEPTION); 429 break; 430 case LiveBroadcast: 431 setInnerText(mediaElementLiveBroadcastStateText(), IGNORE_EXCEPTION); 432 break; 433 } 434} 435 436const AtomicString& MediaControlStatusDisplayElement::shadowPseudoId() const 437{ 438 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-status-display", AtomicString::ConstructFromLiteral)); 439 return id; 440} 441 442 443// ---------------------------- 444 445MediaControlPanelMuteButtonElement::MediaControlPanelMuteButtonElement(Document& document, MediaControls* controls) 446 : MediaControlMuteButtonElement(document, MediaMuteButton) 447 , m_controls(controls) 448{ 449 setPseudo(shadowPseudoId()); 450} 451 452PassRefPtr<MediaControlPanelMuteButtonElement> MediaControlPanelMuteButtonElement::create(Document& document, MediaControls* controls) 453{ 454 ASSERT(controls); 455 456 RefPtr<MediaControlPanelMuteButtonElement> button = adoptRef(new MediaControlPanelMuteButtonElement(document, controls)); 457 button->ensureUserAgentShadowRoot(); 458 button->setType("button"); 459 return button.release(); 460} 461 462void MediaControlPanelMuteButtonElement::defaultEventHandler(Event* event) 463{ 464 if (event->type() == eventNames().mouseoverEvent) 465 m_controls->showVolumeSlider(); 466 467 MediaControlMuteButtonElement::defaultEventHandler(event); 468} 469 470const AtomicString& MediaControlPanelMuteButtonElement::shadowPseudoId() const 471{ 472 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-mute-button", AtomicString::ConstructFromLiteral)); 473 return id; 474} 475 476// ---------------------------- 477 478MediaControlVolumeSliderMuteButtonElement::MediaControlVolumeSliderMuteButtonElement(Document& document) 479 : MediaControlMuteButtonElement(document, MediaMuteButton) 480{ 481 setPseudo(shadowPseudoId()); 482} 483 484PassRefPtr<MediaControlVolumeSliderMuteButtonElement> MediaControlVolumeSliderMuteButtonElement::create(Document& document) 485{ 486 RefPtr<MediaControlVolumeSliderMuteButtonElement> button = adoptRef(new MediaControlVolumeSliderMuteButtonElement(document)); 487 button->ensureUserAgentShadowRoot(); 488 button->setType("button"); 489 return button.release(); 490} 491 492const AtomicString& MediaControlVolumeSliderMuteButtonElement::shadowPseudoId() const 493{ 494 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-volume-slider-mute-button", AtomicString::ConstructFromLiteral)); 495 return id; 496} 497 498// ---------------------------- 499 500MediaControlPlayButtonElement::MediaControlPlayButtonElement(Document& document) 501 : MediaControlInputElement(document, MediaPlayButton) 502{ 503 setPseudo(shadowPseudoId()); 504} 505 506PassRefPtr<MediaControlPlayButtonElement> MediaControlPlayButtonElement::create(Document& document) 507{ 508 RefPtr<MediaControlPlayButtonElement> button = adoptRef(new MediaControlPlayButtonElement(document)); 509 button->ensureUserAgentShadowRoot(); 510 button->setType("button"); 511 return button.release(); 512} 513 514void MediaControlPlayButtonElement::defaultEventHandler(Event* event) 515{ 516 if (event->type() == eventNames().clickEvent) { 517 if (mediaController()->canPlay()) 518 mediaController()->play(); 519 else 520 mediaController()->pause(); 521 updateDisplayType(); 522 event->setDefaultHandled(); 523 } 524 HTMLInputElement::defaultEventHandler(event); 525} 526 527void MediaControlPlayButtonElement::updateDisplayType() 528{ 529 setDisplayType(mediaController()->canPlay() ? MediaPlayButton : MediaPauseButton); 530} 531 532const AtomicString& MediaControlPlayButtonElement::shadowPseudoId() const 533{ 534 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-play-button", AtomicString::ConstructFromLiteral)); 535 return id; 536} 537 538// ---------------------------- 539 540MediaControlOverlayPlayButtonElement::MediaControlOverlayPlayButtonElement(Document& document) 541 : MediaControlInputElement(document, MediaOverlayPlayButton) 542{ 543 setPseudo(shadowPseudoId()); 544} 545 546PassRefPtr<MediaControlOverlayPlayButtonElement> MediaControlOverlayPlayButtonElement::create(Document& document) 547{ 548 RefPtr<MediaControlOverlayPlayButtonElement> button = adoptRef(new MediaControlOverlayPlayButtonElement(document)); 549 button->ensureUserAgentShadowRoot(); 550 button->setType("button"); 551 return button.release(); 552} 553 554void MediaControlOverlayPlayButtonElement::defaultEventHandler(Event* event) 555{ 556 if (event->type() == eventNames().clickEvent && mediaController()->canPlay()) { 557 mediaController()->play(); 558 updateDisplayType(); 559 event->setDefaultHandled(); 560 } 561 HTMLInputElement::defaultEventHandler(event); 562} 563 564void MediaControlOverlayPlayButtonElement::updateDisplayType() 565{ 566 if (mediaController()->canPlay()) { 567 show(); 568 } else 569 hide(); 570} 571 572const AtomicString& MediaControlOverlayPlayButtonElement::shadowPseudoId() const 573{ 574 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-overlay-play-button", AtomicString::ConstructFromLiteral)); 575 return id; 576} 577 578 579// ---------------------------- 580 581MediaControlSeekForwardButtonElement::MediaControlSeekForwardButtonElement(Document& document) 582 : MediaControlSeekButtonElement(document, MediaSeekForwardButton) 583{ 584 setPseudo(shadowPseudoId()); 585} 586 587PassRefPtr<MediaControlSeekForwardButtonElement> MediaControlSeekForwardButtonElement::create(Document& document) 588{ 589 RefPtr<MediaControlSeekForwardButtonElement> button = adoptRef(new MediaControlSeekForwardButtonElement(document)); 590 button->ensureUserAgentShadowRoot(); 591 button->setType("button"); 592 return button.release(); 593} 594 595const AtomicString& MediaControlSeekForwardButtonElement::shadowPseudoId() const 596{ 597 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-seek-forward-button", AtomicString::ConstructFromLiteral)); 598 return id; 599} 600 601// ---------------------------- 602 603MediaControlSeekBackButtonElement::MediaControlSeekBackButtonElement(Document& document) 604 : MediaControlSeekButtonElement(document, MediaSeekBackButton) 605{ 606 setPseudo(shadowPseudoId()); 607} 608 609PassRefPtr<MediaControlSeekBackButtonElement> MediaControlSeekBackButtonElement::create(Document& document) 610{ 611 RefPtr<MediaControlSeekBackButtonElement> button = adoptRef(new MediaControlSeekBackButtonElement(document)); 612 button->ensureUserAgentShadowRoot(); 613 button->setType("button"); 614 return button.release(); 615} 616 617const AtomicString& MediaControlSeekBackButtonElement::shadowPseudoId() const 618{ 619 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-seek-back-button", AtomicString::ConstructFromLiteral)); 620 return id; 621} 622 623// ---------------------------- 624 625MediaControlRewindButtonElement::MediaControlRewindButtonElement(Document& document) 626 : MediaControlInputElement(document, MediaRewindButton) 627{ 628 setPseudo(shadowPseudoId()); 629} 630 631PassRefPtr<MediaControlRewindButtonElement> MediaControlRewindButtonElement::create(Document& document) 632{ 633 RefPtr<MediaControlRewindButtonElement> button = adoptRef(new MediaControlRewindButtonElement(document)); 634 button->ensureUserAgentShadowRoot(); 635 button->setType("button"); 636 return button.release(); 637} 638 639void MediaControlRewindButtonElement::defaultEventHandler(Event* event) 640{ 641 if (event->type() == eventNames().clickEvent) { 642 mediaController()->setCurrentTime(std::max<double>(0, mediaController()->currentTime() - 30)); 643 event->setDefaultHandled(); 644 } 645 HTMLInputElement::defaultEventHandler(event); 646} 647 648const AtomicString& MediaControlRewindButtonElement::shadowPseudoId() const 649{ 650 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-rewind-button", AtomicString::ConstructFromLiteral)); 651 return id; 652} 653 654// ---------------------------- 655 656MediaControlReturnToRealtimeButtonElement::MediaControlReturnToRealtimeButtonElement(Document& document) 657 : MediaControlInputElement(document, MediaReturnToRealtimeButton) 658{ 659 setPseudo(shadowPseudoId()); 660} 661 662PassRefPtr<MediaControlReturnToRealtimeButtonElement> MediaControlReturnToRealtimeButtonElement::create(Document& document) 663{ 664 RefPtr<MediaControlReturnToRealtimeButtonElement> button = adoptRef(new MediaControlReturnToRealtimeButtonElement(document)); 665 button->ensureUserAgentShadowRoot(); 666 button->setType("button"); 667 button->hide(); 668 return button.release(); 669} 670 671void MediaControlReturnToRealtimeButtonElement::defaultEventHandler(Event* event) 672{ 673 if (event->type() == eventNames().clickEvent) { 674 mediaController()->returnToRealtime(); 675 event->setDefaultHandled(); 676 } 677 HTMLInputElement::defaultEventHandler(event); 678} 679 680const AtomicString& MediaControlReturnToRealtimeButtonElement::shadowPseudoId() const 681{ 682 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-return-to-realtime-button", AtomicString::ConstructFromLiteral)); 683 return id; 684} 685 686// ---------------------------- 687 688MediaControlToggleClosedCaptionsButtonElement::MediaControlToggleClosedCaptionsButtonElement(Document& document, MediaControls* controls) 689 : MediaControlInputElement(document, MediaShowClosedCaptionsButton) 690#if PLATFORM(COCOA) || PLATFORM(WIN) || PLATFORM(GTK) 691 , m_controls(controls) 692#endif 693{ 694#if !PLATFORM(COCOA) && !PLATFORM(WIN) || !PLATFORM(GTK) 695 UNUSED_PARAM(controls); 696#endif 697 setPseudo(shadowPseudoId()); 698} 699 700PassRefPtr<MediaControlToggleClosedCaptionsButtonElement> MediaControlToggleClosedCaptionsButtonElement::create(Document& document, MediaControls* controls) 701{ 702 ASSERT(controls); 703 704 RefPtr<MediaControlToggleClosedCaptionsButtonElement> button = adoptRef(new MediaControlToggleClosedCaptionsButtonElement(document, controls)); 705 button->ensureUserAgentShadowRoot(); 706 button->setType("button"); 707 button->hide(); 708 return button.release(); 709} 710 711void MediaControlToggleClosedCaptionsButtonElement::updateDisplayType() 712{ 713 bool captionsVisible = mediaController()->closedCaptionsVisible(); 714 setDisplayType(captionsVisible ? MediaHideClosedCaptionsButton : MediaShowClosedCaptionsButton); 715 setChecked(captionsVisible); 716} 717 718void MediaControlToggleClosedCaptionsButtonElement::defaultEventHandler(Event* event) 719{ 720 if (event->type() == eventNames().clickEvent) { 721 // FIXME: It's not great that the shared code is dictating behavior of platform-specific 722 // UI. Not all ports may want the closed captions button to toggle a list of tracks, so 723 // we have to use #if. 724 // https://bugs.webkit.org/show_bug.cgi?id=101877 725#if !PLATFORM(COCOA) && !PLATFORM(WIN) && !PLATFORM(GTK) 726 mediaController()->setClosedCaptionsVisible(!mediaController()->closedCaptionsVisible()); 727 setChecked(mediaController()->closedCaptionsVisible()); 728 updateDisplayType(); 729#else 730 m_controls->toggleClosedCaptionTrackList(); 731#endif 732 event->setDefaultHandled(); 733 } 734 735 HTMLInputElement::defaultEventHandler(event); 736} 737 738const AtomicString& MediaControlToggleClosedCaptionsButtonElement::shadowPseudoId() const 739{ 740 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-toggle-closed-captions-button", AtomicString::ConstructFromLiteral)); 741 return id; 742} 743 744// ---------------------------- 745 746MediaControlClosedCaptionsContainerElement::MediaControlClosedCaptionsContainerElement(Document& document) 747 : MediaControlDivElement(document, MediaClosedCaptionsContainer) 748{ 749 setPseudo(shadowPseudoId()); 750} 751 752PassRefPtr<MediaControlClosedCaptionsContainerElement> MediaControlClosedCaptionsContainerElement::create(Document& document) 753{ 754 RefPtr<MediaControlClosedCaptionsContainerElement> element = adoptRef(new MediaControlClosedCaptionsContainerElement(document)); 755 element->setAttribute(dirAttr, "auto"); 756 element->hide(); 757 return element.release(); 758} 759 760const AtomicString& MediaControlClosedCaptionsContainerElement::shadowPseudoId() const 761{ 762 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-closed-captions-container", AtomicString::ConstructFromLiteral)); 763 return id; 764} 765 766// ---------------------------- 767 768MediaControlClosedCaptionsTrackListElement::MediaControlClosedCaptionsTrackListElement(Document& document, MediaControls* controls) 769 : MediaControlDivElement(document, MediaClosedCaptionsTrackList) 770#if ENABLE(VIDEO_TRACK) 771 , m_controls(controls) 772#endif 773{ 774#if !ENABLE(VIDEO_TRACK) 775 UNUSED_PARAM(controls); 776#endif 777 setPseudo(shadowPseudoId()); 778} 779 780PassRefPtr<MediaControlClosedCaptionsTrackListElement> MediaControlClosedCaptionsTrackListElement::create(Document& document, MediaControls* controls) 781{ 782 ASSERT(controls); 783 RefPtr<MediaControlClosedCaptionsTrackListElement> element = adoptRef(new MediaControlClosedCaptionsTrackListElement(document, controls)); 784 return element.release(); 785} 786 787void MediaControlClosedCaptionsTrackListElement::defaultEventHandler(Event* event) 788{ 789#if ENABLE(VIDEO_TRACK) 790 if (event->type() == eventNames().clickEvent) { 791 Node* target = event->target()->toNode(); 792 if (!target || !target->isElementNode()) 793 return; 794 795 // When we created the elements in the track list, we gave them a custom 796 // attribute representing the index in the HTMLMediaElement's list of tracks. 797 // Check if the event target has such a custom element and, if so, 798 // tell the HTMLMediaElement to enable that track. 799 800 RefPtr<TextTrack> textTrack; 801 MenuItemToTrackMap::iterator iter = m_menuToTrackMap.find(toElement(target)); 802 if (iter != m_menuToTrackMap.end()) 803 textTrack = iter->value; 804 m_menuToTrackMap.clear(); 805 m_controls->toggleClosedCaptionTrackList(); 806 if (!textTrack) 807 return; 808 809 HTMLMediaElement* mediaElement = parentMediaElement(this); 810 if (!mediaElement) 811 return; 812 813 mediaElement->setSelectedTextTrack(textTrack.get()); 814 815 updateDisplay(); 816 } 817 818 MediaControlDivElement::defaultEventHandler(event); 819#else 820 UNUSED_PARAM(event); 821#endif 822} 823 824const AtomicString& MediaControlClosedCaptionsTrackListElement::shadowPseudoId() const 825{ 826 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-closed-captions-track-list", AtomicString::ConstructFromLiteral)); 827 return id; 828} 829 830void MediaControlClosedCaptionsTrackListElement::updateDisplay() 831{ 832#if ENABLE(VIDEO_TRACK) 833 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, selectedClassValue, ("selected", AtomicString::ConstructFromLiteral)); 834 835 if (!mediaController()->hasClosedCaptions()) 836 return; 837 838 if (!document().page()) 839 return; 840 CaptionUserPreferences::CaptionDisplayMode displayMode = document().page()->group().captionPreferences()->captionDisplayMode(); 841 842 HTMLMediaElement* mediaElement = parentMediaElement(this); 843 if (!mediaElement) 844 return; 845 846 TextTrackList* trackList = mediaElement->textTracks(); 847 if (!trackList || !trackList->length()) 848 return; 849 850 rebuildTrackListMenu(); 851 852 RefPtr<Element> offMenuItem; 853 bool trackMenuItemSelected = false; 854 855 for (unsigned i = 0, length = m_menuItems.size(); i < length; ++i) { 856 RefPtr<Element> trackItem = m_menuItems[i]; 857 858 RefPtr<TextTrack> textTrack; 859 MenuItemToTrackMap::iterator iter = m_menuToTrackMap.find(trackItem.get()); 860 if (iter == m_menuToTrackMap.end()) 861 continue; 862 textTrack = iter->value; 863 if (!textTrack) 864 continue; 865 866 if (textTrack == TextTrack::captionMenuOffItem()) { 867 offMenuItem = trackItem; 868 continue; 869 } 870 871 if (textTrack == TextTrack::captionMenuAutomaticItem()) { 872 if (displayMode == CaptionUserPreferences::Automatic) 873 trackItem->classList()->add(selectedClassValue, ASSERT_NO_EXCEPTION); 874 else 875 trackItem->classList()->remove(selectedClassValue, ASSERT_NO_EXCEPTION); 876 continue; 877 } 878 879 if (displayMode != CaptionUserPreferences::Automatic && textTrack->mode() == TextTrack::showingKeyword()) { 880 trackMenuItemSelected = true; 881 trackItem->classList()->add(selectedClassValue, ASSERT_NO_EXCEPTION); 882 } else 883 trackItem->classList()->remove(selectedClassValue, ASSERT_NO_EXCEPTION); 884 } 885 886 if (offMenuItem) { 887 if (displayMode == CaptionUserPreferences::ForcedOnly && !trackMenuItemSelected) 888 offMenuItem->classList()->add(selectedClassValue, ASSERT_NO_EXCEPTION); 889 else 890 offMenuItem->classList()->remove(selectedClassValue, ASSERT_NO_EXCEPTION); 891 } 892#endif 893} 894 895void MediaControlClosedCaptionsTrackListElement::rebuildTrackListMenu() 896{ 897#if ENABLE(VIDEO_TRACK) 898 // Remove any existing content. 899 removeChildren(); 900 m_menuItems.clear(); 901 m_menuToTrackMap.clear(); 902 903 if (!mediaController()->hasClosedCaptions()) 904 return; 905 906 HTMLMediaElement* mediaElement = parentMediaElement(this); 907 if (!mediaElement) 908 return; 909 910 TextTrackList* trackList = mediaElement->textTracks(); 911 if (!trackList || !trackList->length()) 912 return; 913 914 if (!document().page()) 915 return; 916 CaptionUserPreferences* captionPreferences = document().page()->group().captionPreferences(); 917 Vector<RefPtr<TextTrack>> tracksForMenu = captionPreferences->sortedTrackListForMenu(trackList); 918 919 RefPtr<Element> captionsHeader = document().createElement(h3Tag, ASSERT_NO_EXCEPTION); 920 captionsHeader->appendChild(document().createTextNode(textTrackSubtitlesText())); 921 appendChild(captionsHeader); 922 RefPtr<Element> captionsMenuList = document().createElement(ulTag, ASSERT_NO_EXCEPTION); 923 924 for (unsigned i = 0, length = tracksForMenu.size(); i < length; ++i) { 925 RefPtr<TextTrack> textTrack = tracksForMenu[i]; 926 RefPtr<Element> menuItem = document().createElement(liTag, ASSERT_NO_EXCEPTION); 927 menuItem->appendChild(document().createTextNode(captionPreferences->displayNameForTrack(textTrack.get()))); 928 captionsMenuList->appendChild(menuItem); 929 m_menuItems.append(menuItem); 930 m_menuToTrackMap.add(menuItem, textTrack); 931 } 932 933 appendChild(captionsMenuList); 934#endif 935} 936 937// ---------------------------- 938 939MediaControlTimelineElement::MediaControlTimelineElement(Document& document, MediaControls* controls) 940 : MediaControlInputElement(document, MediaSlider) 941 , m_controls(controls) 942{ 943 setPseudo(shadowPseudoId()); 944} 945 946PassRefPtr<MediaControlTimelineElement> MediaControlTimelineElement::create(Document& document, MediaControls* controls) 947{ 948 ASSERT(controls); 949 950 RefPtr<MediaControlTimelineElement> timeline = adoptRef(new MediaControlTimelineElement(document, controls)); 951 timeline->ensureUserAgentShadowRoot(); 952 timeline->setType("range"); 953 timeline->setAttribute(precisionAttr, "float"); 954 return timeline.release(); 955} 956 957void MediaControlTimelineElement::defaultEventHandler(Event* event) 958{ 959 // Left button is 0. Rejects mouse events not from left button. 960 if (event->isMouseEvent() && toMouseEvent(event)->button()) 961 return; 962 963 if (!renderer()) 964 return; 965 966 if (event->type() == eventNames().mousedownEvent) 967 mediaController()->beginScrubbing(); 968 969 if (event->type() == eventNames().mouseupEvent) 970 mediaController()->endScrubbing(); 971 972 MediaControlInputElement::defaultEventHandler(event); 973 974 if (event->type() == eventNames().mouseoverEvent || event->type() == eventNames().mouseoutEvent || event->type() == eventNames().mousemoveEvent) 975 return; 976 977 double time = value().toDouble(); 978 if (event->type() == eventNames().inputEvent && time != mediaController()->currentTime()) 979 mediaController()->setCurrentTime(time); 980 981 RenderSlider* slider = toRenderSlider(renderer()); 982 if (slider && slider->inDragMode()) 983 m_controls->updateCurrentTimeDisplay(); 984} 985 986#if !PLATFORM(IOS) 987bool MediaControlTimelineElement::willRespondToMouseClickEvents() 988{ 989 if (!renderer()) 990 return false; 991 992 return true; 993} 994#endif // !PLATFORM(IOS) 995 996void MediaControlTimelineElement::setPosition(double currentTime) 997{ 998 setValue(String::number(currentTime)); 999} 1000 1001void MediaControlTimelineElement::setDuration(double duration) 1002{ 1003 setAttribute(maxAttr, AtomicString::number(std::isfinite(duration) ? duration : 0)); 1004} 1005 1006 1007const AtomicString& MediaControlTimelineElement::shadowPseudoId() const 1008{ 1009 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-timeline", AtomicString::ConstructFromLiteral)); 1010 return id; 1011} 1012 1013// ---------------------------- 1014 1015MediaControlPanelVolumeSliderElement::MediaControlPanelVolumeSliderElement(Document& document) 1016 : MediaControlVolumeSliderElement(document) 1017{ 1018 setPseudo(shadowPseudoId()); 1019} 1020 1021PassRefPtr<MediaControlPanelVolumeSliderElement> MediaControlPanelVolumeSliderElement::create(Document& document) 1022{ 1023 RefPtr<MediaControlPanelVolumeSliderElement> slider = adoptRef(new MediaControlPanelVolumeSliderElement(document)); 1024 slider->ensureUserAgentShadowRoot(); 1025 slider->setType("range"); 1026 slider->setAttribute(precisionAttr, "float"); 1027 slider->setAttribute(maxAttr, "1"); 1028 return slider.release(); 1029} 1030 1031const AtomicString& MediaControlPanelVolumeSliderElement::shadowPseudoId() const 1032{ 1033 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-volume-slider", AtomicString::ConstructFromLiteral)); 1034 return id; 1035} 1036 1037// ---------------------------- 1038 1039MediaControlFullscreenVolumeSliderElement::MediaControlFullscreenVolumeSliderElement(Document& document) 1040 : MediaControlVolumeSliderElement(document) 1041{ 1042 setPseudo(shadowPseudoId()); 1043} 1044 1045PassRefPtr<MediaControlFullscreenVolumeSliderElement> MediaControlFullscreenVolumeSliderElement::create(Document& document) 1046{ 1047 RefPtr<MediaControlFullscreenVolumeSliderElement> slider = adoptRef(new MediaControlFullscreenVolumeSliderElement(document)); 1048 slider->ensureUserAgentShadowRoot(); 1049 slider->setType("range"); 1050 slider->setAttribute(precisionAttr, "float"); 1051 slider->setAttribute(maxAttr, "1"); 1052 return slider.release(); 1053} 1054 1055const AtomicString& MediaControlFullscreenVolumeSliderElement::shadowPseudoId() const 1056{ 1057 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-volume-slider", AtomicString::ConstructFromLiteral)); 1058 return id; 1059} 1060 1061// ---------------------------- 1062 1063MediaControlFullscreenButtonElement::MediaControlFullscreenButtonElement(Document& document) 1064 : MediaControlInputElement(document, MediaEnterFullscreenButton) 1065{ 1066 setPseudo(shadowPseudoId()); 1067} 1068 1069PassRefPtr<MediaControlFullscreenButtonElement> MediaControlFullscreenButtonElement::create(Document& document) 1070{ 1071 RefPtr<MediaControlFullscreenButtonElement> button = adoptRef(new MediaControlFullscreenButtonElement(document)); 1072 button->ensureUserAgentShadowRoot(); 1073 button->setType("button"); 1074 button->hide(); 1075 return button.release(); 1076} 1077 1078void MediaControlFullscreenButtonElement::defaultEventHandler(Event* event) 1079{ 1080 if (event->type() == eventNames().clickEvent) { 1081#if ENABLE(FULLSCREEN_API) 1082 // Only use the new full screen API if the fullScreenEnabled setting has 1083 // been explicitly enabled. Otherwise, use the old fullscreen API. This 1084 // allows apps which embed a WebView to retain the existing full screen 1085 // video implementation without requiring them to implement their own full 1086 // screen behavior. 1087 if (document().settings() && document().settings()->fullScreenEnabled()) { 1088 if (document().webkitIsFullScreen() && document().webkitCurrentFullScreenElement() == parentMediaElement(this)) 1089 document().webkitCancelFullScreen(); 1090 else 1091 document().requestFullScreenForElement(parentMediaElement(this), 0, Document::ExemptIFrameAllowFullScreenRequirement); 1092 } else 1093#endif 1094 mediaController()->enterFullscreen(); 1095 event->setDefaultHandled(); 1096 } 1097 HTMLInputElement::defaultEventHandler(event); 1098} 1099 1100const AtomicString& MediaControlFullscreenButtonElement::shadowPseudoId() const 1101{ 1102 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-button", AtomicString::ConstructFromLiteral)); 1103 return id; 1104} 1105 1106void MediaControlFullscreenButtonElement::setIsFullscreen(bool isFullscreen) 1107{ 1108 setDisplayType(isFullscreen ? MediaExitFullscreenButton : MediaEnterFullscreenButton); 1109} 1110 1111// ---------------------------- 1112 1113MediaControlFullscreenVolumeMinButtonElement::MediaControlFullscreenVolumeMinButtonElement(Document& document) 1114 : MediaControlInputElement(document, MediaUnMuteButton) 1115{ 1116 setPseudo(shadowPseudoId()); 1117} 1118 1119PassRefPtr<MediaControlFullscreenVolumeMinButtonElement> MediaControlFullscreenVolumeMinButtonElement::create(Document& document) 1120{ 1121 RefPtr<MediaControlFullscreenVolumeMinButtonElement> button = adoptRef(new MediaControlFullscreenVolumeMinButtonElement(document)); 1122 button->ensureUserAgentShadowRoot(); 1123 button->setType("button"); 1124 return button.release(); 1125} 1126 1127void MediaControlFullscreenVolumeMinButtonElement::defaultEventHandler(Event* event) 1128{ 1129 if (event->type() == eventNames().clickEvent) { 1130 ExceptionCode code = 0; 1131 mediaController()->setVolume(0, code); 1132 event->setDefaultHandled(); 1133 } 1134 HTMLInputElement::defaultEventHandler(event); 1135} 1136 1137const AtomicString& MediaControlFullscreenVolumeMinButtonElement::shadowPseudoId() const 1138{ 1139 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-volume-min-button", AtomicString::ConstructFromLiteral)); 1140 return id; 1141} 1142 1143// ---------------------------- 1144 1145MediaControlFullscreenVolumeMaxButtonElement::MediaControlFullscreenVolumeMaxButtonElement(Document& document) 1146: MediaControlInputElement(document, MediaMuteButton) 1147{ 1148 setPseudo(shadowPseudoId()); 1149} 1150 1151PassRefPtr<MediaControlFullscreenVolumeMaxButtonElement> MediaControlFullscreenVolumeMaxButtonElement::create(Document& document) 1152{ 1153 RefPtr<MediaControlFullscreenVolumeMaxButtonElement> button = adoptRef(new MediaControlFullscreenVolumeMaxButtonElement(document)); 1154 button->ensureUserAgentShadowRoot(); 1155 button->setType("button"); 1156 return button.release(); 1157} 1158 1159void MediaControlFullscreenVolumeMaxButtonElement::defaultEventHandler(Event* event) 1160{ 1161 if (event->type() == eventNames().clickEvent) { 1162 ExceptionCode code = 0; 1163 mediaController()->setVolume(1, code); 1164 event->setDefaultHandled(); 1165 } 1166 HTMLInputElement::defaultEventHandler(event); 1167} 1168 1169const AtomicString& MediaControlFullscreenVolumeMaxButtonElement::shadowPseudoId() const 1170{ 1171 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-fullscreen-volume-max-button", AtomicString::ConstructFromLiteral)); 1172 return id; 1173} 1174 1175// ---------------------------- 1176 1177MediaControlTimeRemainingDisplayElement::MediaControlTimeRemainingDisplayElement(Document& document) 1178 : MediaControlTimeDisplayElement(document, MediaTimeRemainingDisplay) 1179{ 1180 setPseudo(shadowPseudoId()); 1181} 1182 1183PassRefPtr<MediaControlTimeRemainingDisplayElement> MediaControlTimeRemainingDisplayElement::create(Document& document) 1184{ 1185 return adoptRef(new MediaControlTimeRemainingDisplayElement(document)); 1186} 1187 1188static const AtomicString& getMediaControlTimeRemainingDisplayElementShadowPseudoId() 1189{ 1190 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-time-remaining-display", AtomicString::ConstructFromLiteral)); 1191 return id; 1192} 1193 1194const AtomicString& MediaControlTimeRemainingDisplayElement::shadowPseudoId() const 1195{ 1196 return getMediaControlTimeRemainingDisplayElementShadowPseudoId(); 1197} 1198 1199// ---------------------------- 1200 1201MediaControlCurrentTimeDisplayElement::MediaControlCurrentTimeDisplayElement(Document& document) 1202 : MediaControlTimeDisplayElement(document, MediaCurrentTimeDisplay) 1203{ 1204 setPseudo(shadowPseudoId()); 1205} 1206 1207PassRefPtr<MediaControlCurrentTimeDisplayElement> MediaControlCurrentTimeDisplayElement::create(Document& document) 1208{ 1209 return adoptRef(new MediaControlCurrentTimeDisplayElement(document)); 1210} 1211 1212static const AtomicString& getMediaControlCurrentTimeDisplayElementShadowPseudoId() 1213{ 1214 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls-current-time-display", AtomicString::ConstructFromLiteral)); 1215 return id; 1216} 1217 1218const AtomicString& MediaControlCurrentTimeDisplayElement::shadowPseudoId() const 1219{ 1220 return getMediaControlCurrentTimeDisplayElementShadowPseudoId(); 1221} 1222 1223// ---------------------------- 1224 1225#if ENABLE(VIDEO_TRACK) 1226 1227MediaControlTextTrackContainerElement::MediaControlTextTrackContainerElement(Document& document) 1228 : MediaControlDivElement(document, MediaTextTrackDisplayContainer) 1229 , m_updateTimer(this, &MediaControlTextTrackContainerElement::updateTimerFired) 1230 , m_fontSize(0) 1231 , m_fontSizeIsImportant(false) 1232 , m_updateTextTrackRepresentationStyle(false) 1233{ 1234 setPseudo(shadowPseudoId()); 1235} 1236 1237PassRefPtr<MediaControlTextTrackContainerElement> MediaControlTextTrackContainerElement::create(Document& document) 1238{ 1239 RefPtr<MediaControlTextTrackContainerElement> element = adoptRef(new MediaControlTextTrackContainerElement(document)); 1240 element->hide(); 1241 return element.release(); 1242} 1243 1244RenderPtr<RenderElement> MediaControlTextTrackContainerElement::createElementRenderer(PassRef<RenderStyle> style) 1245{ 1246 return createRenderer<RenderTextTrackContainerElement>(*this, WTF::move(style)); 1247} 1248 1249const AtomicString& MediaControlTextTrackContainerElement::textTrackContainerElementShadowPseudoId() 1250{ 1251 DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-text-track-container", AtomicString::ConstructFromLiteral)); 1252 return id; 1253} 1254 1255const AtomicString& MediaControlTextTrackContainerElement::shadowPseudoId() const 1256{ 1257 return textTrackContainerElementShadowPseudoId(); 1258} 1259 1260static bool compareCueIntervalForDisplay(const CueInterval& one, const CueInterval& two) 1261{ 1262 return one.data()->isPositionedAbove(two.data()); 1263}; 1264 1265void MediaControlTextTrackContainerElement::updateDisplay() 1266{ 1267 if (!mediaController()->closedCaptionsVisible()) 1268 removeChildren(); 1269 1270 HTMLMediaElement* mediaElement = parentMediaElement(this); 1271 // 1. If the media element is an audio element, or is another playback 1272 // mechanism with no rendering area, abort these steps. There is nothing to 1273 // render. 1274 if (!mediaElement || !mediaElement->isVideo()) 1275 return; 1276 1277 // 2. Let video be the media element or other playback mechanism. 1278 HTMLVideoElement* video = toHTMLVideoElement(mediaElement); 1279 1280 // 3. Let output be an empty list of absolutely positioned CSS block boxes. 1281 Vector<RefPtr<HTMLDivElement>> output; 1282 1283 // 4. If the user agent is exposing a user interface for video, add to 1284 // output one or more completely transparent positioned CSS block boxes that 1285 // cover the same region as the user interface. 1286 1287 // 5. If the last time these rules were run, the user agent was not exposing 1288 // a user interface for video, but now it is, let reset be true. Otherwise, 1289 // let reset be false. 1290 1291 // There is nothing to be done explicitly for 4th and 5th steps, as 1292 // everything is handled through CSS. The caption box is on top of the 1293 // controls box, in a container set with the -webkit-box display property. 1294 1295 // 6. Let tracks be the subset of video's list of text tracks that have as 1296 // their rules for updating the text track rendering these rules for 1297 // updating the display of WebVTT text tracks, and whose text track mode is 1298 // showing or showing by default. 1299 // 7. Let cues be an empty list of text track cues. 1300 // 8. For each track track in tracks, append to cues all the cues from 1301 // track's list of cues that have their text track cue active flag set. 1302 CueList activeCues = video->currentlyActiveCues(); 1303 1304 // 9. If reset is false, then, for each text track cue cue in cues: if cue's 1305 // text track cue display state has a set of CSS boxes, then add those boxes 1306 // to output, and remove cue from cues. 1307 1308 // There is nothing explicitly to be done here, as all the caching occurs 1309 // within the TextTrackCue instance itself. If parameters of the cue change, 1310 // the display tree is cleared. 1311 1312 // If the number of CSS boxes in the output is less than the number of cues 1313 // we wish to render (e.g., we are adding another cue in a set of roll-up 1314 // cues), remove all the existing CSS boxes representing the cues and re-add 1315 // them so that the new cue is at the bottom. 1316 if (childNodeCount() < activeCues.size()) 1317 removeChildren(); 1318 1319 // Sort the active cues for the appropriate display order. For example, for roll-up 1320 // or paint-on captions, we need to add the cues in reverse chronological order, 1321 // so that the newest captions appear at the bottom. 1322 std::sort(activeCues.begin(), activeCues.end(), &compareCueIntervalForDisplay); 1323 1324 // 10. For each text track cue cue in cues that has not yet had 1325 // corresponding CSS boxes added to output, in text track cue order, run the 1326 // following substeps: 1327 for (size_t i = 0; i < activeCues.size(); ++i) { 1328 if (!mediaController()->closedCaptionsVisible()) 1329 continue; 1330 1331 TextTrackCue* textTrackCue = activeCues[i].data(); 1332 if (!textTrackCue->isRenderable()) 1333 continue; 1334 1335 VTTCue* cue = toVTTCue(textTrackCue); 1336 1337 ASSERT(cue->isActive()); 1338 if (!cue->track() || !cue->track()->isRendered() || !cue->isActive() || cue->text().isEmpty()) 1339 continue; 1340 1341 LOG(Media, "MediaControlTextTrackContainerElement::updateDisplay(%p) - adding and positioning cue #%zu: \"%s\", start=%.2f, end=%.2f, line=%.2f", this, i, cue->text().utf8().data(), cue->startTime(), cue->endTime(), cue->line()); 1342 1343 RefPtr<VTTCueBox> displayBox = cue->getDisplayTree(m_videoDisplaySize.size(), m_fontSize); 1344#if ENABLE(WEBVTT_REGIONS) 1345 if (cue->track()->mode() == TextTrack::disabledKeyword()) 1346 continue; 1347 1348 VTTRegion* region = cue->track()->regions()->getRegionById(cue->regionId()); 1349 if (!region) { 1350 // If cue has an empty text track cue region identifier or there is no 1351 // WebVTT region whose region identifier is identical to cue's text 1352 // track cue region identifier, run the following substeps: 1353#endif 1354 if (displayBox->hasChildNodes() && !contains(displayBox.get())) { 1355 // Note: the display tree of a cue is removed when the active flag of the cue is unset. 1356 appendChild(displayBox, ASSERT_NO_EXCEPTION); 1357 cue->setFontSize(m_fontSize, m_videoDisplaySize.size(), m_fontSizeIsImportant); 1358 } 1359#if ENABLE(WEBVTT_REGIONS) 1360 } else { 1361 // Let region be the WebVTT region whose region identifier 1362 // matches the text track cue region identifier of cue. 1363 RefPtr<HTMLDivElement> regionNode = region->getDisplayTree(); 1364 1365 // Append the region to the viewport, if it was not already. 1366 if (!contains(regionNode.get())) 1367 appendChild(region->getDisplayTree()); 1368 1369 region->appendTextTrackCueBox(displayBox); 1370 } 1371#endif 1372 } 1373 1374 // 11. Return output. 1375 if (hasChildNodes()) { 1376 show(); 1377 updateTextTrackRepresentation(); 1378 } else { 1379 hide(); 1380 clearTextTrackRepresentation(); 1381 } 1382} 1383 1384void MediaControlTextTrackContainerElement::updateActiveCuesFontSize() 1385{ 1386 if (!document().page()) 1387 return; 1388 1389 HTMLMediaElement* mediaElement = parentMediaElement(this); 1390 if (!mediaElement) 1391 return; 1392 1393 float smallestDimension = std::min(m_videoDisplaySize.size().height(), m_videoDisplaySize.size().width()); 1394 float fontScale = document().page()->group().captionPreferences()->captionFontSizeScaleAndImportance(m_fontSizeIsImportant); 1395 m_fontSize = lroundf(smallestDimension * fontScale); 1396 1397 CueList activeCues = mediaElement->currentlyActiveCues(); 1398 for (size_t i = 0; i < activeCues.size(); ++i) { 1399 TextTrackCue* cue = activeCues[i].data(); 1400 if (!cue->isRenderable()) 1401 continue; 1402 1403 toVTTCue(cue)->setFontSize(m_fontSize, m_videoDisplaySize.size(), m_fontSizeIsImportant); 1404 } 1405 1406} 1407 1408void MediaControlTextTrackContainerElement::updateTimerFired(Timer<MediaControlTextTrackContainerElement>&) 1409{ 1410 if (!document().page()) 1411 return; 1412 1413 if (m_textTrackRepresentation) 1414 updateStyleForTextTrackRepresentation(); 1415 1416 updateActiveCuesFontSize(); 1417 updateDisplay(); 1418} 1419 1420void MediaControlTextTrackContainerElement::updateTextTrackRepresentation() 1421{ 1422 HTMLMediaElement* mediaElement = parentMediaElement(this); 1423 if (!mediaElement) 1424 return; 1425 1426 if (!mediaElement->requiresTextTrackRepresentation()) 1427 return; 1428 1429 if (!m_textTrackRepresentation) { 1430 m_textTrackRepresentation = TextTrackRepresentation::create(this); 1431 m_updateTextTrackRepresentationStyle = true; 1432 mediaElement->setTextTrackRepresentation(m_textTrackRepresentation.get()); 1433 } 1434 1435 m_textTrackRepresentation->update(); 1436 updateStyleForTextTrackRepresentation(); 1437} 1438 1439void MediaControlTextTrackContainerElement::clearTextTrackRepresentation() 1440{ 1441 if (!m_textTrackRepresentation) 1442 return; 1443 1444 m_textTrackRepresentation = nullptr; 1445 m_updateTextTrackRepresentationStyle = true; 1446 if (HTMLMediaElement* mediaElement = parentMediaElement(this)) 1447 mediaElement->setTextTrackRepresentation(nullptr); 1448 updateStyleForTextTrackRepresentation(); 1449 updateActiveCuesFontSize(); 1450} 1451 1452void MediaControlTextTrackContainerElement::updateStyleForTextTrackRepresentation() 1453{ 1454 if (!m_updateTextTrackRepresentationStyle) 1455 return; 1456 m_updateTextTrackRepresentationStyle = false; 1457 1458 if (m_textTrackRepresentation) { 1459 setInlineStyleProperty(CSSPropertyWidth, m_videoDisplaySize.size().width(), CSSPrimitiveValue::CSS_PX); 1460 setInlineStyleProperty(CSSPropertyHeight, m_videoDisplaySize.size().height(), CSSPrimitiveValue::CSS_PX); 1461 setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute); 1462 setInlineStyleProperty(CSSPropertyLeft, 0, CSSPrimitiveValue::CSS_PX); 1463 setInlineStyleProperty(CSSPropertyTop, 0, CSSPrimitiveValue::CSS_PX); 1464 return; 1465 } 1466 1467 removeInlineStyleProperty(CSSPropertyPosition); 1468 removeInlineStyleProperty(CSSPropertyWidth); 1469 removeInlineStyleProperty(CSSPropertyHeight); 1470 removeInlineStyleProperty(CSSPropertyLeft); 1471 removeInlineStyleProperty(CSSPropertyTop); 1472} 1473 1474void MediaControlTextTrackContainerElement::enteredFullscreen() 1475{ 1476 if (hasChildNodes()) 1477 updateTextTrackRepresentation(); 1478 updateSizes(true); 1479} 1480 1481void MediaControlTextTrackContainerElement::exitedFullscreen() 1482{ 1483 clearTextTrackRepresentation(); 1484 updateSizes(true); 1485} 1486 1487void MediaControlTextTrackContainerElement::updateSizes(bool forceUpdate) 1488{ 1489 HTMLMediaElement* mediaElement = parentMediaElement(this); 1490 if (!mediaElement) 1491 return; 1492 1493 if (!document().page()) 1494 return; 1495 1496 mediaElement->syncTextTrackBounds(); 1497 1498 IntRect videoBox; 1499 if (m_textTrackRepresentation) 1500 videoBox = m_textTrackRepresentation->bounds(); 1501 else { 1502 if (!mediaElement->renderer() || !mediaElement->renderer()->isVideo()) 1503 return; 1504 videoBox = toRenderVideo(*mediaElement->renderer()).videoBox(); 1505 } 1506 1507 if (!forceUpdate && m_videoDisplaySize == videoBox) 1508 return; 1509 1510 m_videoDisplaySize = videoBox; 1511 m_updateTextTrackRepresentationStyle = true; 1512 1513 // FIXME (121170): This function is called during layout, and should lay out the text tracks immediately. 1514 m_updateTimer.startOneShot(0); 1515} 1516 1517PassRefPtr<Image> MediaControlTextTrackContainerElement::createTextTrackRepresentationImage() 1518{ 1519 if (!hasChildNodes()) 1520 return nullptr; 1521 1522 Frame* frame = document().frame(); 1523 if (!frame) 1524 return nullptr; 1525 1526 document().updateLayout(); 1527 1528 auto renderer = this->renderer(); 1529 if (!renderer) 1530 return nullptr; 1531 1532 if (!renderer->hasLayer()) 1533 return nullptr; 1534 1535 RenderLayer* layer = toRenderLayerModelObject(renderer)->layer(); 1536 1537 float deviceScaleFactor = 1; 1538 if (Page* page = document().page()) 1539 deviceScaleFactor = page->deviceScaleFactor(); 1540 1541 IntRect paintingRect = IntRect(IntPoint(), layer->size()); 1542 1543 std::unique_ptr<ImageBuffer> buffer(ImageBuffer::create(paintingRect.size(), deviceScaleFactor, ColorSpaceDeviceRGB)); 1544 if (!buffer) 1545 return nullptr; 1546 1547 layer->paint(buffer->context(), paintingRect, PaintBehaviorFlattenCompositingLayers, nullptr, RenderLayer::PaintLayerPaintingCompositingAllPhases); 1548 1549 return buffer->copyImage(); 1550} 1551 1552void MediaControlTextTrackContainerElement::textTrackRepresentationBoundsChanged(const IntRect&) 1553{ 1554 if (hasChildNodes()) 1555 updateTextTrackRepresentation(); 1556 updateSizes(); 1557} 1558 1559#endif // ENABLE(VIDEO_TRACK) 1560 1561// ---------------------------- 1562 1563} // namespace WebCore 1564 1565#endif // ENABLE(VIDEO) 1566