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