• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 Google Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 #include "config.h"
32 #include "core/html/track/vtt/VTTRegion.h"
33 
34 #include "bindings/v8/ExceptionMessages.h"
35 #include "bindings/v8/ExceptionState.h"
36 #include "core/dom/ClientRect.h"
37 #include "core/dom/DOMTokenList.h"
38 #include "core/html/HTMLDivElement.h"
39 #include "core/html/track/vtt/VTTParser.h"
40 #include "core/html/track/vtt/VTTScanner.h"
41 #include "core/rendering/RenderInline.h"
42 #include "core/rendering/RenderObject.h"
43 #include "platform/Logging.h"
44 #include "wtf/MathExtras.h"
45 #include "wtf/text/StringBuilder.h"
46 
47 namespace WebCore {
48 
49 // The following values default values are defined within the WebVTT Regions Spec.
50 // https://dvcs.w3.org/hg/text-tracks/raw-file/default/608toVTT/region.html
51 
52 // The region occupies by default 100% of the width of the video viewport.
53 static const float defaultWidth = 100;
54 
55 // The region has, by default, 3 lines of text.
56 static const long defaultHeightInLines = 3;
57 
58 // The region and viewport are anchored in the bottom left corner.
59 static const float defaultAnchorPointX = 0;
60 static const float defaultAnchorPointY = 100;
61 
62 // The region doesn't have scrolling text, by default.
63 static const bool defaultScroll = false;
64 
65 // Default region line-height (vh units)
66 static const float lineHeight = 5.33;
67 
68 // Default scrolling animation time period (s).
69 static const float scrollTime = 0.433;
70 
isNonPercentage(double value,const char * method,ExceptionState & exceptionState)71 static bool isNonPercentage(double value, const char* method, ExceptionState& exceptionState)
72 {
73     if (value < 0 || value > 100) {
74         exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexOutsideRange("value", value, 0.0, ExceptionMessages::InclusiveBound, 100.0, ExceptionMessages::InclusiveBound));
75         return true;
76     }
77     return false;
78 }
79 
VTTRegion()80 VTTRegion::VTTRegion()
81     : m_id(emptyString())
82     , m_width(defaultWidth)
83     , m_heightInLines(defaultHeightInLines)
84     , m_regionAnchor(FloatPoint(defaultAnchorPointX, defaultAnchorPointY))
85     , m_viewportAnchor(FloatPoint(defaultAnchorPointX, defaultAnchorPointY))
86     , m_scroll(defaultScroll)
87     , m_track(nullptr)
88     , m_currentTop(0)
89     , m_scrollTimer(this, &VTTRegion::scrollTimerFired)
90 {
91 }
92 
~VTTRegion()93 VTTRegion::~VTTRegion()
94 {
95 }
96 
setTrack(TextTrack * track)97 void VTTRegion::setTrack(TextTrack* track)
98 {
99     m_track = track;
100 }
101 
setId(const String & id)102 void VTTRegion::setId(const String& id)
103 {
104     m_id = id;
105 }
106 
setWidth(double value,ExceptionState & exceptionState)107 void VTTRegion::setWidth(double value, ExceptionState& exceptionState)
108 {
109     if (isNonPercentage(value, "width", exceptionState))
110         return;
111 
112     m_width = value;
113 }
114 
setHeight(long value,ExceptionState & exceptionState)115 void VTTRegion::setHeight(long value, ExceptionState& exceptionState)
116 {
117     if (value < 0) {
118         exceptionState.throwDOMException(IndexSizeError, "The height provided (" + String::number(value) + ") is negative.");
119         return;
120     }
121 
122     m_heightInLines = value;
123 }
124 
setRegionAnchorX(double value,ExceptionState & exceptionState)125 void VTTRegion::setRegionAnchorX(double value, ExceptionState& exceptionState)
126 {
127     if (isNonPercentage(value, "regionAnchorX", exceptionState))
128         return;
129 
130     m_regionAnchor.setX(value);
131 }
132 
setRegionAnchorY(double value,ExceptionState & exceptionState)133 void VTTRegion::setRegionAnchorY(double value, ExceptionState& exceptionState)
134 {
135     if (isNonPercentage(value, "regionAnchorY", exceptionState))
136         return;
137 
138     m_regionAnchor.setY(value);
139 }
140 
setViewportAnchorX(double value,ExceptionState & exceptionState)141 void VTTRegion::setViewportAnchorX(double value, ExceptionState& exceptionState)
142 {
143     if (isNonPercentage(value, "viewportAnchorX", exceptionState))
144         return;
145 
146     m_viewportAnchor.setX(value);
147 }
148 
setViewportAnchorY(double value,ExceptionState & exceptionState)149 void VTTRegion::setViewportAnchorY(double value, ExceptionState& exceptionState)
150 {
151     if (isNonPercentage(value, "viewportAnchorY", exceptionState))
152         return;
153 
154     m_viewportAnchor.setY(value);
155 }
156 
scroll() const157 const AtomicString VTTRegion::scroll() const
158 {
159     DEFINE_STATIC_LOCAL(const AtomicString, upScrollValueKeyword, ("up", AtomicString::ConstructFromLiteral));
160 
161     if (m_scroll)
162         return upScrollValueKeyword;
163 
164     return "";
165 }
166 
setScroll(const AtomicString & value,ExceptionState & exceptionState)167 void VTTRegion::setScroll(const AtomicString& value, ExceptionState& exceptionState)
168 {
169     DEFINE_STATIC_LOCAL(const AtomicString, upScrollValueKeyword, ("up", AtomicString::ConstructFromLiteral));
170 
171     if (value != emptyString() && value != upScrollValueKeyword) {
172         exceptionState.throwDOMException(SyntaxError, "The value provided ('" + value + "') is invalid. The 'scroll' property must be either the empty string, or 'up'.");
173         return;
174     }
175 
176     m_scroll = value == upScrollValueKeyword;
177 }
178 
updateParametersFromRegion(VTTRegion * region)179 void VTTRegion::updateParametersFromRegion(VTTRegion* region)
180 {
181     m_heightInLines = region->height();
182     m_width = region->width();
183 
184     m_regionAnchor = FloatPoint(region->regionAnchorX(), region->regionAnchorY());
185     m_viewportAnchor = FloatPoint(region->viewportAnchorX(), region->viewportAnchorY());
186 
187     setScroll(region->scroll(), ASSERT_NO_EXCEPTION);
188 }
189 
setRegionSettings(const String & inputString)190 void VTTRegion::setRegionSettings(const String& inputString)
191 {
192     m_settings = inputString;
193 
194     VTTScanner input(inputString);
195 
196     while (!input.isAtEnd()) {
197         input.skipWhile<VTTParser::isValidSettingDelimiter>();
198 
199         if (input.isAtEnd())
200             break;
201 
202         // Scan the name part.
203         RegionSetting name = scanSettingName(input);
204 
205         // Verify that we're looking at a '='.
206         if (name == None || !input.scan('=')) {
207             input.skipUntil<VTTParser::isASpace>();
208             continue;
209         }
210 
211         // Scan the value part.
212         parseSettingValue(name, input);
213     }
214 }
215 
scanSettingName(VTTScanner & input)216 VTTRegion::RegionSetting VTTRegion::scanSettingName(VTTScanner& input)
217 {
218     if (input.scan("id"))
219         return Id;
220     if (input.scan("height"))
221         return Height;
222     if (input.scan("width"))
223         return Width;
224     if (input.scan("viewportanchor"))
225         return ViewportAnchor;
226     if (input.scan("regionanchor"))
227         return RegionAnchor;
228     if (input.scan("scroll"))
229         return Scroll;
230 
231     return None;
232 }
233 
parsedEntireRun(const VTTScanner & input,const VTTScanner::Run & run)234 static inline bool parsedEntireRun(const VTTScanner& input, const VTTScanner::Run& run)
235 {
236     return input.isAt(run.end());
237 }
238 
parseSettingValue(RegionSetting setting,VTTScanner & input)239 void VTTRegion::parseSettingValue(RegionSetting setting, VTTScanner& input)
240 {
241     DEFINE_STATIC_LOCAL(const AtomicString, scrollUpValueKeyword, ("up", AtomicString::ConstructFromLiteral));
242 
243     VTTScanner::Run valueRun = input.collectUntil<VTTParser::isASpace>();
244 
245     switch (setting) {
246     case Id: {
247         String stringValue = input.extractString(valueRun);
248         if (stringValue.find("-->") == kNotFound)
249             m_id = stringValue;
250         break;
251     }
252     case Width: {
253         float floatWidth;
254         if (VTTParser::parseFloatPercentageValue(input, floatWidth) && parsedEntireRun(input, valueRun))
255             m_width = floatWidth;
256         else
257             WTF_LOG(Media, "VTTRegion::parseSettingValue, invalid Width");
258         break;
259     }
260     case Height: {
261         int number;
262         if (input.scanDigits(number) && parsedEntireRun(input, valueRun))
263             m_heightInLines = number;
264         else
265             WTF_LOG(Media, "VTTRegion::parseSettingValue, invalid Height");
266         break;
267     }
268     case RegionAnchor: {
269         FloatPoint anchor;
270         if (VTTParser::parseFloatPercentageValuePair(input, ',', anchor) && parsedEntireRun(input, valueRun))
271             m_regionAnchor = anchor;
272         else
273             WTF_LOG(Media, "VTTRegion::parseSettingValue, invalid RegionAnchor");
274         break;
275     }
276     case ViewportAnchor: {
277         FloatPoint anchor;
278         if (VTTParser::parseFloatPercentageValuePair(input, ',', anchor) && parsedEntireRun(input, valueRun))
279             m_viewportAnchor = anchor;
280         else
281             WTF_LOG(Media, "VTTRegion::parseSettingValue, invalid ViewportAnchor");
282         break;
283     }
284     case Scroll:
285         if (input.scanRun(valueRun, scrollUpValueKeyword))
286             m_scroll = true;
287         else
288             WTF_LOG(Media, "VTTRegion::parseSettingValue, invalid Scroll");
289         break;
290     case None:
291         break;
292     }
293 
294     input.skipRun(valueRun);
295 }
296 
textTrackCueContainerShadowPseudoId()297 const AtomicString& VTTRegion::textTrackCueContainerShadowPseudoId()
298 {
299     DEFINE_STATIC_LOCAL(const AtomicString, trackRegionCueContainerPseudoId,
300         ("-webkit-media-text-track-region-container", AtomicString::ConstructFromLiteral));
301 
302     return trackRegionCueContainerPseudoId;
303 }
304 
textTrackCueContainerScrollingClass()305 const AtomicString& VTTRegion::textTrackCueContainerScrollingClass()
306 {
307     DEFINE_STATIC_LOCAL(const AtomicString, trackRegionCueContainerScrollingClass,
308         ("scrolling", AtomicString::ConstructFromLiteral));
309 
310     return trackRegionCueContainerScrollingClass;
311 }
312 
textTrackRegionShadowPseudoId()313 const AtomicString& VTTRegion::textTrackRegionShadowPseudoId()
314 {
315     DEFINE_STATIC_LOCAL(const AtomicString, trackRegionShadowPseudoId,
316         ("-webkit-media-text-track-region", AtomicString::ConstructFromLiteral));
317 
318     return trackRegionShadowPseudoId;
319 }
320 
getDisplayTree(Document & document)321 PassRefPtrWillBeRawPtr<HTMLDivElement> VTTRegion::getDisplayTree(Document& document)
322 {
323     if (!m_regionDisplayTree) {
324         m_regionDisplayTree = HTMLDivElement::create(document);
325         prepareRegionDisplayTree();
326     }
327 
328     return m_regionDisplayTree;
329 }
330 
willRemoveVTTCueBox(VTTCueBox * box)331 void VTTRegion::willRemoveVTTCueBox(VTTCueBox* box)
332 {
333     WTF_LOG(Media, "VTTRegion::willRemoveVTTCueBox");
334     ASSERT(m_cueContainer->contains(box));
335 
336     double boxHeight = box->getBoundingClientRect()->bottom() - box->getBoundingClientRect()->top();
337 
338     m_cueContainer->classList().remove(textTrackCueContainerScrollingClass(), ASSERT_NO_EXCEPTION);
339 
340     m_currentTop += boxHeight;
341     m_cueContainer->setInlineStyleProperty(CSSPropertyTop, m_currentTop, CSSPrimitiveValue::CSS_PX);
342 }
343 
appendVTTCueBox(PassRefPtrWillBeRawPtr<VTTCueBox> displayBox)344 void VTTRegion::appendVTTCueBox(PassRefPtrWillBeRawPtr<VTTCueBox> displayBox)
345 {
346     ASSERT(m_cueContainer);
347 
348     if (m_cueContainer->contains(displayBox.get()))
349         return;
350 
351     m_cueContainer->appendChild(displayBox);
352     displayLastVTTCueBox();
353 }
354 
displayLastVTTCueBox()355 void VTTRegion::displayLastVTTCueBox()
356 {
357     WTF_LOG(Media, "VTTRegion::displayLastVTTCueBox");
358     ASSERT(m_cueContainer);
359 
360     // FIXME: This should not be causing recalc styles in a loop to set the "top" css
361     // property to move elements. We should just scroll the text track cues on the
362     // compositor with an animation.
363 
364     if (m_scrollTimer.isActive())
365         return;
366 
367     // If it's a scrolling region, add the scrolling class.
368     if (isScrollingRegion())
369         m_cueContainer->classList().add(textTrackCueContainerScrollingClass(), ASSERT_NO_EXCEPTION);
370 
371     float regionBottom = m_regionDisplayTree->getBoundingClientRect()->bottom();
372 
373     // Find first cue that is not entirely displayed and scroll it upwards.
374     for (Element* child = ElementTraversal::firstWithin(*m_cueContainer); child && !m_scrollTimer.isActive(); child = ElementTraversal::nextSibling(*child)) {
375         float childTop = toHTMLDivElement(child)->getBoundingClientRect()->top();
376         float childBottom = toHTMLDivElement(child)->getBoundingClientRect()->bottom();
377 
378         if (regionBottom >= childBottom)
379             continue;
380 
381         float height = childBottom - childTop;
382 
383         m_currentTop -= std::min(height, childBottom - regionBottom);
384         m_cueContainer->setInlineStyleProperty(CSSPropertyTop, m_currentTop, CSSPrimitiveValue::CSS_PX);
385 
386         startTimer();
387     }
388 }
389 
prepareRegionDisplayTree()390 void VTTRegion::prepareRegionDisplayTree()
391 {
392     ASSERT(m_regionDisplayTree);
393 
394     // 7.2 Prepare region CSS boxes
395 
396     // FIXME: Change the code below to use viewport units when
397     // http://crbug/244618 is fixed.
398 
399     // Let regionWidth be the text track region width.
400     // Let width be 'regionWidth vw' ('vw' is a CSS unit)
401     m_regionDisplayTree->setInlineStyleProperty(CSSPropertyWidth,
402         m_width, CSSPrimitiveValue::CSS_PERCENTAGE);
403 
404     // Let lineHeight be '0.0533vh' ('vh' is a CSS unit) and regionHeight be
405     // the text track region height. Let height be 'lineHeight' multiplied
406     // by regionHeight.
407     double height = lineHeight * m_heightInLines;
408     m_regionDisplayTree->setInlineStyleProperty(CSSPropertyHeight,
409         height, CSSPrimitiveValue::CSS_VH);
410 
411     // Let viewportAnchorX be the x dimension of the text track region viewport
412     // anchor and regionAnchorX be the x dimension of the text track region
413     // anchor. Let leftOffset be regionAnchorX multiplied by width divided by
414     // 100.0. Let left be leftOffset subtracted from 'viewportAnchorX vw'.
415     double leftOffset = m_regionAnchor.x() * m_width / 100;
416     m_regionDisplayTree->setInlineStyleProperty(CSSPropertyLeft,
417         m_viewportAnchor.x() - leftOffset,
418         CSSPrimitiveValue::CSS_PERCENTAGE);
419 
420     // Let viewportAnchorY be the y dimension of the text track region viewport
421     // anchor and regionAnchorY be the y dimension of the text track region
422     // anchor. Let topOffset be regionAnchorY multiplied by height divided by
423     // 100.0. Let top be topOffset subtracted from 'viewportAnchorY vh'.
424     double topOffset = m_regionAnchor.y() * height / 100;
425     m_regionDisplayTree->setInlineStyleProperty(CSSPropertyTop,
426         m_viewportAnchor.y() - topOffset,
427         CSSPrimitiveValue::CSS_PERCENTAGE);
428 
429     // The cue container is used to wrap the cues and it is the object which is
430     // gradually scrolled out as multiple cues are appended to the region.
431     m_cueContainer = HTMLDivElement::create(m_regionDisplayTree->document());
432     m_cueContainer->setInlineStyleProperty(CSSPropertyTop,
433         0.0,
434         CSSPrimitiveValue::CSS_PX);
435 
436     m_cueContainer->setShadowPseudoId(textTrackCueContainerShadowPseudoId());
437     m_regionDisplayTree->appendChild(m_cueContainer);
438 
439     // 7.5 Every WebVTT region object is initialised with the following CSS
440     m_regionDisplayTree->setShadowPseudoId(textTrackRegionShadowPseudoId());
441 }
442 
startTimer()443 void VTTRegion::startTimer()
444 {
445     WTF_LOG(Media, "VTTRegion::startTimer");
446 
447     if (m_scrollTimer.isActive())
448         return;
449 
450     double duration = isScrollingRegion() ? scrollTime : 0;
451     m_scrollTimer.startOneShot(duration, FROM_HERE);
452 }
453 
stopTimer()454 void VTTRegion::stopTimer()
455 {
456     WTF_LOG(Media, "VTTRegion::stopTimer");
457 
458     if (m_scrollTimer.isActive())
459         m_scrollTimer.stop();
460 }
461 
scrollTimerFired(Timer<VTTRegion> *)462 void VTTRegion::scrollTimerFired(Timer<VTTRegion>*)
463 {
464     WTF_LOG(Media, "VTTRegion::scrollTimerFired");
465 
466     stopTimer();
467     displayLastVTTCueBox();
468 }
469 
trace(Visitor * visitor)470 void VTTRegion::trace(Visitor* visitor)
471 {
472     visitor->trace(m_cueContainer);
473     visitor->trace(m_regionDisplayTree);
474     visitor->trace(m_track);
475 }
476 
477 } // namespace WebCore
478