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