1/*
2 * Copyright (C) 2013 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#ifndef SVGListPropertyHelper_h
32#define SVGListPropertyHelper_h
33
34#include "bindings/core/v8/ExceptionMessages.h"
35#include "bindings/core/v8/ExceptionStatePlaceholder.h"
36#include "core/dom/ExceptionCode.h"
37#include "core/svg/SVGAnimationElement.h"
38#include "core/svg/properties/SVGPropertyHelper.h"
39#include "wtf/PassRefPtr.h"
40#include "wtf/Vector.h"
41
42namespace blink {
43
44// This is an implementation of the SVG*List property spec:
45// http://www.w3.org/TR/SVG/single-page.html#types-InterfaceSVGLengthList
46template<typename Derived, typename ItemProperty>
47class SVGListPropertyHelper : public SVGPropertyHelper<Derived> {
48public:
49    typedef ItemProperty ItemPropertyType;
50
51    SVGListPropertyHelper()
52    {
53    }
54
55    ~SVGListPropertyHelper()
56    {
57        clear();
58    }
59
60    // used from Blink C++ code:
61
62    ItemPropertyType* at(size_t index)
63    {
64        ASSERT(index < m_values.size());
65        ASSERT(m_values.at(index)->ownerList() == this);
66        return m_values.at(index).get();
67    }
68
69    const ItemPropertyType* at(size_t index) const
70    {
71        return const_cast<SVGListPropertyHelper<Derived, ItemProperty>*>(this)->at(index);
72    }
73
74    class ConstIterator {
75    private:
76        typedef typename Vector<RefPtr<ItemPropertyType> >::const_iterator WrappedType;
77
78    public:
79        ConstIterator(WrappedType it)
80            : m_it(it)
81        {
82        }
83
84        ConstIterator& operator++() { ++m_it; return *this; }
85
86        bool operator==(const ConstIterator& o) const { return m_it == o.m_it; }
87        bool operator!=(const ConstIterator& o) const { return m_it != o.m_it; }
88
89        PassRefPtr<ItemPropertyType> operator*() { return *m_it; }
90        PassRefPtr<ItemPropertyType> operator->() { return *m_it; }
91
92    private:
93        WrappedType m_it;
94    };
95
96    ConstIterator begin() const
97    {
98        return ConstIterator(m_values.begin());
99    }
100
101    ConstIterator lastAppended() const
102    {
103        return ConstIterator(m_values.begin() + m_values.size() - 1);
104    }
105
106    ConstIterator end() const
107    {
108        return ConstIterator(m_values.end());
109    }
110
111    void append(PassRefPtr<ItemPropertyType> passNewItem)
112    {
113        RefPtr<ItemPropertyType> newItem = passNewItem;
114
115        ASSERT(newItem);
116        m_values.append(newItem);
117        newItem->setOwnerList(this);
118    }
119
120    bool operator==(const Derived&) const;
121    bool operator!=(const Derived& other) const
122    {
123        return !(*this == other);
124    }
125
126    bool isEmpty() const
127    {
128        return !length();
129    }
130
131    virtual PassRefPtr<Derived> clone()
132    {
133        RefPtr<Derived> svgList = Derived::create();
134        svgList->deepCopy(static_cast<Derived*>(this));
135        return svgList.release();
136    }
137
138    // SVGList*Property DOM spec:
139
140    size_t length() const
141    {
142        return m_values.size();
143    }
144
145    void clear();
146
147    PassRefPtr<ItemPropertyType> initialize(PassRefPtr<ItemPropertyType>);
148    PassRefPtr<ItemPropertyType> getItem(size_t, ExceptionState&);
149    PassRefPtr<ItemPropertyType> insertItemBefore(PassRefPtr<ItemPropertyType>, size_t);
150    PassRefPtr<ItemPropertyType> removeItem(size_t, ExceptionState&);
151    PassRefPtr<ItemPropertyType> appendItem(PassRefPtr<ItemPropertyType>);
152    PassRefPtr<ItemPropertyType> replaceItem(PassRefPtr<ItemPropertyType>, size_t, ExceptionState&);
153
154protected:
155    void deepCopy(PassRefPtr<Derived>);
156
157    bool adjustFromToListValues(PassRefPtr<Derived> fromList, PassRefPtr<Derived> toList, float percentage, AnimationMode);
158
159    virtual PassRefPtr<ItemPropertyType> createPaddingItem() const
160    {
161        return ItemPropertyType::create();
162    }
163
164private:
165    inline bool checkIndexBound(size_t, ExceptionState&);
166    bool removeFromOldOwnerListAndAdjustIndex(PassRefPtr<ItemPropertyType>, size_t* indexToModify);
167    size_t findItem(PassRefPtr<ItemPropertyType>);
168
169    Vector<RefPtr<ItemPropertyType> > m_values;
170
171    static PassRefPtr<Derived> toDerived(PassRefPtr<SVGPropertyBase> passBase)
172    {
173        if (!passBase)
174            return nullptr;
175
176        RefPtr<SVGPropertyBase> base = passBase;
177        ASSERT(base->type() == Derived::classType());
178        return static_pointer_cast<Derived>(base);
179    }
180};
181
182template<typename Derived, typename ItemProperty>
183bool SVGListPropertyHelper<Derived, ItemProperty>::operator==(const Derived& other) const
184{
185    if (length() != other.length())
186        return false;
187
188    size_t size = length();
189    for (size_t i = 0; i < size; ++i) {
190        if (*at(i) != *other.at(i))
191            return false;
192    }
193
194    return true;
195}
196
197template<typename Derived, typename ItemProperty>
198void SVGListPropertyHelper<Derived, ItemProperty>::clear()
199{
200    // detach all list items as they are no longer part of this list
201    typename Vector<RefPtr<ItemPropertyType> >::const_iterator it = m_values.begin();
202    typename Vector<RefPtr<ItemPropertyType> >::const_iterator itEnd = m_values.end();
203    for (; it != itEnd; ++it) {
204        ASSERT((*it)->ownerList() == this);
205        (*it)->setOwnerList(0);
206    }
207
208    m_values.clear();
209}
210
211template<typename Derived, typename ItemProperty>
212PassRefPtr<ItemProperty> SVGListPropertyHelper<Derived, ItemProperty>::initialize(PassRefPtr<ItemProperty> passNewItem)
213{
214    RefPtr<ItemPropertyType> newItem = passNewItem;
215
216    // Spec: If the inserted item is already in a list, it is removed from its previous list before it is inserted into this list.
217    removeFromOldOwnerListAndAdjustIndex(newItem, 0);
218
219    // Spec: Clears all existing current items from the list and re-initializes the list to hold the single item specified by the parameter.
220    clear();
221    append(newItem);
222    return newItem.release();
223}
224
225template<typename Derived, typename ItemProperty>
226PassRefPtr<ItemProperty> SVGListPropertyHelper<Derived, ItemProperty>::getItem(size_t index, ExceptionState& exceptionState)
227{
228    if (!checkIndexBound(index, exceptionState))
229        return nullptr;
230
231    ASSERT(index < m_values.size());
232    ASSERT(m_values.at(index)->ownerList() == this);
233    return m_values.at(index);
234}
235
236template<typename Derived, typename ItemProperty>
237PassRefPtr<ItemProperty> SVGListPropertyHelper<Derived, ItemProperty>::insertItemBefore(PassRefPtr<ItemProperty> passNewItem, size_t index)
238{
239    // Spec: If the index is greater than or equal to length, then the new item is appended to the end of the list.
240    if (index > m_values.size())
241        index = m_values.size();
242
243    RefPtr<ItemPropertyType> newItem = passNewItem;
244
245    // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
246    if (!removeFromOldOwnerListAndAdjustIndex(newItem, &index)) {
247        // Inserting the item before itself is a no-op.
248        return newItem.release();
249    }
250
251    // 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
252    // 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.
253    m_values.insert(index, newItem);
254    newItem->setOwnerList(this);
255
256    return newItem.release();
257}
258
259template<typename Derived, typename ItemProperty>
260PassRefPtr<ItemProperty> SVGListPropertyHelper<Derived, ItemProperty>::removeItem(size_t index, ExceptionState& exceptionState)
261{
262    if (index >= m_values.size()) {
263        exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexExceedsMaximumBound("index", index, m_values.size()));
264        return nullptr;
265    }
266    ASSERT(m_values.at(index)->ownerList() == this);
267    RefPtr<ItemPropertyType> oldItem = m_values.at(index);
268    m_values.remove(index);
269    oldItem->setOwnerList(0);
270    return oldItem.release();
271}
272
273template<typename Derived, typename ItemProperty>
274PassRefPtr<ItemProperty> SVGListPropertyHelper<Derived, ItemProperty>::appendItem(PassRefPtr<ItemProperty> passNewItem)
275{
276    RefPtr<ItemPropertyType> newItem = passNewItem;
277
278    // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
279    removeFromOldOwnerListAndAdjustIndex(newItem, 0);
280
281    // Append the value and wrapper at the end of the list.
282    append(newItem);
283
284    return newItem.release();
285}
286
287template<typename Derived, typename ItemProperty>
288PassRefPtr<ItemProperty> SVGListPropertyHelper<Derived, ItemProperty>::replaceItem(PassRefPtr<ItemProperty> passNewItem, size_t index, ExceptionState& exceptionState)
289{
290    if (!checkIndexBound(index, exceptionState))
291        return nullptr;
292
293    RefPtr<ItemPropertyType> newItem = passNewItem;
294
295    // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
296    // 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.
297    if (!removeFromOldOwnerListAndAdjustIndex(newItem, &index)) {
298        // Replacing the item with itself is a no-op.
299        return newItem.release();
300    }
301
302    if (m_values.isEmpty()) {
303        // 'newItem' already lived in our list, we removed it, and now we're empty, which means there's nothing to replace.
304        exceptionState.throwDOMException(IndexSizeError, String::format("Failed to replace the provided item at index %zu.", index));
305        return nullptr;
306    }
307
308    // Update the value at the desired position 'index'.
309    RefPtr<ItemPropertyType>& position = m_values[index];
310    ASSERT(position->ownerList() == this);
311    position->setOwnerList(0);
312    position = newItem;
313    newItem->setOwnerList(this);
314
315    return newItem.release();
316}
317
318template<typename Derived, typename ItemProperty>
319bool SVGListPropertyHelper<Derived, ItemProperty>::checkIndexBound(size_t index, ExceptionState& exceptionState)
320{
321    if (index >= m_values.size()) {
322        exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexExceedsMaximumBound("index", index, m_values.size()));
323        return false;
324    }
325
326    return true;
327}
328
329template<typename Derived, typename ItemProperty>
330bool SVGListPropertyHelper<Derived, ItemProperty>::removeFromOldOwnerListAndAdjustIndex(PassRefPtr<ItemPropertyType> passItem, size_t* indexToModify)
331{
332    RefPtr<ItemPropertyType> item = passItem;
333    ASSERT(item);
334    RefPtr<Derived> ownerList = toDerived(item->ownerList());
335    if (!ownerList)
336        return true;
337
338    // Spec: If newItem is already in a list, it is removed from its previous list before it is inserted into this list.
339    // 'newItem' is already living in another list. If it's not our list, synchronize the other lists wrappers after the removal.
340    bool livesInOtherList = ownerList.get() != this;
341    size_t indexToRemove = ownerList->findItem(item);
342    ASSERT(indexToRemove != WTF::kNotFound);
343
344    // Do not remove newItem if already in this list at the target index.
345    if (!livesInOtherList && indexToModify && indexToRemove == *indexToModify)
346        return false;
347
348    ownerList->removeItem(indexToRemove, ASSERT_NO_EXCEPTION);
349
350    if (!indexToModify)
351        return true;
352
353    // If the item lived in our list, adjust the insertion index.
354    if (!livesInOtherList) {
355        size_t& index = *indexToModify;
356        // Spec: If the item is already in this list, note that the index of the item to (replace|insert before) is before the removal of the item.
357        if (static_cast<size_t>(indexToRemove) < index)
358            --index;
359    }
360
361    return true;
362}
363
364template<typename Derived, typename ItemProperty>
365size_t SVGListPropertyHelper<Derived, ItemProperty>::findItem(PassRefPtr<ItemPropertyType> item)
366{
367    return m_values.find(item);
368}
369
370template<typename Derived, typename ItemProperty>
371void SVGListPropertyHelper<Derived, ItemProperty>::deepCopy(PassRefPtr<Derived> passFrom)
372{
373    RefPtr<Derived> from = passFrom;
374
375    clear();
376    typename Vector<RefPtr<ItemPropertyType> >::const_iterator it = from->m_values.begin();
377    typename Vector<RefPtr<ItemPropertyType> >::const_iterator itEnd = from->m_values.end();
378    for (; it != itEnd; ++it) {
379        append((*it)->clone());
380    }
381}
382
383template<typename Derived, typename ItemProperty>
384bool SVGListPropertyHelper<Derived, ItemProperty>::adjustFromToListValues(PassRefPtr<Derived> passFromList, PassRefPtr<Derived> passToList, float percentage, AnimationMode mode)
385{
386    RefPtr<Derived> fromList = passFromList;
387    RefPtr<Derived> toList = passToList;
388
389    // If no 'to' value is given, nothing to animate.
390    size_t toListSize = toList->length();
391    if (!toListSize)
392        return false;
393
394    // If the 'from' value is given and it's length doesn't match the 'to' value list length, fallback to a discrete animation.
395    size_t fromListSize = fromList->length();
396    if (fromListSize != toListSize && fromListSize) {
397        if (percentage < 0.5) {
398            if (mode != ToAnimation)
399                deepCopy(fromList);
400        } else {
401            deepCopy(toList);
402        }
403
404        return false;
405    }
406
407    ASSERT(!fromListSize || fromListSize == toListSize);
408    if (length() < toListSize) {
409        size_t paddingCount = toListSize - length();
410        for (size_t i = 0; i < paddingCount; ++i)
411            append(createPaddingItem());
412    }
413
414    return true;
415}
416
417}
418
419#endif // SVGListPropertyHelper_h
420