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/v8/ExceptionMessages.h"
     34 #include "bindings/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 WebCore {
     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     ScriptWrappable::init(this);
    227     UseCounter::count(document, UseCounter::VTTCue);
    228 }
    229 
    230 VTTCue::~VTTCue()
    231 {
    232     // Using oilpan, if m_displayTree is in the document it will strongly keep
    233     // the cue alive. Thus, if the cue is dead, either m_displayTree is not in
    234     // the document or the entire document is dead too.
    235 #if !ENABLE(OILPAN)
    236     // FIXME: This is scary, we should make the life cycle smarter so the destructor
    237     // doesn't need to do DOM mutations.
    238     if (m_displayTree)
    239         m_displayTree->remove(ASSERT_NO_EXCEPTION);
    240 #endif
    241 }
    242 
    243 #ifndef NDEBUG
    244 String VTTCue::toString() const
    245 {
    246     return String::format("%p id=%s interval=%f-->%f cue=%s)", this, id().utf8().data(), startTime(), endTime(), text().utf8().data());
    247 }
    248 #endif
    249 
    250 VTTCueBox& VTTCue::ensureDisplayTree()
    251 {
    252     if (!m_displayTree)
    253         m_displayTree = VTTCueBox::create(document(), this);
    254     return *m_displayTree;
    255 }
    256 
    257 void VTTCue::cueDidChange()
    258 {
    259     TextTrackCue::cueDidChange();
    260     m_displayTreeShouldChange = true;
    261 }
    262 
    263 const String& VTTCue::vertical() const
    264 {
    265     switch (m_writingDirection) {
    266     case Horizontal:
    267         return horizontalKeyword();
    268     case VerticalGrowingLeft:
    269         return verticalGrowingLeftKeyword();
    270     case VerticalGrowingRight:
    271         return verticalGrowingRightKeyword();
    272     default:
    273         ASSERT_NOT_REACHED();
    274         return emptyString();
    275     }
    276 }
    277 
    278 void VTTCue::setVertical(const String& value)
    279 {
    280     WritingDirection direction = m_writingDirection;
    281     if (value == horizontalKeyword())
    282         direction = Horizontal;
    283     else if (value == verticalGrowingLeftKeyword())
    284         direction = VerticalGrowingLeft;
    285     else if (value == verticalGrowingRightKeyword())
    286         direction = VerticalGrowingRight;
    287     else
    288         ASSERT_NOT_REACHED();
    289 
    290     if (direction == m_writingDirection)
    291         return;
    292 
    293     cueWillChange();
    294     m_writingDirection = direction;
    295     cueDidChange();
    296 }
    297 
    298 void VTTCue::setSnapToLines(bool value)
    299 {
    300     if (m_snapToLines == value)
    301         return;
    302 
    303     cueWillChange();
    304     m_snapToLines = value;
    305     cueDidChange();
    306 }
    307 
    308 void VTTCue::setLine(int position, ExceptionState& exceptionState)
    309 {
    310     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-line
    311     // On setting, if the text track cue snap-to-lines flag is not set, and the new
    312     // value is negative or greater than 100, then throw an IndexSizeError exception.
    313     if (!m_snapToLines && (position < 0 || position > 100)) {
    314         exceptionState.throwDOMException(IndexSizeError, "The snap-to-lines flag is not set, and the value provided (" + String::number(position) + ") is not between 0 and 100.");
    315         return;
    316     }
    317 
    318     // Otherwise, set the text track cue line position to the new value.
    319     if (m_linePosition == position)
    320         return;
    321 
    322     cueWillChange();
    323     m_linePosition = position;
    324     m_computedLinePosition = calculateComputedLinePosition();
    325     cueDidChange();
    326 }
    327 
    328 void VTTCue::setPosition(int position, ExceptionState& exceptionState)
    329 {
    330     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-position
    331     // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError exception.
    332     // Otherwise, set the text track cue text position to the new value.
    333     if (isInvalidPercentage(position, exceptionState))
    334         return;
    335 
    336     // Otherwise, set the text track cue line position to the new value.
    337     if (m_textPosition == position)
    338         return;
    339 
    340     cueWillChange();
    341     m_textPosition = position;
    342     cueDidChange();
    343 }
    344 
    345 void VTTCue::setSize(int size, ExceptionState& exceptionState)
    346 {
    347     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-size
    348     // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError
    349     // exception. Otherwise, set the text track cue size to the new value.
    350     if (isInvalidPercentage(size, exceptionState))
    351         return;
    352 
    353     // Otherwise, set the text track cue line position to the new value.
    354     if (m_cueSize == size)
    355         return;
    356 
    357     cueWillChange();
    358     m_cueSize = size;
    359     cueDidChange();
    360 }
    361 
    362 const String& VTTCue::align() const
    363 {
    364     switch (m_cueAlignment) {
    365     case Start:
    366         return startKeyword();
    367     case Middle:
    368         return middleKeyword();
    369     case End:
    370         return endKeyword();
    371     case Left:
    372         return leftKeyword();
    373     case Right:
    374         return rightKeyword();
    375     default:
    376         ASSERT_NOT_REACHED();
    377         return emptyString();
    378     }
    379 }
    380 
    381 void VTTCue::setAlign(const String& value)
    382 {
    383     CueAlignment alignment = m_cueAlignment;
    384     if (value == startKeyword())
    385         alignment = Start;
    386     else if (value == middleKeyword())
    387         alignment = Middle;
    388     else if (value == endKeyword())
    389         alignment = End;
    390     else if (value == leftKeyword())
    391         alignment = Left;
    392     else if (value == rightKeyword())
    393         alignment = Right;
    394     else
    395         ASSERT_NOT_REACHED();
    396 
    397     if (alignment == m_cueAlignment)
    398         return;
    399 
    400     cueWillChange();
    401     m_cueAlignment = alignment;
    402     cueDidChange();
    403 }
    404 
    405 void VTTCue::setText(const String& text)
    406 {
    407     if (m_text == text)
    408         return;
    409 
    410     cueWillChange();
    411     // Clear the document fragment but don't bother to create it again just yet as we can do that
    412     // when it is requested.
    413     m_vttNodeTree = nullptr;
    414     m_text = text;
    415     cueDidChange();
    416 }
    417 
    418 void VTTCue::createVTTNodeTree()
    419 {
    420     if (!m_vttNodeTree)
    421         m_vttNodeTree = VTTParser::createDocumentFragmentFromCueText(document(), m_text);
    422 }
    423 
    424 void VTTCue::copyVTTNodeToDOMTree(ContainerNode* vttNode, ContainerNode* parent)
    425 {
    426     for (Node* node = vttNode->firstChild(); node; node = node->nextSibling()) {
    427         RefPtrWillBeRawPtr<Node> clonedNode;
    428         if (node->isVTTElement())
    429             clonedNode = toVTTElement(node)->createEquivalentHTMLElement(document());
    430         else
    431             clonedNode = node->cloneNode(false);
    432         parent->appendChild(clonedNode);
    433         if (node->isContainerNode())
    434             copyVTTNodeToDOMTree(toContainerNode(node), toContainerNode(clonedNode));
    435     }
    436 }
    437 
    438 PassRefPtrWillBeRawPtr<DocumentFragment> VTTCue::getCueAsHTML()
    439 {
    440     createVTTNodeTree();
    441     RefPtrWillBeRawPtr<DocumentFragment> clonedFragment = DocumentFragment::create(document());
    442     copyVTTNodeToDOMTree(m_vttNodeTree.get(), clonedFragment.get());
    443     return clonedFragment.release();
    444 }
    445 
    446 PassRefPtrWillBeRawPtr<DocumentFragment> VTTCue::createCueRenderingTree()
    447 {
    448     createVTTNodeTree();
    449     RefPtrWillBeRawPtr<DocumentFragment> clonedFragment = DocumentFragment::create(document());
    450     m_vttNodeTree->cloneChildNodes(clonedFragment.get());
    451     return clonedFragment.release();
    452 }
    453 
    454 void VTTCue::setRegionId(const String& regionId)
    455 {
    456     if (m_regionId == regionId)
    457         return;
    458 
    459     cueWillChange();
    460     m_regionId = regionId;
    461     cueDidChange();
    462 }
    463 
    464 void VTTCue::notifyRegionWhenRemovingDisplayTree(bool notifyRegion)
    465 {
    466     m_notifyRegion = notifyRegion;
    467 }
    468 
    469 int VTTCue::calculateComputedLinePosition()
    470 {
    471     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-computed-line-position
    472 
    473     // If the text track cue line position is numeric, then that is the text
    474     // track cue computed line position.
    475     if (m_linePosition != undefinedPosition)
    476         return m_linePosition;
    477 
    478     // If the text track cue snap-to-lines flag of the text track cue is not
    479     // set, the text track cue computed line position is the value 100;
    480     if (!m_snapToLines)
    481         return 100;
    482 
    483     // Otherwise, it is the value returned by the following algorithm:
    484 
    485     // If cue is not associated with a text track, return -1 and abort these
    486     // steps.
    487     if (!track())
    488         return -1;
    489 
    490     // Let n be the number of text tracks whose text track mode is showing or
    491     // showing by default and that are in the media element's list of text
    492     // tracks before track.
    493     int n = track()->trackIndexRelativeToRenderedTracks();
    494 
    495     // Increment n by one.
    496     n++;
    497 
    498     // Negate n.
    499     n = -n;
    500 
    501     return n;
    502 }
    503 
    504 class VTTTextRunIterator : public TextRunIterator {
    505 public:
    506     VTTTextRunIterator() { }
    507     VTTTextRunIterator(const TextRun* textRun, unsigned offset) : TextRunIterator(textRun, offset) { }
    508 
    509     bool atParagraphSeparator() const
    510     {
    511         // Within a cue, paragraph boundaries are only denoted by Type B characters,
    512         // such as U+000A LINE FEED (LF), U+0085 NEXT LINE (NEL), and U+2029 PARAGRAPH SEPARATOR.
    513         return WTF::Unicode::category(current()) & WTF::Unicode::Separator_Paragraph;
    514     }
    515 };
    516 
    517 // Almost the same as determineDirectionality in core/html/HTMLElement.cpp, but
    518 // that one uses a "plain" TextRunIterator (which only checks for '\n').
    519 static TextDirection determineDirectionality(const String& value, bool& hasStrongDirectionality)
    520 {
    521     TextRun run(value);
    522     BidiResolver<VTTTextRunIterator, BidiCharacterRun> bidiResolver;
    523     bidiResolver.setStatus(BidiStatus(LTR, false));
    524     bidiResolver.setPositionIgnoringNestedIsolates(VTTTextRunIterator(&run, 0));
    525     return bidiResolver.determineParagraphDirectionality(&hasStrongDirectionality);
    526 }
    527 
    528 static CSSValueID determineTextDirection(DocumentFragment* vttRoot)
    529 {
    530     DEFINE_STATIC_LOCAL(const String, rtTag, ("rt"));
    531     ASSERT(vttRoot);
    532 
    533     // Apply the Unicode Bidirectional Algorithm's Paragraph Level steps to the
    534     // concatenation of the values of each WebVTT Text Object in nodes, in a
    535     // pre-order, depth-first traversal, excluding WebVTT Ruby Text Objects and
    536     // their descendants.
    537     TextDirection textDirection = LTR;
    538     for (Node* node = vttRoot->firstChild(); node; node = NodeTraversal::next(*node, vttRoot)) {
    539         if (!node->isTextNode() || node->localName() == rtTag)
    540             continue;
    541 
    542         bool hasStrongDirectionality;
    543         textDirection = determineDirectionality(node->nodeValue(), hasStrongDirectionality);
    544         if (hasStrongDirectionality)
    545             break;
    546     }
    547     return isLeftToRightDirection(textDirection) ? CSSValueLtr : CSSValueRtl;
    548 }
    549 
    550 void VTTCue::calculateDisplayParameters()
    551 {
    552     createVTTNodeTree();
    553 
    554     // Steps 10.2, 10.3
    555     m_displayDirection = determineTextDirection(m_vttNodeTree.get());
    556 
    557     if (m_displayDirection == CSSValueRtl)
    558         UseCounter::count(document(), UseCounter::VTTCueRenderRtl);
    559 
    560     // 10.4 If the text track cue writing direction is horizontal, then let
    561     // block-flow be 'tb'. Otherwise, if the text track cue writing direction is
    562     // vertical growing left, then let block-flow be 'lr'. Otherwise, the text
    563     // track cue writing direction is vertical growing right; let block-flow be
    564     // 'rl'.
    565 
    566     // The above step is done through the writing direction static map.
    567 
    568     // 10.5 Determine the value of maximum size for cue as per the appropriate
    569     // rules from the following list:
    570     int maximumSize = m_textPosition;
    571     if ((m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueLtr)
    572         || (m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueRtl)
    573         || (m_writingDirection == Horizontal && m_cueAlignment == Left)
    574         || (m_writingDirection == VerticalGrowingLeft && (m_cueAlignment == Start || m_cueAlignment == Left))
    575         || (m_writingDirection == VerticalGrowingRight && (m_cueAlignment == Start || m_cueAlignment == Left))) {
    576         maximumSize = 100 - m_textPosition;
    577     } else if ((m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueLtr)
    578         || (m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueRtl)
    579         || (m_writingDirection == Horizontal && m_cueAlignment == Right)
    580         || (m_writingDirection == VerticalGrowingLeft && (m_cueAlignment == End || m_cueAlignment == Right))
    581         || (m_writingDirection == VerticalGrowingRight && (m_cueAlignment == End || m_cueAlignment == Right))) {
    582         maximumSize = m_textPosition;
    583     } else if (m_cueAlignment == Middle) {
    584         maximumSize = m_textPosition <= 50 ? m_textPosition : (100 - m_textPosition);
    585         maximumSize = maximumSize * 2;
    586     } else {
    587         ASSERT_NOT_REACHED();
    588     }
    589 
    590     // 10.6 If the text track cue size is less than maximum size, then let size
    591     // be text track cue size. Otherwise, let size be maximum size.
    592     m_displaySize = std::min(m_cueSize, maximumSize);
    593 
    594     // FIXME: Understand why step 10.7 is missing (just a copy/paste error?)
    595     // Could be done within a spec implementation check - http://crbug.com/301580
    596 
    597     // 10.8 Determine the value of x-position or y-position for cue as per the
    598     // appropriate rules from the following list:
    599     if (m_writingDirection == Horizontal) {
    600         switch (m_cueAlignment) {
    601         case Start:
    602             if (m_displayDirection == CSSValueLtr)
    603                 m_displayPosition.first = m_textPosition;
    604             else
    605                 m_displayPosition.first = 100 - m_textPosition - m_displaySize;
    606             break;
    607         case End:
    608             if (m_displayDirection == CSSValueRtl)
    609                 m_displayPosition.first = 100 - m_textPosition;
    610             else
    611                 m_displayPosition.first = m_textPosition - m_displaySize;
    612             break;
    613         case Left:
    614             if (m_displayDirection == CSSValueLtr)
    615                 m_displayPosition.first = m_textPosition;
    616             else
    617                 m_displayPosition.first = 100 - m_textPosition;
    618             break;
    619         case Right:
    620             if (m_displayDirection == CSSValueLtr)
    621                 m_displayPosition.first = m_textPosition - m_displaySize;
    622             else
    623                 m_displayPosition.first = 100 - m_textPosition - m_displaySize;
    624             break;
    625         case Middle:
    626             if (m_displayDirection == CSSValueLtr)
    627                 m_displayPosition.first = m_textPosition - m_displaySize / 2;
    628             else
    629                 m_displayPosition.first = 100 - m_textPosition - m_displaySize / 2;
    630             break;
    631         default:
    632             ASSERT_NOT_REACHED();
    633         }
    634     } else {
    635         // Cases for m_writingDirection being VerticalGrowing{Left|Right}
    636         switch (m_cueAlignment) {
    637         case Start:
    638         case Left:
    639             m_displayPosition.second = m_textPosition;
    640             break;
    641         case End:
    642         case Right:
    643             m_displayPosition.second = m_textPosition - m_displaySize;
    644             break;
    645         case Middle:
    646             m_displayPosition.second = m_textPosition - m_displaySize / 2;
    647             break;
    648         default:
    649             ASSERT_NOT_REACHED();
    650         }
    651     }
    652 
    653     // A text track cue has a text track cue computed line position whose value
    654     // is defined in terms of the other aspects of the cue.
    655     m_computedLinePosition = calculateComputedLinePosition();
    656 
    657     // 10.9 Determine the value of whichever of x-position or y-position is not
    658     // yet calculated for cue as per the appropriate rules from the following
    659     // list:
    660     if (m_snapToLines && m_displayPosition.second == undefinedPosition && m_writingDirection == Horizontal)
    661         m_displayPosition.second = 0;
    662 
    663     if (!m_snapToLines && m_displayPosition.second == undefinedPosition && m_writingDirection == Horizontal)
    664         m_displayPosition.second = m_computedLinePosition;
    665 
    666     if (m_snapToLines && m_displayPosition.first == undefinedPosition
    667         && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
    668         m_displayPosition.first = 0;
    669 
    670     if (!m_snapToLines && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
    671         m_displayPosition.first = m_computedLinePosition;
    672 }
    673 
    674 void VTTCue::markFutureAndPastNodes(ContainerNode* root, double previousTimestamp, double movieTime)
    675 {
    676     DEFINE_STATIC_LOCAL(const String, timestampTag, ("timestamp"));
    677 
    678     bool isPastNode = true;
    679     double currentTimestamp = previousTimestamp;
    680     if (currentTimestamp > movieTime)
    681         isPastNode = false;
    682 
    683     for (Node* child = root->firstChild(); child; child = NodeTraversal::next(*child, root)) {
    684         if (child->nodeName() == timestampTag) {
    685             double currentTimestamp;
    686             bool check = VTTParser::collectTimeStamp(child->nodeValue(), currentTimestamp);
    687             ASSERT_UNUSED(check, check);
    688 
    689             if (currentTimestamp > movieTime)
    690                 isPastNode = false;
    691         }
    692 
    693         if (child->isVTTElement()) {
    694             toVTTElement(child)->setIsPastNode(isPastNode);
    695             // Make an elemenet id match a cue id for style matching purposes.
    696             if (!id().isEmpty())
    697                 toElement(child)->setIdAttribute(id());
    698         }
    699     }
    700 }
    701 
    702 void VTTCue::updateDisplayTree(double movieTime)
    703 {
    704     // The display tree may contain WebVTT timestamp objects representing
    705     // timestamps (processing instructions), along with displayable nodes.
    706 
    707     if (!track()->isRendered())
    708         return;
    709 
    710     // Clear the contents of the set.
    711     m_cueBackgroundBox->removeChildren();
    712 
    713     // Update the two sets containing past and future WebVTT objects.
    714     RefPtrWillBeRawPtr<DocumentFragment> referenceTree = createCueRenderingTree();
    715     markFutureAndPastNodes(referenceTree.get(), startTime(), movieTime);
    716     m_cueBackgroundBox->appendChild(referenceTree, ASSERT_NO_EXCEPTION);
    717 }
    718 
    719 PassRefPtrWillBeRawPtr<VTTCueBox> VTTCue::getDisplayTree(const IntSize& videoSize)
    720 {
    721     RefPtrWillBeRawPtr<VTTCueBox> displayTree(ensureDisplayTree());
    722     if (!m_displayTreeShouldChange || !track()->isRendered())
    723         return displayTree.release();
    724 
    725     // 10.1 - 10.10
    726     calculateDisplayParameters();
    727 
    728     // 10.11. Apply the terms of the CSS specifications to nodes within the
    729     // following constraints, thus obtaining a set of CSS boxes positioned
    730     // relative to an initial containing block:
    731     displayTree->removeChildren();
    732 
    733     // The document tree is the tree of WebVTT Node Objects rooted at nodes.
    734 
    735     // The children of the nodes must be wrapped in an anonymous box whose
    736     // 'display' property has the value 'inline'. This is the WebVTT cue
    737     // background box.
    738 
    739     // Note: This is contained by default in m_cueBackgroundBox.
    740     m_cueBackgroundBox->setShadowPseudoId(cueShadowPseudoId());
    741     displayTree->appendChild(m_cueBackgroundBox);
    742 
    743     // FIXME(BUG 79916): Runs of children of WebVTT Ruby Objects that are not
    744     // WebVTT Ruby Text Objects must be wrapped in anonymous boxes whose
    745     // 'display' property has the value 'ruby-base'.
    746 
    747     // FIXME(BUG 79916): Text runs must be wrapped according to the CSS
    748     // line-wrapping rules, except that additionally, regardless of the value of
    749     // the 'white-space' property, lines must be wrapped at the edge of their
    750     // containing blocks, even if doing so requires splitting a word where there
    751     // is no line breaking opportunity. (Thus, normally text wraps as needed,
    752     // but if there is a particularly long word, it does not overflow as it
    753     // normally would in CSS, it is instead forcibly wrapped at the box's edge.)
    754     displayTree->applyCSSProperties(videoSize);
    755 
    756     m_displayTreeShouldChange = false;
    757 
    758     // 10.15. Let cue's text track cue display state have the CSS boxes in
    759     // boxes.
    760     return displayTree.release();
    761 }
    762 
    763 void VTTCue::removeDisplayTree()
    764 {
    765     if (m_notifyRegion && track()->regions()) {
    766         // The region needs to be informed about the cue removal.
    767         VTTRegion* region = track()->regions()->getRegionById(m_regionId);
    768         if (region)
    769             region->willRemoveVTTCueBox(m_displayTree.get());
    770     }
    771 
    772     if (m_displayTree)
    773         m_displayTree->remove(ASSERT_NO_EXCEPTION);
    774 }
    775 
    776 void VTTCue::updateDisplay(const IntSize& videoSize, HTMLDivElement& container)
    777 {
    778     UseCounter::count(document(), UseCounter::VTTCueRender);
    779 
    780     if (m_writingDirection != Horizontal)
    781         UseCounter::count(document(), UseCounter::VTTCueRenderVertical);
    782 
    783     if (!m_snapToLines)
    784         UseCounter::count(document(), UseCounter::VTTCueRenderSnapToLinesFalse);
    785 
    786     if (m_linePosition != undefinedPosition)
    787         UseCounter::count(document(), UseCounter::VTTCueRenderLineNotAuto);
    788 
    789     if (m_textPosition != 50)
    790         UseCounter::count(document(), UseCounter::VTTCueRenderPositionNot50);
    791 
    792     if (m_cueSize != 100)
    793         UseCounter::count(document(), UseCounter::VTTCueRenderSizeNot100);
    794 
    795     if (m_cueAlignment != Middle)
    796         UseCounter::count(document(), UseCounter::VTTCueRenderAlignNotMiddle);
    797 
    798     RefPtrWillBeRawPtr<VTTCueBox> displayBox = getDisplayTree(videoSize);
    799     VTTRegion* region = 0;
    800     if (track()->regions())
    801         region = track()->regions()->getRegionById(regionId());
    802 
    803     if (!region) {
    804         // If cue has an empty text track cue region identifier or there is no
    805         // WebVTT region whose region identifier is identical to cue's text
    806         // track cue region identifier, run the following substeps:
    807         if (displayBox->hasChildren() && !container.contains(displayBox.get())) {
    808             // Note: the display tree of a cue is removed when the active flag of the cue is unset.
    809             container.appendChild(displayBox);
    810         }
    811     } else {
    812         // Let region be the WebVTT region whose region identifier
    813         // matches the text track cue region identifier of cue.
    814         RefPtrWillBeRawPtr<HTMLDivElement> regionNode = region->getDisplayTree(document());
    815 
    816         // Append the region to the viewport, if it was not already.
    817         if (!container.contains(regionNode.get()))
    818             container.appendChild(regionNode);
    819 
    820         region->appendVTTCueBox(displayBox);
    821     }
    822 }
    823 
    824 std::pair<double, double> VTTCue::getPositionCoordinates() const
    825 {
    826     // This method is used for setting x and y when snap to lines is not set.
    827     std::pair<double, double> coordinates;
    828 
    829     if (m_writingDirection == Horizontal && m_displayDirection == CSSValueLtr) {
    830         coordinates.first = m_textPosition;
    831         coordinates.second = m_computedLinePosition;
    832 
    833         return coordinates;
    834     }
    835 
    836     if (m_writingDirection == Horizontal && m_displayDirection == CSSValueRtl) {
    837         coordinates.first = 100 - m_textPosition;
    838         coordinates.second = m_computedLinePosition;
    839 
    840         return coordinates;
    841     }
    842 
    843     if (m_writingDirection == VerticalGrowingLeft) {
    844         coordinates.first = 100 - m_computedLinePosition;
    845         coordinates.second = m_textPosition;
    846 
    847         return coordinates;
    848     }
    849 
    850     if (m_writingDirection == VerticalGrowingRight) {
    851         coordinates.first = m_computedLinePosition;
    852         coordinates.second = m_textPosition;
    853 
    854         return coordinates;
    855     }
    856 
    857     ASSERT_NOT_REACHED();
    858 
    859     return coordinates;
    860 }
    861 
    862 VTTCue::CueSetting VTTCue::settingName(VTTScanner& input)
    863 {
    864     CueSetting parsedSetting = None;
    865     if (input.scan("vertical"))
    866         parsedSetting = Vertical;
    867     else if (input.scan("line"))
    868         parsedSetting = Line;
    869     else if (input.scan("position"))
    870         parsedSetting = Position;
    871     else if (input.scan("size"))
    872         parsedSetting = Size;
    873     else if (input.scan("align"))
    874         parsedSetting = Align;
    875     else if (RuntimeEnabledFeatures::webVTTRegionsEnabled() && input.scan("region"))
    876         parsedSetting = RegionId;
    877     // Verify that a ':' follows.
    878     if (parsedSetting != None && input.scan(':'))
    879         return parsedSetting;
    880     return None;
    881 }
    882 
    883 // Used for 'position' and 'size'.
    884 static bool scanPercentage(VTTScanner& input, const VTTScanner::Run& valueRun, int& number)
    885 {
    886     // 1. If value contains any characters other than U+0025 PERCENT SIGN
    887     //    characters (%) and characters in the range U+0030 DIGIT ZERO (0) to
    888     //    U+0039 DIGIT NINE (9), then jump to the step labeled next setting.
    889     // 2. If value does not contain at least one character in the range U+0030
    890     //    DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump to the step
    891     //    labeled next setting.
    892     if (!input.scanDigits(number))
    893         return false;
    894 
    895     // 3. If any character in value other than the last character is a U+0025
    896     //    PERCENT SIGN character (%), then jump to the step labeled next
    897     //    setting.
    898     // 4. If the last character in value is not a U+0025 PERCENT SIGN character
    899     //    (%), then jump to the step labeled next setting.
    900     if (!input.scan('%') || !input.isAt(valueRun.end()))
    901         return false;
    902 
    903     // 5. Ignoring the trailing percent sign, interpret value as an integer,
    904     //    and let number be that number.
    905     // 6. If number is not in the range 0  number  100, then jump to the step
    906     //    labeled next setting.
    907     return number >= 0 && number <= 100;
    908 }
    909 
    910 void VTTCue::parseSettings(const String& inputString)
    911 {
    912     VTTScanner input(inputString);
    913 
    914     while (!input.isAtEnd()) {
    915 
    916         // The WebVTT cue settings part of a WebVTT cue consists of zero or more of the following components, in any order,
    917         // separated from each other by one or more U+0020 SPACE characters or U+0009 CHARACTER TABULATION (tab) characters.
    918         input.skipWhile<VTTParser::isValidSettingDelimiter>();
    919 
    920         if (input.isAtEnd())
    921             break;
    922 
    923         // When the user agent is to parse the WebVTT settings given by a string input for a text track cue cue,
    924         // the user agent must run the following steps:
    925         // 1. Let settings be the result of splitting input on spaces.
    926         // 2. For each token setting in the list settings, run the following substeps:
    927         //    1. If setting does not contain a U+003A COLON character (:), or if the first U+003A COLON character (:)
    928         //       in setting is either the first or last character of setting, then jump to the step labeled next setting.
    929         //    2. Let name be the leading substring of setting up to and excluding the first U+003A COLON character (:) in that string.
    930         CueSetting name = settingName(input);
    931 
    932         // 3. Let value be the trailing substring of setting starting from the character immediately after the first U+003A COLON character (:) in that string.
    933         VTTScanner::Run valueRun = input.collectUntil<VTTParser::isValidSettingDelimiter>();
    934 
    935         // 4. Run the appropriate substeps that apply for the value of name, as follows:
    936         switch (name) {
    937         case Vertical: {
    938             // If name is a case-sensitive match for "vertical"
    939             // 1. If value is a case-sensitive match for the string "rl", then let cue's text track cue writing direction
    940             //    be vertical growing left.
    941             if (input.scanRun(valueRun, verticalGrowingLeftKeyword()))
    942                 m_writingDirection = VerticalGrowingLeft;
    943 
    944             // 2. Otherwise, if value is a case-sensitive match for the string "lr", then let cue's text track cue writing
    945             //    direction be vertical growing right.
    946             else if (input.scanRun(valueRun, verticalGrowingRightKeyword()))
    947                 m_writingDirection = VerticalGrowingRight;
    948             break;
    949         }
    950         case Line: {
    951             // 1-2 - Collect chars that are either '-', '%', or a digit.
    952             // 1. If value contains any characters other than U+002D HYPHEN-MINUS characters (-), U+0025 PERCENT SIGN
    953             //    characters (%), and characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump
    954             //    to the step labeled next setting.
    955             bool isNegative = input.scan('-');
    956             int linePosition;
    957             unsigned numDigits = input.scanDigits(linePosition);
    958             bool isPercentage = input.scan('%');
    959 
    960             if (!input.isAt(valueRun.end()))
    961                 break;
    962 
    963             // 2. If value does not contain at least one character in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT
    964             //    NINE (9), then jump to the step labeled next setting.
    965             // 3. If any character in value other than the first character is a U+002D HYPHEN-MINUS character (-), then
    966             //    jump to the step labeled next setting.
    967             // 4. If any character in value other than the last character is a U+0025 PERCENT SIGN character (%), then
    968             //    jump to the step labeled next setting.
    969 
    970             // 5. If the first character in value is a U+002D HYPHEN-MINUS character (-) and the last character in value is a
    971             //    U+0025 PERCENT SIGN character (%), then jump to the step labeled next setting.
    972             if (!numDigits || (isPercentage && isNegative))
    973                 break;
    974 
    975             // 6. Ignoring the trailing percent sign, if any, interpret value as a (potentially signed) integer, and
    976             //    let number be that number.
    977             // 7. If the last character in value is a U+0025 PERCENT SIGN character (%), but number is not in the range
    978             //    0  number  100, then jump to the step labeled next setting.
    979             // 8. Let cue's text track cue line position be number.
    980             // 9. If the last character in value is a U+0025 PERCENT SIGN character (%), then let cue's text track cue
    981             //    snap-to-lines flag be false. Otherwise, let it be true.
    982             if (isPercentage) {
    983                 if (linePosition < 0 || linePosition > 100)
    984                     break;
    985                 // 10 - If '%' then set snap-to-lines flag to false.
    986                 m_snapToLines = false;
    987             } else {
    988                 if (isNegative)
    989                     linePosition = -linePosition;
    990                 m_snapToLines = true;
    991             }
    992             m_linePosition = linePosition;
    993             break;
    994         }
    995         case Position: {
    996             int number;
    997             // Steps 1 - 6.
    998             if (!scanPercentage(input, valueRun, number))
    999                 break;
   1000 
   1001             // 7. Let cue's text track cue text position be number.
   1002             m_textPosition = number;
   1003             break;
   1004         }
   1005         case Size: {
   1006             int number;
   1007             // Steps 1 - 6.
   1008             if (!scanPercentage(input, valueRun, number))
   1009                 break;
   1010 
   1011             // 7. Let cue's text track cue size be number.
   1012             m_cueSize = number;
   1013             break;
   1014         }
   1015         case Align: {
   1016             // 1. If value is a case-sensitive match for the string "start", then let cue's text track cue alignment be start alignment.
   1017             if (input.scanRun(valueRun, startKeyword()))
   1018                 m_cueAlignment = Start;
   1019 
   1020             // 2. If value is a case-sensitive match for the string "middle", then let cue's text track cue alignment be middle alignment.
   1021             else if (input.scanRun(valueRun, middleKeyword()))
   1022                 m_cueAlignment = Middle;
   1023 
   1024             // 3. If value is a case-sensitive match for the string "end", then let cue's text track cue alignment be end alignment.
   1025             else if (input.scanRun(valueRun, endKeyword()))
   1026                 m_cueAlignment = End;
   1027 
   1028             // 4. If value is a case-sensitive match for the string "left", then let cue's text track cue alignment be left alignment.
   1029             else if (input.scanRun(valueRun, leftKeyword()))
   1030                 m_cueAlignment = Left;
   1031 
   1032             // 5. If value is a case-sensitive match for the string "right", then let cue's text track cue alignment be right alignment.
   1033             else if (input.scanRun(valueRun, rightKeyword()))
   1034                 m_cueAlignment = Right;
   1035             break;
   1036         }
   1037         case RegionId:
   1038             m_regionId = input.extractString(valueRun);
   1039             break;
   1040         case None:
   1041             break;
   1042         }
   1043 
   1044         // Make sure the entire run is consumed.
   1045         input.skipRun(valueRun);
   1046     }
   1047 
   1048     // If cue's line position is not auto or cue's size is not 100 or cue's
   1049     // writing direction is not horizontal, but cue's region identifier is not
   1050     // the empty string, let cue's region identifier be the empty string.
   1051     if (m_regionId.isEmpty())
   1052         return;
   1053 
   1054     if (m_linePosition != undefinedPosition || m_cueSize != 100 || m_writingDirection != Horizontal)
   1055         m_regionId = emptyString();
   1056 }
   1057 
   1058 CSSValueID VTTCue::getCSSAlignment() const
   1059 {
   1060     return displayAlignmentMap[m_cueAlignment];
   1061 }
   1062 
   1063 CSSValueID VTTCue::getCSSWritingDirection() const
   1064 {
   1065     return m_displayDirection;
   1066 }
   1067 
   1068 CSSValueID VTTCue::getCSSWritingMode() const
   1069 {
   1070     return displayWritingModeMap[m_writingDirection];
   1071 }
   1072 
   1073 int VTTCue::getCSSSize() const
   1074 {
   1075     ASSERT(m_displaySize != undefinedSize);
   1076     return m_displaySize;
   1077 }
   1078 
   1079 std::pair<double, double> VTTCue::getCSSPosition() const
   1080 {
   1081     if (!m_snapToLines)
   1082         return getPositionCoordinates();
   1083 
   1084     return m_displayPosition;
   1085 }
   1086 
   1087 ExecutionContext* VTTCue::executionContext() const
   1088 {
   1089     ASSERT(m_cueBackgroundBox);
   1090     return m_cueBackgroundBox->executionContext();
   1091 }
   1092 
   1093 Document& VTTCue::document() const
   1094 {
   1095     ASSERT(m_cueBackgroundBox);
   1096     return m_cueBackgroundBox->document();
   1097 }
   1098 
   1099 void VTTCue::trace(Visitor* visitor)
   1100 {
   1101     visitor->trace(m_vttNodeTree);
   1102     visitor->trace(m_cueBackgroundBox);
   1103     visitor->trace(m_displayTree);
   1104     TextTrackCue::trace(visitor);
   1105 }
   1106 
   1107 } // namespace WebCore
   1108