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 the Mozilla SVG project.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Crocodile Clips Ltd..
19 : * Portions created by the Initial Developer are Copyright (C) 2001
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Alex Fritze <alex.fritze@crocodile-clips.com> (original author)
24 : * Jonathan Watt <jonathan.watt@strath.ac.uk>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "DOMSVGTransformList.h"
41 : #include "DOMSVGTransform.h"
42 : #include "DOMSVGMatrix.h"
43 : #include "SVGAnimatedTransformList.h"
44 : #include "nsSVGElement.h"
45 : #include "nsContentUtils.h"
46 : #include "dombindings.h"
47 :
48 : // local helper functions
49 : namespace {
50 :
51 0 : void UpdateListIndicesFromIndex(
52 : nsTArray<mozilla::DOMSVGTransform*>& aItemsArray,
53 : PRUint32 aStartingIndex)
54 : {
55 0 : PRUint32 length = aItemsArray.Length();
56 :
57 0 : for (PRUint32 i = aStartingIndex; i < length; ++i) {
58 0 : if (aItemsArray[i]) {
59 0 : aItemsArray[i]->UpdateListIndex(i);
60 : }
61 : }
62 0 : }
63 :
64 : } // anonymous namespace
65 :
66 : namespace mozilla {
67 :
68 : // We could use NS_IMPL_CYCLE_COLLECTION_1, except that in Unlink() we need to
69 : // clear our DOMSVGAnimatedTransformList's weak ref to us to be safe. (The other
70 : // option would be to not unlink and rely on the breaking of the other edges in
71 : // the cycle, as NS_SVG_VAL_IMPL_CYCLE_COLLECTION does.)
72 1464 : NS_IMPL_CYCLE_COLLECTION_CLASS(DOMSVGTransformList)
73 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(DOMSVGTransformList)
74 : // No need to null check tmp - script/SMIL can't detach us from mAList
75 0 : ( tmp->IsAnimValList() ? tmp->mAList->mAnimVal : tmp->mAList->mBaseVal ) =
76 0 : nsnull;
77 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mAList)
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(DOMSVGTransformList)
81 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mAList)
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(DOMSVGTransformList)
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(DOMSVGTransformList)
89 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(DOMSVGTransformList)
90 :
91 : } // namespace mozilla
92 : DOMCI_DATA(SVGTransformList, mozilla::DOMSVGTransformList)
93 : namespace mozilla {
94 :
95 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMSVGTransformList)
96 0 : NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
97 0 : NS_INTERFACE_MAP_ENTRY(nsIDOMSVGTransformList)
98 0 : NS_INTERFACE_MAP_ENTRY(nsISupports)
99 0 : NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGTransformList)
100 0 : NS_INTERFACE_MAP_END
101 :
102 : //----------------------------------------------------------------------
103 : // DOMSVGTransformList methods:
104 :
105 : JSObject*
106 0 : DOMSVGTransformList::WrapObject(JSContext *cx, XPCWrappedNativeScope *scope,
107 : bool *triedToWrap)
108 : {
109 : return mozilla::dom::binding::SVGTransformList::create(cx, scope, this,
110 0 : triedToWrap);
111 : }
112 :
113 : nsIDOMSVGTransform*
114 0 : DOMSVGTransformList::GetItemAt(PRUint32 aIndex)
115 : {
116 0 : if (IsAnimValList()) {
117 0 : Element()->FlushAnimations();
118 : }
119 0 : if (aIndex < Length()) {
120 0 : EnsureItemAt(aIndex);
121 0 : return mItems[aIndex];
122 : }
123 0 : return nsnull;
124 : }
125 :
126 : void
127 0 : DOMSVGTransformList::InternalListLengthWillChange(PRUint32 aNewLength)
128 : {
129 0 : PRUint32 oldLength = mItems.Length();
130 :
131 0 : if (aNewLength > DOMSVGTransform::MaxListIndex()) {
132 : // It's safe to get out of sync with our internal list as long as we have
133 : // FEWER items than it does.
134 0 : aNewLength = DOMSVGTransform::MaxListIndex();
135 : }
136 :
137 0 : nsRefPtr<DOMSVGTransformList> kungFuDeathGrip;
138 0 : if (aNewLength < oldLength) {
139 : // RemovingFromList() might clear last reference to |this|.
140 : // Retain a temporary reference to keep from dying before returning.
141 0 : kungFuDeathGrip = this;
142 : }
143 :
144 : // If our length will decrease, notify the items that will be removed:
145 0 : for (PRUint32 i = aNewLength; i < oldLength; ++i) {
146 0 : if (mItems[i]) {
147 0 : mItems[i]->RemovingFromList();
148 : }
149 : }
150 :
151 0 : if (!mItems.SetLength(aNewLength)) {
152 : // We silently ignore SetLength OOM failure since being out of sync is safe
153 : // so long as we have *fewer* items than our internal list.
154 0 : mItems.Clear();
155 : return;
156 : }
157 :
158 : // If our length has increased, null out the new pointers:
159 0 : for (PRUint32 i = oldLength; i < aNewLength; ++i) {
160 0 : mItems[i] = nsnull;
161 : }
162 : }
163 :
164 : SVGTransformList&
165 0 : DOMSVGTransformList::InternalList() const
166 : {
167 0 : SVGAnimatedTransformList *alist = Element()->GetAnimatedTransformList();
168 0 : return IsAnimValList() && alist->mAnimVal ?
169 0 : *alist->mAnimVal :
170 0 : alist->mBaseVal;
171 : }
172 :
173 : //----------------------------------------------------------------------
174 : // nsIDOMSVGTransformList methods:
175 :
176 : /* readonly attribute unsigned long numberOfItems; */
177 : NS_IMETHODIMP
178 0 : DOMSVGTransformList::GetNumberOfItems(PRUint32 *aNumberOfItems)
179 : {
180 0 : if (IsAnimValList()) {
181 0 : Element()->FlushAnimations();
182 : }
183 0 : *aNumberOfItems = Length();
184 0 : return NS_OK;
185 : }
186 :
187 : /* readonly attribute unsigned long length; */
188 : NS_IMETHODIMP
189 0 : DOMSVGTransformList::GetLength(PRUint32 *aLength)
190 : {
191 0 : return GetNumberOfItems(aLength);
192 : }
193 :
194 : /* void clear (); */
195 : NS_IMETHODIMP
196 0 : DOMSVGTransformList::Clear()
197 : {
198 0 : if (IsAnimValList()) {
199 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
200 : }
201 :
202 0 : if (Length() > 0) {
203 0 : nsAttrValue emptyOrOldValue = Element()->WillChangeTransformList();
204 : // Notify any existing DOM items of removal *before* truncating the lists
205 : // so that they can find their SVGTransform internal counterparts and copy
206 : // their values. This also notifies the animVal list:
207 0 : mAList->InternalBaseValListWillChangeLengthTo(0);
208 :
209 0 : mItems.Clear();
210 0 : InternalList().Clear();
211 0 : Element()->DidChangeTransformList(emptyOrOldValue);
212 0 : if (mAList->IsAnimating()) {
213 0 : Element()->AnimationNeedsResample();
214 : }
215 : }
216 0 : return NS_OK;
217 : }
218 :
219 : /* nsIDOMSVGTransform initialize (in nsIDOMSVGTransform newItem); */
220 : NS_IMETHODIMP
221 0 : DOMSVGTransformList::Initialize(nsIDOMSVGTransform *newItem,
222 : nsIDOMSVGTransform **_retval)
223 : {
224 0 : *_retval = nsnull;
225 0 : if (IsAnimValList()) {
226 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
227 : }
228 :
229 : // If newItem is already in a list we should insert a clone of newItem, and
230 : // for consistency, this should happen even if *this* is the list that
231 : // newItem is currently in. Note that in the case of newItem being in this
232 : // list, the Clear() call before the InsertItemBefore() call would remove it
233 : // from this list, and so the InsertItemBefore() call would not insert a
234 : // clone of newItem, it would actually insert newItem. To prevent that from
235 : // happening we have to do the clone here, if necessary.
236 :
237 0 : nsCOMPtr<DOMSVGTransform> domItem = do_QueryInterface(newItem);
238 0 : if (!domItem) {
239 0 : return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
240 : }
241 0 : if (domItem->HasOwner()) {
242 0 : newItem = domItem->Clone();
243 : }
244 :
245 0 : Clear();
246 0 : return InsertItemBefore(newItem, 0, _retval);
247 : }
248 :
249 : /* nsIDOMSVGTransform getItem (in unsigned long index); */
250 : NS_IMETHODIMP
251 0 : DOMSVGTransformList::GetItem(PRUint32 index, nsIDOMSVGTransform **_retval)
252 : {
253 0 : *_retval = GetItemAt(index);
254 0 : if (!*_retval) {
255 0 : return NS_ERROR_DOM_INDEX_SIZE_ERR;
256 : }
257 0 : NS_ADDREF(*_retval);
258 0 : return NS_OK;
259 : }
260 :
261 : /* nsIDOMSVGTransform insertItemBefore (in nsIDOMSVGTransform newItem,
262 : * in unsigned long index); */
263 : NS_IMETHODIMP
264 0 : DOMSVGTransformList::InsertItemBefore(nsIDOMSVGTransform *newItem,
265 : PRUint32 index,
266 : nsIDOMSVGTransform **_retval)
267 : {
268 0 : *_retval = nsnull;
269 0 : if (IsAnimValList()) {
270 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
271 : }
272 :
273 0 : index = NS_MIN(index, Length());
274 0 : if (index >= DOMSVGTransform::MaxListIndex()) {
275 0 : return NS_ERROR_DOM_INDEX_SIZE_ERR;
276 : }
277 :
278 0 : nsCOMPtr<DOMSVGTransform> domItem = do_QueryInterface(newItem);
279 0 : if (!domItem) {
280 0 : return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
281 : }
282 0 : if (domItem->HasOwner()) {
283 0 : domItem = domItem->Clone(); // must do this before changing anything!
284 : }
285 :
286 : // Ensure we have enough memory so we can avoid complex error handling below:
287 0 : if (!mItems.SetCapacity(mItems.Length() + 1) ||
288 0 : !InternalList().SetCapacity(InternalList().Length() + 1)) {
289 0 : return NS_ERROR_OUT_OF_MEMORY;
290 : }
291 :
292 0 : nsAttrValue emptyOrOldValue = Element()->WillChangeTransformList();
293 : // Now that we know we're inserting, keep animVal list in sync as necessary.
294 0 : MaybeInsertNullInAnimValListAt(index);
295 :
296 0 : InternalList().InsertItem(index, domItem->ToSVGTransform());
297 0 : mItems.InsertElementAt(index, domItem.get());
298 :
299 : // This MUST come after the insertion into InternalList(), or else under the
300 : // insertion into InternalList() the values read from domItem would be bad
301 : // data from InternalList() itself!:
302 0 : domItem->InsertingIntoList(this, index, IsAnimValList());
303 :
304 0 : UpdateListIndicesFromIndex(mItems, index + 1);
305 :
306 0 : Element()->DidChangeTransformList(emptyOrOldValue);
307 0 : if (mAList->IsAnimating()) {
308 0 : Element()->AnimationNeedsResample();
309 : }
310 0 : *_retval = domItem.forget().get();
311 0 : return NS_OK;
312 : }
313 :
314 : /* nsIDOMSVGTransform replaceItem (in nsIDOMSVGTransform newItem,
315 : * in unsigned long index); */
316 : NS_IMETHODIMP
317 0 : DOMSVGTransformList::ReplaceItem(nsIDOMSVGTransform *newItem,
318 : PRUint32 index,
319 : nsIDOMSVGTransform **_retval)
320 : {
321 0 : *_retval = nsnull;
322 0 : if (IsAnimValList()) {
323 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
324 : }
325 :
326 0 : nsCOMPtr<DOMSVGTransform> domItem = do_QueryInterface(newItem);
327 0 : if (!domItem) {
328 0 : return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
329 : }
330 0 : if (index >= Length()) {
331 0 : return NS_ERROR_DOM_INDEX_SIZE_ERR;
332 : }
333 0 : if (domItem->HasOwner()) {
334 0 : domItem = domItem->Clone(); // must do this before changing anything!
335 : }
336 :
337 0 : nsAttrValue emptyOrOldValue = Element()->WillChangeTransformList();
338 0 : if (mItems[index]) {
339 : // Notify any existing DOM item of removal *before* modifying the lists so
340 : // that the DOM item can copy the *old* value at its index:
341 0 : mItems[index]->RemovingFromList();
342 : }
343 :
344 0 : InternalList()[index] = domItem->ToSVGTransform();
345 0 : mItems[index] = domItem;
346 :
347 : // This MUST come after the ToSVGPoint() call, otherwise that call
348 : // would end up reading bad data from InternalList()!
349 0 : domItem->InsertingIntoList(this, index, IsAnimValList());
350 :
351 0 : Element()->DidChangeTransformList(emptyOrOldValue);
352 0 : if (mAList->IsAnimating()) {
353 0 : Element()->AnimationNeedsResample();
354 : }
355 0 : NS_ADDREF(*_retval = domItem.get());
356 0 : return NS_OK;
357 : }
358 :
359 : /* nsIDOMSVGTransform removeItem (in unsigned long index); */
360 : NS_IMETHODIMP
361 0 : DOMSVGTransformList::RemoveItem(PRUint32 index, nsIDOMSVGTransform **_retval)
362 : {
363 0 : *_retval = nsnull;
364 0 : if (IsAnimValList()) {
365 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
366 : }
367 :
368 0 : if (index >= Length()) {
369 0 : return NS_ERROR_DOM_INDEX_SIZE_ERR;
370 : }
371 :
372 0 : nsAttrValue emptyOrOldValue = Element()->WillChangeTransformList();
373 : // Now that we know we're removing, keep animVal list in sync as necessary.
374 : // Do this *before* touching InternalList() so the removed item can get its
375 : // internal value.
376 0 : MaybeRemoveItemFromAnimValListAt(index);
377 :
378 : // We have to return the removed item, so make sure it exists:
379 0 : EnsureItemAt(index);
380 :
381 : // Notify the DOM item of removal *before* modifying the lists so that the
382 : // DOM item can copy its *old* value:
383 0 : mItems[index]->RemovingFromList();
384 0 : NS_ADDREF(*_retval = mItems[index]);
385 :
386 0 : InternalList().RemoveItem(index);
387 0 : mItems.RemoveElementAt(index);
388 :
389 0 : UpdateListIndicesFromIndex(mItems, index);
390 :
391 0 : Element()->DidChangeTransformList(emptyOrOldValue);
392 0 : if (mAList->IsAnimating()) {
393 0 : Element()->AnimationNeedsResample();
394 : }
395 0 : return NS_OK;
396 : }
397 :
398 : /* nsIDOMSVGTransform appendItem (in nsIDOMSVGTransform newItem); */
399 : NS_IMETHODIMP
400 0 : DOMSVGTransformList::AppendItem(nsIDOMSVGTransform *newItem,
401 : nsIDOMSVGTransform **_retval)
402 : {
403 0 : return InsertItemBefore(newItem, Length(), _retval);
404 : }
405 :
406 : /* nsIDOMSVGTransform createSVGTransformFromMatrix (in nsIDOMSVGMatrix matrix);
407 : */
408 : NS_IMETHODIMP
409 0 : DOMSVGTransformList::CreateSVGTransformFromMatrix(nsIDOMSVGMatrix *matrix,
410 : nsIDOMSVGTransform **_retval)
411 : {
412 0 : nsCOMPtr<DOMSVGMatrix> domItem = do_QueryInterface(matrix);
413 0 : if (!domItem) {
414 0 : return NS_ERROR_DOM_SVG_WRONG_TYPE_ERR;
415 : }
416 :
417 0 : NS_ADDREF(*_retval = new DOMSVGTransform(domItem->Matrix()));
418 0 : return NS_OK;
419 : }
420 :
421 : /* nsIDOMSVGTransform consolidate (); */
422 : NS_IMETHODIMP
423 0 : DOMSVGTransformList::Consolidate(nsIDOMSVGTransform **_retval)
424 : {
425 0 : *_retval = nsnull;
426 0 : if (IsAnimValList()) {
427 0 : return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
428 : }
429 :
430 0 : if (Length() == 0)
431 0 : return NS_OK;
432 :
433 : // Note that SVG 1.1 says, "The consolidation operation creates new
434 : // SVGTransform object as the first and only item in the list" hence, even if
435 : // Length() == 1 we can't return that one item (after making it a matrix
436 : // type). We must orphan the existing item and then make a new one.
437 :
438 : // First calculate our matrix
439 0 : gfxMatrix mx = InternalList().GetConsolidationMatrix();
440 :
441 : // Then orphan the existing items
442 0 : Clear();
443 :
444 : // And append the new transform
445 0 : nsRefPtr<DOMSVGTransform> transform = new DOMSVGTransform(mx);
446 0 : return InsertItemBefore(transform, Length(), _retval);
447 : }
448 :
449 : //----------------------------------------------------------------------
450 : // Implementation helpers:
451 :
452 : void
453 0 : DOMSVGTransformList::EnsureItemAt(PRUint32 aIndex)
454 : {
455 0 : if (!mItems[aIndex]) {
456 0 : mItems[aIndex] = new DOMSVGTransform(this, aIndex, IsAnimValList());
457 : }
458 0 : }
459 :
460 : void
461 0 : DOMSVGTransformList::MaybeInsertNullInAnimValListAt(PRUint32 aIndex)
462 : {
463 0 : NS_ABORT_IF_FALSE(!IsAnimValList(), "call from baseVal to animVal");
464 :
465 0 : DOMSVGTransformList* animVal = mAList->mAnimVal;
466 :
467 0 : if (!animVal || mAList->IsAnimating()) {
468 : // No animVal list wrapper, or animVal not a clone of baseVal
469 0 : return;
470 : }
471 :
472 0 : NS_ABORT_IF_FALSE(animVal->mItems.Length() == mItems.Length(),
473 : "animVal list not in sync!");
474 :
475 : animVal->mItems.InsertElementAt(aIndex,
476 0 : static_cast<DOMSVGTransform*>(nsnull));
477 :
478 0 : UpdateListIndicesFromIndex(animVal->mItems, aIndex + 1);
479 : }
480 :
481 : void
482 0 : DOMSVGTransformList::MaybeRemoveItemFromAnimValListAt(PRUint32 aIndex)
483 : {
484 0 : NS_ABORT_IF_FALSE(!IsAnimValList(), "call from baseVal to animVal");
485 :
486 : // This needs to be a strong reference; otherwise, the RemovingFromList call
487 : // below might drop the last reference to animVal before we're done with it.
488 0 : nsRefPtr<DOMSVGTransformList> animVal = mAList->mAnimVal;
489 :
490 0 : if (!animVal || mAList->IsAnimating()) {
491 : // No animVal list wrapper, or animVal not a clone of baseVal
492 : return;
493 : }
494 :
495 0 : NS_ABORT_IF_FALSE(animVal->mItems.Length() == mItems.Length(),
496 : "animVal list not in sync!");
497 :
498 0 : if (animVal->mItems[aIndex]) {
499 0 : animVal->mItems[aIndex]->RemovingFromList();
500 : }
501 0 : animVal->mItems.RemoveElementAt(aIndex);
502 :
503 0 : UpdateListIndicesFromIndex(animVal->mItems, aIndex);
504 : }
505 :
506 4392 : } // namespace mozilla
|