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