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