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