1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla SVG Project code.
16 : *
17 : * The Initial Developer of the Original Code is the Mozilla Foundation.
18 : * Portions created by the Initial Developer are Copyright (C) 2010
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : *
23 : * Alternatively, the contents of this file may be used under the terms of
24 : * either the GNU General Public License Version 2 or later (the "GPL"), or
25 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
26 : * in which case the provisions of the GPL or the LGPL are applicable instead
27 : * of those above. If you wish to allow use of your version of this file only
28 : * under the terms of either the GPL or the LGPL, and not to allow others to
29 : * use your version of this file under the terms of the MPL, indicate your
30 : * decision by deleting the provisions above and replace them with the notice
31 : * and other provisions required by the GPL or the LGPL. If you do not delete
32 : * the provisions above, a recipient may use your version of this file under
33 : * the terms of any one of the MPL, the GPL or the LGPL.
34 : *
35 : * ***** END LICENSE BLOCK ***** */
36 :
37 : #include "nsSVGElement.h"
38 : #include "DOMSVGPointList.h"
39 : #include "DOMSVGPoint.h"
40 : #include "nsDOMError.h"
41 : #include "SVGAnimatedPointList.h"
42 : #include "nsCOMPtr.h"
43 : #include "nsSVGAttrTearoffTable.h"
44 : #include "nsContentUtils.h"
45 : #include "dombindings.h"
46 :
47 : // See the comment in this file's header.
48 :
49 : // local helper functions
50 : namespace {
51 :
52 : using mozilla::DOMSVGPoint;
53 :
54 : void
55 0 : UpdateListIndicesFromIndex(nsTArray<DOMSVGPoint*>& aItemsArray,
56 : PRUint32 aStartingIndex)
57 : {
58 0 : PRUint32 length = aItemsArray.Length();
59 :
60 0 : for (PRUint32 i = aStartingIndex; i < length; ++i) {
61 0 : if (aItemsArray[i]) {
62 0 : aItemsArray[i]->UpdateListIndex(i);
63 : }
64 : }
65 0 : }
66 :
67 : } // namespace
68 :
69 : namespace mozilla {
70 :
71 : static nsSVGAttrTearoffTable<void, DOMSVGPointList>
72 1464 : sSVGPointListTearoffTable;
73 :
74 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGPointList)
75 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGPointList)
76 : // No unlinking of mElement, we'd need to null out the value pointer (the
77 : // object it points to is held by the element) and null-check it everywhere.
78 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
79 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
80 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DOMSVGPointList)
81 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mElement, nsIContent)
82 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
83 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
84 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(DOMSVGPointList)
85 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
86 0 : NS_IMPL_CYCLE_COLLECTION_TRACE_END
87 :
88 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(DOMSVGPointList)
89 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGPointList)
90 :
91 : } // namespace mozilla
92 : DOMCI_DATA(SVGPointList, mozilla::DOMSVGPointList)
93 : namespace mozilla {
94 :
95 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGPointList)
96 0 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
97 0 : NS_INTERFACE_MAP_ENTRY(nsIDOMSVGPointList)
98 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
99 0 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGPointList)
100 0 : NS_INTERFACE_MAP_END
101 :
102 :
103 : /* static */ already_AddRefed<DOMSVGPointList>
104 0 : DOMSVGPointList::GetDOMWrapper(void *aList,
105 : nsSVGElement *aElement,
106 : bool aIsAnimValList)
107 : {
108 : DOMSVGPointList *wrapper =
109 0 : sSVGPointListTearoffTable.GetTearoff(aList);
110 0 : if (!wrapper) {
111 0 : wrapper = new DOMSVGPointList(aElement, aIsAnimValList);
112 0 : sSVGPointListTearoffTable.AddTearoff(aList, wrapper);
113 : }
114 0 : NS_ADDREF(wrapper);
115 0 : return wrapper;
116 : }
117 :
118 : /* static */ DOMSVGPointList*
119 0 : DOMSVGPointList::GetDOMWrapperIfExists(void *aList)
120 : {
121 0 : return sSVGPointListTearoffTable.GetTearoff(aList);
122 : }
123 :
124 0 : DOMSVGPointList::~DOMSVGPointList()
125 : {
126 : // There are now no longer any references to us held by script or list items.
127 : // Note we must use GetAnimValKey/GetBaseValKey here, NOT InternalList()!
128 : void *key = mIsAnimValList ?
129 0 : InternalAList().GetAnimValKey() :
130 0 : InternalAList().GetBaseValKey();
131 0 : sSVGPointListTearoffTable.RemoveTearoff(key);
132 0 : }
133 :
134 : JSObject*
135 0 : DOMSVGPointList::WrapObject(JSContext *cx, XPCWrappedNativeScope *scope,
136 : bool *triedToWrap)
137 : {
138 : return mozilla::dom::binding::SVGPointList::create(cx, scope, this,
139 0 : triedToWrap);
140 : }
141 :
142 : nsIDOMSVGPoint*
143 0 : DOMSVGPointList::GetItemAt(PRUint32 aIndex)
144 : {
145 0 : if (IsAnimValList()) {
146 0 : Element()->FlushAnimations();
147 : }
148 0 : if (aIndex < Length()) {
149 0 : EnsureItemAt(aIndex);
150 0 : return mItems[aIndex];
151 : }
152 0 : return nsnull;
153 : }
154 :
155 : void
156 0 : DOMSVGPointList::InternalListWillChangeTo(const SVGPointList& aNewValue)
157 : {
158 : // When the number of items in our internal counterpart changes, we MUST stay
159 : // in sync. Everything in the scary comment in
160 : // DOMSVGLengthList::InternalBaseValListWillChangeTo applies here too!
161 :
162 0 : PRUint32 oldLength = mItems.Length();
163 :
164 0 : PRUint32 newLength = aNewValue.Length();
165 0 : if (newLength > DOMSVGPoint::MaxListIndex()) {
166 : // It's safe to get out of sync with our internal list as long as we have
167 : // FEWER items than it does.
168 0 : newLength = DOMSVGPoint::MaxListIndex();
169 : }
170 :
171 0 : nsRefPtr<DOMSVGPointList> kungFuDeathGrip;
172 0 : if (newLength < oldLength) {
173 : // RemovingFromList() might clear last reference to |this|.
174 : // Retain a temporary reference to keep from dying before returning.
175 0 : kungFuDeathGrip = this;
176 : }
177 :
178 : // If our length will decrease, notify the items that will be removed:
179 0 : for (PRUint32 i = newLength; i < oldLength; ++i) {
180 0 : if (mItems[i]) {
181 0 : mItems[i]->RemovingFromList();
182 : }
183 : }
184 :
185 0 : if (!mItems.SetLength(newLength)) {
186 : // We silently ignore SetLength OOM failure since being out of sync is safe
187 : // so long as we have *fewer* items than our internal list.
188 0 : mItems.Clear();
189 : return;
190 : }
191 :
192 : // If our length has increased, null out the new pointers:
193 0 : for (PRUint32 i = oldLength; i < newLength; ++i) {
194 0 : mItems[i] = nsnull;
195 : }
196 : }
197 :
198 : bool
199 0 : DOMSVGPointList::AttrIsAnimating() const
200 : {
201 0 : return InternalAList().IsAnimating();
202 : }
203 :
204 : SVGPointList&
205 0 : DOMSVGPointList::InternalList() const
206 : {
207 0 : SVGAnimatedPointList *alist = mElement->GetAnimatedPointList();
208 0 : return mIsAnimValList && alist->IsAnimating() ? *alist->mAnimVal : alist->mBaseVal;
209 : }
210 :
211 : SVGAnimatedPointList&
212 0 : DOMSVGPointList::InternalAList() const
213 : {
214 0 : NS_ABORT_IF_FALSE(mElement->GetAnimatedPointList(), "Internal error");
215 0 : return *mElement->GetAnimatedPointList();
216 : }
217 :
218 : // ----------------------------------------------------------------------------
219 : // nsIDOMSVGPointList implementation:
220 :
221 : NS_IMETHODIMP
222 0 : DOMSVGPointList::GetNumberOfItems(PRUint32 *aNumberOfItems)
223 : {
224 0 : if (IsAnimValList()) {
225 0 : Element()->FlushAnimations();
226 : }
227 0 : *aNumberOfItems = Length();
228 0 : return NS_OK;
229 : }
230 :
231 : NS_IMETHODIMP
232 0 : DOMSVGPointList::Clear()
233 : {
234 0 : if (IsAnimValList()) {
235 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
236 : }
237 :
238 0 : if (Length() > 0) {
239 0 : nsAttrValue emptyOrOldValue = Element()->WillChangePointList();
240 : // DOM list items that are to be removed must be removed before we change
241 : // the internal list, otherwise they wouldn't be able to copy their
242 : // internal counterparts' values!
243 :
244 0 : InternalListWillChangeTo(SVGPointList()); // clears mItems
245 :
246 0 : if (!AttrIsAnimating()) {
247 : // The anim val list is in sync with the base val list
248 : DOMSVGPointList *animList =
249 0 : GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
250 0 : if (animList) {
251 0 : animList->InternalListWillChangeTo(SVGPointList()); // clears its mItems
252 : }
253 : }
254 :
255 0 : InternalList().Clear();
256 0 : Element()->DidChangePointList(emptyOrOldValue);
257 0 : if (AttrIsAnimating()) {
258 0 : Element()->AnimationNeedsResample();
259 : }
260 : }
261 0 : return NS_OK;
262 : }
263 :
264 : NS_IMETHODIMP
265 0 : DOMSVGPointList::Initialize(nsIDOMSVGPoint *aNewItem,
266 : nsIDOMSVGPoint **_retval)
267 : {
268 0 : *_retval = nsnull;
269 0 : if (IsAnimValList()) {
270 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
271 : }
272 :
273 : // If aNewItem is already in a list we should insert a clone of aNewItem,
274 : // and for consistency, this should happen even if *this* is the list that
275 : // aNewItem is currently in. Note that in the case of aNewItem being in this
276 : // list, the Clear() call before the InsertItemBefore() call would remove it
277 : // from this list, and so the InsertItemBefore() call would not insert a
278 : // clone of aNewItem, it would actually insert aNewItem. To prevent that
279 : // from happening we have to do the clone here, if necessary.
280 :
281 0 : nsCOMPtr<DOMSVGPoint> domItem = do_QueryInterface(aNewItem);
282 0 : if (!domItem) {
283 0 : return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
284 : }
285 0 : if (domItem->HasOwner() || domItem->IsReadonly()) {
286 0 : aNewItem = domItem->Clone();
287 : }
288 :
289 0 : Clear();
290 0 : return InsertItemBefore(aNewItem, 0, _retval);
291 : }
292 :
293 : NS_IMETHODIMP
294 0 : DOMSVGPointList::GetItem(PRUint32 aIndex,
295 : nsIDOMSVGPoint **_retval)
296 : {
297 0 : *_retval = GetItemAt(aIndex);
298 0 : if (!*_retval) {
299 0 : return NS_ERROR_DOM_INDEX_SIZE_ERR;
300 : }
301 0 : NS_ADDREF(*_retval);
302 0 : return NS_OK;
303 : }
304 :
305 : NS_IMETHODIMP
306 0 : DOMSVGPointList::InsertItemBefore(nsIDOMSVGPoint *aNewItem,
307 : PRUint32 aIndex,
308 : nsIDOMSVGPoint **_retval)
309 : {
310 0 : *_retval = nsnull;
311 0 : if (IsAnimValList()) {
312 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
313 : }
314 :
315 0 : aIndex = NS_MIN(aIndex, Length());
316 0 : if (aIndex >= DOMSVGPoint::MaxListIndex()) {
317 0 : return NS_ERROR_DOM_INDEX_SIZE_ERR;
318 : }
319 :
320 0 : nsCOMPtr<DOMSVGPoint> domItem = do_QueryInterface(aNewItem);
321 0 : if (!domItem) {
322 0 : return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
323 : }
324 0 : if (domItem->HasOwner() || domItem->IsReadonly()) {
325 0 : domItem = domItem->Clone(); // must do this before changing anything!
326 : }
327 :
328 : // Ensure we have enough memory so we can avoid complex error handling below:
329 0 : if (!mItems.SetCapacity(mItems.Length() + 1) ||
330 0 : !InternalList().SetCapacity(InternalList().Length() + 1)) {
331 0 : return NS_ERROR_OUT_OF_MEMORY;
332 : }
333 :
334 0 : nsAttrValue emptyOrOldValue = Element()->WillChangePointList();
335 : // Now that we know we're inserting, keep animVal list in sync as necessary.
336 0 : MaybeInsertNullInAnimValListAt(aIndex);
337 :
338 0 : InternalList().InsertItem(aIndex, domItem->ToSVGPoint());
339 0 : mItems.InsertElementAt(aIndex, domItem.get());
340 :
341 : // This MUST come after the insertion into InternalList(), or else under the
342 : // insertion into InternalList() the values read from domItem would be bad
343 : // data from InternalList() itself!:
344 0 : domItem->InsertingIntoList(this, aIndex, IsAnimValList());
345 :
346 0 : UpdateListIndicesFromIndex(mItems, aIndex + 1);
347 :
348 0 : Element()->DidChangePointList(emptyOrOldValue);
349 0 : if (AttrIsAnimating()) {
350 0 : Element()->AnimationNeedsResample();
351 : }
352 0 : *_retval = domItem.forget().get();
353 0 : return NS_OK;
354 : }
355 :
356 : NS_IMETHODIMP
357 0 : DOMSVGPointList::ReplaceItem(nsIDOMSVGPoint *aNewItem,
358 : PRUint32 aIndex,
359 : nsIDOMSVGPoint **_retval)
360 : {
361 0 : *_retval = nsnull;
362 0 : if (IsAnimValList()) {
363 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
364 : }
365 :
366 0 : nsCOMPtr<DOMSVGPoint> domItem = do_QueryInterface(aNewItem);
367 0 : if (!domItem) {
368 0 : return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
369 : }
370 0 : if (aIndex >= Length()) {
371 0 : return NS_ERROR_DOM_INDEX_SIZE_ERR;
372 : }
373 0 : if (domItem->HasOwner() || domItem->IsReadonly()) {
374 0 : domItem = domItem->Clone(); // must do this before changing anything!
375 : }
376 :
377 0 : nsAttrValue emptyOrOldValue = Element()->WillChangePointList();
378 0 : if (mItems[aIndex]) {
379 : // Notify any existing DOM item of removal *before* modifying the lists so
380 : // that the DOM item can copy the *old* value at its index:
381 0 : mItems[aIndex]->RemovingFromList();
382 : }
383 :
384 0 : InternalList()[aIndex] = domItem->ToSVGPoint();
385 0 : mItems[aIndex] = domItem;
386 :
387 : // This MUST come after the ToSVGPoint() call, otherwise that call
388 : // would end up reading bad data from InternalList()!
389 0 : domItem->InsertingIntoList(this, aIndex, IsAnimValList());
390 :
391 0 : Element()->DidChangePointList(emptyOrOldValue);
392 0 : if (AttrIsAnimating()) {
393 0 : Element()->AnimationNeedsResample();
394 : }
395 0 : NS_ADDREF(*_retval = domItem.get());
396 0 : return NS_OK;
397 : }
398 :
399 : NS_IMETHODIMP
400 0 : DOMSVGPointList::RemoveItem(PRUint32 aIndex,
401 : nsIDOMSVGPoint **_retval)
402 : {
403 0 : *_retval = nsnull;
404 0 : if (IsAnimValList()) {
405 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
406 : }
407 :
408 0 : if (aIndex >= Length()) {
409 0 : return NS_ERROR_DOM_INDEX_SIZE_ERR;
410 : }
411 :
412 0 : nsAttrValue emptyOrOldValue = Element()->WillChangePointList();
413 : // Now that we know we're removing, keep animVal list in sync as necessary.
414 : // Do this *before* touching InternalList() so the removed item can get its
415 : // internal value.
416 0 : MaybeRemoveItemFromAnimValListAt(aIndex);
417 :
418 : // We have to return the removed item, so make sure it exists:
419 0 : EnsureItemAt(aIndex);
420 :
421 : // Notify the DOM item of removal *before* modifying the lists so that the
422 : // DOM item can copy its *old* value:
423 0 : mItems[aIndex]->RemovingFromList();
424 0 : NS_ADDREF(*_retval = mItems[aIndex]);
425 :
426 0 : InternalList().RemoveItem(aIndex);
427 0 : mItems.RemoveElementAt(aIndex);
428 :
429 0 : UpdateListIndicesFromIndex(mItems, aIndex);
430 :
431 0 : Element()->DidChangePointList(emptyOrOldValue);
432 0 : if (AttrIsAnimating()) {
433 0 : Element()->AnimationNeedsResample();
434 : }
435 0 : return NS_OK;
436 : }
437 :
438 : NS_IMETHODIMP
439 0 : DOMSVGPointList::AppendItem(nsIDOMSVGPoint *aNewItem,
440 : nsIDOMSVGPoint **_retval)
441 : {
442 0 : return InsertItemBefore(aNewItem, Length(), _retval);
443 : }
444 :
445 : NS_IMETHODIMP
446 0 : DOMSVGPointList::GetLength(PRUint32 *aNumberOfItems)
447 : {
448 0 : return GetNumberOfItems(aNumberOfItems);
449 : }
450 :
451 : void
452 0 : DOMSVGPointList::EnsureItemAt(PRUint32 aIndex)
453 : {
454 0 : if (!mItems[aIndex]) {
455 0 : mItems[aIndex] = new DOMSVGPoint(this, aIndex, IsAnimValList());
456 : }
457 0 : }
458 :
459 : void
460 0 : DOMSVGPointList::MaybeInsertNullInAnimValListAt(PRUint32 aIndex)
461 : {
462 0 : NS_ABORT_IF_FALSE(!IsAnimValList(), "call from baseVal to animVal");
463 :
464 0 : if (AttrIsAnimating()) {
465 : // animVal not a clone of baseVal
466 0 : return;
467 : }
468 :
469 : // The anim val list is in sync with the base val list
470 : DOMSVGPointList *animVal =
471 0 : GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
472 0 : if (!animVal) {
473 : // No animVal list wrapper
474 0 : return;
475 : }
476 :
477 0 : NS_ABORT_IF_FALSE(animVal->mItems.Length() == mItems.Length(),
478 : "animVal list not in sync!");
479 :
480 0 : animVal->mItems.InsertElementAt(aIndex, static_cast<DOMSVGPoint*>(nsnull));
481 :
482 0 : UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1);
483 : }
484 :
485 : void
486 0 : DOMSVGPointList::MaybeRemoveItemFromAnimValListAt(PRUint32 aIndex)
487 : {
488 0 : NS_ABORT_IF_FALSE(!IsAnimValList(), "call from baseVal to animVal");
489 :
490 0 : if (AttrIsAnimating()) {
491 : // animVal not a clone of baseVal
492 0 : return;
493 : }
494 :
495 : // This needs to be a strong reference; otherwise, the RemovingFromList call
496 : // below might drop the last reference to animVal before we're done with it.
497 : nsRefPtr<DOMSVGPointList> animVal =
498 0 : GetDOMWrapperIfExists(InternalAList().GetAnimValKey());
499 0 : if (!animVal) {
500 : // No animVal list wrapper
501 : return;
502 : }
503 :
504 0 : NS_ABORT_IF_FALSE(animVal->mItems.Length() == mItems.Length(),
505 : "animVal list not in sync!");
506 :
507 0 : if (animVal->mItems[aIndex]) {
508 0 : animVal->mItems[aIndex]->RemovingFromList();
509 : }
510 0 : animVal->mItems.RemoveElementAt(aIndex);
511 :
512 0 : UpdateListIndicesFromIndex(animVal->mItems, aIndex);
513 : }
514 :
515 4392 : } // namespace mozilla
|