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