Home | History | Annotate | Download | only in vtt
      1 /*
      2  * Copyright (c) 2013, Opera Software ASA. 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
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  * 3. Neither the name of Opera Software ASA nor the names of its
     13  *    contributors may be used to endorse or promote products derived
     14  *    from this software without specific prior written permission.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
     19  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
     20  * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
     21  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
     23  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
     25  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
     27  * OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 #include "config.h"
     31 #include "core/html/track/vtt/VTTCue.h"
     32 
     33 #include "bindings/core/v8/ExceptionMessages.h"
     34 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
     35 #include "core/CSSPropertyNames.h"
     36 #include "core/CSSValueKeywords.h"
     37 #include "core/dom/DocumentFragment.h"
     38 #include "core/dom/NodeTraversal.h"
     39 #include "core/events/Event.h"
     40 #include "core/frame/UseCounter.h"
     41 #include "core/html/HTMLDivElement.h"
     42 #include "core/html/track/TextTrack.h"
     43 #include "core/html/track/TextTrackCueList.h"
     44 #include "core/html/track/vtt/VTTElement.h"
     45 #include "core/html/track/vtt/VTTParser.h"
     46 #include "core/html/track/vtt/VTTRegionList.h"
     47 #include "core/html/track/vtt/VTTScanner.h"
     48 #include "core/rendering/RenderVTTCue.h"
     49 #include "platform/RuntimeEnabledFeatures.h"
     50 #include "platform/text/BidiResolver.h"
     51 #include "platform/text/TextRunIterator.h"
     52 #include "wtf/MathExtras.h"
     53 #include "wtf/text/StringBuilder.h"
     54 
     55 namespace blink {
     56 
     57 static const int undefinedPosition = -1;
     58 static const int undefinedSize = -1;
     59 
     60 static const CSSValueID displayWritingModeMap[] = {
     61     CSSValueHorizontalTb, CSSValueVerticalRl, CSSValueVerticalLr
     62 };
     63 COMPILE_ASSERT(WTF_ARRAY_LENGTH(displayWritingModeMap) == VTTCue::NumberOfWritingDirections,
     64     displayWritingModeMap_has_wrong_size);
     65 
     66 static const CSSValueID displayAlignmentMap[] = {
     67     CSSValueStart, CSSValueCenter, CSSValueEnd, CSSValueLeft, CSSValueRight
     68 };
     69 COMPILE_ASSERT(WTF_ARRAY_LENGTH(displayAlignmentMap) == VTTCue::NumberOfAlignments,
     70     displayAlignmentMap_has_wrong_size);
     71 
     72 static const String& startKeyword()
     73 {
     74     DEFINE_STATIC_LOCAL(const String, start, ("start"));
     75     return start;
     76 }
     77 
     78 static const String& middleKeyword()
     79 {
     80     DEFINE_STATIC_LOCAL(const String, middle, ("middle"));
     81     return middle;
     82 }
     83 
     84 static const String& endKeyword()
     85 {
     86     DEFINE_STATIC_LOCAL(const String, end, ("end"));
     87     return end;
     88 }
     89 
     90 static const String& leftKeyword()
     91 {
     92     DEFINE_STATIC_LOCAL(const String, left, ("left"));
     93     return left;
     94 }
     95 
     96 static const String& rightKeyword()
     97 {
     98     DEFINE_STATIC_LOCAL(const String, right, ("right"));
     99     return right;
    100 }
    101 
    102 static const String& horizontalKeyword()
    103 {
    104     return emptyString();
    105 }
    106 
    107 static const String& verticalGrowingLeftKeyword()
    108 {
    109     DEFINE_STATIC_LOCAL(const String, verticalrl, ("rl"));
    110     return verticalrl;
    111 }
    112 
    113 static const String& verticalGrowingRightKeyword()
    114 {
    115     DEFINE_STATIC_LOCAL(const String, verticallr, ("lr"));
    116     return verticallr;
    117 }
    118 
    119 static bool isInvalidPercentage(int value, ExceptionState& exceptionState)
    120 {
    121     if (value < 0 || value > 100) {
    122         exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexOutsideRange("value", value, 0, ExceptionMessages::InclusiveBound, 100, ExceptionMessages::InclusiveBound));
    123         return true;
    124     }
    125     return false;
    126 }
    127 
    128 VTTCueBox::VTTCueBox(Document& document, VTTCue* cue)
    129     : HTMLDivElement(document)
    130     , m_cue(cue)
    131 {
    132     setShadowPseudoId(AtomicString("-webkit-media-text-track-display", AtomicString::ConstructFromLiteral));
    133 }
    134 
    135 void VTTCueBox::applyCSSProperties(const IntSize&)
    136 {
    137     // FIXME: Apply all the initial CSS positioning properties. http://wkb.ug/79916
    138     if (!m_cue->regionId().isEmpty()) {
    139         setInlineStyleProperty(CSSPropertyPosition, CSSValueRelative);
    140         return;
    141     }
    142 
    143     // 3.5.1 On the (root) List of WebVTT Node Objects:
    144 
    145     // the 'position' property must be set to 'absolute'
    146     setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
    147 
    148     //  the 'unicode-bidi' property must be set to 'plaintext'
    149     setInlineStyleProperty(CSSPropertyUnicodeBidi, CSSValueWebkitPlaintext);
    150 
    151     // the 'direction' property must be set to direction
    152     setInlineStyleProperty(CSSPropertyDirection, m_cue->getCSSWritingDirection());
    153 
    154     // the 'writing-mode' property must be set to writing-mode
    155     setInlineStyleProperty(CSSPropertyWebkitWritingMode, m_cue->getCSSWritingMode());
    156 
    157     std::pair<float, float> position = m_cue->getCSSPosition();
    158 
    159     // the 'top' property must be set to top,
    160     setInlineStyleProperty(CSSPropertyTop, position.second, CSSPrimitiveValue::CSS_PERCENTAGE);
    161 
    162     // the 'left' property must be set to left
    163     setInlineStyleProperty(CSSPropertyLeft, position.first, CSSPrimitiveValue::CSS_PERCENTAGE);
    164 
    165     // the 'width' property must be set to width, and the 'height' property  must be set to height
    166     if (m_cue->vertical() == horizontalKeyword()) {
    167         setInlineStyleProperty(CSSPropertyWidth, static_cast<double>(m_cue->getCSSSize()), CSSPrimitiveValue::CSS_PERCENTAGE);
    168         setInlineStyleProperty(CSSPropertyHeight, CSSValueAuto);
    169     } else {
    170         setInlineStyleProperty(CSSPropertyWidth, CSSValueAuto);
    171         setInlineStyleProperty(CSSPropertyHeight, static_cast<double>(m_cue->getCSSSize()),  CSSPrimitiveValue::CSS_PERCENTAGE);
    172     }
    173 
    174     // The 'text-align' property on the (root) List of WebVTT Node Objects must
    175     // be set to the value in the second cell of the row of the table below
    176     // whose first cell is the value of the corresponding cue's text track cue
    177     // alignment:
    178     setInlineStyleProperty(CSSPropertyTextAlign, m_cue->getCSSAlignment());
    179 
    180     if (!m_cue->snapToLines()) {
    181         // 10.13.1 Set up x and y:
    182         // Note: x and y are set through the CSS left and top above.
    183 
    184         // 10.13.2 Position the boxes in boxes such that the point x% along the
    185         // width of the bounding box of the boxes in boxes is x% of the way
    186         // across the width of the video's rendering area, and the point y%
    187         // along the height of the bounding box of the boxes in boxes is y%
    188         // of the way across the height of the video's rendering area, while
    189         // maintaining the relative positions of the boxes in boxes to each
    190         // other.
    191         setInlineStyleProperty(CSSPropertyWebkitTransform,
    192             String::format("translate(-%.2f%%, -%.2f%%)", position.first, position.second));
    193 
    194         setInlineStyleProperty(CSSPropertyWhiteSpace, CSSValuePre);
    195     }
    196 }
    197 
    198 RenderObject* VTTCueBox::createRenderer(RenderStyle*)
    199 {
    200     return new RenderVTTCue(this);
    201 }
    202 
    203 void VTTCueBox::trace(Visitor* visitor)
    204 {
    205     visitor->trace(m_cue);
    206     HTMLDivElement::trace(visitor);
    207 }
    208 
    209 VTTCue::VTTCue(Document& document, double startTime, double endTime, const String& text)
    210     : TextTrackCue(startTime, endTime)
    211     , m_text(text)
    212     , m_linePosition(undefinedPosition)
    213     , m_computedLinePosition(undefinedPosition)
    214     , m_textPosition(50)
    215     , m_cueSize(100)
    216     , m_writingDirection(Horizontal)
    217     , m_cueAlignment(Middle)
    218     , m_vttNodeTree(nullptr)
    219     , m_cueBackgroundBox(HTMLDivElement::create(document))
    220     , m_displayDirection(CSSValueLtr)
    221     , m_displaySize(undefinedSize)
    222     , m_snapToLines(true)
    223     , m_displayTreeShouldChange(true)
    224     , m_notifyRegion(true)
    225 {
    226     UseCounter::count(document, UseCounter::VTTCue);
    227 }
    228 
    229 VTTCue::~VTTCue()
    230 {
    231     // Using oilpan, if m_displayTree is in the document it will strongly keep
    232     // the cue alive. Thus, if the cue is dead, either m_displayTree is not in
    233     // the document or the entire document is dead too.
    234 #if !ENABLE(OILPAN)
    235     // FIXME: This is scary, we should make the life cycle smarter so the destructor
    236     // doesn't need to do DOM mutations.
    237     if (m_displayTree)
    238         m_displayTree->remove(ASSERT_NO_EXCEPTION);
    239 #endif
    240 }
    241 
    242 #ifndef NDEBUG
    243 String VTTCue::toString() const
    244 {
    245     return String::format("%p id=%s interval=%f-->%f cue=%s)", this, id().utf8().data(), startTime(), endTime(), text().utf8().data());
    246 }
    247 #endif
    248 
    249 VTTCueBox& VTTCue::ensureDisplayTree()
    250 {
    251     if (!m_displayTree)
    252         m_displayTree = VTTCueBox::create(document(), this);
    253     return *m_displayTree;
    254 }
    255 
    256 void VTTCue::cueDidChange()
    257 {
    258     TextTrackCue::cueDidChange();
    259     m_displayTreeShouldChange = true;
    260 }
    261 
    262 const String& VTTCue::vertical() const
    263 {
    264     switch (m_writingDirection) {
    265     case Horizontal:
    266         return horizontalKeyword();
    267     case VerticalGrowingLeft:
    268         return verticalGrowingLeftKeyword();
    269     case VerticalGrowingRight:
    270         return verticalGrowingRightKeyword();
    271     default:
    272         ASSERT_NOT_REACHED();
    273         return emptyString();
    274     }
    275 }
    276 
    277 void VTTCue::setVertical(const String& value)
    278 {
    279     WritingDirection direction = m_writingDirection;
    280     if (value == horizontalKeyword())
    281         direction = Horizontal;
    282     else if (value == verticalGrowingLeftKeyword())
    283         direction = VerticalGrowingLeft;
    284     else if (value == verticalGrowingRightKeyword())
    285         direction = VerticalGrowingRight;
    286     else
    287         ASSERT_NOT_REACHED();
    288 
    289     if (direction == m_writingDirection)
    290         return;
    291 
    292     cueWillChange();
    293     m_writingDirection = direction;
    294     cueDidChange();
    295 }
    296 
    297 void VTTCue::setSnapToLines(bool value)
    298 {
    299     if (m_snapToLines == value)
    300         return;
    301 
    302     cueWillChange();
    303     m_snapToLines = value;
    304     cueDidChange();
    305 }
    306 
    307 void VTTCue::setLine(int position, ExceptionState& exceptionState)
    308 {
    309     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-line
    310     // On setting, if the text track cue snap-to-lines flag is not set, and the new
    311     // value is negative or greater than 100, then throw an IndexSizeError exception.
    312     if (!m_snapToLines && (position < 0 || position > 100)) {
    313         exceptionState.throwDOMException(IndexSizeError, "The snap-to-lines flag is not set, and the value provided (" + String::number(position) + ") is not between 0 and 100.");
    314         return;
    315     }
    316 
    317     // Otherwise, set the text track cue line position to the new value.
    318     if (m_linePosition == position)
    319         return;
    320 
    321     cueWillChange();
    322     m_linePosition = position;
    323     m_computedLinePosition = calculateComputedLinePosition();
    324     cueDidChange();
    325 }
    326 
    327 void VTTCue::setPosition(int position, ExceptionState& exceptionState)
    328 {
    329     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-position
    330     // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError exception.
    331     // Otherwise, set the text track cue text position to the new value.
    332     if (isInvalidPercentage(position, exceptionState))
    333         return;
    334 
    335     // Otherwise, set the text track cue line position to the new value.
    336     if (m_textPosition == position)
    337         return;
    338 
    339     cueWillChange();
    340     m_textPosition = position;
    341     cueDidChange();
    342 }
    343 
    344 void VTTCue::setSize(int size, ExceptionState& exceptionState)
    345 {
    346     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-size
    347     // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError
    348     // exception. Otherwise, set the text track cue size to the new value.
    349     if (isInvalidPercentage(size, exceptionState))
    350         return;
    351 
    352     // Otherwise, set the text track cue line position to the new value.
    353     if (m_cueSize == size)
    354         return;
    355 
    356     cueWillChange();
    357     m_cueSize = size;
    358     cueDidChange();
    359 }
    360 
    361 const String& VTTCue::align() const
    362 {
    363     switch (m_cueAlignment) {
    364     case Start:
    365         return startKeyword();
    366     case Middle:
    367         return middleKeyword();
    368     case End:
    369         return endKeyword();
    370     case Left:
    371         return leftKeyword();
    372     case Right:
    373         return rightKeyword();
    374     default:
    375         ASSERT_NOT_REACHED();
    376         return emptyString();
    377     }
    378 }
    379 
    380 void VTTCue::setAlign(const String& value)
    381 {
    382     CueAlignment alignment = m_cueAlignment;
    383     if (value == startKeyword())
    384         alignment = Start;
    385     else if (value == middleKeyword())
    386         alignment = Middle;
    387     else if (value == endKeyword())
    388         alignment = End;
    389     else if (value == leftKeyword())
    390         alignment = Left;
    391     else if (value == rightKeyword())
    392         alignment = Right;
    393     else
    394         ASSERT_NOT_REACHED();
    395 
    396     if (alignment == m_cueAlignment)
    397         return;
    398 
    399     cueWillChange();
    400     m_cueAlignment = alignment;
    401     cueDidChange();
    402 }
    403 
    404 void VTTCue::setText(const String& text)
    405 {
    406     if (m_text == text)
    407         return;
    408 
    409     cueWillChange();
    410     // Clear the document fragment but don't bother to create it again just yet as we can do that
    411     // when it is requested.
    412     m_vttNodeTree = nullptr;
    413     m_text = text;
    414     cueDidChange();
    415 }
    416 
    417 void VTTCue::createVTTNodeTree()
    418 {
    419     if (!m_vttNodeTree)
    420         m_vttNodeTree = VTTParser::createDocumentFragmentFromCueText(document(), m_text);
    421 }
    422 
    423 void VTTCue::copyVTTNodeToDOMTree(ContainerNode* vttNode, ContainerNode* parent)
    424 {
    425     for (Node* node = vttNode->firstChild(); node; node = node->nextSibling()) {
    426         RefPtrWillBeRawPtr<Node> clonedNode;
    427         if (node->isVTTElement())
    428             clonedNode = toVTTElement(node)->createEquivalentHTMLElement(document());
    429         else
    430             clonedNode = node->cloneNode(false);
    431         parent->appendChild(clonedNode);
    432         if (node->isContainerNode())
    433             copyVTTNodeToDOMTree(toContainerNode(node), toContainerNode(clonedNode));
    434     }
    435 }
    436 
    437 PassRefPtrWillBeRawPtr<DocumentFragment> VTTCue::getCueAsHTML()
    438 {
    439     createVTTNodeTree();
    440     RefPtrWillBeRawPtr<DocumentFragment> clonedFragment = DocumentFragment::create(document());
    441     copyVTTNodeToDOMTree(m_vttNodeTree.get(), clonedFragment.get());
    442     return clonedFragment.release();
    443 }
    444 
    445 PassRefPtrWillBeRawPtr<DocumentFragment> VTTCue::createCueRenderingTree()
    446 {
    447     createVTTNodeTree();
    448     RefPtrWillBeRawPtr<DocumentFragment> clonedFragment = DocumentFragment::create(document());
    449     m_vttNodeTree->cloneChildNodes(clonedFragment.get());
    450     return clonedFragment.release();
    451 }
    452 
    453 void VTTCue::setRegionId(const String& regionId)
    454 {
    455     if (m_regionId == regionId)
    456         return;
    457 
    458     cueWillChange();
    459     m_regionId = regionId;
    460     cueDidChange();
    461 }
    462 
    463 void VTTCue::notifyRegionWhenRemovingDisplayTree(bool notifyRegion)
    464 {
    465     m_notifyRegion = notifyRegion;
    466 }
    467 
    468 int VTTCue::calculateComputedLinePosition()
    469 {
    470     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-computed-line-position
    471 
    472     // If the text track cue line position is numeric, then that is the text
    473     // track cue computed line position.
    474     if (m_linePosition != undefinedPosition)
    475         return m_linePosition;
    476 
    477     // If the text track cue snap-to-lines flag of the text track cue is not
    478     // set, the text track cue computed line position is the value 100;
    479     if (!m_snapToLines)
    480         return 100;
    481 
    482     // Otherwise, it is the value returned by the following algorithm:
    483 
    484     // If cue is not associated with a text track, return -1 and abort these
    485     // steps.
    486     if (!track())
    487         return -1;
    488 
    489     // Let n be the number of text tracks whose text track mode is showing or
    490     // showing by default and that are in the media element's list of text
    491     // tracks before track.
    492     int n = track()->trackIndexRelativeToRenderedTracks();
    493 
    494     // Increment n by one.
    495     n++;
    496 
    497     // Negate n.
    498     n = -n;
    499 
    500     return n;
    501 }
    502 
    503 class VTTTextRunIterator : public TextRunIterator {
    504 public:
    505     VTTTextRunIterator() { }
    506     VTTTextRunIterator(const TextRun* textRun, unsigned offset) : TextRunIterator(textRun, offset) { }
    507 
    508     bool atParagraphSeparator() const
    509     {
    510         // Within a cue, paragraph boundaries are only denoted by Type B characters,
    511         // such as U+000A LINE FEED (LF), U+0085 NEXT LINE (NEL), and U+2029 PARAGRAPH SEPARATOR.
    512         return WTF::Unicode::category(current()) & WTF::Unicode::Separator_Paragraph;
    513     }
    514 };
    515 
    516 // Almost the same as determineDirectionality in core/html/HTMLElement.cpp, but
    517 // that one uses a "plain" TextRunIterator (which only checks for '\n').
    518 static TextDirection determineDirectionality(const String& value, bool& hasStrongDirectionality)
    519 {
    520     TextRun run(value);
    521     BidiResolver<VTTTextRunIterator, BidiCharacterRun> bidiResolver;
    522     bidiResolver.setStatus(BidiStatus(LTR, false));
    523     bidiResolver.setPositionIgnoringNestedIsolates(VTTTextRunIterator(&run, 0));
    524     return bidiResolver.determineParagraphDirectionality(&hasStrongDirectionality);
    525 }
    526 
    527 static CSSValueID determineTextDirection(DocumentFragment* vttRoot)
    528 {
    529     DEFINE_STATIC_LOCAL(const String, rtTag, ("rt"));
    530     ASSERT(vttRoot);
    531 
    532     // Apply the Unicode Bidirectional Algorithm's Paragraph Level steps to the
    533     // concatenation of the values of each WebVTT Text Object in nodes, in a
    534     // pre-order, depth-first traversal, excluding WebVTT Ruby Text Objects and
    535     // their descendants.
    536     TextDirection textDirection = LTR;
    537     for (Node* node = vttRoot->firstChild(); node; node = NodeTraversal::next(*node, vttRoot)) {
    538         if (!node->isTextNode() || node->localName() == rtTag)
    539             continue;
    540 
    541         bool hasStrongDirectionality;
    542         textDirection = determineDirectionality(node->nodeValue(), hasStrongDirectionality);
    543         if (hasStrongDirectionality)
    544             break;
    545     }
    546     return isLeftToRightDirection(textDirection) ? CSSValueLtr : CSSValueRtl;
    547 }
    548 
    549 void VTTCue::calculateDisplayParameters()
    550 {
    551     createVTTNodeTree();
    552 
    553     // Steps 10.2, 10.3
    554     m_displayDirection = determineTextDirection(m_vttNodeTree.get());
    555 
    556     if (m_displayDirection == CSSValueRtl)
    557         UseCounter::count(document(), UseCounter::VTTCueRenderRtl);
    558 
    559     // 10.4 If the text track cue writing direction is horizontal, then let
    560     // block-flow be 'tb'. Otherwise, if the text track cue writing direction is
    561     // vertical growing left, then let block-flow be 'lr'. Otherwise, the text
    562     // track cue writing direction is vertical growing right; let block-flow be
    563     // 'rl'.
    564 
    565     // The above step is done through the writing direction static map.
    566 
    567     // 10.5 Determine the value of maximum size for cue as per the appropriate
    568     // rules from the following list:
    569     int maximumSize = m_textPosition;
    570     if ((m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueLtr)
    571         || (m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueRtl)
    572         || (m_writingDirection == Horizontal && m_cueAlignment == Left)
    573         || (m_writingDirection == VerticalGrowingLeft && (m_cueAlignment == Start || m_cueAlignment == Left))
    574         || (m_writingDirection == VerticalGrowingRight && (m_cueAlignment == Start || m_cueAlignment == Left))) {
    575         maximumSize = 100 - m_textPosition;
    576     } else if ((m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueLtr)
    577         || (m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueRtl)
    578         || (m_writingDirection == Horizontal && m_cueAlignment == Right)
    579         || (m_writingDirection == VerticalGrowingLeft && (m_cueAlignment == End || m_cueAlignment == Right))
    580         || (m_writingDirection == VerticalGrowingRight && (m_cueAlignment == End || m_cueAlignment == Right))) {
    581         maximumSize = m_textPosition;
    582     } else if (m_cueAlignment == Middle) {
    583         maximumSize = m_textPosition <= 50 ? m_textPosition : (100 - m_textPosition);
    584         maximumSize = maximumSize * 2;
    585     } else {
    586         ASSERT_NOT_REACHED();
    587     }
    588 
    589     // 10.6 If the text track cue size is less than maximum size, then let size
    590     // be text track cue size. Otherwise, let size be maximum size.
    591     m_displaySize = std::min(m_cueSize, maximumSize);
    592 
    593     // FIXME: Understand why step 10.7 is missing (just a copy/paste error?)
    594     // Could be done within a spec implementation check - http://crbug.com/301580
    595 
    596     // 10.8 Determine the value of x-position or y-position for cue as per the
    597     // appropriate rules from the following list:
    598     if (m_writingDirection == Horizontal) {
    599         switch (m_cueAlignment) {
    600         case Start:
    601             if (m_displayDirection == CSSValueLtr)
    602                 m_displayPosition.first = m_textPosition;
    603             else
    604                 m_displayPosition.first = 100 - m_textPosition - m_displaySize;
    605             break;
    606         case End:
    607             if (m_displayDirection == CSSValueRtl)
    608                 m_displayPosition.first = 100 - m_textPosition;
    609             else
    610                 m_displayPosition.first = m_textPosition - m_displaySize;
    611             break;
    612         case Left:
    613             if (m_displayDirection == CSSValueLtr)
    614                 m_displayPosition.first = m_textPosition;
    615             else
    616                 m_displayPosition.first = 100 - m_textPosition;
    617             break;
    618         case Right:
    619             if (m_displayDirection == CSSValueLtr)
    620                 m_displayPosition.first = m_textPosition - m_displaySize;
    621             else
    622                 m_displayPosition.first = 100 - m_textPosition - m_displaySize;
    623             break;
    624         case Middle:
    625             if (m_displayDirection == CSSValueLtr)
    626                 m_displayPosition.first = m_textPosition - m_displaySize / 2;
    627             else
    628                 m_displayPosition.first = 100 - m_textPosition - m_displaySize / 2;
    629             break;
    630         default:
    631             ASSERT_NOT_REACHED();
    632         }
    633     } else {
    634         // Cases for m_writingDirection being VerticalGrowing{Left|Right}
    635         switch (m_cueAlignment) {
    636         case Start:
    637         case Left:
    638             m_displayPosition.second = m_textPosition;
    639             break;
    640         case End:
    641         case Right:
    642             m_displayPosition.second = m_textPosition - m_displaySize;
    643             break;
    644         case Middle:
    645             m_displayPosition.second = m_textPosition - m_displaySize / 2;
    646             break;
    647         default:
    648             ASSERT_NOT_REACHED();
    649         }
    650     }
    651 
    652     // A text track cue has a text track cue computed line position whose value
    653     // is defined in terms of the other aspects of the cue.
    654     m_computedLinePosition = calculateComputedLinePosition();
    655 
    656     // 10.9 Determine the value of whichever of x-position or y-position is not
    657     // yet calculated for cue as per the appropriate rules from the following
    658     // list:
    659     if (m_snapToLines && m_displayPosition.second == undefinedPosition && m_writingDirection == Horizontal)
    660         m_displayPosition.second = 0;
    661 
    662     if (!m_snapToLines && m_displayPosition.second == undefinedPosition && m_writingDirection == Horizontal)
    663         m_displayPosition.second = m_computedLinePosition;
    664 
    665     if (m_snapToLines && m_displayPosition.first == undefinedPosition
    666         && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
    667         m_displayPosition.first = 0;
    668 
    669     if (!m_snapToLines && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
    670         m_displayPosition.first = m_computedLinePosition;
    671 }
    672 
    673 void VTTCue::markFutureAndPastNodes(ContainerNode* root, double previousTimestamp, double movieTime)
    674 {
    675     DEFINE_STATIC_LOCAL(const String, timestampTag, ("timestamp"));
    676 
    677     bool isPastNode = true;
    678     double currentTimestamp = previousTimestamp;
    679     if (currentTimestamp > movieTime)
    680         isPastNode = false;
    681 
    682     for (Node* child = root->firstChild(); child; child = NodeTraversal::next(*child, root)) {
    683         if (child->nodeName() == timestampTag) {
    684             double currentTimestamp;
    685             bool check = VTTParser::collectTimeStamp(child->nodeValue(), currentTimestamp);
    686             ASSERT_UNUSED(check, check);
    687 
    688             if (currentTimestamp > movieTime)
    689                 isPastNode = false;
    690         }
    691 
    692         if (child->isVTTElement()) {
    693             toVTTElement(child)->setIsPastNode(isPastNode);
    694             // Make an elemenet id match a cue id for style matching purposes.
    695             if (!id().isEmpty())
    696                 toElement(child)->setIdAttribute(id());
    697         }
    698     }
    699 }
    700 
    701 void VTTCue::updateDisplayTree(double movieTime)
    702 {
    703     // The display tree may contain WebVTT timestamp objects representing
    704     // timestamps (processing instructions), along with displayable nodes.
    705 
    706     if (!track()->isRendered())
    707         return;
    708 
    709     // Clear the contents of the set.
    710     m_cueBackgroundBox->removeChildren();
    711 
    712     // Update the two sets containing past and future WebVTT objects.
    713     RefPtrWillBeRawPtr<DocumentFragment> referenceTree = createCueRenderingTree();
    714     markFutureAndPastNodes(referenceTree.get(), startTime(), movieTime);
    715     m_cueBackgroundBox->appendChild(referenceTree, ASSERT_NO_EXCEPTION);
    716 }
    717 
    718 PassRefPtrWillBeRawPtr<VTTCueBox> VTTCue::getDisplayTree(const IntSize& videoSize)
    719 {
    720     RefPtrWillBeRawPtr<VTTCueBox> displayTree(ensureDisplayTree());
    721     if (!m_displayTreeShouldChange || !track()->isRendered())
    722         return displayTree.release();
    723 
    724     // 10.1 - 10.10
    725     calculateDisplayParameters();
    726 
    727     // 10.11. Apply the terms of the CSS specifications to nodes within the
    728     // following constraints, thus obtaining a set of CSS boxes positioned
    729     // relative to an initial containing block:
    730     displayTree->removeChildren();
    731 
    732     // The document tree is the tree of WebVTT Node Objects rooted at nodes.
    733 
    734     // The children of the nodes must be wrapped in an anonymous box whose
    735     // 'display' property has the value 'inline'. This is the WebVTT cue
    736     // background box.
    737 
    738     // Note: This is contained by default in m_cueBackgroundBox.
    739     m_cueBackgroundBox->setShadowPseudoId(cueShadowPseudoId());
    740     displayTree->appendChild(m_cueBackgroundBox);
    741 
    742     // FIXME(BUG 79916): Runs of children of WebVTT Ruby Objects that are not
    743     // WebVTT Ruby Text Objects must be wrapped in anonymous boxes whose
    744     // 'display' property has the value 'ruby-base'.
    745 
    746     // FIXME(BUG 79916): Text runs must be wrapped according to the CSS
    747     // line-wrapping rules, except that additionally, regardless of the value of
    748     // the 'white-space' property, lines must be wrapped at the edge of their
    749     // containing blocks, even if doing so requires splitting a word where there
    750     // is no line breaking opportunity. (Thus, normally text wraps as needed,
    751     // but if there is a particularly long word, it does not overflow as it
    752     // normally would in CSS, it is instead forcibly wrapped at the box's edge.)
    753     displayTree->applyCSSProperties(videoSize);
    754 
    755     m_displayTreeShouldChange = false;
    756 
    757     // 10.15. Let cue's text track cue display state have the CSS boxes in
    758     // boxes.
    759     return displayTree.release();
    760 }
    761 
    762 void VTTCue::removeDisplayTree()
    763 {
    764     if (m_notifyRegion && track()->regions()) {
    765         // The region needs to be informed about the cue removal.
    766         VTTRegion* region = track()->regions()->getRegionById(m_regionId);
    767         if (region)
    768             region->willRemoveVTTCueBox(m_displayTree.get());
    769     }
    770 
    771     if (m_displayTree)
    772         m_displayTree->remove(ASSERT_NO_EXCEPTION);
    773 }
    774 
    775 void VTTCue::updateDisplay(const IntSize& videoSize, HTMLDivElement& container)
    776 {
    777     UseCounter::count(document(), UseCounter::VTTCueRender);
    778 
    779     if (m_writingDirection != Horizontal)
    780         UseCounter::count(document(), UseCounter::VTTCueRenderVertical);
    781 
    782     if (!m_snapToLines)
    783         UseCounter::count(document(), UseCounter::VTTCueRenderSnapToLinesFalse);
    784 
    785     if (m_linePosition != undefinedPosition)
    786         UseCounter::count(document(), UseCounter::VTTCueRenderLineNotAuto);
    787 
    788     if (m_textPosition != 50)
    789         UseCounter::count(document(), UseCounter::VTTCueRenderPositionNot50);
    790 
    791     if (m_cueSize != 100)
    792         UseCounter::count(document(), UseCounter::VTTCueRenderSizeNot100);
    793 
    794     if (m_cueAlignment != Middle)
    795         UseCounter::count(document(), UseCounter::VTTCueRenderAlignNotMiddle);
    796 
    797     RefPtrWillBeRawPtr<VTTCueBox> displayBox = getDisplayTree(videoSize);
    798     VTTRegion* region = 0;
    799     if (track()->regions())
    800         region = track()->regions()->getRegionById(regionId());
    801 
    802     if (!region) {
    803         // If cue has an empty text track cue region identifier or there is no
    804         // WebVTT region whose region identifier is identical to cue's text
    805         // track cue region identifier, run the following substeps:
    806         if (displayBox->hasChildren() && !container.contains(displayBox.get())) {
    807             // Note: the display tree of a cue is removed when the active flag of the cue is unset.
    808             container.appendChild(displayBox);
    809         }
    810     } else {
    811         // Let region be the WebVTT region whose region identifier
    812         // matches the text track cue region identifier of cue.
    813         RefPtrWillBeRawPtr<HTMLDivElement> regionNode = region->getDisplayTree(document());
    814 
    815         // Append the region to the viewport, if it was not already.
    816         if (!container.contains(regionNode.get()))
    817             container.appendChild(regionNode);
    818 
    819         region->appendVTTCueBox(displayBox);
    820     }
    821 }
    822 
    823 std::pair<double, double> VTTCue::getPositionCoordinates() const
    824 {
    825     // This method is used for setting x and y when snap to lines is not set.
    826     std::pair<double, double> coordinates;
    827 
    828     if (m_writingDirection == Horizontal && m_displayDirection == CSSValueLtr) {
    829         coordinates.first = m_textPosition;
    830         coordinates.second = m_computedLinePosition;
    831 
    832         return coordinates;
    833     }
    834 
    835     if (m_writingDirection == Horizontal && m_displayDirection == CSSValueRtl) {
    836         coordinates.first = 100 - m_textPosition;
    837         coordinates.second = m_computedLinePosition;
    838 
    839         return coordinates;
    840     }
    841 
    842     if (m_writingDirection == VerticalGrowingLeft) {
    843         coordinates.first = 100 - m_computedLinePosition;
    844         coordinates.second = m_textPosition;
    845 
    846         return coordinates;
    847     }
    848 
    849     if (m_writingDirection == VerticalGrowingRight) {
    850         coordinates.first = m_computedLinePosition;
    851         coordinates.second = m_textPosition;
    852 
    853         return coordinates;
    854     }
    855 
    856     ASSERT_NOT_REACHED();
    857 
    858     return coordinates;
    859 }
    860 
    861 VTTCue::CueSetting VTTCue::settingName(VTTScanner& input)
    862 {
    863     CueSetting parsedSetting = None;
    864     if (input.scan("vertical"))
    865         parsedSetting = Vertical;
    866     else if (input.scan("line"))
    867         parsedSetting = Line;
    868     else if (input.scan("position"))
    869         parsedSetting = Position;
    870     else if (input.scan("size"))
    871         parsedSetting = Size;
    872     else if (input.scan("align"))
    873         parsedSetting = Align;
    874     else if (RuntimeEnabledFeatures::webVTTRegionsEnabled() && input.scan("region"))
    875         parsedSetting = RegionId;
    876     // Verify that a ':' follows.
    877     if (parsedSetting != None && input.scan(':'))
    878         return parsedSetting;
    879     return None;
    880 }
    881 
    882 // Used for 'position' and 'size'.
    883 static bool scanPercentage(VTTScanner& input, const VTTScanner::Run& valueRun, int& number)
    884 {
    885     // 1. If value contains any characters other than U+0025 PERCENT SIGN
    886     //    characters (%) and characters in the range U+0030 DIGIT ZERO (0) to
    887     //    U+0039 DIGIT NINE (9), then jump to the step labeled next setting.
    888     // 2. If value does not contain at least one character in the range U+0030
    889     //    DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump to the step
    890     //    labeled next setting.
    891     if (!input.scanDigits(number))
    892         return false;
    893 
    894     // 3. If any character in value other than the last character is a U+0025
    895     //    PERCENT SIGN character (%), then jump to the step labeled next
    896     //    setting.
    897     // 4. If the last character in value is not a U+0025 PERCENT SIGN character
    898     //    (%), then jump to the step labeled next setting.
    899     if (!input.scan('%') || !input.isAt(valueRun.end()))
    900         return false;
    901 
    902     // 5. Ignoring the trailing percent sign, interpret value as an integer,
    903     //    and let number be that number.
    904     // 6. If number is not in the range 0  number  100, then jump to the step
    905     //    labeled next setting.
    906     return number >= 0 && number <= 100;
    907 }
    908 
    909 void VTTCue::parseSettings(const String& inputString)
    910 {
    911     VTTScanner input(inputString);
    912 
    913     while (!input.isAtEnd()) {
    914 
    915         // The WebVTT cue settings part of a WebVTT cue consists of zero or more of the following components, in any order,
    916         // separated from each other by one or more U+0020 SPACE characters or U+0009 CHARACTER TABULATION (tab) characters.
    917         input.skipWhile<VTTParser::isValidSettingDelimiter>();
    918 
    919         if (input.isAtEnd())
    920             break;
    921 
    922         // When the user agent is to parse the WebVTT settings given by a string input for a text track cue cue,
    923         // the user agent must run the following steps:
    924         // 1. Let settings be the result of splitting input on spaces.
    925         // 2. For each token setting in the list settings, run the following substeps:
    926         //    1. If setting does not contain a U+003A COLON character (:), or if the first U+003A COLON character (:)
    927         //       in setting is either the first or last character of setting, then jump to the step labeled next setting.
    928         //    2. Let name be the leading substring of setting up to and excluding the first U+003A COLON character (:) in that string.
    929         CueSetting name = settingName(input);
    930 
    931         // 3. Let value be the trailing substring of setting starting from the character immediately after the first U+003A COLON character (:) in that string.
    932         VTTScanner::Run valueRun = input.collectUntil<VTTParser::isValidSettingDelimiter>();
    933 
    934         // 4. Run the appropriate substeps that apply for the value of name, as follows:
    935         switch (name) {
    936         case Vertical: {
    937             // If name is a case-sensitive match for "vertical"
    938             // 1. If value is a case-sensitive match for the string "rl", then let cue's text track cue writing direction
    939             //    be vertical growing left.
    940             if (input.scanRun(valueRun, verticalGrowingLeftKeyword()))
    941                 m_writingDirection = VerticalGrowingLeft;
    942 
    943             // 2. Otherwise, if value is a case-sensitive match for the string "lr", then let cue's text track cue writing
    944             //    direction be vertical growing right.
    945             else if (input.scanRun(valueRun, verticalGrowingRightKeyword()))
    946                 m_writingDirection = VerticalGrowingRight;
    947             break;
    948         }
    949         case Line: {
    950             // 1-2 - Collect chars that are either '-', '%', or a digit.
    951             // 1. If value contains any characters other than U+002D HYPHEN-MINUS characters (-), U+0025 PERCENT SIGN
    952             //    characters (%), and characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump
    953             //    to the step labeled next setting.
    954             bool isNegative = input.scan('-');
    955             int linePosition;
    956             unsigned numDigits = input.scanDigits(linePosition);
    957             bool isPercentage = input.scan('%');
    958 
    959             if (!input.isAt(valueRun.end()))
    960                 break;
    961 
    962             // 2. If value does not contain at least one character in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT
    963             //    NINE (9), then jump to the step labeled next setting.
    964             // 3. If any character in value other than the first character is a U+002D HYPHEN-MINUS character (-), then
    965             //    jump to the step labeled next setting.
    966             // 4. If any character in value other than the last character is a U+0025 PERCENT SIGN character (%), then
    967             //    jump to the step labeled next setting.
    968 
    969             // 5. If the first character in value is a U+002D HYPHEN-MINUS character (-) and the last character in value is a
    970             //    U+0025 PERCENT SIGN character (%), then jump to the step labeled next setting.
    971             if (!numDigits || (isPercentage && isNegative))
    972                 break;
    973 
    974             // 6. Ignoring the trailing percent sign, if any, interpret value as a (potentially signed) integer, and
    975             //    let number be that number.
    976             // 7. If the last character in value is a U+0025 PERCENT SIGN character (%), but number is not in the range
    977             //    0  number  100, then jump to the step labeled next setting.
    978             // 8. Let cue's text track cue line position be number.
    979             // 9. If the last character in value is a U+0025 PERCENT SIGN character (%), then let cue's text track cue
    980             //    snap-to-lines flag be false. Otherwise, let it be true.
    981             if (isPercentage) {
    982                 if (linePosition < 0 || linePosition > 100)
    983                     break;
    984                 // 10 - If '%' then set snap-to-lines flag to false.
    985                 m_snapToLines = false;
    986             } else {
    987                 if (isNegative)
    988                     linePosition = -linePosition;
    989                 m_snapToLines = true;
    990             }
    991             m_linePosition = linePosition;
    992             break;
    993         }
    994         case Position: {
    995             int number;
    996             // Steps 1 - 6.
    997             if (!scanPercentage(input, valueRun, number))
    998                 break;
    999 
   1000             // 7. Let cue's text track cue text position be number.
   1001             m_textPosition = number;
   1002             break;
   1003         }
   1004         case Size: {
   1005             int number;
   1006             // Steps 1 - 6.
   1007             if (!scanPercentage(input, valueRun, number))
   1008                 break;
   1009 
   1010             // 7. Let cue's text track cue size be number.
   1011             m_cueSize = number;
   1012             break;
   1013         }
   1014         case Align: {
   1015             // 1. If value is a case-sensitive match for the string "start", then let cue's text track cue alignment be start alignment.
   1016             if (input.scanRun(valueRun, startKeyword()))
   1017                 m_cueAlignment = Start;
   1018 
   1019             // 2. If value is a case-sensitive match for the string "middle", then let cue's text track cue alignment be middle alignment.
   1020             else if (input.scanRun(valueRun, middleKeyword()))
   1021                 m_cueAlignment = Middle;
   1022 
   1023             // 3. If value is a case-sensitive match for the string "end", then let cue's text track cue alignment be end alignment.
   1024             else if (input.scanRun(valueRun, endKeyword()))
   1025                 m_cueAlignment = End;
   1026 
   1027             // 4. If value is a case-sensitive match for the string "left", then let cue's text track cue alignment be left alignment.
   1028             else if (input.scanRun(valueRun, leftKeyword()))
   1029                 m_cueAlignment = Left;
   1030 
   1031             // 5. If value is a case-sensitive match for the string "right", then let cue's text track cue alignment be right alignment.
   1032             else if (input.scanRun(valueRun, rightKeyword()))
   1033                 m_cueAlignment = Right;
   1034             break;
   1035         }
   1036         case RegionId:
   1037             m_regionId = input.extractString(valueRun);
   1038             break;
   1039         case None:
   1040             break;
   1041         }
   1042 
   1043         // Make sure the entire run is consumed.
   1044         input.skipRun(valueRun);
   1045     }
   1046 
   1047     // If cue's line position is not auto or cue's size is not 100 or cue's
   1048     // writing direction is not horizontal, but cue's region identifier is not
   1049     // the empty string, let cue's region identifier be the empty string.
   1050     if (m_regionId.isEmpty())
   1051         return;
   1052 
   1053     if (m_linePosition != undefinedPosition || m_cueSize != 100 || m_writingDirection != Horizontal)
   1054         m_regionId = emptyString();
   1055 }
   1056 
   1057 CSSValueID VTTCue::getCSSAlignment() const
   1058 {
   1059     return displayAlignmentMap[m_cueAlignment];
   1060 }
   1061 
   1062 CSSValueID VTTCue::getCSSWritingDirection() const
   1063 {
   1064     return m_displayDirection;
   1065 }
   1066 
   1067 CSSValueID VTTCue::getCSSWritingMode() const
   1068 {
   1069     return displayWritingModeMap[m_writingDirection];
   1070 }
   1071 
   1072 int VTTCue::getCSSSize() const
   1073 {
   1074     ASSERT(m_displaySize != undefinedSize);
   1075     return m_displaySize;
   1076 }
   1077 
   1078 std::pair<double, double> VTTCue::getCSSPosition() const
   1079 {
   1080     if (!m_snapToLines)
   1081         return getPositionCoordinates();
   1082 
   1083     return m_displayPosition;
   1084 }
   1085 
   1086 ExecutionContext* VTTCue::executionContext() const
   1087 {
   1088     ASSERT(m_cueBackgroundBox);
   1089     return m_cueBackgroundBox->executionContext();
   1090 }
   1091 
   1092 Document& VTTCue::document() const
   1093 {
   1094     ASSERT(m_cueBackgroundBox);
   1095     return m_cueBackgroundBox->document();
   1096 }
   1097 
   1098 void VTTCue::trace(Visitor* visitor)
   1099 {
   1100     visitor->trace(m_vttNodeTree);
   1101     visitor->trace(m_cueBackgroundBox);
   1102     visitor->trace(m_displayTree);
   1103     TextTrackCue::trace(visitor);
   1104 }
   1105 
   1106 } // namespace blink
   1107