1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 /* base class for DOM objects for element.style and cssStyleRule.style */
7 
8 #include "nsDOMCSSDeclaration.h"
9 
10 #include "nsCSSParser.h"
11 #include "mozilla/DeclarationBlockInlines.h"
12 #include "mozilla/StyleSheetInlines.h"
13 #include "mozilla/css/Rule.h"
14 #include "mozilla/DeclarationBlockInlines.h"
15 #include "mozilla/dom/CSS2PropertiesBinding.h"
16 #include "nsCSSProps.h"
17 #include "nsCOMPtr.h"
18 #include "mozAutoDocUpdate.h"
19 #include "nsIURI.h"
20 #include "mozilla/dom/BindingUtils.h"
21 #include "nsContentUtils.h"
22 #include "nsQueryObject.h"
23 #include "mozilla/layers/ScrollLinkedEffectDetector.h"
24 
25 using namespace mozilla;
26 
~nsDOMCSSDeclaration()27 nsDOMCSSDeclaration::~nsDOMCSSDeclaration()
28 {
29 }
30 
31 /* virtual */ JSObject*
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)32 nsDOMCSSDeclaration::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
33 {
34   return dom::CSS2PropertiesBinding::Wrap(aCx, this, aGivenProto);
35 }
36 
37 NS_INTERFACE_TABLE_HEAD(nsDOMCSSDeclaration)
NS_INTERFACE_TABLE(nsDOMCSSDeclaration,nsICSSDeclaration,nsIDOMCSSStyleDeclaration)38   NS_INTERFACE_TABLE(nsDOMCSSDeclaration,
39                      nsICSSDeclaration,
40                      nsIDOMCSSStyleDeclaration)
41   NS_INTERFACE_TABLE_TO_MAP_SEGUE
42 NS_INTERFACE_MAP_END
43 
44 NS_IMETHODIMP
45 nsDOMCSSDeclaration::GetPropertyValue(const nsCSSPropertyID aPropID,
46                                       nsAString& aValue)
47 {
48   NS_PRECONDITION(aPropID != eCSSProperty_UNKNOWN,
49                   "Should never pass eCSSProperty_UNKNOWN around");
50 
51   aValue.Truncate();
52   if (DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read)) {
53     decl->GetPropertyValueByID(aPropID, aValue);
54   }
55   return NS_OK;
56 }
57 
58 NS_IMETHODIMP
SetPropertyValue(const nsCSSPropertyID aPropID,const nsAString & aValue)59 nsDOMCSSDeclaration::SetPropertyValue(const nsCSSPropertyID aPropID,
60                                       const nsAString& aValue)
61 {
62   switch (aPropID) {
63     case eCSSProperty_background_position:
64     case eCSSProperty_background_position_x:
65     case eCSSProperty_background_position_y:
66     case eCSSProperty_transform:
67     case eCSSProperty_top:
68     case eCSSProperty_left:
69     case eCSSProperty_bottom:
70     case eCSSProperty_right:
71     case eCSSProperty_margin:
72     case eCSSProperty_margin_top:
73     case eCSSProperty_margin_left:
74     case eCSSProperty_margin_bottom:
75     case eCSSProperty_margin_right:
76     case eCSSProperty_margin_inline_start:
77     case eCSSProperty_margin_inline_end:
78     case eCSSProperty_margin_block_start:
79     case eCSSProperty_margin_block_end:
80       mozilla::layers::ScrollLinkedEffectDetector::PositioningPropertyMutated();
81       break;
82     default:
83       break;
84   }
85 
86   if (aValue.IsEmpty()) {
87     // If the new value of the property is an empty string we remove the
88     // property.
89     return RemovePropertyInternal(aPropID);
90   }
91 
92   return ParsePropertyValue(aPropID, aValue, false);
93 }
94 
95 
96 NS_IMETHODIMP
GetCssText(nsAString & aCssText)97 nsDOMCSSDeclaration::GetCssText(nsAString& aCssText)
98 {
99   DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read);
100   aCssText.Truncate();
101 
102   if (decl) {
103     decl->ToString(aCssText);
104   }
105 
106   return NS_OK;
107 }
108 
109 NS_IMETHODIMP
SetCssText(const nsAString & aCssText)110 nsDOMCSSDeclaration::SetCssText(const nsAString& aCssText)
111 {
112   // We don't need to *do* anything with the old declaration, but we need
113   // to ensure that it exists, or else SetCSSDeclaration may crash.
114   DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_Modify);
115   if (!olddecl) {
116     return NS_ERROR_NOT_AVAILABLE;
117   }
118 
119   CSSParsingEnvironment env;
120   GetCSSParsingEnvironment(env);
121   if (!env.mPrincipal) {
122     return NS_ERROR_NOT_AVAILABLE;
123   }
124 
125   // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
126   // Attribute setting code, which leads in turn to BeginUpdate.  We
127   // need to start the update now so that the old rule doesn't get used
128   // between when we mutate the declaration and when we set the new
129   // rule (see stack in bug 209575).
130   mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
131 
132   RefPtr<DeclarationBlock> newdecl;
133   if (olddecl->IsServo()) {
134     newdecl = ServoDeclarationBlock::FromCssText(aCssText);
135   } else {
136     RefPtr<css::Declaration> decl(new css::Declaration());
137     decl->InitializeEmpty();
138     nsCSSParser cssParser(env.mCSSLoader);
139     bool changed;
140     nsresult result = cssParser.ParseDeclarations(aCssText, env.mSheetURI,
141                                                   env.mBaseURI, env.mPrincipal,
142                                                   decl, &changed);
143     if (NS_FAILED(result) || !changed) {
144       return result;
145     }
146     newdecl = decl.forget();
147   }
148 
149   return SetCSSDeclaration(newdecl);
150 }
151 
152 NS_IMETHODIMP
GetLength(uint32_t * aLength)153 nsDOMCSSDeclaration::GetLength(uint32_t* aLength)
154 {
155   DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read);
156 
157   if (decl) {
158     *aLength = decl->Count();
159   } else {
160     *aLength = 0;
161   }
162 
163   return NS_OK;
164 }
165 
166 already_AddRefed<dom::CSSValue>
GetPropertyCSSValue(const nsAString & aPropertyName,ErrorResult & aRv)167 nsDOMCSSDeclaration::GetPropertyCSSValue(const nsAString& aPropertyName, ErrorResult& aRv)
168 {
169   // We don't support CSSValue yet so we'll just return null...
170 
171   return nullptr;
172 }
173 
174 void
IndexedGetter(uint32_t aIndex,bool & aFound,nsAString & aPropName)175 nsDOMCSSDeclaration::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aPropName)
176 {
177   DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read);
178   aFound = decl && decl->GetNthProperty(aIndex, aPropName);
179 }
180 
181 NS_IMETHODIMP
GetPropertyValue(const nsAString & aPropertyName,nsAString & aReturn)182 nsDOMCSSDeclaration::GetPropertyValue(const nsAString& aPropertyName,
183                                       nsAString& aReturn)
184 {
185   aReturn.Truncate();
186   if (DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read)) {
187     decl->GetPropertyValue(aPropertyName, aReturn);
188   }
189   return NS_OK;
190 }
191 
192 NS_IMETHODIMP
GetAuthoredPropertyValue(const nsAString & aPropertyName,nsAString & aReturn)193 nsDOMCSSDeclaration::GetAuthoredPropertyValue(const nsAString& aPropertyName,
194                                               nsAString& aReturn)
195 {
196   if (DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read)) {
197     decl->GetAuthoredPropertyValue(aPropertyName, aReturn);
198   }
199   return NS_OK;
200 }
201 
202 NS_IMETHODIMP
GetPropertyPriority(const nsAString & aPropertyName,nsAString & aReturn)203 nsDOMCSSDeclaration::GetPropertyPriority(const nsAString& aPropertyName,
204                                          nsAString& aReturn)
205 {
206   DeclarationBlock* decl = GetCSSDeclaration(eOperation_Read);
207 
208   aReturn.Truncate();
209   if (decl && decl->GetPropertyIsImportant(aPropertyName)) {
210     aReturn.AssignLiteral("important");
211   }
212 
213   return NS_OK;
214 }
215 
216 NS_IMETHODIMP
SetProperty(const nsAString & aPropertyName,const nsAString & aValue,const nsAString & aPriority)217 nsDOMCSSDeclaration::SetProperty(const nsAString& aPropertyName,
218                                  const nsAString& aValue,
219                                  const nsAString& aPriority)
220 {
221   if (aValue.IsEmpty()) {
222     // If the new value of the property is an empty string we remove the
223     // property.
224     // XXX this ignores the priority string, should it?
225     return RemovePropertyInternal(aPropertyName);
226   }
227 
228   // In the common (and fast) cases we can use the property id
229   nsCSSPropertyID propID =
230     nsCSSProps::LookupProperty(aPropertyName, CSSEnabledState::eForAllContent);
231   if (propID == eCSSProperty_UNKNOWN) {
232     return NS_OK;
233   }
234 
235   bool important;
236   if (aPriority.IsEmpty()) {
237     important = false;
238   } else if (aPriority.EqualsLiteral("important")) {
239     important = true;
240   } else {
241     // XXX silent failure?
242     return NS_OK;
243   }
244 
245   if (propID == eCSSPropertyExtra_variable) {
246     return ParseCustomPropertyValue(aPropertyName, aValue, important);
247   }
248   return ParsePropertyValue(propID, aValue, important);
249 }
250 
251 NS_IMETHODIMP
RemoveProperty(const nsAString & aPropertyName,nsAString & aReturn)252 nsDOMCSSDeclaration::RemoveProperty(const nsAString& aPropertyName,
253                                     nsAString& aReturn)
254 {
255   nsresult rv = GetPropertyValue(aPropertyName, aReturn);
256   NS_ENSURE_SUCCESS(rv, rv);
257   return RemovePropertyInternal(aPropertyName);
258 }
259 
260 /* static */ void
GetCSSParsingEnvironmentForRule(css::Rule * aRule,CSSParsingEnvironment & aCSSParseEnv)261 nsDOMCSSDeclaration::GetCSSParsingEnvironmentForRule(css::Rule* aRule,
262                                                      CSSParsingEnvironment& aCSSParseEnv)
263 {
264   CSSStyleSheet* sheet = aRule ? aRule->GetStyleSheet() : nullptr;
265   if (!sheet) {
266     aCSSParseEnv.mPrincipal = nullptr;
267     return;
268   }
269 
270   nsIDocument* document = sheet->GetOwningDocument();
271   aCSSParseEnv.mSheetURI = sheet->GetSheetURI();
272   aCSSParseEnv.mBaseURI = sheet->GetBaseURI();
273   aCSSParseEnv.mPrincipal = sheet->Principal();
274   aCSSParseEnv.mCSSLoader = document ? document->CSSLoader() : nullptr;
275 }
276 
277 nsresult
ParsePropertyValue(const nsCSSPropertyID aPropID,const nsAString & aPropValue,bool aIsImportant)278 nsDOMCSSDeclaration::ParsePropertyValue(const nsCSSPropertyID aPropID,
279                                         const nsAString& aPropValue,
280                                         bool aIsImportant)
281 {
282   DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_Modify);
283   if (!olddecl) {
284     return NS_ERROR_NOT_AVAILABLE;
285   }
286 
287   CSSParsingEnvironment env;
288   GetCSSParsingEnvironment(env);
289   if (!env.mPrincipal) {
290     return NS_ERROR_NOT_AVAILABLE;
291   }
292 
293   // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
294   // Attribute setting code, which leads in turn to BeginUpdate.  We
295   // need to start the update now so that the old rule doesn't get used
296   // between when we mutate the declaration and when we set the new
297   // rule (see stack in bug 209575).
298   mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
299   RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
300 
301   bool changed;
302   if (decl->IsGecko()) {
303     nsCSSParser cssParser(env.mCSSLoader);
304     cssParser.ParseProperty(aPropID, aPropValue,
305                             env.mSheetURI, env.mBaseURI, env.mPrincipal,
306                             decl->AsGecko(), &changed, aIsImportant);
307   } else {
308     nsIAtom* atom = nsCSSProps::AtomForProperty(aPropID);
309     NS_ConvertUTF16toUTF8 value(aPropValue);
310     changed = Servo_DeclarationBlock_SetProperty(
311       decl->AsServo()->Raw(), atom, false, &value, aIsImportant);
312   }
313   if (!changed) {
314     // Parsing failed -- but we don't throw an exception for that.
315     return NS_OK;
316   }
317 
318   return SetCSSDeclaration(decl);
319 }
320 
321 nsresult
ParseCustomPropertyValue(const nsAString & aPropertyName,const nsAString & aPropValue,bool aIsImportant)322 nsDOMCSSDeclaration::ParseCustomPropertyValue(const nsAString& aPropertyName,
323                                               const nsAString& aPropValue,
324                                               bool aIsImportant)
325 {
326   MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aPropertyName));
327 
328   DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_Modify);
329   if (!olddecl) {
330     return NS_ERROR_NOT_AVAILABLE;
331   }
332 
333   CSSParsingEnvironment env;
334   GetCSSParsingEnvironment(env);
335   if (!env.mPrincipal) {
336     return NS_ERROR_NOT_AVAILABLE;
337   }
338 
339   // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
340   // Attribute setting code, which leads in turn to BeginUpdate.  We
341   // need to start the update now so that the old rule doesn't get used
342   // between when we mutate the declaration and when we set the new
343   // rule (see stack in bug 209575).
344   mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
345   RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
346 
347   bool changed;
348   auto propName = Substring(aPropertyName, CSS_CUSTOM_NAME_PREFIX_LENGTH);
349   if (decl->IsGecko()) {
350     nsCSSParser cssParser(env.mCSSLoader);
351     cssParser.ParseVariable(propName, aPropValue, env.mSheetURI,
352                             env.mBaseURI, env.mPrincipal, decl->AsGecko(),
353                             &changed, aIsImportant);
354   } else {
355     RefPtr<nsIAtom> atom = NS_Atomize(propName);
356     NS_ConvertUTF16toUTF8 value(aPropValue);
357     changed = Servo_DeclarationBlock_SetProperty(
358       decl->AsServo()->Raw(), atom, true, &value, aIsImportant);
359   }
360   if (!changed) {
361     // Parsing failed -- but we don't throw an exception for that.
362     return NS_OK;
363   }
364 
365   return SetCSSDeclaration(decl);
366 }
367 
368 nsresult
RemovePropertyInternal(nsCSSPropertyID aPropID)369 nsDOMCSSDeclaration::RemovePropertyInternal(nsCSSPropertyID aPropID)
370 {
371   DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_RemoveProperty);
372   if (!olddecl) {
373     return NS_OK; // no decl, so nothing to remove
374   }
375 
376   // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
377   // Attribute setting code, which leads in turn to BeginUpdate.  We
378   // need to start the update now so that the old rule doesn't get used
379   // between when we mutate the declaration and when we set the new
380   // rule (see stack in bug 209575).
381   mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
382 
383   RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
384   decl->RemovePropertyByID(aPropID);
385   return SetCSSDeclaration(decl);
386 }
387 
388 nsresult
RemovePropertyInternal(const nsAString & aPropertyName)389 nsDOMCSSDeclaration::RemovePropertyInternal(const nsAString& aPropertyName)
390 {
391   DeclarationBlock* olddecl = GetCSSDeclaration(eOperation_RemoveProperty);
392   if (!olddecl) {
393     return NS_OK; // no decl, so nothing to remove
394   }
395 
396   // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
397   // Attribute setting code, which leads in turn to BeginUpdate.  We
398   // need to start the update now so that the old rule doesn't get used
399   // between when we mutate the declaration and when we set the new
400   // rule (see stack in bug 209575).
401   mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
402 
403   RefPtr<DeclarationBlock> decl = olddecl->EnsureMutable();
404   decl->RemoveProperty(aPropertyName);
405   return SetCSSDeclaration(decl);
406 }
407