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