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 /* DOM object for element.style */
8 
9 #include "nsDOMCSSAttrDeclaration.h"
10 
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/Element.h"
13 #include "mozilla/dom/SVGElement.h"
14 #include "mozilla/dom/MutationEventBinding.h"
15 #include "mozilla/DeclarationBlock.h"
16 #include "mozilla/InternalMutationEvent.h"
17 #include "mozilla/SMILCSSValueType.h"
18 #include "mozilla/SMILValue.h"
19 #include "mozAutoDocUpdate.h"
20 #include "nsWrapperCacheInlines.h"
21 #include "nsIFrame.h"
22 #include "ActiveLayerTracker.h"
23 
24 using namespace mozilla;
25 using namespace mozilla::dom;
26 
nsDOMCSSAttributeDeclaration(Element * aElement,bool aIsSMILOverride)27 nsDOMCSSAttributeDeclaration::nsDOMCSSAttributeDeclaration(Element* aElement,
28                                                            bool aIsSMILOverride)
29     : mElement(aElement), mIsSMILOverride(aIsSMILOverride) {
30   NS_ASSERTION(aElement, "Inline style for a NULL element?");
31 }
32 
33 nsDOMCSSAttributeDeclaration::~nsDOMCSSAttributeDeclaration() = default;
34 
35 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsDOMCSSAttributeDeclaration, mElement)
36 
37 // mElement holds a strong ref to us, so if it's going to be
38 // skipped, the attribute declaration can't be part of a garbage
39 // cycle.
40 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDOMCSSAttributeDeclaration)
41   if (tmp->mElement && Element::CanSkip(tmp->mElement, true)) {
42     if (tmp->PreservingWrapper()) {
43       tmp->MarkWrapperLive();
44     }
45     return true;
46   }
47   return tmp->HasKnownLiveWrapper();
48 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
49 
50 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDOMCSSAttributeDeclaration)
51   return tmp->HasKnownLiveWrapper() ||
52          (tmp->mElement && Element::CanSkipInCC(tmp->mElement));
53 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
54 
55 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsDOMCSSAttributeDeclaration)
56   return tmp->HasKnownLiveWrapper() ||
57          (tmp->mElement && Element::CanSkipThis(tmp->mElement));
58 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
59 
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCSSAttributeDeclaration)60 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCSSAttributeDeclaration)
61   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
62 NS_INTERFACE_MAP_END_INHERITING(nsDOMCSSDeclaration)
63 
64 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCSSAttributeDeclaration)
65 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCSSAttributeDeclaration)
66 
67 nsresult nsDOMCSSAttributeDeclaration::SetCSSDeclaration(
68     DeclarationBlock* aDecl, MutationClosureData* aClosureData) {
69   NS_ASSERTION(mElement, "Must have Element to set the declaration!");
70 
71   // Whenever changing element.style values, aClosureData must be non-null.
72   // SMIL doesn't update Element's attribute values, so closure data isn't
73   // needed.
74   MOZ_ASSERT_IF(!mIsSMILOverride, aClosureData);
75 
76   // The closure needs to have been called by now, otherwise we shouldn't be
77   // getting here when the attribute hasn't changed.
78   MOZ_ASSERT_IF(aClosureData && aClosureData->mShouldBeCalled,
79                 aClosureData->mWasCalled);
80 
81   aDecl->SetDirty();
82   if (mIsSMILOverride) {
83     mElement->SetSMILOverrideStyleDeclaration(*aDecl);
84     return NS_OK;
85   }
86   return mElement->SetInlineStyleDeclaration(*aDecl, *aClosureData);
87 }
88 
DocToUpdate()89 Document* nsDOMCSSAttributeDeclaration::DocToUpdate() {
90   // We need OwnerDoc() rather than GetUncomposedDoc() because it might
91   // be the BeginUpdate call that inserts mElement into the document.
92   return mElement->OwnerDoc();
93 }
94 
GetOrCreateCSSDeclaration(Operation aOperation,DeclarationBlock ** aCreated)95 DeclarationBlock* nsDOMCSSAttributeDeclaration::GetOrCreateCSSDeclaration(
96     Operation aOperation, DeclarationBlock** aCreated) {
97   MOZ_ASSERT(aOperation != Operation::Modify || aCreated);
98 
99   if (!mElement) return nullptr;
100 
101   DeclarationBlock* declaration;
102   if (mIsSMILOverride) {
103     declaration = mElement->GetSMILOverrideStyleDeclaration();
104   } else {
105     declaration = mElement->GetInlineStyleDeclaration();
106   }
107 
108   if (declaration) {
109     return declaration;
110   }
111 
112   if (aOperation != Operation::Modify) {
113     return nullptr;
114   }
115 
116   // cannot fail
117   RefPtr<DeclarationBlock> decl = new DeclarationBlock();
118   // Mark the declaration dirty so that it can be reused by the caller.
119   // Normally SetDirty is called later in SetCSSDeclaration.
120   decl->SetDirty();
121 #ifdef DEBUG
122   RefPtr<DeclarationBlock> mutableDecl = decl->EnsureMutable();
123   MOZ_ASSERT(mutableDecl == decl);
124 #endif
125   decl.swap(*aCreated);
126   return *aCreated;
127 }
128 
129 nsDOMCSSDeclaration::ParsingEnvironment
GetParsingEnvironment(nsIPrincipal * aSubjectPrincipal) const130 nsDOMCSSAttributeDeclaration::GetParsingEnvironment(
131     nsIPrincipal* aSubjectPrincipal) const {
132   return {
133       mElement->GetURLDataForStyleAttr(aSubjectPrincipal),
134       mElement->OwnerDoc()->GetCompatibilityMode(),
135       mElement->OwnerDoc()->CSSLoader(),
136   };
137 }
138 
139 template <typename SetterFunc>
SetSMILValueHelper(SetterFunc aFunc)140 nsresult nsDOMCSSAttributeDeclaration::SetSMILValueHelper(SetterFunc aFunc) {
141   MOZ_ASSERT(mIsSMILOverride);
142 
143   // No need to do the ActiveLayerTracker / ScrollLinkedEffectDetector bits,
144   // since we're in a SMIL animation anyway, no need to try to detect we're a
145   // scripted animation.
146   RefPtr<DeclarationBlock> created;
147   DeclarationBlock* olddecl =
148       GetOrCreateCSSDeclaration(Operation::Modify, getter_AddRefs(created));
149   if (!olddecl) {
150     return NS_ERROR_NOT_AVAILABLE;
151   }
152   mozAutoDocUpdate autoUpdate(DocToUpdate(), true);
153   RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
154 
155   bool changed = aFunc(*decl);
156 
157   if (changed) {
158     // We can pass nullptr as the latter param, since this is
159     // mIsSMILOverride == true case.
160     SetCSSDeclaration(decl, nullptr);
161   }
162   return NS_OK;
163 }
164 
SetSMILValue(const nsCSSPropertyID,const SMILValue & aValue)165 nsresult nsDOMCSSAttributeDeclaration::SetSMILValue(
166     const nsCSSPropertyID /*aPropID*/, const SMILValue& aValue) {
167   MOZ_ASSERT(aValue.mType == &SMILCSSValueType::sSingleton,
168              "We should only try setting a CSS value type");
169   return SetSMILValueHelper([&aValue](DeclarationBlock& aDecl) {
170     return SMILCSSValueType::SetPropertyValues(aValue, aDecl);
171   });
172 }
173 
SetSMILValue(const nsCSSPropertyID aPropID,const SVGAnimatedLength & aLength)174 nsresult nsDOMCSSAttributeDeclaration::SetSMILValue(
175     const nsCSSPropertyID aPropID, const SVGAnimatedLength& aLength) {
176   return SetSMILValueHelper([aPropID, &aLength](DeclarationBlock& aDecl) {
177     return SVGElement::UpdateDeclarationBlockFromLength(
178         aDecl, aPropID, aLength, SVGElement::ValToUse::Anim);
179   });
180 }
181 
SetSMILValue(const nsCSSPropertyID,const SVGAnimatedPathSegList & aPath)182 nsresult nsDOMCSSAttributeDeclaration::SetSMILValue(
183     const nsCSSPropertyID /*aPropID*/, const SVGAnimatedPathSegList& aPath) {
184   return SetSMILValueHelper([&aPath](DeclarationBlock& aDecl) {
185     return SVGElement::UpdateDeclarationBlockFromPath(
186         aDecl, aPath, SVGElement::ValToUse::Anim);
187   });
188 }
189 
SetPropertyValue(const nsCSSPropertyID aPropID,const nsACString & aValue,nsIPrincipal * aSubjectPrincipal,ErrorResult & aRv)190 void nsDOMCSSAttributeDeclaration::SetPropertyValue(
191     const nsCSSPropertyID aPropID, const nsACString& aValue,
192     nsIPrincipal* aSubjectPrincipal, ErrorResult& aRv) {
193   // Scripted modifications to style.opacity or style.transform (or other
194   // transform-like properties, e.g. style.translate, style.rotate, style.scale)
195   // could immediately force us into the animated state if heuristics suggest
196   // this is scripted animation.
197   // FIXME: This is missing the margin shorthand and the logical versions of
198   // the margin properties, see bug 1266287.
199   if (aPropID == eCSSProperty_opacity || aPropID == eCSSProperty_transform ||
200       aPropID == eCSSProperty_translate || aPropID == eCSSProperty_rotate ||
201       aPropID == eCSSProperty_scale || aPropID == eCSSProperty_offset_path ||
202       aPropID == eCSSProperty_offset_distance ||
203       aPropID == eCSSProperty_offset_rotate ||
204       aPropID == eCSSProperty_offset_anchor) {
205     nsIFrame* frame = mElement->GetPrimaryFrame();
206     if (frame) {
207       ActiveLayerTracker::NotifyInlineStyleRuleModified(frame, aPropID, aValue,
208                                                         this);
209     }
210   }
211   nsDOMCSSDeclaration::SetPropertyValue(aPropID, aValue, aSubjectPrincipal,
212                                         aRv);
213 }
214 
MutationClosureFunction(void * aData)215 void nsDOMCSSAttributeDeclaration::MutationClosureFunction(void* aData) {
216   auto* data = static_cast<MutationClosureData*>(aData);
217   MOZ_ASSERT(
218       data->mShouldBeCalled,
219       "Did we pass a non-null closure to the style system unnecessarily?");
220   if (data->mWasCalled) {
221     return;
222   }
223   data->mWasCalled = true;
224   data->mElement->InlineStyleDeclarationWillChange(*data);
225 }
226