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