Home | History | Annotate | Download | only in track
      1 /*
      2  * Copyright (C) 2013 Google Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 
     33 #if ENABLE(WEBVTT_REGIONS)
     34 
     35 #include "core/html/track/TextTrackRegion.h"
     36 
     37 #include "bindings/v8/ExceptionState.h"
     38 #include "bindings/v8/ExceptionStatePlaceholder.h"
     39 #include "core/dom/ClientRect.h"
     40 #include "core/html/DOMTokenList.h"
     41 #include "core/html/HTMLDivElement.h"
     42 #include "core/html/track/WebVTTParser.h"
     43 #include "core/platform/Logging.h"
     44 #include "core/rendering/RenderInline.h"
     45 #include "core/rendering/RenderObject.h"
     46 #include "wtf/MathExtras.h"
     47 #include "wtf/text/StringBuilder.h"
     48 
     49 namespace WebCore {
     50 
     51 // The following values default values are defined within the WebVTT Regions Spec.
     52 // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/region.html
     53 
     54 // The region occupies by default 100% of the width of the video viewport.
     55 static const float defaultWidth = 100;
     56 
     57 // The region has, by default, 3 lines of text.
     58 static const long defaultHeightInLines = 3;
     59 
     60 // The region and viewport are anchored in the bottom left corner.
     61 static const float defaultAnchorPointX = 0;
     62 static const float defaultAnchorPointY = 100;
     63 
     64 // The region doesn't have scrolling text, by default.
     65 static const bool defaultScroll = false;
     66 
     67 // Default region line-height (vh units)
     68 static const float lineHeight = 5.33;
     69 
     70 // Default scrolling animation time period (s).
     71 static const float scrollTime = 0.433;
     72 
     73 TextTrackRegion::TextTrackRegion(ScriptExecutionContext* context)
     74     : ContextLifecycleObserver(context)
     75     , m_id(emptyString())
     76     , m_width(defaultWidth)
     77     , m_heightInLines(defaultHeightInLines)
     78     , m_regionAnchor(FloatPoint(defaultAnchorPointX, defaultAnchorPointY))
     79     , m_viewportAnchor(FloatPoint(defaultAnchorPointX, defaultAnchorPointY))
     80     , m_scroll(defaultScroll)
     81     , m_regionDisplayTree(0)
     82     , m_cueContainer(0)
     83     , m_track(0)
     84     , m_currentTop(0)
     85     , m_scrollTimer(this, &TextTrackRegion::scrollTimerFired)
     86 {
     87 }
     88 
     89 TextTrackRegion::~TextTrackRegion()
     90 {
     91 }
     92 
     93 void TextTrackRegion::setTrack(TextTrack* track)
     94 {
     95     m_track = track;
     96 }
     97 
     98 void TextTrackRegion::setId(const String& id)
     99 {
    100     m_id = id;
    101 }
    102 
    103 void TextTrackRegion::setWidth(double value, ExceptionState& es)
    104 {
    105     if (std::isinf(value) || std::isnan(value)) {
    106         es.throwTypeError();
    107         return;
    108     }
    109 
    110     if (value < 0 || value > 100) {
    111         es.throwDOMException(IndexSizeError);
    112         return;
    113     }
    114 
    115     m_width = value;
    116 }
    117 
    118 void TextTrackRegion::setHeight(long value, ExceptionState& es)
    119 {
    120     if (value < 0) {
    121         es.throwDOMException(IndexSizeError);
    122         return;
    123     }
    124 
    125     m_heightInLines = value;
    126 }
    127 
    128 void TextTrackRegion::setRegionAnchorX(double value, ExceptionState& es)
    129 {
    130     if (std::isinf(value) || std::isnan(value)) {
    131         es.throwTypeError();
    132         return;
    133     }
    134 
    135     if (value < 0 || value > 100) {
    136         es.throwDOMException(IndexSizeError);
    137         return;
    138     }
    139 
    140     m_regionAnchor.setX(value);
    141 }
    142 
    143 void TextTrackRegion::setRegionAnchorY(double value, ExceptionState& es)
    144 {
    145     if (std::isinf(value) || std::isnan(value)) {
    146         es.throwTypeError();
    147         return;
    148     }
    149 
    150     if (value < 0 || value > 100) {
    151         es.throwDOMException(IndexSizeError);
    152         return;
    153     }
    154 
    155     m_regionAnchor.setY(value);
    156 }
    157 
    158 void TextTrackRegion::setViewportAnchorX(double value, ExceptionState& es)
    159 {
    160     if (std::isinf(value) || std::isnan(value)) {
    161         es.throwTypeError();
    162         return;
    163     }
    164 
    165     if (value < 0 || value > 100) {
    166         es.throwDOMException(IndexSizeError);
    167         return;
    168     }
    169 
    170     m_viewportAnchor.setX(value);
    171 }
    172 
    173 void TextTrackRegion::setViewportAnchorY(double value, ExceptionState& es)
    174 {
    175     if (std::isinf(value) || std::isnan(value)) {
    176         es.throwTypeError();
    177         return;
    178     }
    179 
    180     if (value < 0 || value > 100) {
    181         es.throwDOMException(IndexSizeError);
    182         return;
    183     }
    184 
    185     m_viewportAnchor.setY(value);
    186 }
    187 
    188 const AtomicString TextTrackRegion::scroll() const
    189 {
    190     DEFINE_STATIC_LOCAL(const AtomicString, upScrollValueKeyword, ("up", AtomicString::ConstructFromLiteral));
    191 
    192     if (m_scroll)
    193         return upScrollValueKeyword;
    194 
    195     return "";
    196 }
    197 
    198 void TextTrackRegion::setScroll(const AtomicString& value, ExceptionState& es)
    199 {
    200     DEFINE_STATIC_LOCAL(const AtomicString, upScrollValueKeyword, ("up", AtomicString::ConstructFromLiteral));
    201 
    202     if (value != emptyString() && value != upScrollValueKeyword) {
    203         es.throwDOMException(SyntaxError);
    204         return;
    205     }
    206 
    207     m_scroll = value == upScrollValueKeyword;
    208 }
    209 
    210 void TextTrackRegion::updateParametersFromRegion(TextTrackRegion* region)
    211 {
    212     m_heightInLines = region->height();
    213     m_width = region->width();
    214 
    215     m_regionAnchor = FloatPoint(region->regionAnchorX(), region->regionAnchorY());
    216     m_viewportAnchor = FloatPoint(region->viewportAnchorX(), region->viewportAnchorY());
    217 
    218     setScroll(region->scroll(), ASSERT_NO_EXCEPTION);
    219 }
    220 
    221 void TextTrackRegion::setRegionSettings(const String& input)
    222 {
    223     m_settings = input;
    224     unsigned position = 0;
    225 
    226     while (position < input.length()) {
    227         while (position < input.length() && WebVTTParser::isValidSettingDelimiter(input[position]))
    228             position++;
    229 
    230         if (position >= input.length())
    231             break;
    232 
    233         parseSetting(input, &position);
    234     }
    235 }
    236 
    237 TextTrackRegion::RegionSetting TextTrackRegion::getSettingFromString(const String& setting)
    238 {
    239     DEFINE_STATIC_LOCAL(const AtomicString, idKeyword, ("id", AtomicString::ConstructFromLiteral));
    240     DEFINE_STATIC_LOCAL(const AtomicString, heightKeyword, ("height", AtomicString::ConstructFromLiteral));
    241     DEFINE_STATIC_LOCAL(const AtomicString, widthKeyword, ("width", AtomicString::ConstructFromLiteral));
    242     DEFINE_STATIC_LOCAL(const AtomicString, regionAnchorKeyword, ("regionanchor", AtomicString::ConstructFromLiteral));
    243     DEFINE_STATIC_LOCAL(const AtomicString, viewportAnchorKeyword, ("viewportanchor", AtomicString::ConstructFromLiteral));
    244     DEFINE_STATIC_LOCAL(const AtomicString, scrollKeyword, ("scroll", AtomicString::ConstructFromLiteral));
    245 
    246     if (setting == idKeyword)
    247         return Id;
    248     if (setting == heightKeyword)
    249         return Height;
    250     if (setting == widthKeyword)
    251         return Width;
    252     if (setting == viewportAnchorKeyword)
    253         return ViewportAnchor;
    254     if (setting == regionAnchorKeyword)
    255         return RegionAnchor;
    256     if (setting == scrollKeyword)
    257         return Scroll;
    258 
    259     return None;
    260 }
    261 
    262 void TextTrackRegion::parseSettingValue(RegionSetting setting, const String& value)
    263 {
    264     DEFINE_STATIC_LOCAL(const AtomicString, scrollUpValueKeyword, ("up", AtomicString::ConstructFromLiteral));
    265 
    266     bool isValidSetting;
    267     String numberAsString;
    268     int number;
    269     unsigned position;
    270     FloatPoint anchorPosition;
    271 
    272     switch (setting) {
    273     case Id:
    274         if (value.find("-->") == notFound)
    275             m_id = value;
    276         break;
    277     case Width:
    278         number = WebVTTParser::parseFloatPercentageValue(value, isValidSetting);
    279         if (isValidSetting)
    280             m_width = number;
    281         else
    282             LOG(Media, "TextTrackRegion::parseSettingValue, invalid Width");
    283         break;
    284     case Height:
    285         position = 0;
    286 
    287         numberAsString = WebVTTParser::collectDigits(value, &position);
    288         number = value.toInt(&isValidSetting);
    289 
    290         if (isValidSetting && number >= 0)
    291             m_heightInLines = number;
    292         else
    293             LOG(Media, "TextTrackRegion::parseSettingValue, invalid Height");
    294         break;
    295     case RegionAnchor:
    296         anchorPosition = WebVTTParser::parseFloatPercentageValuePair(value, ',', isValidSetting);
    297         if (isValidSetting)
    298             m_regionAnchor = anchorPosition;
    299         else
    300             LOG(Media, "TextTrackRegion::parseSettingValue, invalid RegionAnchor");
    301         break;
    302     case ViewportAnchor:
    303         anchorPosition = WebVTTParser::parseFloatPercentageValuePair(value, ',', isValidSetting);
    304         if (isValidSetting)
    305             m_viewportAnchor = anchorPosition;
    306         else
    307             LOG(Media, "TextTrackRegion::parseSettingValue, invalid ViewportAnchor");
    308         break;
    309     case Scroll:
    310         if (value == scrollUpValueKeyword)
    311             m_scroll = true;
    312         else
    313             LOG(Media, "TextTrackRegion::parseSettingValue, invalid Scroll");
    314         break;
    315     case None:
    316         break;
    317     }
    318 }
    319 
    320 void TextTrackRegion::parseSetting(const String& input, unsigned* position)
    321 {
    322     String setting = WebVTTParser::collectWord(input, position);
    323 
    324     size_t equalOffset = setting.find('=', 1);
    325     if (equalOffset == notFound || !equalOffset || equalOffset == setting.length() - 1)
    326         return;
    327 
    328     RegionSetting name = getSettingFromString(setting.substring(0, equalOffset));
    329     String value = setting.substring(equalOffset + 1, setting.length() - 1);
    330 
    331     parseSettingValue(name, value);
    332 }
    333 
    334 const AtomicString& TextTrackRegion::textTrackCueContainerShadowPseudoId()
    335 {
    336     DEFINE_STATIC_LOCAL(const AtomicString, trackRegionCueContainerPseudoId,
    337         ("-webkit-media-text-track-region-container", AtomicString::ConstructFromLiteral));
    338 
    339     return trackRegionCueContainerPseudoId;
    340 }
    341 
    342 const AtomicString& TextTrackRegion::textTrackCueContainerScrollingClass()
    343 {
    344     DEFINE_STATIC_LOCAL(const AtomicString, trackRegionCueContainerScrollingClass,
    345         ("scrolling", AtomicString::ConstructFromLiteral));
    346 
    347     return trackRegionCueContainerScrollingClass;
    348 }
    349 
    350 const AtomicString& TextTrackRegion::textTrackRegionShadowPseudoId()
    351 {
    352     DEFINE_STATIC_LOCAL(const AtomicString, trackRegionShadowPseudoId,
    353         ("-webkit-media-text-track-region", AtomicString::ConstructFromLiteral));
    354 
    355     return trackRegionShadowPseudoId;
    356 }
    357 
    358 PassRefPtr<HTMLDivElement> TextTrackRegion::getDisplayTree()
    359 {
    360     if (!m_regionDisplayTree) {
    361         m_regionDisplayTree = HTMLDivElement::create(ownerDocument());
    362         prepareRegionDisplayTree();
    363     }
    364 
    365     return m_regionDisplayTree;
    366 }
    367 
    368 void TextTrackRegion::willRemoveTextTrackCueBox(TextTrackCueBox* box)
    369 {
    370     LOG(Media, "TextTrackRegion::willRemoveTextTrackCueBox");
    371     ASSERT(m_cueContainer->contains(box));
    372 
    373     double boxHeight = box->getBoundingClientRect()->bottom() - box->getBoundingClientRect()->top();
    374     float regionBottom = m_regionDisplayTree->getBoundingClientRect()->bottom();
    375 
    376     m_cueContainer->classList()->remove(textTrackCueContainerScrollingClass(), IGNORE_EXCEPTION);
    377 
    378     m_currentTop += boxHeight;
    379     m_cueContainer->setInlineStyleProperty(CSSPropertyTop, m_currentTop, CSSPrimitiveValue::CSS_PX);
    380 }
    381 
    382 
    383 void TextTrackRegion::appendTextTrackCueBox(PassRefPtr<TextTrackCueBox> displayBox)
    384 {
    385     if (m_cueContainer->contains(displayBox.get()))
    386         return;
    387 
    388     m_cueContainer->appendChild(displayBox, ASSERT_NO_EXCEPTION, AttachNow);
    389     displayLastTextTrackCueBox();
    390 }
    391 
    392 void TextTrackRegion::displayLastTextTrackCueBox()
    393 {
    394     LOG(Media, "TextTrackRegion::displayLastTextTrackCueBox");
    395     ASSERT(m_cueContainer);
    396 
    397     // The container needs to be rendered, if it is not empty and the region is not currently scrolling.
    398     if (!m_cueContainer->renderer() || !m_cueContainer->childNodeCount() || m_scrollTimer.isActive())
    399         return;
    400 
    401     // If it's a scrolling region, add the scrolling class.
    402     if (isScrollingRegion())
    403         m_cueContainer->classList()->add(textTrackCueContainerScrollingClass(), IGNORE_EXCEPTION);
    404 
    405     float regionBottom = m_regionDisplayTree->getBoundingClientRect()->bottom();
    406 
    407     // Find first cue that is not entirely displayed and scroll it upwards.
    408     for (int i = 0; i < m_cueContainer->childNodeCount() && !m_scrollTimer.isActive(); ++i) {
    409         float childTop = static_cast<HTMLDivElement*>(m_cueContainer->childNode(i))->getBoundingClientRect()->top();
    410         float childBottom = static_cast<HTMLDivElement*>(m_cueContainer->childNode(i))->getBoundingClientRect()->bottom();
    411 
    412         if (regionBottom >= childBottom)
    413             continue;
    414 
    415         float height = childBottom - childTop;
    416 
    417         m_currentTop -= std::min(height, childBottom - regionBottom);
    418         m_cueContainer->setInlineStyleProperty(CSSPropertyTop, m_currentTop, CSSPrimitiveValue::CSS_PX);
    419 
    420         startTimer();
    421     }
    422 }
    423 
    424 void TextTrackRegion::prepareRegionDisplayTree()
    425 {
    426     ASSERT(m_regionDisplayTree);
    427 
    428     // 7.2 Prepare region CSS boxes
    429 
    430     // FIXME: Change the code below to use viewport units when
    431     // http://crbug/244618 is fixed.
    432 
    433     // Let regionWidth be the text track region width.
    434     // Let width be 'regionWidth vw' ('vw' is a CSS unit)
    435     m_regionDisplayTree->setInlineStyleProperty(CSSPropertyWidth,
    436         m_width, CSSPrimitiveValue::CSS_PERCENTAGE);
    437 
    438     // Let lineHeight be '0.0533vh' ('vh' is a CSS unit) and regionHeight be
    439     // the text track region height. Let height be 'lineHeight' multiplied
    440     // by regionHeight.
    441     double height = lineHeight * m_heightInLines;
    442     m_regionDisplayTree->setInlineStyleProperty(CSSPropertyHeight,
    443         height, CSSPrimitiveValue::CSS_VH);
    444 
    445     // Let viewportAnchorX be the x dimension of the text track region viewport
    446     // anchor and regionAnchorX be the x dimension of the text track region
    447     // anchor. Let leftOffset be regionAnchorX multiplied by width divided by
    448     // 100.0. Let left be leftOffset subtracted from 'viewportAnchorX vw'.
    449     double leftOffset = m_regionAnchor.x() * m_width / 100;
    450     m_regionDisplayTree->setInlineStyleProperty(CSSPropertyLeft,
    451         m_viewportAnchor.x() - leftOffset,
    452         CSSPrimitiveValue::CSS_PERCENTAGE);
    453 
    454     // Let viewportAnchorY be the y dimension of the text track region viewport
    455     // anchor and regionAnchorY be the y dimension of the text track region
    456     // anchor. Let topOffset be regionAnchorY multiplied by height divided by
    457     // 100.0. Let top be topOffset subtracted from 'viewportAnchorY vh'.
    458     double topOffset = m_regionAnchor.y() * height / 100;
    459     m_regionDisplayTree->setInlineStyleProperty(CSSPropertyTop,
    460         m_viewportAnchor.y() - topOffset,
    461         CSSPrimitiveValue::CSS_PERCENTAGE);
    462 
    463 
    464     // The cue container is used to wrap the cues and it is the object which is
    465     // gradually scrolled out as multiple cues are appended to the region.
    466     m_cueContainer = HTMLDivElement::create(ownerDocument());
    467     m_cueContainer->setInlineStyleProperty(CSSPropertyTop,
    468         0.0,
    469         CSSPrimitiveValue::CSS_PX);
    470 
    471     m_cueContainer->setPart(textTrackCueContainerShadowPseudoId());
    472     m_regionDisplayTree->appendChild(m_cueContainer);
    473 
    474     // 7.5 Every WebVTT region object is initialised with the following CSS
    475     m_regionDisplayTree->setPart(textTrackRegionShadowPseudoId());
    476 }
    477 
    478 void TextTrackRegion::startTimer()
    479 {
    480     LOG(Media, "TextTrackRegion::startTimer");
    481 
    482     if (m_scrollTimer.isActive())
    483         return;
    484 
    485     double duration = isScrollingRegion() ? scrollTime : 0;
    486     m_scrollTimer.startOneShot(duration);
    487 }
    488 
    489 void TextTrackRegion::stopTimer()
    490 {
    491     LOG(Media, "TextTrackRegion::stopTimer");
    492 
    493     if (m_scrollTimer.isActive())
    494         m_scrollTimer.stop();
    495 }
    496 
    497 void TextTrackRegion::scrollTimerFired(Timer<TextTrackRegion>*)
    498 {
    499     LOG(Media, "TextTrackRegion::scrollTimerFired");
    500 
    501     stopTimer();
    502     displayLastTextTrackCueBox();
    503 }
    504 
    505 } // namespace WebCore
    506 
    507 #endif
    508