Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(213)

Side by Side Diff: Source/core/html/track/TextTrackCue.cpp

Issue 77853002: Move VTT functionality from TextTrackCue.* to VTTCue.* (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: Address comments in https://codereview.chromium.org/77853002/#msg2. Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 /* 1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved. 2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2011, 2012, 2013 Apple Inc. All rights reserved. 3 * Copyright (C) 2011, 2012, 2013 Apple Inc. All rights reserved.
4 * 4 *
5 * Redistribution and use in source and binary forms, with or without 5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are 6 * modification, are permitted provided that the following conditions are
7 * met: 7 * met:
8 * 8 *
9 * * Redistributions of source code must retain the above copyright 9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer. 10 * notice, this list of conditions and the following disclaimer.
(...skipping 14 matching lines...) Expand all
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 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. 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */ 30 */
31 31
32 #include "config.h" 32 #include "config.h"
33 #include "core/html/track/TextTrackCue.h" 33 #include "core/html/track/TextTrackCue.h"
34 34
35 #include "CSSPropertyNames.h"
36 #include "CSSValueKeywords.h"
37 #include "RuntimeEnabledFeatures.h"
38 #include "bindings/v8/ExceptionStatePlaceholder.h" 35 #include "bindings/v8/ExceptionStatePlaceholder.h"
39 #include "core/dom/DocumentFragment.h"
40 #include "core/events/Event.h" 36 #include "core/events/Event.h"
41 #include "core/dom/NodeTraversal.h"
42 #include "core/html/HTMLDivElement.h" 37 #include "core/html/HTMLDivElement.h"
43 #include "core/html/track/TextTrack.h" 38 #include "core/html/track/TextTrack.h"
44 #include "core/html/track/TextTrackCueList.h" 39 #include "core/html/track/TextTrackCueList.h"
45 #include "core/html/track/vtt/VTTElement.h"
46 #include "core/html/track/vtt/VTTParser.h"
47 #include "core/html/track/vtt/VTTRegionList.h"
48 #include "core/rendering/RenderTextTrackCue.h"
49 #include "wtf/MathExtras.h"
50 #include "wtf/text/StringBuilder.h"
51 40
52 namespace WebCore { 41 namespace WebCore {
53 42
54 static const int invalidCueIndex = -1; 43 static const int invalidCueIndex = -1;
55 static const int undefinedPosition = -1;
56 44
57 static const CSSValueID displayWritingModeMap[] = { 45 // ----------------------------
58 CSSValueHorizontalTb, CSSValueVerticalRl, CSSValueVerticalLr
59 };
60 COMPILE_ASSERT(WTF_ARRAY_LENGTH(displayWritingModeMap) == TextTrackCue::NumberOf WritingDirections,
61 displayWritingModeMap_has_wrong_size);
62 46
63 static const CSSValueID displayAlignmentMap[] = { 47 TextTrackCueBox::TextTrackCueBox(Document& document)
64 CSSValueStart, CSSValueCenter, CSSValueEnd, CSSValueLeft, CSSValueRight 48 : HTMLDivElement(document)
65 };
66 COMPILE_ASSERT(WTF_ARRAY_LENGTH(displayAlignmentMap) == TextTrackCue::NumberOfAl ignments,
67 displayAlignmentMap_has_wrong_size);
68
69 static const String& startKeyword()
70 { 49 {
71 DEFINE_STATIC_LOCAL(const String, start, ("start"));
72 return start;
73 }
74
75 static const String& middleKeyword()
76 {
77 DEFINE_STATIC_LOCAL(const String, middle, ("middle"));
78 return middle;
79 }
80
81 static const String& endKeyword()
82 {
83 DEFINE_STATIC_LOCAL(const String, end, ("end"));
84 return end;
85 }
86
87 static const String& leftKeyword()
88 {
89 DEFINE_STATIC_LOCAL(const String, left, ("left"));
90 return left;
91 }
92
93 static const String& rightKeyword()
94 {
95 DEFINE_STATIC_LOCAL(const String, right, ("right"));
96 return right;
97 }
98
99 static const String& horizontalKeyword()
100 {
101 return emptyString();
102 }
103
104 static const String& verticalGrowingLeftKeyword()
105 {
106 DEFINE_STATIC_LOCAL(const String, verticalrl, ("rl"));
107 return verticalrl;
108 }
109
110 static const String& verticalGrowingRightKeyword()
111 {
112 DEFINE_STATIC_LOCAL(const String, verticallr, ("lr"));
113 return verticallr;
114 } 50 }
115 51
116 // ---------------------------- 52 // ----------------------------
117 53
118 TextTrackCueBox::TextTrackCueBox(Document& document, TextTrackCue* cue) 54 TextTrackCue::TextTrackCue(double start, double end)
119 : HTMLDivElement(document)
120 , m_cue(cue)
121 {
122 setPart(textTrackCueBoxShadowPseudoId());
123 }
124
125 TextTrackCue* TextTrackCueBox::getCue() const
126 {
127 return m_cue;
128 }
129
130 void TextTrackCueBox::applyCSSProperties(const IntSize&)
131 {
132 // FIXME: Apply all the initial CSS positioning properties. http://wkb.ug/79 916
133 if (!m_cue->regionId().isEmpty()) {
134 setInlineStyleProperty(CSSPropertyPosition, CSSValueRelative);
135 return;
136 }
137
138 // 3.5.1 On the (root) List of WebVTT Node Objects:
139
140 // the 'position' property must be set to 'absolute'
141 setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
142
143 // the 'unicode-bidi' property must be set to 'plaintext'
144 setInlineStyleProperty(CSSPropertyUnicodeBidi, CSSValueWebkitPlaintext);
145
146 // the 'direction' property must be set to direction
147 setInlineStyleProperty(CSSPropertyDirection, m_cue->getCSSWritingDirection() );
148
149 // the 'writing-mode' property must be set to writing-mode
150 setInlineStyleProperty(CSSPropertyWebkitWritingMode, m_cue->getCSSWritingMod e());
151
152 std::pair<float, float> position = m_cue->getCSSPosition();
153
154 // the 'top' property must be set to top,
155 setInlineStyleProperty(CSSPropertyTop, position.second, CSSPrimitiveValue::C SS_PERCENTAGE);
156
157 // the 'left' property must be set to left
158 setInlineStyleProperty(CSSPropertyLeft, position.first, CSSPrimitiveValue::C SS_PERCENTAGE);
159
160 // the 'width' property must be set to width, and the 'height' property mus t be set to height
161 if (m_cue->vertical() == horizontalKeyword()) {
162 setInlineStyleProperty(CSSPropertyWidth, static_cast<double>(m_cue->getC SSSize()), CSSPrimitiveValue::CSS_PERCENTAGE);
163 setInlineStyleProperty(CSSPropertyHeight, CSSValueAuto);
164 } else {
165 setInlineStyleProperty(CSSPropertyWidth, CSSValueAuto);
166 setInlineStyleProperty(CSSPropertyHeight, static_cast<double>(m_cue->get CSSSize()), CSSPrimitiveValue::CSS_PERCENTAGE);
167 }
168
169 // The 'text-align' property on the (root) List of WebVTT Node Objects must
170 // be set to the value in the second cell of the row of the table below
171 // whose first cell is the value of the corresponding cue's text track cue
172 // alignment:
173 setInlineStyleProperty(CSSPropertyTextAlign, m_cue->getCSSAlignment());
174
175 if (!m_cue->snapToLines()) {
176 // 10.13.1 Set up x and y:
177 // Note: x and y are set through the CSS left and top above.
178
179 // 10.13.2 Position the boxes in boxes such that the point x% along the
180 // width of the bounding box of the boxes in boxes is x% of the way
181 // across the width of the video's rendering area, and the point y%
182 // along the height of the bounding box of the boxes in boxes is y%
183 // of the way across the height of the video's rendering area, while
184 // maintaining the relative positions of the boxes in boxes to each
185 // other.
186 setInlineStyleProperty(CSSPropertyWebkitTransform,
187 String::format("translate(-%.2f%%, -%.2f%%)", position.first, po sition.second));
188
189 setInlineStyleProperty(CSSPropertyWhiteSpace, CSSValuePre);
190 }
191 }
192
193 const AtomicString& TextTrackCueBox::textTrackCueBoxShadowPseudoId()
194 {
195 DEFINE_STATIC_LOCAL(const AtomicString, trackDisplayBoxShadowPseudoId, ("-we bkit-media-text-track-display", AtomicString::ConstructFromLiteral));
196 return trackDisplayBoxShadowPseudoId;
197 }
198
199 RenderObject* TextTrackCueBox::createRenderer(RenderStyle*)
200 {
201 return new RenderTextTrackCue(this);
202 }
203
204 // ----------------------------
205
206 TextTrackCue::TextTrackCue(Document& document, double start, double end, const S tring& content)
207 : m_startTime(start) 55 : m_startTime(start)
208 , m_endTime(end) 56 , m_endTime(end)
209 , m_content(content)
210 , m_linePosition(undefinedPosition)
211 , m_computedLinePosition(undefinedPosition)
212 , m_textPosition(50)
213 , m_cueSize(100)
214 , m_cueIndex(invalidCueIndex) 57 , m_cueIndex(invalidCueIndex)
215 , m_writingDirection(Horizontal)
216 , m_cueAlignment(Middle)
217 , m_webVTTNodeTree(0)
218 , m_track(0) 58 , m_track(0)
219 , m_isActive(false) 59 , m_isActive(false)
220 , m_pauseOnExit(false) 60 , m_pauseOnExit(false)
221 , m_snapToLines(true)
222 , m_cueBackgroundBox(HTMLDivElement::create(document))
223 , m_displayTreeShouldChange(true)
224 , m_displayDirection(CSSValueLtr)
225 , m_notifyRegion(true)
226 { 61 {
227 } 62 }
228 63
229 TextTrackCue::~TextTrackCue()
230 {
231 displayTreeInternal()->remove(ASSERT_NO_EXCEPTION);
232 }
233
234 PassRefPtr<TextTrackCueBox> TextTrackCue::displayTreeInternal()
235 {
236 if (!m_displayTree)
237 m_displayTree = TextTrackCueBox::create(document(), this);
238 return m_displayTree;
239 }
240
241 void TextTrackCue::cueWillChange() 64 void TextTrackCue::cueWillChange()
242 { 65 {
243 if (m_track) 66 if (m_track)
244 m_track->cueWillChange(this); 67 m_track->cueWillChange(this);
245 } 68 }
246 69
247 void TextTrackCue::cueDidChange() 70 void TextTrackCue::cueDidChange()
248 { 71 {
249 if (m_track) 72 if (m_track)
250 m_track->cueDidChange(this); 73 m_track->cueDidChange(this);
251
252 m_displayTreeShouldChange = true;
253 } 74 }
254 75
255 TextTrack* TextTrackCue::track() const 76 TextTrack* TextTrackCue::track() const
256 { 77 {
257 return m_track; 78 return m_track;
258 } 79 }
259 80
260 void TextTrackCue::setTrack(TextTrack* track) 81 void TextTrackCue::setTrack(TextTrack* track)
261 { 82 {
262 m_track = track; 83 m_track = track;
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
309 void TextTrackCue::setPauseOnExit(bool value) 130 void TextTrackCue::setPauseOnExit(bool value)
310 { 131 {
311 if (m_pauseOnExit == value) 132 if (m_pauseOnExit == value)
312 return; 133 return;
313 134
314 cueWillChange(); 135 cueWillChange();
315 m_pauseOnExit = value; 136 m_pauseOnExit = value;
316 cueDidChange(); 137 cueDidChange();
317 } 138 }
318 139
319 const String& TextTrackCue::vertical() const
320 {
321 switch (m_writingDirection) {
322 case Horizontal:
323 return horizontalKeyword();
324 case VerticalGrowingLeft:
325 return verticalGrowingLeftKeyword();
326 case VerticalGrowingRight:
327 return verticalGrowingRightKeyword();
328 default:
329 ASSERT_NOT_REACHED();
330 return emptyString();
331 }
332 }
333
334 void TextTrackCue::setVertical(const String& value, ExceptionState& exceptionSta te)
335 {
336 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-ele ment.html#dom-texttrackcue-vertical
337 // On setting, the text track cue writing direction must be set to the value given
338 // in the first cell of the row in the table above whose second cell is a
339 // case-sensitive match for the new value, if any. If none of the values mat ch, then
340 // the user agent must instead throw a SyntaxError exception.
341
342 WritingDirection direction = m_writingDirection;
343 if (value == horizontalKeyword())
344 direction = Horizontal;
345 else if (value == verticalGrowingLeftKeyword())
346 direction = VerticalGrowingLeft;
347 else if (value == verticalGrowingRightKeyword())
348 direction = VerticalGrowingRight;
349 else
350 exceptionState.throwUninformativeAndGenericDOMException(SyntaxError);
351
352 if (direction == m_writingDirection)
353 return;
354
355 cueWillChange();
356 m_writingDirection = direction;
357 cueDidChange();
358 }
359
360 void TextTrackCue::setSnapToLines(bool value)
361 {
362 if (m_snapToLines == value)
363 return;
364
365 cueWillChange();
366 m_snapToLines = value;
367 cueDidChange();
368 }
369
370 void TextTrackCue::setLine(int position, ExceptionState& exceptionState)
371 {
372 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-ele ment.html#dom-texttrackcue-line
373 // On setting, if the text track cue snap-to-lines flag is not set, and the new
374 // value is negative or greater than 100, then throw an IndexSizeError excep tion.
375 if (!m_snapToLines && (position < 0 || position > 100)) {
376 exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError);
377 return;
378 }
379
380 // Otherwise, set the text track cue line position to the new value.
381 if (m_linePosition == position)
382 return;
383
384 cueWillChange();
385 m_linePosition = position;
386 m_computedLinePosition = calculateComputedLinePosition();
387 cueDidChange();
388 }
389
390 void TextTrackCue::setPosition(int position, ExceptionState& exceptionState)
391 {
392 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-ele ment.html#dom-texttrackcue-position
393 // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError exception.
394 // Otherwise, set the text track cue text position to the new value.
395 if (position < 0 || position > 100) {
396 exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError);
397 return;
398 }
399
400 // Otherwise, set the text track cue line position to the new value.
401 if (m_textPosition == position)
402 return;
403
404 cueWillChange();
405 m_textPosition = position;
406 cueDidChange();
407 }
408
409 void TextTrackCue::setSize(int size, ExceptionState& exceptionState)
410 {
411 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-ele ment.html#dom-texttrackcue-size
412 // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError
413 // exception. Otherwise, set the text track cue size to the new value.
414 if (size < 0 || size > 100) {
415 exceptionState.throwUninformativeAndGenericDOMException(IndexSizeError);
416 return;
417 }
418
419 // Otherwise, set the text track cue line position to the new value.
420 if (m_cueSize == size)
421 return;
422
423 cueWillChange();
424 m_cueSize = size;
425 cueDidChange();
426 }
427
428 const String& TextTrackCue::align() const
429 {
430 switch (m_cueAlignment) {
431 case Start:
432 return startKeyword();
433 case Middle:
434 return middleKeyword();
435 case End:
436 return endKeyword();
437 case Left:
438 return leftKeyword();
439 case Right:
440 return rightKeyword();
441 default:
442 ASSERT_NOT_REACHED();
443 return emptyString();
444 }
445 }
446
447 void TextTrackCue::setAlign(const String& value, ExceptionState& exceptionState)
448 {
449 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-ele ment.html#dom-texttrackcue-align
450 // On setting, the text track cue alignment must be set to the value given i n the
451 // first cell of the row in the table above whose second cell is a case-sens itive
452 // match for the new value, if any. If none of the values match, then the us er
453 // agent must instead throw a SyntaxError exception.
454
455 CueAlignment alignment = m_cueAlignment;
456 if (value == startKeyword())
457 alignment = Start;
458 else if (value == middleKeyword())
459 alignment = Middle;
460 else if (value == endKeyword())
461 alignment = End;
462 else if (value == leftKeyword())
463 alignment = Left;
464 else if (value == rightKeyword())
465 alignment = Right;
466 else
467 exceptionState.throwUninformativeAndGenericDOMException(SyntaxError);
468
469 if (alignment == m_cueAlignment)
470 return;
471
472 cueWillChange();
473 m_cueAlignment = alignment;
474 cueDidChange();
475 }
476
477 void TextTrackCue::setText(const String& text)
478 {
479 if (m_content == text)
480 return;
481
482 cueWillChange();
483 // Clear the document fragment but don't bother to create it again just yet as we can do that
484 // when it is requested.
485 m_webVTTNodeTree = 0;
486 m_content = text;
487 cueDidChange();
488 }
489
490 int TextTrackCue::cueIndex() 140 int TextTrackCue::cueIndex()
491 { 141 {
492 if (m_cueIndex == invalidCueIndex) 142 if (m_cueIndex == invalidCueIndex)
493 m_cueIndex = track()->cues()->getCueIndex(this); 143 m_cueIndex = track()->cues()->getCueIndex(this);
494 144
495 return m_cueIndex; 145 return m_cueIndex;
496 } 146 }
497 147
498 void TextTrackCue::invalidateCueIndex() 148 void TextTrackCue::invalidateCueIndex()
499 { 149 {
500 m_cueIndex = invalidCueIndex; 150 m_cueIndex = invalidCueIndex;
501 } 151 }
502 152
503 void TextTrackCue::createVTTNodeTree()
504 {
505 if (!m_webVTTNodeTree)
506 m_webVTTNodeTree = VTTParser::createDocumentFragmentFromCueText(document (), m_content);
507 }
508
509 void TextTrackCue::copyVTTNodeToDOMTree(ContainerNode* webVTTNode, ContainerNode * parent)
510 {
511 for (Node* node = webVTTNode->firstChild(); node; node = node->nextSibling() ) {
512 RefPtr<Node> clonedNode;
513 if (node->isVTTElement())
514 clonedNode = toVTTElement(node)->createEquivalentHTMLElement(documen t());
515 else
516 clonedNode = node->cloneNode(false);
517 parent->appendChild(clonedNode);
518 if (node->isContainerNode())
519 copyVTTNodeToDOMTree(toContainerNode(node), toContainerNode(clonedNo de));
520 }
521 }
522
523 PassRefPtr<DocumentFragment> TextTrackCue::getCueAsHTML()
524 {
525 createVTTNodeTree();
526 RefPtr<DocumentFragment> clonedFragment = DocumentFragment::create(document( ));
527 copyVTTNodeToDOMTree(m_webVTTNodeTree.get(), clonedFragment.get());
528 return clonedFragment.release();
529 }
530
531 PassRefPtr<DocumentFragment> TextTrackCue::createCueRenderingTree()
532 {
533 RefPtr<DocumentFragment> clonedFragment;
534 createVTTNodeTree();
535 clonedFragment = DocumentFragment::create(document());
536 m_webVTTNodeTree->cloneChildNodes(clonedFragment.get());
537 return clonedFragment.release();
538 }
539
540 bool TextTrackCue::dispatchEvent(PassRefPtr<Event> event) 153 bool TextTrackCue::dispatchEvent(PassRefPtr<Event> event)
541 { 154 {
542 // When a TextTrack's mode is disabled: no cues are active, no events fired. 155 // When a TextTrack's mode is disabled: no cues are active, no events fired.
543 if (!track() || track()->mode() == TextTrack::disabledKeyword()) 156 if (!track() || track()->mode() == TextTrack::disabledKeyword())
544 return false; 157 return false;
545 158
546 return EventTarget::dispatchEvent(event); 159 return EventTarget::dispatchEvent(event);
547 } 160 }
548 161
549 void TextTrackCue::setRegionId(const String& regionId)
550 {
551 if (m_regionId == regionId)
552 return;
553
554 cueWillChange();
555 m_regionId = regionId;
556 cueDidChange();
557 }
558
559 void TextTrackCue::notifyRegionWhenRemovingDisplayTree(bool notifyRegion)
560 {
561 m_notifyRegion = notifyRegion;
562 }
563
564 bool TextTrackCue::isActive() 162 bool TextTrackCue::isActive()
565 { 163 {
566 return m_isActive && track() && track()->mode() != TextTrack::disabledKeywor d(); 164 return m_isActive && track() && track()->mode() != TextTrack::disabledKeywor d();
567 } 165 }
568 166
569 void TextTrackCue::setIsActive(bool active) 167 void TextTrackCue::setIsActive(bool active)
570 { 168 {
571 m_isActive = active; 169 m_isActive = active;
572 170
573 // Remove the display tree as soon as the cue becomes inactive. 171 // Remove the display tree as soon as the cue becomes inactive.
574 if (!active) 172 if (!active)
575 removeDisplayTree(); 173 removeDisplayTree();
576 } 174 }
577 175
578 int TextTrackCue::calculateComputedLinePosition()
579 {
580 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-ele ment.html#text-track-cue-computed-line-position
581
582 // If the text track cue line position is numeric, then that is the text
583 // track cue computed line position.
584 if (m_linePosition != undefinedPosition)
585 return m_linePosition;
586
587 // If the text track cue snap-to-lines flag of the text track cue is not
588 // set, the text track cue computed line position is the value 100;
589 if (!m_snapToLines)
590 return 100;
591
592 // Otherwise, it is the value returned by the following algorithm:
593
594 // If cue is not associated with a text track, return -1 and abort these
595 // steps.
596 if (!track())
597 return -1;
598
599 // Let n be the number of text tracks whose text track mode is showing or
600 // showing by default and that are in the media element's list of text
601 // tracks before track.
602 int n = track()->trackIndexRelativeToRenderedTracks();
603
604 // Increment n by one.
605 n++;
606
607 // Negate n.
608 n = -n;
609
610 return n;
611 }
612
613 static bool isCueParagraphSeparator(UChar character)
614 {
615 // Within a cue, paragraph boundaries are only denoted by Type B characters,
616 // such as U+000A LINE FEED (LF), U+0085 NEXT LINE (NEL), and U+2029 PARAGRA PH SEPARATOR.
617 return WTF::Unicode::category(character) & WTF::Unicode::Separator_Paragraph ;
618 }
619
620 void TextTrackCue::determineTextDirection()
621 {
622 DEFINE_STATIC_LOCAL(const String, rtTag, ("rt"));
623 createVTTNodeTree();
624
625 // Apply the Unicode Bidirectional Algorithm's Paragraph Level steps to the
626 // concatenation of the values of each WebVTT Text Object in nodes, in a
627 // pre-order, depth-first traversal, excluding WebVTT Ruby Text Objects and
628 // their descendants.
629 StringBuilder paragraphBuilder;
630 for (Node* node = m_webVTTNodeTree->firstChild(); node; node = NodeTraversal ::next(*node, m_webVTTNodeTree.get())) {
631 if (!node->isTextNode() || node->localName() == rtTag)
632 continue;
633
634 paragraphBuilder.append(node->nodeValue());
635 }
636
637 String paragraph = paragraphBuilder.toString();
638 if (!paragraph.length())
639 return;
640
641 for (size_t i = 0; i < paragraph.length(); ++i) {
642 UChar current = paragraph[i];
643 if (!current || isCueParagraphSeparator(current))
644 return;
645
646 if (UChar current = paragraph[i]) {
647 WTF::Unicode::Direction charDirection = WTF::Unicode::direction(curr ent);
648 if (charDirection == WTF::Unicode::LeftToRight) {
649 m_displayDirection = CSSValueLtr;
650 return;
651 }
652 if (charDirection == WTF::Unicode::RightToLeft
653 || charDirection == WTF::Unicode::RightToLeftArabic) {
654 m_displayDirection = CSSValueRtl;
655 return;
656 }
657 }
658 }
659 }
660
661 void TextTrackCue::calculateDisplayParameters()
662 {
663 // Steps 10.2, 10.3
664 determineTextDirection();
665
666 // 10.4 If the text track cue writing direction is horizontal, then let
667 // block-flow be 'tb'. Otherwise, if the text track cue writing direction is
668 // vertical growing left, then let block-flow be 'lr'. Otherwise, the text
669 // track cue writing direction is vertical growing right; let block-flow be
670 // 'rl'.
671
672 // The above step is done through the writing direction static map.
673
674 // 10.5 Determine the value of maximum size for cue as per the appropriate
675 // rules from the following list:
676 int maximumSize = m_textPosition;
677 if ((m_writingDirection == Horizontal && m_cueAlignment == Start && m_displa yDirection == CSSValueLtr)
678 || (m_writingDirection == Horizontal && m_cueAlignment == End && m_d isplayDirection == CSSValueRtl)
679 || (m_writingDirection == Horizontal && m_cueAlignment == Left)
680 || (m_writingDirection == VerticalGrowingLeft && (m_cueAlignment == Start || m_cueAlignment == Left))
681 || (m_writingDirection == VerticalGrowingRight && (m_cueAlignment == Start || m_cueAlignment == Left))) {
682 maximumSize = 100 - m_textPosition;
683 } else if ((m_writingDirection == Horizontal && m_cueAlignment == End && m_d isplayDirection == CSSValueLtr)
684 || (m_writingDirection == Horizontal && m_cueAlignment == Start && m _displayDirection == CSSValueRtl)
685 || (m_writingDirection == Horizontal && m_cueAlignment == Right)
686 || (m_writingDirection == VerticalGrowingLeft && (m_cueAlignment == End || m_cueAlignment == Right))
687 || (m_writingDirection == VerticalGrowingRight && (m_cueAlignment == End || m_cueAlignment == Right))) {
688 maximumSize = m_textPosition;
689 } else if (m_cueAlignment == Middle) {
690 maximumSize = m_textPosition <= 50 ? m_textPosition : (100 - m_textPosit ion);
691 maximumSize = maximumSize * 2;
692 } else {
693 ASSERT_NOT_REACHED();
694 }
695
696 // 10.6 If the text track cue size is less than maximum size, then let size
697 // be text track cue size. Otherwise, let size be maximum size.
698 m_displaySize = std::min(m_cueSize, maximumSize);
699
700 // FIXME: Understand why step 10.7 is missing (just a copy/paste error?)
701 // Could be done within a spec implementation check - http://crbug.com/30158 0
702
703 // 10.8 Determine the value of x-position or y-position for cue as per the
704 // appropriate rules from the following list:
705 if (m_writingDirection == Horizontal) {
706 switch (m_cueAlignment) {
707 case Start:
708 if (m_displayDirection == CSSValueLtr)
709 m_displayPosition.first = m_textPosition;
710 else
711 m_displayPosition.first = 100 - m_textPosition - m_displaySize;
712 break;
713 case End:
714 if (m_displayDirection == CSSValueRtl)
715 m_displayPosition.first = 100 - m_textPosition;
716 else
717 m_displayPosition.first = m_textPosition - m_displaySize;
718 break;
719 case Left:
720 if (m_displayDirection == CSSValueLtr)
721 m_displayPosition.first = m_textPosition;
722 else
723 m_displayPosition.first = 100 - m_textPosition;
724 break;
725 case Right:
726 if (m_displayDirection == CSSValueLtr)
727 m_displayPosition.first = m_textPosition - m_displaySize;
728 else
729 m_displayPosition.first = 100 - m_textPosition - m_displaySize;
730 break;
731 case Middle:
732 if (m_displayDirection == CSSValueLtr)
733 m_displayPosition.first = m_textPosition - m_displaySize / 2;
734 else
735 m_displayPosition.first = 100 - m_textPosition - m_displaySize / 2;
736 break;
737 case NumberOfAlignments:
738 ASSERT_NOT_REACHED();
739 }
740 } else {
741 // Cases for m_writingDirection being VerticalGrowing{Left|Right}
742 switch (m_cueAlignment) {
743 case Start:
744 case Left:
745 m_displayPosition.second = m_textPosition;
746 break;
747 case End:
748 case Right:
749 m_displayPosition.second = m_textPosition - m_displaySize;
750 break;
751 case Middle:
752 m_displayPosition.second = m_textPosition - m_displaySize / 2;
753 break;
754 case NumberOfAlignments:
755 ASSERT_NOT_REACHED();
756 }
757 }
758
759 // A text track cue has a text track cue computed line position whose value
760 // is defined in terms of the other aspects of the cue.
761 m_computedLinePosition = calculateComputedLinePosition();
762
763 // 10.9 Determine the value of whichever of x-position or y-position is not
764 // yet calculated for cue as per the appropriate rules from the following
765 // list:
766 if (m_snapToLines && m_displayPosition.second == undefinedPosition && m_writ ingDirection == Horizontal)
767 m_displayPosition.second = 0;
768
769 if (!m_snapToLines && m_displayPosition.second == undefinedPosition && m_wri tingDirection == Horizontal)
770 m_displayPosition.second = m_computedLinePosition;
771
772 if (m_snapToLines && m_displayPosition.first == undefinedPosition
773 && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
774 m_displayPosition.first = 0;
775
776 if (!m_snapToLines && (m_writingDirection == VerticalGrowingLeft || m_writin gDirection == VerticalGrowingRight))
777 m_displayPosition.first = m_computedLinePosition;
778 }
779
780 void TextTrackCue::markFutureAndPastNodes(ContainerNode* root, double previousTi mestamp, double movieTime)
781 {
782 DEFINE_STATIC_LOCAL(const String, timestampTag, ("timestamp"));
783
784 bool isPastNode = true;
785 double currentTimestamp = previousTimestamp;
786 if (currentTimestamp > movieTime)
787 isPastNode = false;
788
789 for (Node* child = root->firstChild(); child; child = NodeTraversal::next(*c hild, root)) {
790 if (child->nodeName() == timestampTag) {
791 unsigned position = 0;
792 String timestamp = child->nodeValue();
793 double currentTimestamp = VTTParser::collectTimeStamp(timestamp, &po sition);
794 ASSERT(currentTimestamp != -1);
795
796 if (currentTimestamp > movieTime)
797 isPastNode = false;
798 }
799
800 if (child->isVTTElement()) {
801 toVTTElement(child)->setIsPastNode(isPastNode);
802 // Make an elemenet id match a cue id for style matching purposes.
803 if (!m_id.isEmpty())
804 toElement(child)->setIdAttribute(m_id);
805 }
806 }
807 }
808
809 void TextTrackCue::updateDisplayTree(double movieTime)
810 {
811 // The display tree may contain WebVTT timestamp objects representing
812 // timestamps (processing instructions), along with displayable nodes.
813
814 if (!track()->isRendered())
815 return;
816
817 // Clear the contents of the set.
818 m_cueBackgroundBox->removeChildren();
819
820 // Update the two sets containing past and future WebVTT objects.
821 RefPtr<DocumentFragment> referenceTree = createCueRenderingTree();
822 markFutureAndPastNodes(referenceTree.get(), startTime(), movieTime);
823 m_cueBackgroundBox->appendChild(referenceTree, ASSERT_NO_EXCEPTION);
824 }
825
826 PassRefPtr<TextTrackCueBox> TextTrackCue::getDisplayTree(const IntSize& videoSiz e)
827 {
828 RefPtr<TextTrackCueBox> displayTree = displayTreeInternal();
829 if (!m_displayTreeShouldChange || !track()->isRendered())
830 return displayTree;
831
832 // 10.1 - 10.10
833 calculateDisplayParameters();
834
835 // 10.11. Apply the terms of the CSS specifications to nodes within the
836 // following constraints, thus obtaining a set of CSS boxes positioned
837 // relative to an initial containing block:
838 displayTree->removeChildren();
839
840 // The document tree is the tree of WebVTT Node Objects rooted at nodes.
841
842 // The children of the nodes must be wrapped in an anonymous box whose
843 // 'display' property has the value 'inline'. This is the WebVTT cue
844 // background box.
845
846 // Note: This is contained by default in m_cueBackgroundBox.
847 m_cueBackgroundBox->setPart(cueShadowPseudoId());
848 displayTree->appendChild(m_cueBackgroundBox);
849
850 // FIXME(BUG 79916): Runs of children of WebVTT Ruby Objects that are not
851 // WebVTT Ruby Text Objects must be wrapped in anonymous boxes whose
852 // 'display' property has the value 'ruby-base'.
853
854 // FIXME(BUG 79916): Text runs must be wrapped according to the CSS
855 // line-wrapping rules, except that additionally, regardless of the value of
856 // the 'white-space' property, lines must be wrapped at the edge of their
857 // containing blocks, even if doing so requires splitting a word where there
858 // is no line breaking opportunity. (Thus, normally text wraps as needed,
859 // but if there is a particularly long word, it does not overflow as it
860 // normally would in CSS, it is instead forcibly wrapped at the box's edge.)
861 displayTree->applyCSSProperties(videoSize);
862
863 m_displayTreeShouldChange = false;
864
865 // 10.15. Let cue's text track cue display state have the CSS boxes in
866 // boxes.
867 return displayTree;
868 }
869
870 void TextTrackCue::removeDisplayTree()
871 {
872 if (m_notifyRegion && m_track->regions()) {
873 // The region needs to be informed about the cue removal.
874 VTTRegion* region = m_track->regions()->getRegionById(m_regionId);
875 if (region)
876 region->willRemoveTextTrackCueBox(m_displayTree.get());
877 }
878
879 displayTreeInternal()->remove(ASSERT_NO_EXCEPTION);
880 }
881
882 std::pair<double, double> TextTrackCue::getPositionCoordinates() const
883 {
884 // This method is used for setting x and y when snap to lines is not set.
885 std::pair<double, double> coordinates;
886
887 if (m_writingDirection == Horizontal && m_displayDirection == CSSValueLtr) {
888 coordinates.first = m_textPosition;
889 coordinates.second = m_computedLinePosition;
890
891 return coordinates;
892 }
893
894 if (m_writingDirection == Horizontal && m_displayDirection == CSSValueRtl) {
895 coordinates.first = 100 - m_textPosition;
896 coordinates.second = m_computedLinePosition;
897
898 return coordinates;
899 }
900
901 if (m_writingDirection == VerticalGrowingLeft) {
902 coordinates.first = 100 - m_computedLinePosition;
903 coordinates.second = m_textPosition;
904
905 return coordinates;
906 }
907
908 if (m_writingDirection == VerticalGrowingRight) {
909 coordinates.first = m_computedLinePosition;
910 coordinates.second = m_textPosition;
911
912 return coordinates;
913 }
914
915 ASSERT_NOT_REACHED();
916
917 return coordinates;
918 }
919
920 TextTrackCue::CueSetting TextTrackCue::settingName(const String& name)
921 {
922 DEFINE_STATIC_LOCAL(const String, verticalKeyword, ("vertical"));
923 DEFINE_STATIC_LOCAL(const String, lineKeyword, ("line"));
924 DEFINE_STATIC_LOCAL(const String, positionKeyword, ("position"));
925 DEFINE_STATIC_LOCAL(const String, sizeKeyword, ("size"));
926 DEFINE_STATIC_LOCAL(const String, alignKeyword, ("align"));
927 DEFINE_STATIC_LOCAL(const String, regionIdKeyword, ("region"));
928
929 if (name == verticalKeyword)
930 return Vertical;
931 else if (name == lineKeyword)
932 return Line;
933 else if (name == positionKeyword)
934 return Position;
935 else if (name == sizeKeyword)
936 return Size;
937 else if (name == alignKeyword)
938 return Align;
939 else if (RuntimeEnabledFeatures::webVTTRegionsEnabled() && name == regionIdK eyword)
940 return RegionId;
941
942 return None;
943 }
944
945 void TextTrackCue::parseSettings(const String& input)
946 {
947 unsigned position = 0;
948
949 while (position < input.length()) {
950
951 // The WebVTT cue settings part of a WebVTT cue consists of zero or more of the following components, in any order,
952 // separated from each other by one or more U+0020 SPACE characters or U +0009 CHARACTER TABULATION (tab) characters.
953 while (position < input.length() && VTTParser::isValidSettingDelimiter(i nput[position]))
954 position++;
955 if (position >= input.length())
956 break;
957
958 // When the user agent is to parse the WebVTT settings given by a string input for a text track cue cue,
959 // the user agent must run the following steps:
960 // 1. Let settings be the result of splitting input on spaces.
961 // 2. For each token setting in the list settings, run the following sub steps:
962 // 1. If setting does not contain a U+003A COLON character (:), or if the first U+003A COLON character (:)
963 // in setting is either the first or last character of setting, th en jump to the step labeled next setting.
964 unsigned endOfSetting = position;
965 String setting = VTTParser::collectWord(input, &endOfSetting);
966 CueSetting name;
967 size_t colonOffset = setting.find(':', 1);
968 if (colonOffset == kNotFound || !colonOffset || colonOffset == setting.l ength() - 1)
969 goto NextSetting;
970
971 // 2. Let name be the leading substring of setting up to and excluding t he first U+003A COLON character (:) in that string.
972 name = settingName(setting.substring(0, colonOffset));
973
974 // 3. Let value be the trailing substring of setting starting from the c haracter immediately after the first U+003A COLON character (:) in that string.
975 position += colonOffset + 1;
976 if (position >= input.length())
977 break;
978
979 // 4. Run the appropriate substeps that apply for the value of name, as follows:
980 switch (name) {
981 case Vertical:
982 {
983 // If name is a case-sensitive match for "vertical"
984 // 1. If value is a case-sensitive match for the string "rl", then l et cue's text track cue writing direction
985 // be vertical growing left.
986 String writingDirection = VTTParser::collectWord(input, &position);
987 if (writingDirection == verticalGrowingLeftKeyword())
988 m_writingDirection = VerticalGrowingLeft;
989
990 // 2. Otherwise, if value is a case-sensitive match for the string " lr", then let cue's text track cue writing
991 // direction be vertical growing right.
992 else if (writingDirection == verticalGrowingRightKeyword())
993 m_writingDirection = VerticalGrowingRight;
994 }
995 break;
996 case Line:
997 {
998 // 1-2 - Collect chars that are either '-', '%', or a digit.
999 // 1. If value contains any characters other than U+002D HYPHEN-MINU S characters (-), U+0025 PERCENT SIGN
1000 // characters (%), and characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump
1001 // to the step labeled next setting.
1002 StringBuilder linePositionBuilder;
1003 while (position < input.length() && (input[position] == '-' || input [position] == '%' || isASCIIDigit(input[position])))
1004 linePositionBuilder.append(input[position++]);
1005 if (position < input.length() && !VTTParser::isValidSettingDelimiter (input[position]))
1006 break;
1007
1008 // 2. If value does not contain at least one character in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT
1009 // NINE (9), then jump to the step labeled next setting.
1010 // 3. If any character in value other than the first character is a U+002D HYPHEN-MINUS character (-), then
1011 // jump to the step labeled next setting.
1012 // 4. If any character in value other than the last character is a U +0025 PERCENT SIGN character (%), then
1013 // jump to the step labeled next setting.
1014 String linePosition = linePositionBuilder.toString();
1015 if (linePosition.find('-', 1) != kNotFound || linePosition.reverseFi nd("%", linePosition.length() - 2) != kNotFound)
1016 break;
1017
1018 // 5. If the first character in value is a U+002D HYPHEN-MINUS chara cter (-) and the last character in value is a
1019 // U+0025 PERCENT SIGN character (%), then jump to the step label ed next setting.
1020 if (linePosition[0] == '-' && linePosition[linePosition.length() - 1 ] == '%')
1021 break;
1022
1023 // 6. Ignoring the trailing percent sign, if any, interpret value as a (potentially signed) integer, and
1024 // let number be that number.
1025 // NOTE: toInt ignores trailing non-digit characters, such as '%'.
1026 bool validNumber;
1027 int number = linePosition.toInt(&validNumber);
1028 if (!validNumber)
1029 break;
1030
1031 // 7. If the last character in value is a U+0025 PERCENT SIGN charac ter (%), but number is not in the range
1032 // 0 ≤ number ≤ 100, then jump to the step labeled next setting.
1033 // 8. Let cue's text track cue line position be number.
1034 // 9. If the last character in value is a U+0025 PERCENT SIGN charac ter (%), then let cue's text track cue
1035 // snap-to-lines flag be false. Otherwise, let it be true.
1036 if (linePosition[linePosition.length() - 1] == '%') {
1037 if (number < 0 || number > 100)
1038 break;
1039
1040 // 10 - If '%' then set snap-to-lines flag to false.
1041 m_snapToLines = false;
1042 }
1043
1044 m_linePosition = number;
1045 }
1046 break;
1047 case Position:
1048 {
1049 // 1. If value contains any characters other than U+0025 PERCENT SIG N characters (%) and characters in the range
1050 // U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump to t he step labeled next setting.
1051 // 2. If value does not contain at least one character in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9),
1052 // then jump to the step labeled next setting.
1053 String textPosition = VTTParser::collectDigits(input, &position);
1054 if (textPosition.isEmpty())
1055 break;
1056 if (position >= input.length())
1057 break;
1058
1059 // 3. If any character in value other than the last character is a U +0025 PERCENT SIGN character (%), then jump
1060 // to the step labeled next setting.
1061 // 4. If the last character in value is not a U+0025 PERCENT SIGN ch aracter (%), then jump to the step labeled
1062 // next setting.
1063 if (input[position++] != '%')
1064 break;
1065 if (position < input.length() && !VTTParser::isValidSettingDelimiter (input[position]))
1066 break;
1067
1068 // 5. Ignoring the trailing percent sign, interpret value as an inte ger, and let number be that number.
1069 // 6. If number is not in the range 0 ≤ number ≤ 100, then jump to t he step labeled next setting.
1070 // NOTE: toInt ignores trailing non-digit characters, such as '%'.
1071 bool validNumber;
1072 int number = textPosition.toInt(&validNumber);
1073 if (!validNumber)
1074 break;
1075 if (number < 0 || number > 100)
1076 break;
1077
1078 // 7. Let cue's text track cue text position be number.
1079 m_textPosition = number;
1080 }
1081 break;
1082 case Size:
1083 {
1084 // 1. If value contains any characters other than U+0025 PERCENT SIG N characters (%) and characters in the
1085 // range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jum p to the step labeled next setting.
1086 // 2. If value does not contain at least one character in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT
1087 // NINE (9), then jump to the step labeled next setting.
1088 String cueSize = VTTParser::collectDigits(input, &position);
1089 if (cueSize.isEmpty())
1090 break;
1091 if (position >= input.length())
1092 break;
1093
1094 // 3. If any character in value other than the last character is a U +0025 PERCENT SIGN character (%),
1095 // then jump to the step labeled next setting.
1096 // 4. If the last character in value is not a U+0025 PERCENT SIGN ch aracter (%), then jump to the step
1097 // labeled next setting.
1098 if (input[position++] != '%')
1099 break;
1100 if (position < input.length() && !VTTParser::isValidSettingDelimiter (input[position]))
1101 break;
1102
1103 // 5. Ignoring the trailing percent sign, interpret value as an inte ger, and let number be that number.
1104 // 6. If number is not in the range 0 ≤ number ≤ 100, then jump to t he step labeled next setting.
1105 bool validNumber;
1106 int number = cueSize.toInt(&validNumber);
1107 if (!validNumber)
1108 break;
1109 if (number < 0 || number > 100)
1110 break;
1111
1112 // 7. Let cue's text track cue size be number.
1113 m_cueSize = number;
1114 }
1115 break;
1116 case Align:
1117 {
1118 String cueAlignment = VTTParser::collectWord(input, &position);
1119
1120 // 1. If value is a case-sensitive match for the string "start", the n let cue's text track cue alignment be start alignment.
1121 if (cueAlignment == startKeyword())
1122 m_cueAlignment = Start;
1123
1124 // 2. If value is a case-sensitive match for the string "middle", th en let cue's text track cue alignment be middle alignment.
1125 else if (cueAlignment == middleKeyword())
1126 m_cueAlignment = Middle;
1127
1128 // 3. If value is a case-sensitive match for the string "end", then let cue's text track cue alignment be end alignment.
1129 else if (cueAlignment == endKeyword())
1130 m_cueAlignment = End;
1131
1132 // 4. If value is a case-sensitive match for the string "left", then let cue's text track cue alignment be left alignment.
1133 else if (cueAlignment == leftKeyword())
1134 m_cueAlignment = Left;
1135
1136 // 5. If value is a case-sensitive match for the string "right", the n let cue's text track cue alignment be right alignment.
1137 else if (cueAlignment == rightKeyword())
1138 m_cueAlignment = Right;
1139 }
1140 break;
1141 case RegionId:
1142 m_regionId = VTTParser::collectWord(input, &position);
1143 break;
1144 case None:
1145 break;
1146 }
1147
1148 NextSetting:
1149 position = endOfSetting;
1150 }
1151
1152 // If cue's line position is not auto or cue's size is not 100 or cue's
1153 // writing direction is not horizontal, but cue's region identifier is not
1154 // the empty string, let cue's region identifier be the empty string.
1155 if (m_regionId.isEmpty())
1156 return;
1157
1158 if (m_linePosition != undefinedPosition || m_cueSize != 100 || m_writingDire ction != Horizontal)
1159 m_regionId = emptyString();
1160 }
1161
1162 CSSValueID TextTrackCue::getCSSAlignment() const
1163 {
1164 return displayAlignmentMap[m_cueAlignment];
1165 }
1166
1167 CSSValueID TextTrackCue::getCSSWritingDirection() const
1168 {
1169 return m_displayDirection;
1170 }
1171
1172 CSSValueID TextTrackCue::getCSSWritingMode() const
1173 {
1174 return displayWritingModeMap[m_writingDirection];
1175 }
1176
1177 int TextTrackCue::getCSSSize() const
1178 {
1179 return m_displaySize;
1180 }
1181
1182 std::pair<double, double> TextTrackCue::getCSSPosition() const
1183 {
1184 if (!m_snapToLines)
1185 return getPositionCoordinates();
1186
1187 return m_displayPosition;
1188 }
1189
1190 const AtomicString& TextTrackCue::interfaceName() const 176 const AtomicString& TextTrackCue::interfaceName() const
1191 { 177 {
1192 return EventTargetNames::TextTrackCue; 178 return EventTargetNames::TextTrackCue;
1193 } 179 }
1194 180
1195 ExecutionContext* TextTrackCue::executionContext() const
1196 {
1197 ASSERT(m_cueBackgroundBox);
1198 return m_cueBackgroundBox->executionContext();
1199 }
1200
1201 Document& TextTrackCue::document() const
1202 {
1203 ASSERT(m_cueBackgroundBox);
1204 return m_cueBackgroundBox->document();
1205 }
1206
1207 } // namespace WebCore 181 } // namespace WebCore
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698