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