r184718 and r184725 caused four tests to begin crashing
[WebKit-https.git] / Source / WebCore / svg / properties / SVGListProperty.h
1 /*
2  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifndef SVGListProperty_h
21 #define SVGListProperty_h
22
23 #include "SVGException.h"
24 #include "SVGPropertyTearOff.h"
25 #include "SVGPropertyTraits.h"
26
27 namespace WebCore {
28
29 enum ListModification {
30     ListModificationUnknown = 0,
31     ListModificationInsert = 1,
32     ListModificationReplace = 2,
33     ListModificationRemove = 3,
34     ListModificationAppend = 4
35 };
36
37 template<typename PropertyType>
38 class SVGAnimatedListPropertyTearOff;
39
40 template<typename PropertyType>
41 class SVGListProperty : public SVGProperty {
42 public:
43     typedef SVGListProperty<PropertyType> Self;
44
45     typedef typename SVGPropertyTraits<PropertyType>::ListItemType ListItemType;
46     typedef SVGPropertyTearOff<ListItemType> ListItemTearOff;
47     typedef RefPtr<ListItemTearOff> PtrListItemTearOff;
48     typedef SVGAnimatedListPropertyTearOff<PropertyType> AnimatedListPropertyTearOff;
49     typedef typename SVGAnimatedListPropertyTearOff<PropertyType>::ListWrapperCache ListWrapperCache;
50
51     bool canAlterList(ExceptionCode& ec) const
52     {
53         if (m_role == AnimValRole) {
54             ec = NO_MODIFICATION_ALLOWED_ERR;
55             return false;
56         }
57
58         return true;
59     }
60
61     static void detachListWrappersAndResize(ListWrapperCache* wrappers, unsigned newListSize = 0)
62     {
63         // See SVGPropertyTearOff::detachWrapper() for an explanation about what's happening here.
64         ASSERT(wrappers);
65         unsigned size = wrappers->size();
66         for (unsigned i = 0; i < size; ++i) {
67             if (ListItemTearOff* item = wrappers->at(i).get())
68                 item->detachWrapper();
69         }
70
71         // Reinitialize the wrapper cache to be equal to the new values size, after the XML DOM changed the list.
72         if (newListSize)
73             wrappers->fill(0, newListSize);
74         else
75             wrappers->clear();
76     }
77
78     void detachListWrappers(unsigned newListSize)
79     {
80         detachListWrappersAndResize(m_wrappers, newListSize);
81     }
82
83     void setValuesAndWrappers(PropertyType* values, ListWrapperCache* wrappers, bool shouldOwnValues)
84     {
85         // This is only used for animVal support, to switch the underlying values & wrappers
86         // to the current animated values, once animation for a list starts.
87         ASSERT(m_values);
88         ASSERT(m_wrappers);
89         ASSERT(m_role == AnimValRole);
90         if (m_ownsValues)
91             delete m_values;
92         m_values = values;
93         m_ownsValues = shouldOwnValues;
94         m_wrappers = wrappers;
95         ASSERT(m_values->size() == m_wrappers->size());
96     }
97
98     // SVGList::clear()
99     void clearValues(ExceptionCode& ec)
100     {
101         if (!canAlterList(ec))
102             return;
103
104         m_values->clear();
105         commitChange();
106     }
107
108     void clearValuesAndWrappers(ExceptionCode& ec)
109     {
110         if (!canAlterList(ec))
111             return;
112
113         detachListWrappers(0);
114         m_values->clear();
115         commitChange();
116     }
117
118     // SVGList::numberOfItems()
119     unsigned numberOfItems() const
120     {
121         return m_values->size();
122     }
123
124     // SVGList::initialize()
125     ListItemType initializeValues(const ListItemType& newItem, ExceptionCode& ec)
126     {
127         if (!canAlterList(ec))
128             return ListItemType();
129
130         // Spec: If the inserted item is already in a list, it is removed from its previous list before it is inserted into this list.
131         processIncomingListItemValue(newItem, 0);
132
133         // Spec: Clears all existing current items from the list and re-initializes the list to hold the single item specified by the parameter.
134         m_values->clear();
135         m_values->append(newItem);
136
137         commitChange();
138         return newItem;
139     }
140
141     PtrListItemTearOff initializeValuesAndWrappers(PtrListItemTearOff newItem, ExceptionCode& ec)
142     {
143         ASSERT(m_wrappers);
144         if (!canAlterList(ec))
145             return nullptr;
146
147         // Not specified, but FF/Opera do it this way, and it's just sane.
148         if (!newItem) {
149             ec = SVGException::SVG_WRONG_TYPE_ERR;
150             return nullptr;
151         }
152
153         ASSERT(m_values->size() == m_wrappers->size());
154
155         // Spec: If the inserted item is already in a list, it is removed from its previous list before it is inserted into this list.
156         processIncomingListItemWrapper(newItem, 0);
157
158         // Spec: Clears all existing current items from the list and re-initializes the list to hold the single item specified by the parameter.
159         detachListWrappers(0);
160         m_values->clear();
161
162         m_values->append(newItem->propertyReference());
163         m_wrappers->append(newItem);
164
165         commitChange();
166         return newItem;
167     }
168
169     // SVGList::getItem()
170     bool canGetItem(unsigned index, ExceptionCode& ec)
171     {
172         if (index >= m_values->size()) {
173             ec = INDEX_SIZE_ERR;
174             return false;
175         }
176
177         return true;
178     }
179
180     ListItemType getItemValues(unsigned index, ExceptionCode& ec)
181     {
182         if (!canGetItem(index, ec))
183             return ListItemType();
184
185         // Spec: Returns the specified item from the list. The returned item is the item itself and not a copy.
186         return m_values->at(index);
187     }
188
189     PtrListItemTearOff getItemValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, unsigned index, ExceptionCode& ec)
190     {
191         ASSERT(m_wrappers);
192         if (!canGetItem(index, ec))
193             return nullptr;
194
195         // Spec: Returns the specified item from the list. The returned item is the item itself and not a copy.
196         // Any changes made to the item are immediately reflected in the list.
197         ASSERT(m_values->size() == m_wrappers->size());
198         RefPtr<ListItemTearOff> wrapper = m_wrappers->at(index);
199         if (!wrapper) {
200             // Create new wrapper, which is allowed to directly modify the item in the list, w/o copying and cache the wrapper in our map.
201             // It is also associated with our animated property, so it can notify the SVG Element which holds the SVGAnimated*List
202             // that it has been modified (and thus can call svgAttributeChanged(associatedAttributeName)).
203             wrapper = ListItemTearOff::create(animatedList, UndefinedRole, m_values->at(index));
204             m_wrappers->at(index) = wrapper;
205         }
206
207         return wrapper.release();
208     }
209
210     // SVGList::insertItemBefore()
211     ListItemType insertItemBeforeValues(const ListItemType& newItem, unsigned index, ExceptionCode& ec)
212     {
213         if (!canAlterList(ec))
214             return ListItemType();
215
216         // Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list.
217         if (index > m_values->size())
218             index = m_values->size();
219
220         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
221         if (!processIncomingListItemValue(newItem, &index)) {
222             // Inserting the item before itself is a no-op.
223             return newItem;
224         }
225
226         // Spec: Inserts a new item into the list at the specified position. The index of the item before which the new item is to be
227         // inserted. The first item is number 0. If the index is equal to 0, then the new item is inserted at the front of the list.
228         m_values->insert(index, newItem);
229
230         commitChange();
231         return newItem;
232     }
233
234     PtrListItemTearOff insertItemBeforeValuesAndWrappers(PtrListItemTearOff newItem, unsigned index, ExceptionCode& ec)
235     {
236         ASSERT(m_wrappers);
237         if (!canAlterList(ec))
238             return nullptr;
239
240         // Not specified, but FF/Opera do it this way, and it's just sane.
241         if (!newItem) {
242             ec = SVGException::SVG_WRONG_TYPE_ERR;
243             return nullptr;
244         }
245
246         // Spec: If the index is greater than or equal to numberOfItems, then the new item is appended to the end of the list.
247         if (index > m_values->size())
248              index = m_values->size();
249
250         ASSERT(m_values->size() == m_wrappers->size());
251
252         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
253         if (!processIncomingListItemWrapper(newItem, &index))
254             return newItem.release();
255
256         // Spec: Inserts a new item into the list at the specified position. The index of the item before which the new item is to be
257         // inserted. The first item is number 0. If the index is equal to 0, then the new item is inserted at the front of the list.
258         m_values->insert(index, newItem->propertyReference());
259
260         // Store new wrapper at position 'index', change its underlying value, so mutations of newItem, directly affect the item in the list.
261         m_wrappers->insert(index, newItem);
262
263         commitChange();
264         return newItem;
265     }
266
267     // SVGList::replaceItem()
268     bool canReplaceItem(unsigned index, ExceptionCode& ec)
269     {
270         if (!canAlterList(ec))
271             return false;
272
273         if (index >= m_values->size()) {
274             ec = INDEX_SIZE_ERR;
275             return false;
276         }
277
278         return true;
279     }
280
281     ListItemType replaceItemValues(const ListItemType& newItem, unsigned index, ExceptionCode& ec)
282     {
283         if (!canReplaceItem(index, ec))
284             return ListItemType();
285
286         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
287         // Spec: If the item is already in this list, note that the index of the item to replace is before the removal of the item.
288         if (!processIncomingListItemValue(newItem, &index)) {
289             // Replacing the item with itself is a no-op.
290             return newItem;
291         }
292
293         if (m_values->isEmpty()) {
294             // 'newItem' already lived in our list, we removed it, and now we're empty, which means there's nothing to replace.
295             ec = INDEX_SIZE_ERR;
296             return ListItemType();
297         }
298
299         // Update the value at the desired position 'index'. 
300         m_values->at(index) = newItem;
301
302         commitChange();
303         return newItem;
304     }
305
306     PtrListItemTearOff replaceItemValuesAndWrappers(PtrListItemTearOff newItem, unsigned index, ExceptionCode& ec)
307     {
308         ASSERT(m_wrappers);
309         if (!canReplaceItem(index, ec))
310             return nullptr;
311
312         // Not specified, but FF/Opera do it this way, and it's just sane.
313         if (!newItem) {
314             ec = SVGException::SVG_WRONG_TYPE_ERR;
315             return nullptr;
316         }
317
318         ASSERT(m_values->size() == m_wrappers->size());
319
320         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
321         // Spec: If the item is already in this list, note that the index of the item to replace is before the removal of the item.
322         if (!processIncomingListItemWrapper(newItem, &index))
323             return newItem;
324
325         if (m_values->isEmpty()) {
326             ASSERT(m_wrappers->isEmpty());
327             // 'newItem' already lived in our list, we removed it, and now we're empty, which means there's nothing to replace.
328             ec = INDEX_SIZE_ERR;
329             return nullptr;
330         }
331
332         // Detach the existing wrapper.
333         RefPtr<ListItemTearOff> oldItem = m_wrappers->at(index);
334         if (oldItem)
335             oldItem->detachWrapper();
336
337         // Update the value and the wrapper at the desired position 'index'. 
338         m_values->at(index) = newItem->propertyReference();
339         m_wrappers->at(index) = newItem;
340
341         commitChange();
342         return newItem;
343     }
344
345     // SVGList::removeItem()
346     bool canRemoveItem(unsigned index, ExceptionCode& ec)
347     {
348         if (!canAlterList(ec))
349             return false;
350
351         if (index >= m_values->size()) {
352             ec = INDEX_SIZE_ERR;
353             return false;
354         }
355
356         return true;
357     }
358
359     ListItemType removeItemValues(unsigned index, ExceptionCode& ec)
360     {
361         if (!canRemoveItem(index, ec))
362             return ListItemType();
363
364         ListItemType oldItem = m_values->at(index);
365         m_values->remove(index);
366
367         commitChange();
368         return oldItem;
369     }
370
371     PtrListItemTearOff removeItemValuesAndWrappers(AnimatedListPropertyTearOff* animatedList, unsigned index, ExceptionCode& ec)
372     {
373         ASSERT(m_wrappers);
374         if (!canRemoveItem(index, ec))
375             return nullptr;
376
377         ASSERT(m_values->size() == m_wrappers->size());
378
379         // Detach the existing wrapper.
380         RefPtr<ListItemTearOff> oldItem = m_wrappers->at(index);
381         if (!oldItem)
382             oldItem = ListItemTearOff::create(animatedList, UndefinedRole, m_values->at(index));
383
384         oldItem->detachWrapper();
385         m_wrappers->remove(index);
386         m_values->remove(index);
387
388         commitChange();
389         return oldItem.release();
390     }
391
392     // SVGList::appendItem()
393     ListItemType appendItemValues(const ListItemType& newItem, ExceptionCode& ec)
394     {
395         if (!canAlterList(ec))
396             return ListItemType();
397
398         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
399         processIncomingListItemValue(newItem, 0);
400
401         // Append the value at the end of the list.
402         m_values->append(newItem);
403
404         commitChange(ListModificationAppend);
405         return newItem;
406     }
407
408     PtrListItemTearOff appendItemValuesAndWrappers(PtrListItemTearOff newItem, ExceptionCode& ec)
409     {
410         ASSERT(m_wrappers);
411         if (!canAlterList(ec))
412             return nullptr;
413
414         // Not specified, but FF/Opera do it this way, and it's just sane.
415         if (!newItem) {
416             ec = SVGException::SVG_WRONG_TYPE_ERR;
417             return nullptr;
418         }
419
420         ASSERT(m_values->size() == m_wrappers->size());
421
422         // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
423         processIncomingListItemWrapper(newItem, 0);
424
425         // Append the value and wrapper at the end of the list.
426         m_values->append(newItem->propertyReference());
427         m_wrappers->append(newItem);
428
429         commitChange(ListModificationAppend);
430         return newItem;
431     }
432
433     PropertyType& values()
434     {
435         ASSERT(m_values);
436         return *m_values;
437     }
438
439     ListWrapperCache& wrappers() const
440     {
441         ASSERT(m_wrappers);
442         return *m_wrappers;
443     }
444
445 protected:
446     SVGListProperty(SVGPropertyRole role, PropertyType& values, ListWrapperCache* wrappers)
447         : m_role(role)
448         , m_ownsValues(false)
449         , m_values(&values)
450         , m_wrappers(wrappers)
451     {
452     }
453
454     virtual ~SVGListProperty()
455     {
456         if (m_ownsValues)
457             delete m_values;
458     }
459
460     virtual void commitChange() = 0;
461     virtual void commitChange(ListModification)
462     {
463         commitChange();
464     }
465
466     virtual bool processIncomingListItemValue(const ListItemType& newItem, unsigned* indexToModify) = 0;
467     virtual bool processIncomingListItemWrapper(RefPtr<ListItemTearOff>& newItem, unsigned* indexToModify) = 0;
468
469     SVGPropertyRole m_role;
470     bool m_ownsValues;
471     PropertyType* m_values;
472     ListWrapperCache* m_wrappers;
473 };
474
475 }
476
477 #endif // SVGListProperty_h