/src/mozilla-central/dom/svg/DOMSVGPathSegList.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=8 sts=2 et sw=2 tw=80: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "nsSVGElement.h" |
8 | | #include "DOMSVGPathSegList.h" |
9 | | #include "DOMSVGPathSeg.h" |
10 | | #include "nsError.h" |
11 | | #include "SVGAnimatedPathSegList.h" |
12 | | #include "nsCOMPtr.h" |
13 | | #include "nsSVGAttrTearoffTable.h" |
14 | | #include "SVGPathSegUtils.h" |
15 | | #include "mozilla/dom/SVGPathSegListBinding.h" |
16 | | |
17 | | // See the comment in this file's header. |
18 | | |
19 | | namespace mozilla { |
20 | | |
21 | | static inline |
22 | | nsSVGAttrTearoffTable<void, DOMSVGPathSegList>& |
23 | | SVGPathSegListTearoffTable() |
24 | 0 | { |
25 | 0 | static nsSVGAttrTearoffTable<void, DOMSVGPathSegList> |
26 | 0 | sSVGPathSegListTearoffTable; |
27 | 0 | return sSVGPathSegListTearoffTable; |
28 | 0 | } |
29 | | |
30 | | NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPathSegList) |
31 | | |
32 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPathSegList) |
33 | 0 | // No unlinking of mElement, we'd need to null out the value pointer (the |
34 | 0 | // object it points to is held by the element) and null-check it everywhere. |
35 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER |
36 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
37 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPathSegList) |
38 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement) |
39 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
40 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGPathSegList) |
41 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER |
42 | 0 | NS_IMPL_CYCLE_COLLECTION_TRACE_END |
43 | | |
44 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGPathSegList) |
45 | | NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGPathSegList) |
46 | | |
47 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGPathSegList) |
48 | 0 | NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY |
49 | 0 | NS_INTERFACE_MAP_ENTRY(nsISupports) |
50 | 0 | NS_INTERFACE_MAP_END |
51 | | |
52 | | |
53 | | //---------------------------------------------------------------------- |
54 | | // Helper class: AutoChangePathSegListNotifier |
55 | | // Stack-based helper class to pair calls to WillChangePathSegList and |
56 | | // DidChangePathSegList. |
57 | | class MOZ_RAII AutoChangePathSegListNotifier |
58 | | { |
59 | | public: |
60 | | explicit AutoChangePathSegListNotifier(DOMSVGPathSegList* aPathSegList MOZ_GUARD_OBJECT_NOTIFIER_PARAM) |
61 | | : mPathSegList(aPathSegList) |
62 | 0 | { |
63 | 0 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
64 | 0 | MOZ_ASSERT(mPathSegList, "Expecting non-null pathSegList"); |
65 | 0 | mEmptyOrOldValue = |
66 | 0 | mPathSegList->Element()->WillChangePathSegList(); |
67 | 0 | } |
68 | | |
69 | | ~AutoChangePathSegListNotifier() |
70 | 0 | { |
71 | 0 | mPathSegList->Element()->DidChangePathSegList(mEmptyOrOldValue); |
72 | 0 | if (mPathSegList->AttrIsAnimating()) { |
73 | 0 | mPathSegList->Element()->AnimationNeedsResample(); |
74 | 0 | } |
75 | 0 | } |
76 | | |
77 | | private: |
78 | | DOMSVGPathSegList* const mPathSegList; |
79 | | nsAttrValue mEmptyOrOldValue; |
80 | | MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER |
81 | | }; |
82 | | |
83 | | /* static */ already_AddRefed<DOMSVGPathSegList> |
84 | | DOMSVGPathSegList::GetDOMWrapper(void *aList, |
85 | | nsSVGElement *aElement, |
86 | | bool aIsAnimValList) |
87 | 0 | { |
88 | 0 | RefPtr<DOMSVGPathSegList> wrapper = |
89 | 0 | SVGPathSegListTearoffTable().GetTearoff(aList); |
90 | 0 | if (!wrapper) { |
91 | 0 | wrapper = new DOMSVGPathSegList(aElement, aIsAnimValList); |
92 | 0 | SVGPathSegListTearoffTable().AddTearoff(aList, wrapper); |
93 | 0 | } |
94 | 0 | return wrapper.forget(); |
95 | 0 | } |
96 | | |
97 | | /* static */ DOMSVGPathSegList* |
98 | | DOMSVGPathSegList::GetDOMWrapperIfExists(void *aList) |
99 | 0 | { |
100 | 0 | return SVGPathSegListTearoffTable().GetTearoff(aList); |
101 | 0 | } |
102 | | |
103 | | DOMSVGPathSegList::~DOMSVGPathSegList() |
104 | 0 | { |
105 | 0 | // There are now no longer any references to us held by script or list items. |
106 | 0 | // Note we must use GetAnimValKey/GetBaseValKey here, NOT InternalList()! |
107 | 0 | void *key = mIsAnimValList ? |
108 | 0 | InternalAList().GetAnimValKey() : |
109 | 0 | InternalAList().GetBaseValKey(); |
110 | 0 | SVGPathSegListTearoffTable().RemoveTearoff(key); |
111 | 0 | } |
112 | | |
113 | | JSObject* |
114 | | DOMSVGPathSegList::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto) |
115 | 0 | { |
116 | 0 | return mozilla::dom::SVGPathSegList_Binding::Wrap(cx, this, aGivenProto); |
117 | 0 | } |
118 | | |
119 | | void |
120 | | DOMSVGPathSegList::InternalListWillChangeTo(const SVGPathData& aNewValue) |
121 | 0 | { |
122 | 0 | // When the number of items in our internal counterpart changes, we MUST stay |
123 | 0 | // in sync. Everything in the scary comment in |
124 | 0 | // DOMSVGLengthList::InternalBaseValListWillChangeTo applies here just as |
125 | 0 | // much, but we have the additional issue that failing to stay in sync would |
126 | 0 | // mean that - assuming we aren't reading bad memory - we would likely end up |
127 | 0 | // decoding command types from argument floats when looking in our |
128 | 0 | // SVGPathData's data array! Either way, we'll likely then go down |
129 | 0 | // MOZ_ASSERT_UNREACHABLE code paths, or end up reading/setting more bad |
130 | 0 | // memory!! |
131 | 0 |
|
132 | 0 | // The only time that our other DOM list type implementations remove items is |
133 | 0 | // if those items become surplus items due to an attribute change or SMIL |
134 | 0 | // animation sample shortening the list. In general though, they try to keep |
135 | 0 | // their existing DOM items, even when things change. To be consistent, we'd |
136 | 0 | // really like to do the same thing. However, because different types of path |
137 | 0 | // segment correspond to different DOMSVGPathSeg subclasses, the type of |
138 | 0 | // items in our list are generally not the same, which makes this harder for |
139 | 0 | // us. We have to remove DOM segments if their type is not the same as the |
140 | 0 | // type of the new internal segment at their index. |
141 | 0 | // |
142 | 0 | // We also need to sync up mInternalDataIndex, but since we need to loop over |
143 | 0 | // all the items in the new list checking types anyway, that's almost |
144 | 0 | // insignificant in terms of overhead. |
145 | 0 | // |
146 | 0 | // Note that this method is called on every single SMIL animation resample |
147 | 0 | // and we have no way to short circuit the overhead since we don't have a |
148 | 0 | // way to tell if the call is due to a new animation, or a resample of an |
149 | 0 | // existing animation (when the number and type of items would be the same). |
150 | 0 | // (Note that a new animation could start overriding an existing animation at |
151 | 0 | // any time, so checking IsAnimating() wouldn't work.) Because we get called |
152 | 0 | // on every sample, it would not be acceptable alternative to throw away all |
153 | 0 | // our items and let them be recreated lazily, since that would break what |
154 | 0 | // script sees! |
155 | 0 |
|
156 | 0 | uint32_t length = mItems.Length(); |
157 | 0 | uint32_t index = 0; |
158 | 0 |
|
159 | 0 | uint32_t dataLength = aNewValue.mData.Length(); |
160 | 0 | uint32_t dataIndex = 0; // index into aNewValue's raw data array |
161 | 0 |
|
162 | 0 | uint32_t newSegType; |
163 | 0 |
|
164 | 0 | RefPtr<DOMSVGPathSegList> kungFuDeathGrip; |
165 | 0 | if (length) { |
166 | 0 | // RemovingFromList() might clear last reference to |this|. |
167 | 0 | // Retain a temporary reference to keep from dying before returning. |
168 | 0 | // |
169 | 0 | // NOTE: For path-seg lists (unlike other list types), we have to do this |
170 | 0 | // *whenever our list is nonempty* (even if we're growing in length). |
171 | 0 | // That's because the path-seg-type of any segment could differ between old |
172 | 0 | // list vs. new list, which will make us destroy & recreate that segment, |
173 | 0 | // which could remove the last reference to us. |
174 | 0 | // |
175 | 0 | // (We explicitly *don't* want to create a kungFuDeathGrip in the length=0 |
176 | 0 | // case, though, because we do hit this code inside our constructor before |
177 | 0 | // any other owning references have been added, and at that point, the |
178 | 0 | // deathgrip-removal would make us die before we exit our constructor.) |
179 | 0 | kungFuDeathGrip = this; |
180 | 0 | } |
181 | 0 |
|
182 | 0 | while (index < length && dataIndex < dataLength) { |
183 | 0 | newSegType = SVGPathSegUtils::DecodeType(aNewValue.mData[dataIndex]); |
184 | 0 | if (ItemAt(index) && ItemAt(index)->Type() != newSegType) { |
185 | 0 | ItemAt(index)->RemovingFromList(); |
186 | 0 | ItemAt(index) = nullptr; |
187 | 0 | } |
188 | 0 | // Only after the RemovingFromList() can we touch mInternalDataIndex! |
189 | 0 | mItems[index].mInternalDataIndex = dataIndex; |
190 | 0 | ++index; |
191 | 0 | dataIndex += 1 + SVGPathSegUtils::ArgCountForType(newSegType); |
192 | 0 | } |
193 | 0 |
|
194 | 0 | MOZ_ASSERT((index == length && dataIndex <= dataLength) || |
195 | 0 | (index <= length && dataIndex == dataLength), |
196 | 0 | "very bad - list corruption?"); |
197 | 0 |
|
198 | 0 | if (index < length) { |
199 | 0 | // aNewValue has fewer items than our previous internal counterpart |
200 | 0 |
|
201 | 0 | uint32_t newLength = index; |
202 | 0 |
|
203 | 0 | // Remove excess items from the list: |
204 | 0 | for (; index < length; ++index) { |
205 | 0 | if (ItemAt(index)) { |
206 | 0 | ItemAt(index)->RemovingFromList(); |
207 | 0 | ItemAt(index) = nullptr; |
208 | 0 | } |
209 | 0 | } |
210 | 0 |
|
211 | 0 | // Only now may we truncate mItems |
212 | 0 | mItems.TruncateLength(newLength); |
213 | 0 | } else if (dataIndex < dataLength) { |
214 | 0 | // aNewValue has more items than our previous internal counterpart |
215 | 0 |
|
216 | 0 | // Sync mItems: |
217 | 0 | while (dataIndex < dataLength) { |
218 | 0 | if (mItems.Length() && |
219 | 0 | mItems.Length() - 1 > DOMSVGPathSeg::MaxListIndex()) { |
220 | 0 | // It's safe to get out of sync with our internal list as long as we |
221 | 0 | // have FEWER items than it does. |
222 | 0 | return; |
223 | 0 | } |
224 | 0 | if (!mItems.AppendElement(ItemProxy(nullptr, dataIndex), fallible)) { |
225 | 0 | // OOM |
226 | 0 | ErrorResult rv; |
227 | 0 | Clear(rv); |
228 | 0 | MOZ_ASSERT(!rv.Failed()); |
229 | 0 | return; |
230 | 0 | } |
231 | 0 | dataIndex += 1 + SVGPathSegUtils::ArgCountForType(SVGPathSegUtils::DecodeType(aNewValue.mData[dataIndex])); |
232 | 0 | } |
233 | 0 | } |
234 | 0 |
|
235 | 0 | MOZ_ASSERT(dataIndex == dataLength, "Serious processing error"); |
236 | 0 | MOZ_ASSERT(index == length, "Serious counting error"); |
237 | 0 | } |
238 | | |
239 | | bool |
240 | | DOMSVGPathSegList::AttrIsAnimating() const |
241 | 0 | { |
242 | 0 | return InternalAList().IsAnimating(); |
243 | 0 | } |
244 | | |
245 | | bool |
246 | | DOMSVGPathSegList::AnimListMirrorsBaseList() const |
247 | 0 | { |
248 | 0 | return GetDOMWrapperIfExists(InternalAList().GetAnimValKey()) && |
249 | 0 | !AttrIsAnimating(); |
250 | 0 | } |
251 | | |
252 | | SVGPathData& |
253 | | DOMSVGPathSegList::InternalList() const |
254 | 0 | { |
255 | 0 | SVGAnimatedPathSegList *alist = mElement->GetAnimPathSegList(); |
256 | 0 | return mIsAnimValList && alist->IsAnimating() ? *alist->mAnimVal : alist->mBaseVal; |
257 | 0 | } |
258 | | |
259 | | SVGAnimatedPathSegList& |
260 | | DOMSVGPathSegList::InternalAList() const |
261 | 0 | { |
262 | 0 | MOZ_ASSERT(mElement->GetAnimPathSegList(), "Internal error"); |
263 | 0 | return *mElement->GetAnimPathSegList(); |
264 | 0 | } |
265 | | |
266 | | // ---------------------------------------------------------------------------- |
267 | | // nsIDOMSVGPathSegList implementation: |
268 | | |
269 | | void |
270 | | DOMSVGPathSegList::Clear(ErrorResult& aError) |
271 | 0 | { |
272 | 0 | if (IsAnimValList()) { |
273 | 0 | aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
274 | 0 | return; |
275 | 0 | } |
276 | 0 | |
277 | 0 | if (LengthNoFlush() > 0) { |
278 | 0 | AutoChangePathSegListNotifier notifier(this); |
279 | 0 | // DOM list items that are to be removed must be removed before we change |
280 | 0 | // the internal list, otherwise they wouldn't be able to copy their |
281 | 0 | // internal counterparts' values! |
282 | 0 |
|
283 | 0 | InternalListWillChangeTo(SVGPathData()); // clears mItems |
284 | 0 |
|
285 | 0 | if (!AttrIsAnimating()) { |
286 | 0 | // The anim val list is in sync with the base val list |
287 | 0 | DOMSVGPathSegList *animList = |
288 | 0 | GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); |
289 | 0 | if (animList) { |
290 | 0 | animList->InternalListWillChangeTo(SVGPathData()); // clears its mItems |
291 | 0 | } |
292 | 0 | } |
293 | 0 |
|
294 | 0 | InternalList().Clear(); |
295 | 0 | } |
296 | 0 | } |
297 | | |
298 | | already_AddRefed<DOMSVGPathSeg> |
299 | | DOMSVGPathSegList::Initialize(DOMSVGPathSeg& aNewItem, ErrorResult& aError) |
300 | 0 | { |
301 | 0 | if (IsAnimValList()) { |
302 | 0 | aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
303 | 0 | return nullptr; |
304 | 0 | } |
305 | 0 | |
306 | 0 | // If aNewItem is already in a list we should insert a clone of aNewItem, |
307 | 0 | // and for consistency, this should happen even if *this* is the list that |
308 | 0 | // aNewItem is currently in. Note that in the case of aNewItem being in this |
309 | 0 | // list, the Clear() call before the InsertItemBefore() call would remove it |
310 | 0 | // from this list, and so the InsertItemBefore() call would not insert a |
311 | 0 | // clone of aNewItem, it would actually insert aNewItem. To prevent that |
312 | 0 | // from happening we have to do the clone here, if necessary. |
313 | 0 | |
314 | 0 | RefPtr<DOMSVGPathSeg> domItem = &aNewItem; |
315 | 0 | if (aNewItem.HasOwner()) { |
316 | 0 | domItem = aNewItem.Clone(); |
317 | 0 | } |
318 | 0 |
|
319 | 0 | Clear(aError); |
320 | 0 | MOZ_ASSERT(!aError.Failed(), "How could this fail?"); |
321 | 0 | return InsertItemBefore(*domItem, 0, aError); |
322 | 0 | } |
323 | | |
324 | | already_AddRefed<DOMSVGPathSeg> |
325 | | DOMSVGPathSegList::GetItem(uint32_t index, ErrorResult& error) |
326 | 0 | { |
327 | 0 | bool found; |
328 | 0 | RefPtr<DOMSVGPathSeg> item = IndexedGetter(index, found, error); |
329 | 0 | if (!found) { |
330 | 0 | error.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
331 | 0 | } |
332 | 0 | return item.forget(); |
333 | 0 | } |
334 | | |
335 | | already_AddRefed<DOMSVGPathSeg> |
336 | | DOMSVGPathSegList::IndexedGetter(uint32_t aIndex, bool& aFound, |
337 | | ErrorResult& aError) |
338 | 0 | { |
339 | 0 | if (IsAnimValList()) { |
340 | 0 | Element()->FlushAnimations(); |
341 | 0 | } |
342 | 0 | aFound = aIndex < LengthNoFlush(); |
343 | 0 | if (aFound) { |
344 | 0 | return GetItemAt(aIndex); |
345 | 0 | } |
346 | 0 | return nullptr; |
347 | 0 | } |
348 | | |
349 | | already_AddRefed<DOMSVGPathSeg> |
350 | | DOMSVGPathSegList::InsertItemBefore(DOMSVGPathSeg& aNewItem, |
351 | | uint32_t aIndex, |
352 | | ErrorResult& aError) |
353 | 0 | { |
354 | 0 | if (IsAnimValList()) { |
355 | 0 | aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
356 | 0 | return nullptr; |
357 | 0 | } |
358 | 0 | |
359 | 0 | uint32_t internalIndex; |
360 | 0 | if (aIndex < LengthNoFlush()) { |
361 | 0 | internalIndex = mItems[aIndex].mInternalDataIndex; |
362 | 0 | } else { |
363 | 0 | aIndex = LengthNoFlush(); |
364 | 0 | internalIndex = InternalList().mData.Length(); |
365 | 0 | } |
366 | 0 | if (aIndex >= DOMSVGPathSeg::MaxListIndex()) { |
367 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
368 | 0 | return nullptr; |
369 | 0 | } |
370 | 0 | |
371 | 0 | RefPtr<DOMSVGPathSeg> domItem = &aNewItem; |
372 | 0 | if (domItem->HasOwner()) { |
373 | 0 | domItem = domItem->Clone(); // must do this before changing anything! |
374 | 0 | } |
375 | 0 |
|
376 | 0 | uint32_t argCount = SVGPathSegUtils::ArgCountForType(domItem->Type()); |
377 | 0 |
|
378 | 0 | // Ensure we have enough memory so we can avoid complex error handling below: |
379 | 0 | if (!mItems.SetCapacity(mItems.Length() + 1, fallible) || |
380 | 0 | !InternalList().mData.SetCapacity(InternalList().mData.Length() + 1 + argCount, |
381 | 0 | fallible)) { |
382 | 0 | aError.Throw(NS_ERROR_OUT_OF_MEMORY); |
383 | 0 | return nullptr; |
384 | 0 | } |
385 | 0 | if (AnimListMirrorsBaseList()) { |
386 | 0 | DOMSVGPathSegList *animVal = |
387 | 0 | GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); |
388 | 0 | MOZ_ASSERT(animVal, "animVal should be a valid pointer"); |
389 | 0 | if (!animVal->mItems.SetCapacity( |
390 | 0 | animVal->mItems.Length() + 1, fallible)) { |
391 | 0 | aError.Throw(NS_ERROR_OUT_OF_MEMORY); |
392 | 0 | return nullptr; |
393 | 0 | } |
394 | 0 | } |
395 | 0 | |
396 | 0 | AutoChangePathSegListNotifier notifier(this); |
397 | 0 | // Now that we know we're inserting, keep animVal list in sync as necessary. |
398 | 0 | MaybeInsertNullInAnimValListAt(aIndex, internalIndex, argCount); |
399 | 0 |
|
400 | 0 | float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS]; |
401 | 0 | domItem->ToSVGPathSegEncodedData(segAsRaw); |
402 | 0 |
|
403 | 0 | MOZ_ALWAYS_TRUE(InternalList().mData.InsertElementsAt(internalIndex, |
404 | 0 | segAsRaw, |
405 | 0 | 1 + argCount, |
406 | 0 | fallible)); |
407 | 0 | MOZ_ALWAYS_TRUE(mItems.InsertElementAt(aIndex, |
408 | 0 | ItemProxy(domItem.get(), |
409 | 0 | internalIndex), |
410 | 0 | fallible)); |
411 | 0 |
|
412 | 0 | // This MUST come after the insertion into InternalList(), or else under the |
413 | 0 | // insertion into InternalList() the values read from domItem would be bad |
414 | 0 | // data from InternalList() itself!: |
415 | 0 | domItem->InsertingIntoList(this, aIndex, IsAnimValList()); |
416 | 0 |
|
417 | 0 | UpdateListIndicesFromIndex(aIndex + 1, argCount + 1); |
418 | 0 |
|
419 | 0 | return domItem.forget(); |
420 | 0 | } |
421 | | |
422 | | already_AddRefed<DOMSVGPathSeg> |
423 | | DOMSVGPathSegList::ReplaceItem(DOMSVGPathSeg& aNewItem, |
424 | | uint32_t aIndex, |
425 | | ErrorResult& aError) |
426 | 0 | { |
427 | 0 | if (IsAnimValList()) { |
428 | 0 | aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
429 | 0 | return nullptr; |
430 | 0 | } |
431 | 0 | |
432 | 0 | if (aIndex >= LengthNoFlush()) { |
433 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
434 | 0 | return nullptr; |
435 | 0 | } |
436 | 0 | |
437 | 0 | RefPtr<DOMSVGPathSeg> domItem = &aNewItem; |
438 | 0 | if (domItem->HasOwner()) { |
439 | 0 | domItem = domItem->Clone(); // must do this before changing anything! |
440 | 0 | } |
441 | 0 |
|
442 | 0 | AutoChangePathSegListNotifier notifier(this); |
443 | 0 | if (ItemAt(aIndex)) { |
444 | 0 | // Notify any existing DOM item of removal *before* modifying the lists so |
445 | 0 | // that the DOM item can copy the *old* value at its index: |
446 | 0 | ItemAt(aIndex)->RemovingFromList(); |
447 | 0 | } |
448 | 0 |
|
449 | 0 | uint32_t internalIndex = mItems[aIndex].mInternalDataIndex; |
450 | 0 | // We use InternalList() to get oldArgCount since we may not have a DOM |
451 | 0 | // wrapper at the index being replaced. |
452 | 0 | uint32_t oldType = SVGPathSegUtils::DecodeType(InternalList().mData[internalIndex]); |
453 | 0 |
|
454 | 0 | // NOTE: ArgCountForType returns a (small) unsigned value, but we're |
455 | 0 | // intentionally putting it in a signed variable, because we're going to |
456 | 0 | // subtract these values and might produce something negative. |
457 | 0 | int32_t oldArgCount = SVGPathSegUtils::ArgCountForType(oldType); |
458 | 0 | int32_t newArgCount = SVGPathSegUtils::ArgCountForType(domItem->Type()); |
459 | 0 |
|
460 | 0 | float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS]; |
461 | 0 | domItem->ToSVGPathSegEncodedData(segAsRaw); |
462 | 0 |
|
463 | 0 | if (!InternalList().mData.ReplaceElementsAt(internalIndex, 1 + oldArgCount, |
464 | 0 | segAsRaw, 1 + newArgCount, |
465 | 0 | fallible)) { |
466 | 0 | aError.Throw(NS_ERROR_OUT_OF_MEMORY); |
467 | 0 | return nullptr; |
468 | 0 | } |
469 | 0 | ItemAt(aIndex) = domItem; |
470 | 0 |
|
471 | 0 | // This MUST come after the ToSVGPathSegEncodedData call, otherwise that call |
472 | 0 | // would end up reading bad data from InternalList()! |
473 | 0 | domItem->InsertingIntoList(this, aIndex, IsAnimValList()); |
474 | 0 |
|
475 | 0 | int32_t delta = newArgCount - oldArgCount; |
476 | 0 | if (delta != 0) { |
477 | 0 | for (uint32_t i = aIndex + 1; i < LengthNoFlush(); ++i) { |
478 | 0 | mItems[i].mInternalDataIndex += delta; |
479 | 0 | } |
480 | 0 | } |
481 | 0 |
|
482 | 0 | return domItem.forget(); |
483 | 0 | } |
484 | | |
485 | | already_AddRefed<DOMSVGPathSeg> |
486 | | DOMSVGPathSegList::RemoveItem(uint32_t aIndex, |
487 | | ErrorResult& aError) |
488 | 0 | { |
489 | 0 | if (IsAnimValList()) { |
490 | 0 | aError.Throw(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); |
491 | 0 | return nullptr; |
492 | 0 | } |
493 | 0 | |
494 | 0 | if (aIndex >= LengthNoFlush()) { |
495 | 0 | aError.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR); |
496 | 0 | return nullptr; |
497 | 0 | } |
498 | 0 | // We have to return the removed item, so get it, creating it if necessary: |
499 | 0 | RefPtr<DOMSVGPathSeg> result = GetItemAt(aIndex); |
500 | 0 |
|
501 | 0 | AutoChangePathSegListNotifier notifier(this); |
502 | 0 | // Notify the DOM item of removal *before* modifying the lists so that the |
503 | 0 | // DOM item can copy its *old* value: |
504 | 0 | ItemAt(aIndex)->RemovingFromList(); |
505 | 0 |
|
506 | 0 | uint32_t internalIndex = mItems[aIndex].mInternalDataIndex; |
507 | 0 | uint32_t segType = SVGPathSegUtils::DecodeType(InternalList().mData[internalIndex]); |
508 | 0 | // NOTE: ArgCountForType returns a (small) unsigned value, but we're |
509 | 0 | // intentionally putting it in a signed value, because we're going to |
510 | 0 | // negate it, and you can't negate an unsigned value. |
511 | 0 | int32_t argCount = SVGPathSegUtils::ArgCountForType(segType); |
512 | 0 |
|
513 | 0 | // Now that we know we're removing, keep animVal list in sync as necessary. |
514 | 0 | // Do this *before* touching InternalList() so the removed item can get its |
515 | 0 | // internal value. |
516 | 0 | MaybeRemoveItemFromAnimValListAt(aIndex, argCount); |
517 | 0 |
|
518 | 0 | InternalList().mData.RemoveElementsAt(internalIndex, 1 + argCount); |
519 | 0 | mItems.RemoveElementAt(aIndex); |
520 | 0 |
|
521 | 0 | UpdateListIndicesFromIndex(aIndex, -(argCount + 1)); |
522 | 0 |
|
523 | 0 | return result.forget(); |
524 | 0 | } |
525 | | |
526 | | already_AddRefed<DOMSVGPathSeg> |
527 | | DOMSVGPathSegList::GetItemAt(uint32_t aIndex) |
528 | 0 | { |
529 | 0 | MOZ_ASSERT(aIndex < mItems.Length()); |
530 | 0 |
|
531 | 0 | if (!ItemAt(aIndex)) { |
532 | 0 | ItemAt(aIndex) = DOMSVGPathSeg::CreateFor(this, aIndex, IsAnimValList()); |
533 | 0 | } |
534 | 0 | RefPtr<DOMSVGPathSeg> result = ItemAt(aIndex); |
535 | 0 | return result.forget(); |
536 | 0 | } |
537 | | |
538 | | void |
539 | | DOMSVGPathSegList:: |
540 | | MaybeInsertNullInAnimValListAt(uint32_t aIndex, |
541 | | uint32_t aInternalIndex, |
542 | | uint32_t aArgCountForItem) |
543 | 0 | { |
544 | 0 | MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); |
545 | 0 |
|
546 | 0 | if (!AnimListMirrorsBaseList()) { |
547 | 0 | return; |
548 | 0 | } |
549 | 0 | |
550 | 0 | // The anim val list is in sync with the base val list |
551 | 0 | DOMSVGPathSegList *animVal = |
552 | 0 | GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); |
553 | 0 |
|
554 | 0 | MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); |
555 | 0 | MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), |
556 | 0 | "animVal list not in sync!"); |
557 | 0 | MOZ_ALWAYS_TRUE(animVal->mItems.InsertElementAt(aIndex, |
558 | 0 | ItemProxy(nullptr, |
559 | 0 | aInternalIndex), |
560 | 0 | fallible)); |
561 | 0 |
|
562 | 0 | animVal->UpdateListIndicesFromIndex(aIndex + 1, 1 + aArgCountForItem); |
563 | 0 | } |
564 | | |
565 | | void |
566 | | DOMSVGPathSegList:: |
567 | | MaybeRemoveItemFromAnimValListAt(uint32_t aIndex, |
568 | | int32_t aArgCountForItem) |
569 | 0 | { |
570 | 0 | MOZ_ASSERT(!IsAnimValList(), "call from baseVal to animVal"); |
571 | 0 |
|
572 | 0 | if (!AnimListMirrorsBaseList()) { |
573 | 0 | return; |
574 | 0 | } |
575 | 0 | |
576 | 0 | // This needs to be a strong reference; otherwise, the RemovingFromList call |
577 | 0 | // below might drop the last reference to animVal before we're done with it. |
578 | 0 | RefPtr<DOMSVGPathSegList> animVal = |
579 | 0 | GetDOMWrapperIfExists(InternalAList().GetAnimValKey()); |
580 | 0 |
|
581 | 0 | MOZ_ASSERT(animVal, "AnimListMirrorsBaseList() promised a non-null animVal"); |
582 | 0 | MOZ_ASSERT(animVal->mItems.Length() == mItems.Length(), |
583 | 0 | "animVal list not in sync!"); |
584 | 0 |
|
585 | 0 | if (animVal->ItemAt(aIndex)) { |
586 | 0 | animVal->ItemAt(aIndex)->RemovingFromList(); |
587 | 0 | } |
588 | 0 | animVal->mItems.RemoveElementAt(aIndex); |
589 | 0 |
|
590 | 0 | animVal->UpdateListIndicesFromIndex(aIndex, -(1 + aArgCountForItem)); |
591 | 0 | } |
592 | | |
593 | | void |
594 | | DOMSVGPathSegList::UpdateListIndicesFromIndex(uint32_t aStartingIndex, |
595 | | int32_t aInternalDataIndexDelta) |
596 | 0 | { |
597 | 0 | uint32_t length = mItems.Length(); |
598 | 0 |
|
599 | 0 | for (uint32_t i = aStartingIndex; i < length; ++i) { |
600 | 0 | mItems[i].mInternalDataIndex += aInternalDataIndexDelta; |
601 | 0 | if (ItemAt(i)) { |
602 | 0 | ItemAt(i)->UpdateListIndex(i); |
603 | 0 | } |
604 | 0 | } |
605 | 0 | } |
606 | | |
607 | | } // namespace mozilla |