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