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