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 42 namespace 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 46 template<typename Derived, typename ItemProperty> 47 class SVGListPropertyHelper : public SVGPropertyHelper<Derived> { 48 public: 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 154 protected: 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 164 private: 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 182 template<typename Derived, typename ItemProperty> 183 bool 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 197 template<typename Derived, typename ItemProperty> 198 void 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 211 template<typename Derived, typename ItemProperty> 212 PassRefPtr<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 225 template<typename Derived, typename ItemProperty> 226 PassRefPtr<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 236 template<typename Derived, typename ItemProperty> 237 PassRefPtr<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 259 template<typename Derived, typename ItemProperty> 260 PassRefPtr<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 273 template<typename Derived, typename ItemProperty> 274 PassRefPtr<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 287 template<typename Derived, typename ItemProperty> 288 PassRefPtr<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 318 template<typename Derived, typename ItemProperty> 319 bool 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 329 template<typename Derived, typename ItemProperty> 330 bool 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 364 template<typename Derived, typename ItemProperty> 365 size_t SVGListPropertyHelper<Derived, ItemProperty>::findItem(PassRefPtr<ItemPropertyType> item) 366 { 367 return m_values.find(item); 368 } 369 370 template<typename Derived, typename ItemProperty> 371 void 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 383 template<typename Derived, typename ItemProperty> 384 bool 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