Line data Source code
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 : #ifndef MOZILLA_DOMSVGANIMATEDLENGTHLIST_H__
8 : #define MOZILLA_DOMSVGANIMATEDLENGTHLIST_H__
9 :
10 : #include "nsCOMPtr.h"
11 : #include "nsCycleCollectionParticipant.h"
12 : #include "nsSVGElement.h"
13 : #include "mozilla/Attributes.h"
14 :
15 : namespace mozilla {
16 :
17 : class SVGAnimatedLengthList;
18 : class SVGLengthList;
19 : class DOMSVGLengthList;
20 :
21 : /**
22 : * Class DOMSVGAnimatedLengthList
23 : *
24 : * This class is used to create the DOM tearoff objects that wrap internal
25 : * SVGAnimatedLengthList objects. We have this internal-DOM split because DOM
26 : * classes are relatively heavy-weight objects with non-optimal interfaces for
27 : * internal code, and they're relatively infrequently used. Having separate
28 : * internal and DOM classes does add complexity - especially for lists where
29 : * the internal list and DOM lists (and their items) need to be kept in sync -
30 : * but it keeps the internal classes light and fast, and in 99% of cases
31 : * they're all that's used. DOM wrappers are only instantiated when script
32 : * demands it.
33 : *
34 : * Ownership model:
35 : *
36 : * The diagram below shows the ownership model between the various DOM objects
37 : * in the tree of DOM objects that correspond to an SVG length list attribute.
38 : * The angled brackets ">" and "<" denote a reference from one object to
39 : * another, where the "!" character denotes a strong reference, and the "~"
40 : * character denotes a weak reference.
41 : *
42 : * .----<!----. .----<!----. .----<!----.
43 : * | | | | | |
44 : * element ~> DOMSVGAnimatedLengthList ~> DOMSVGLengthList ~> DOMSVGLength
45 : *
46 : * Rationale:
47 : *
48 : * The following three paragraphs explain the main three requirements that must
49 : * be met by any design. These are followed by an explanation of the rationale
50 : * behind our particular design.
51 : *
52 : * 1: DOMSVGAnimatedLengthList, DOMSVGLengthLists and DOMSVGLength get to their
53 : * internal counterparts via their element, and they use their element to send
54 : * out appropriate notifications when they change. Because of this, having
55 : * their element disappear out from under them would be very bad. To keep their
56 : * element alive at least as long as themselves, each of these classes must
57 : * contain a _strong_ reference (directly or indirectly) to their element.
58 : *
59 : * 2: Another central requirement of any design is the SVG specification's
60 : * requirement that script must always be given the exact same objects each
61 : * time it accesses a given object in a DOM object tree corresponding to an SVG
62 : * length list attribute. In practice "always" actually means "whenever script
63 : * has kept a references to a DOM object it previously accessed", since a
64 : * script will only be able to detect any difference in object identity if it
65 : * has a previous reference to compare against.
66 : *
67 : * 3: The wiggle room in the "same object" requirement leads us to a third
68 : * (self imposed) requirement: if script no longer has a reference to a given
69 : * DOM object from an object tree corresponding to an SVG length list
70 : * attribute, and if that object doesn't currently have any descendants, then
71 : * that object should be released to free up memory.
72 : *
73 : * To help in understanding our current design, consider this BROKEN design:
74 : *
75 : * .-------------------------------<!-------------------------.
76 : * |--------------------<!----------------. |
77 : * |----<!----. | |
78 : * | | | |
79 : * element ~> DOMSVGAnimatedLengthList !> DOMSVGLengthList !> DOMSVGLength
80 : *
81 : * Having all the objects keep a reference directly to their element like this
82 : * would reduce the number of dereferences that they need to make to get their
83 : * internal counterpart. Hovewer, this design does not meet the "same object"
84 : * requirement of the SVG specification. If script keeps a reference to a
85 : * DOMSVGLength or DOMSVGLengthList object, but not to that object's
86 : * DOMSVGAnimatedLengthList, then the DOMSVGAnimatedLengthList may be garbage
87 : * collected. We'd then have no way to return the same DOMSVGLength /
88 : * DOMSVGLengthList object that the script has a reference to if the script
89 : * went looking for it via the DOMSVGAnimatedLengthList property on the
90 : * element - we'd end up creating a fresh DOMSVGAnimatedLengthList, with no
91 : * knowlegde of the existing DOMSVGLengthList or DOMSVGLength object.
92 : *
93 : * The way we solve this problem is by making sure that parent objects cannot
94 : * die until all their children are dead by having child objects hold a strong
95 : * reference to their parent object. Note that this design means that the child
96 : * objects hold a strong reference to their element too, albeit indirectly via
97 : * the strong reference to their parent object:
98 : *
99 : * .----<!----. .----<!----. .----<!----.
100 : * | | | | | |
101 : * element ~> DOMSVGAnimatedLengthList ~> DOMSVGLengthList ~> DOMSVGLength
102 : *
103 : * One drawback of this design is that objects must look up their parent
104 : * chain to find their element, but that overhead is relatively small.
105 : */
106 : class DOMSVGAnimatedLengthList final : public nsWrapperCache
107 : {
108 : friend class DOMSVGLengthList;
109 :
110 : public:
111 0 : NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(DOMSVGAnimatedLengthList)
112 0 : NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(DOMSVGAnimatedLengthList)
113 :
114 : /**
115 : * Factory method to create and return a DOMSVGAnimatedLengthList wrapper
116 : * for a given internal SVGAnimatedLengthList object. The factory takes care
117 : * of caching the object that it returns so that the same object can be
118 : * returned for the given SVGAnimatedLengthList each time it is requested.
119 : * The cached object is only removed from the cache when it is destroyed due
120 : * to there being no more references to it or to any of its descendant
121 : * objects. If that happens, any subsequent call requesting the DOM wrapper
122 : * for the SVGAnimatedLengthList will naturally result in a new
123 : * DOMSVGAnimatedLengthList being returned.
124 : */
125 : static already_AddRefed<DOMSVGAnimatedLengthList>
126 : GetDOMWrapper(SVGAnimatedLengthList *aList,
127 : nsSVGElement *aElement,
128 : uint8_t aAttrEnum,
129 : uint8_t aAxis);
130 :
131 : /**
132 : * This method returns the DOMSVGAnimatedLengthList wrapper for an internal
133 : * SVGAnimatedLengthList object if it currently has a wrapper. If it does
134 : * not, then nullptr is returned.
135 : */
136 : static DOMSVGAnimatedLengthList*
137 : GetDOMWrapperIfExists(SVGAnimatedLengthList *aList);
138 :
139 : /**
140 : * Called by internal code to notify us when we need to sync the length of
141 : * our baseVal DOM list with its internal list. This is called just prior to
142 : * the length of the internal baseVal list being changed so that any DOM list
143 : * items that need to be removed from the DOM list can first get their values
144 : * from their internal counterpart.
145 : *
146 : * The only time this method could fail is on OOM when trying to increase the
147 : * length of the DOM list. If that happens then this method simply clears the
148 : * list and returns. Callers just proceed as normal, and we simply accept
149 : * that the DOM list will be empty (until successfully set to a new value).
150 : */
151 : void InternalBaseValListWillChangeTo(const SVGLengthList& aNewValue);
152 : void InternalAnimValListWillChangeTo(const SVGLengthList& aNewValue);
153 :
154 : /**
155 : * Returns true if our attribute is animating (in which case our animVal is
156 : * not simply a mirror of our baseVal).
157 : */
158 : bool IsAnimating() const;
159 :
160 : // WebIDL
161 0 : nsSVGElement* GetParentObject() const { return mElement; }
162 : virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
163 : // These aren't weak refs because mBaseVal and mAnimVal are weak
164 : already_AddRefed<DOMSVGLengthList> BaseVal();
165 : already_AddRefed<DOMSVGLengthList> AnimVal();
166 :
167 : private:
168 :
169 : /**
170 : * Only our static GetDOMWrapper() factory method may create objects of our
171 : * type.
172 : */
173 0 : DOMSVGAnimatedLengthList(nsSVGElement *aElement, uint8_t aAttrEnum, uint8_t aAxis)
174 0 : : mBaseVal(nullptr)
175 : , mAnimVal(nullptr)
176 : , mElement(aElement)
177 : , mAttrEnum(aAttrEnum)
178 0 : , mAxis(aAxis)
179 : {
180 0 : }
181 :
182 : ~DOMSVGAnimatedLengthList();
183 :
184 : /// Get a reference to this DOM wrapper object's internal counterpart.
185 : SVGAnimatedLengthList& InternalAList();
186 : const SVGAnimatedLengthList& InternalAList() const;
187 :
188 : // Weak refs to our DOMSVGLengthList baseVal/animVal objects. These objects
189 : // are friends and take care of clearing these pointers when they die, making
190 : // these true weak references.
191 : DOMSVGLengthList *mBaseVal;
192 : DOMSVGLengthList *mAnimVal;
193 :
194 : // Strong ref to our element to keep it alive. We hold this not only for
195 : // ourself, but also for our base/animVal and all of their items.
196 : RefPtr<nsSVGElement> mElement;
197 :
198 : uint8_t mAttrEnum;
199 : uint8_t mAxis;
200 : };
201 :
202 : } // namespace mozilla
203 :
204 : #endif // MOZILLA_DOMSVGANIMATEDLENGTHLIST_H__
|