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