Make TextTrackCue more mutable
[WebKit-https.git] / Source / WebCore / html / TextTrackCue.cpp
1 /*
2  * Copyright (C) 2011 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
33 #if ENABLE(VIDEO_TRACK)
34
35 #include "TextTrackCue.h"
36
37 #include "Event.h"
38 #include "DocumentFragment.h"
39 #include "TextTrack.h"
40 #include "WebVTTParser.h"
41 #include <wtf/text/StringBuilder.h>
42
43 namespace WebCore {
44
45 static const AtomicString& startKeyword()
46 {
47     DEFINE_STATIC_LOCAL(const AtomicString, start, ("start"));
48     return start;
49 }
50
51 static const AtomicString& middleKeyword()
52 {
53     DEFINE_STATIC_LOCAL(const AtomicString, middle, ("middle"));
54     return middle;
55 }
56
57 static const AtomicString& endKeyword()
58 {
59     DEFINE_STATIC_LOCAL(const AtomicString, end, ("end"));
60     return end;
61 }
62
63 static const AtomicString& horizontalKeyword()
64 {
65     DEFINE_STATIC_LOCAL(const AtomicString, horizontal, ("horizontal"));
66     return horizontal;
67 }
68
69 static const AtomicString& verticalKeyword()
70 {
71     DEFINE_STATIC_LOCAL(const AtomicString, vertical, ("vertical"));
72     return vertical;
73 }
74 static const AtomicString& verticallrKeyword()
75 {
76     DEFINE_STATIC_LOCAL(const AtomicString, verticallr, ("vertical-lr"));
77     return verticallr;
78 }
79     
80 TextTrackCue::TextTrackCue(ScriptExecutionContext* context, const String& id, double start, double end, const String& content, const String& settings, bool pauseOnExit)
81     : m_id(id)
82     , m_startTime(start)
83     , m_endTime(end)
84     , m_content(content)
85     , m_writingDirection(Horizontal)
86     , m_linePosition(-1)
87     , m_textPosition(50)
88     , m_cueSize(100)
89     , m_cueAlignment(Middle)
90     , m_scriptExecutionContext(context)
91     , m_isActive(false)
92     , m_pauseOnExit(pauseOnExit)
93     , m_snapToLines(true)
94 {
95     parseSettings(settings);
96 }
97
98 TextTrackCue::~TextTrackCue()
99 {
100 }
101
102 void TextTrackCue::cueWillChange()
103 {
104     if (m_track)
105         m_track->cueWillChange(this);
106 }
107
108 void TextTrackCue::cueDidChange()
109 {
110     if (m_track)
111         m_track->cueDidChange(this);
112 }
113
114 TextTrack* TextTrackCue::track() const
115 {
116     return m_track.get();
117 }
118
119 void TextTrackCue::setTrack(PassRefPtr<TextTrack>track)
120 {
121     m_track = track;
122 }
123
124 void TextTrackCue::setId(const String& id)
125 {
126     if (m_id == id)
127         return;
128
129     cueWillChange();
130     m_id = id;
131     cueDidChange();
132 }
133
134 void TextTrackCue::setStartTime(double value)
135 {
136     if (m_startTime == value)
137         return;
138     
139     cueWillChange();
140     m_startTime = value;
141     cueDidChange();
142 }
143     
144 void TextTrackCue::setEndTime(double value)
145 {
146     if (m_endTime == value)
147         return;
148     
149     cueWillChange();
150     m_endTime = value;
151     cueDidChange();
152 }
153     
154 void TextTrackCue::setPauseOnExit(bool value)
155 {
156     if (m_pauseOnExit == value)
157         return;
158     
159     cueWillChange();
160     m_pauseOnExit = value;
161     cueDidChange();
162 }
163
164 String TextTrackCue::direction() const
165 {
166     switch (m_writingDirection) {
167     case Horizontal: 
168         return horizontalKeyword();
169     case VerticalGrowingLeft:
170         return verticalKeyword();
171     case VerticalGrowingRight:
172         return verticallrKeyword();
173     default:
174         ASSERT_NOT_REACHED();
175         return "";
176     }
177 }
178
179 void TextTrackCue::setDirection(const String& value, ExceptionCode& ec)
180 {
181     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-direction
182     // On setting, the text track cue writing direction must be set to the value given 
183     // in the first cell of the row in the table above whose second cell is a 
184     // case-sensitive match for the new value, if any. If none of the values match, then
185     // the user agent must instead throw a SyntaxError exception.
186     
187     Direction direction = m_writingDirection;
188     if (value == horizontalKeyword())
189         direction = Horizontal;
190     else if (value == verticalKeyword())
191         direction = VerticalGrowingLeft;
192     else if (value == verticallrKeyword())
193         direction = VerticalGrowingRight;
194     else
195         ec = SYNTAX_ERR;
196     
197     if (direction == m_writingDirection)
198         return;
199
200     cueWillChange();
201     m_writingDirection = direction;
202     cueDidChange();
203 }
204
205 void TextTrackCue::setSnapToLines(bool value)
206 {
207     if (m_snapToLines == value)
208         return;
209     
210     cueWillChange();
211     m_snapToLines = value;
212     cueDidChange();
213 }
214
215 void TextTrackCue::setLinePosition(int position, ExceptionCode& ec)
216 {
217     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-lineposition
218     // On setting, if the text track cue snap-to-lines flag is not set, and the new
219     // value is negative or greater than 100, then throw an IndexSizeError exception.
220     if (!m_snapToLines && (position < 0 || position > 100)) {
221         ec = INDEX_SIZE_ERR;
222         return;
223     }
224     
225     // Otherwise, set the text track cue line position to the new value.
226     if (m_linePosition == position)
227         return;
228     
229     cueWillChange();
230     m_linePosition = position; 
231     cueDidChange();
232 }
233
234 void TextTrackCue::setTextPosition(int position, ExceptionCode& ec)
235 {
236     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-lineposition
237     // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError exception.
238     // Otherwise, set the text track cue text position to the new value.
239     if (position < 0 || position > 100) {
240         ec = INDEX_SIZE_ERR;
241         return;
242     }
243     
244     // Otherwise, set the text track cue line position to the new value.
245     if (m_textPosition == position)
246         return;
247     
248     cueWillChange();
249     m_textPosition = position; 
250     cueDidChange();
251 }
252
253 void TextTrackCue::setSize(int size, ExceptionCode& ec)
254 {
255     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-size
256     // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError
257     // exception. Otherwise, set the text track cue size to the new value.
258     if (size < 0 || size > 100) {
259         ec = INDEX_SIZE_ERR;
260         return;
261     }
262     
263     // Otherwise, set the text track cue line position to the new value.
264     if (m_cueSize == size)
265         return;
266     
267     cueWillChange();
268     m_cueSize = size;
269     cueDidChange();
270 }
271
272 String TextTrackCue::alignment() const
273 {
274     switch (m_cueAlignment) {
275     case Start:
276         return startKeyword();
277     case Middle:
278         return middleKeyword();
279     case End:
280         return endKeyword();
281     default:
282         ASSERT_NOT_REACHED();
283         return "";
284     }
285 }
286
287 void TextTrackCue::setAlignment(const String& value, ExceptionCode& ec)
288 {
289     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-alignment
290     // On setting, the text track cue alignment must be set to the value given in the 
291     // first cell of the row in the table above whose second cell is a case-sensitive
292     // match for the new value, if any. If none of the values match, then the user
293     // agent must instead throw a SyntaxError exception.
294     
295     Alignment alignment = m_cueAlignment;
296     if (value == startKeyword())
297         alignment = Start;
298     else if (value == middleKeyword())
299         alignment = Middle;
300     else if (value == endKeyword())
301         alignment = End;
302     else
303         ec = SYNTAX_ERR;
304     
305     if (alignment == m_cueAlignment)
306         return;
307
308     cueWillChange();
309     m_cueAlignment = alignment;
310     cueDidChange();
311 }
312     
313 String TextTrackCue::getCueAsSource()
314 {
315     return m_content;
316 }
317
318 PassRefPtr<DocumentFragment> TextTrackCue::getCueAsHTML()
319 {
320     return m_documentFragment;
321 }
322
323 void TextTrackCue::setCueHTML(PassRefPtr<DocumentFragment> fragment)
324 {
325     m_documentFragment = fragment;
326 }
327
328 bool TextTrackCue::isActive()
329 {
330     return m_isActive && track() && track()->mode() != TextTrack::DISABLED;
331 }
332
333 void TextTrackCue::setIsActive(bool active)
334 {
335     m_isActive = active;
336
337     // When a TextTrack's mode is disabled: No cues are active, no events are fired ...
338     if (!track() || track()->mode() == TextTrack::DISABLED)
339         return;
340
341     ExceptionCode ec = 0;
342     if (active)
343         dispatchEvent(Event::create(eventNames().enterEvent, false, false), ec);
344     else
345         dispatchEvent(Event::create(eventNames().exitEvent, false, false), ec);
346
347     if (m_track)
348         m_track->fireCueChangeEvent();
349 }
350
351 void TextTrackCue::parseSettings(const String& input)
352 {
353     // 4.8.10.13.3 Parse the WebVTT settings.
354     // 1 - Initial setup.
355     unsigned position = 0;
356     while (position < input.length()) {
357         // Discard any space characters between or after settings (not in the spec, but we think it should be).
358         while (position < input.length() && WebVTTParser::isASpace(input[position]))
359             position++;
360
361         // 2-4 Settings - get the next character representing a settings.
362         char setting = input[position++];
363         if (position >= input.length())
364             return;
365
366         // 5-7 - If the character at position is not ':', set setting to empty string.
367         if (input[position++] != ':')
368             setting = '\0';
369         if (position >= input.length())
370             return;
371
372         // 8 - Gather settings based on value of setting.
373         switch (setting) {
374         case 'D':
375             {
376             // 1-3 - Collect the next word and set the writing direction accordingly.
377             String writingDirection = WebVTTParser::collectWord(input, &position);
378             if (writingDirection == verticalKeyword())
379                 m_writingDirection = VerticalGrowingLeft;
380             else if (writingDirection == verticallrKeyword())
381                 m_writingDirection = VerticalGrowingRight;
382             }
383             break;
384         case 'L':
385             {
386             // 1-2 - Collect chars that are either '-', '%', or a digit.
387             StringBuilder linePositionBuilder;
388             while (position < input.length() && (input[position] == '-' || input[position] == '%' || isASCIIDigit(input[position])))
389                 linePositionBuilder.append(input[position++]);
390             if (position < input.length() && !WebVTTParser::isASpace(input[position]))
391                 goto Otherwise;
392             String linePosition = linePositionBuilder.toString();
393
394             // 3-5 - If there is not at least one digit character,
395             //       a '-' occurs anywhere other than the front, or
396             //       a '%' occurs anywhere other than the end, then
397             //       ignore this setting and keep looking.
398             if (linePosition.find('-', 1) != notFound || linePosition.reverseFind("%", linePosition.length() - 2) != notFound)
399                 break;
400
401             // 6 - If the first char is a '-' and the last char is a '%', ignore and keep looking.
402             if (linePosition[0] == '-' && linePosition[linePosition.length() - 1] == '%')
403                 break;
404
405             // 7 - Interpret as number (toInt ignores trailing non-digit characters,
406             //     such as a possible '%').
407             bool validNumber;
408             int number = linePosition.toInt(&validNumber);
409             if (!validNumber)
410                 break;
411
412             // 8 - If the last char is a '%' and the value is not between 0 and 100, ignore and keep looking.
413             if (linePosition[linePosition.length() - 1] == '%') {
414                 if (number < 0 || number > 100)
415                     break;
416
417                 // 10 - If '%' then set snap-to-lines flag to false.
418                 m_snapToLines = false;
419             }
420
421             // 9 - Set cue line position to the number found.
422             m_linePosition = number;
423             }
424             break;
425         case 'T':
426             {
427             // 1-2 - Collect characters that are digits.
428             String textPosition = WebVTTParser::collectDigits(input, &position);
429             if (position >= input.length())
430                 break;
431
432             // 3 - Character at end must be '%', otherwise ignore and keep looking.
433             if (input[position++] != '%')
434                 goto Otherwise;
435
436             // 4-6 - Ensure no other chars in this setting and setting is not empty.
437             if (position < input.length() && !WebVTTParser::isASpace(input[position]))
438                 goto Otherwise;
439             if (textPosition.isEmpty())
440                 break;
441
442             // 7-8 - Interpret as number and make sure it is between 0 and 100
443             // (toInt ignores trailing non-digit characters, such as a possible '%').
444             bool validNumber;
445             int number = textPosition.toInt(&validNumber);
446             if (!validNumber)
447                 break;
448             if (number < 0 || number > 100)
449               break;
450
451             // 9 - Set cue text position to the number found.
452             m_textPosition = number;
453             }
454             break;
455         case 'S':
456             {
457             // 1-2 - Collect characters that are digits.
458             String cueSize = WebVTTParser::collectDigits(input, &position);
459             if (position >= input.length())
460                 break;
461
462             // 3 - Character at end must be '%', otherwise ignore and keep looking.
463             if (input[position++] != '%')
464                 goto Otherwise;
465
466             // 4-6 - Ensure no other chars in this setting and setting is not empty.
467             if (position < input.length() && !WebVTTParser::isASpace(input[position]))
468                 goto Otherwise;
469             if (cueSize.isEmpty())
470                 break;
471
472             // 7-8 - Interpret as number and make sure it is between 0 and 100.
473             bool validNumber;
474             int number = cueSize.toInt(&validNumber);
475             if (!validNumber)
476                 break;
477             if (number < 0 || number > 100)
478                 break;
479
480             // 9 - Set cue size to the number found.
481             m_cueSize = number;
482             }
483             break;
484         case 'A':
485             {
486             // 1-4 - Collect the next word and set the cue alignment accordingly.
487             String cueAlignment = WebVTTParser::collectWord(input, &position);
488             if (cueAlignment == startKeyword())
489                 m_cueAlignment = Start;
490             else if (cueAlignment == middleKeyword())
491                 m_cueAlignment = Middle;
492             else if (cueAlignment == endKeyword())
493                 m_cueAlignment = End;
494             }
495             break;
496         }
497
498         continue;
499
500 Otherwise:
501         // Collect a sequence of characters that are not space characters and discard them.
502         WebVTTParser::collectWord(input, &position);
503     }
504 }
505
506 const AtomicString& TextTrackCue::interfaceName() const
507 {
508     return eventNames().interfaceForTextTrackCue;
509 }
510
511 ScriptExecutionContext* TextTrackCue::scriptExecutionContext() const
512 {
513     return m_scriptExecutionContext;
514 }
515
516 EventTargetData* TextTrackCue::eventTargetData()
517 {
518     return &m_eventTargetData;
519 }
520
521 EventTargetData* TextTrackCue::ensureEventTargetData()
522 {
523     return &m_eventTargetData;
524 }
525
526 } // namespace WebCore
527
528 #endif