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