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