michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* base class for DOM objects for element.style and cssStyleRule.style */ michael@0: michael@0: #include "nsDOMCSSDeclaration.h" michael@0: michael@0: #include "nsCSSParser.h" michael@0: #include "nsCSSStyleSheet.h" michael@0: #include "mozilla/css/Rule.h" michael@0: #include "mozilla/css/Declaration.h" michael@0: #include "mozilla/dom/CSS2PropertiesBinding.h" michael@0: #include "nsCSSProps.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "mozAutoDocUpdate.h" michael@0: #include "nsIURI.h" michael@0: #include "mozilla/dom/BindingUtils.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: nsDOMCSSDeclaration::~nsDOMCSSDeclaration() michael@0: { michael@0: } michael@0: michael@0: /* virtual */ JSObject* michael@0: nsDOMCSSDeclaration::WrapObject(JSContext* aCx) michael@0: { michael@0: return dom::CSS2PropertiesBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: NS_INTERFACE_TABLE_HEAD(nsDOMCSSDeclaration) michael@0: NS_INTERFACE_TABLE(nsDOMCSSDeclaration, michael@0: nsICSSDeclaration, michael@0: nsIDOMCSSStyleDeclaration) michael@0: NS_INTERFACE_TABLE_TO_MAP_SEGUE michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMCSSDeclaration::GetPropertyValue(const nsCSSProperty aPropID, michael@0: nsAString& aValue) michael@0: { michael@0: NS_PRECONDITION(aPropID != eCSSProperty_UNKNOWN, michael@0: "Should never pass eCSSProperty_UNKNOWN around"); michael@0: michael@0: css::Declaration* decl = GetCSSDeclaration(false); michael@0: michael@0: aValue.Truncate(); michael@0: if (decl) { michael@0: decl->GetValue(aPropID, aValue); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsDOMCSSDeclaration::GetCustomPropertyValue(const nsAString& aPropertyName, michael@0: nsAString& aValue) michael@0: { michael@0: MOZ_ASSERT(Substring(aPropertyName, 0, michael@0: CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--")); michael@0: michael@0: css::Declaration* decl = GetCSSDeclaration(false); michael@0: if (!decl) { michael@0: aValue.Truncate(); michael@0: return; michael@0: } michael@0: michael@0: decl->GetVariableDeclaration(Substring(aPropertyName, michael@0: CSS_CUSTOM_NAME_PREFIX_LENGTH), michael@0: aValue); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMCSSDeclaration::SetPropertyValue(const nsCSSProperty aPropID, michael@0: const nsAString& aValue) michael@0: { michael@0: if (aValue.IsEmpty()) { michael@0: // If the new value of the property is an empty string we remove the michael@0: // property. michael@0: return RemoveProperty(aPropID); michael@0: } michael@0: michael@0: return ParsePropertyValue(aPropID, aValue, false); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMCSSDeclaration::GetCssText(nsAString& aCssText) michael@0: { michael@0: css::Declaration* decl = GetCSSDeclaration(false); michael@0: aCssText.Truncate(); michael@0: michael@0: if (decl) { michael@0: decl->ToString(aCssText); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMCSSDeclaration::SetCssText(const nsAString& aCssText) michael@0: { michael@0: // We don't need to *do* anything with the old declaration, but we need michael@0: // to ensure that it exists, or else SetCSSDeclaration may crash. michael@0: css::Declaration* olddecl = GetCSSDeclaration(true); michael@0: if (!olddecl) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: CSSParsingEnvironment env; michael@0: GetCSSParsingEnvironment(env); michael@0: if (!env.mPrincipal) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to michael@0: // Attribute setting code, which leads in turn to BeginUpdate. We michael@0: // need to start the update now so that the old rule doesn't get used michael@0: // between when we mutate the declaration and when we set the new michael@0: // rule (see stack in bug 209575). michael@0: mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true); michael@0: michael@0: nsAutoPtr decl(new css::Declaration()); michael@0: decl->InitializeEmpty(); michael@0: nsCSSParser cssParser(env.mCSSLoader); michael@0: bool changed; michael@0: nsresult result = cssParser.ParseDeclarations(aCssText, env.mSheetURI, michael@0: env.mBaseURI, michael@0: env.mPrincipal, decl, &changed); michael@0: if (NS_FAILED(result) || !changed) { michael@0: return result; michael@0: } michael@0: michael@0: return SetCSSDeclaration(decl.forget()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMCSSDeclaration::GetLength(uint32_t* aLength) michael@0: { michael@0: css::Declaration* decl = GetCSSDeclaration(false); michael@0: michael@0: if (decl) { michael@0: *aLength = decl->Count(); michael@0: } else { michael@0: *aLength = 0; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsDOMCSSDeclaration::GetPropertyCSSValue(const nsAString& aPropertyName, ErrorResult& aRv) michael@0: { michael@0: // We don't support CSSValue yet so we'll just return null... michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: nsDOMCSSDeclaration::IndexedGetter(uint32_t aIndex, bool& aFound, nsAString& aPropName) michael@0: { michael@0: css::Declaration* decl = GetCSSDeclaration(false); michael@0: aFound = decl && decl->GetNthProperty(aIndex, aPropName); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMCSSDeclaration::GetPropertyValue(const nsAString& aPropertyName, michael@0: nsAString& aReturn) michael@0: { michael@0: const nsCSSProperty propID = michael@0: nsCSSProps::LookupProperty(aPropertyName, michael@0: nsCSSProps::eEnabledForAllContent); michael@0: if (propID == eCSSProperty_UNKNOWN) { michael@0: aReturn.Truncate(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (propID == eCSSPropertyExtra_variable) { michael@0: GetCustomPropertyValue(aPropertyName, aReturn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return GetPropertyValue(propID, aReturn); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMCSSDeclaration::GetAuthoredPropertyValue(const nsAString& aPropertyName, michael@0: nsAString& aReturn) michael@0: { michael@0: const nsCSSProperty propID = michael@0: nsCSSProps::LookupProperty(aPropertyName, michael@0: nsCSSProps::eEnabledForAllContent); michael@0: if (propID == eCSSProperty_UNKNOWN) { michael@0: aReturn.Truncate(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (propID == eCSSPropertyExtra_variable) { michael@0: GetCustomPropertyValue(aPropertyName, aReturn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: css::Declaration* decl = GetCSSDeclaration(false); michael@0: if (!decl) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: decl->GetAuthoredValue(propID, aReturn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMCSSDeclaration::GetPropertyPriority(const nsAString& aPropertyName, michael@0: nsAString& aReturn) michael@0: { michael@0: css::Declaration* decl = GetCSSDeclaration(false); michael@0: michael@0: aReturn.Truncate(); michael@0: if (decl && decl->GetValueIsImportant(aPropertyName)) { michael@0: aReturn.AssignLiteral("important"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMCSSDeclaration::SetProperty(const nsAString& aPropertyName, michael@0: const nsAString& aValue, michael@0: const nsAString& aPriority) michael@0: { michael@0: // In the common (and fast) cases we can use the property id michael@0: nsCSSProperty propID = michael@0: nsCSSProps::LookupProperty(aPropertyName, michael@0: nsCSSProps::eEnabledForAllContent); michael@0: if (propID == eCSSProperty_UNKNOWN) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (aValue.IsEmpty()) { michael@0: // If the new value of the property is an empty string we remove the michael@0: // property. michael@0: // XXX this ignores the priority string, should it? michael@0: if (propID == eCSSPropertyExtra_variable) { michael@0: return RemoveCustomProperty(aPropertyName); michael@0: } michael@0: return RemoveProperty(propID); michael@0: } michael@0: michael@0: bool important; michael@0: if (aPriority.IsEmpty()) { michael@0: important = false; michael@0: } else if (aPriority.EqualsLiteral("important")) { michael@0: important = true; michael@0: } else { michael@0: // XXX silent failure? michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (propID == eCSSPropertyExtra_variable) { michael@0: return ParseCustomPropertyValue(aPropertyName, aValue, important); michael@0: } michael@0: return ParsePropertyValue(propID, aValue, important); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsDOMCSSDeclaration::RemoveProperty(const nsAString& aPropertyName, michael@0: nsAString& aReturn) michael@0: { michael@0: const nsCSSProperty propID = michael@0: nsCSSProps::LookupProperty(aPropertyName, michael@0: nsCSSProps::eEnabledForAllContent); michael@0: if (propID == eCSSProperty_UNKNOWN) { michael@0: aReturn.Truncate(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (propID == eCSSPropertyExtra_variable) { michael@0: RemoveCustomProperty(aPropertyName); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult rv = GetPropertyValue(propID, aReturn); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return RemoveProperty(propID); michael@0: } michael@0: michael@0: /* static */ void michael@0: nsDOMCSSDeclaration::GetCSSParsingEnvironmentForRule(css::Rule* aRule, michael@0: CSSParsingEnvironment& aCSSParseEnv) michael@0: { michael@0: nsIStyleSheet* sheet = aRule ? aRule->GetStyleSheet() : nullptr; michael@0: nsRefPtr cssSheet(do_QueryObject(sheet)); michael@0: if (!cssSheet) { michael@0: aCSSParseEnv.mPrincipal = nullptr; michael@0: return; michael@0: } michael@0: michael@0: nsIDocument* document = sheet->GetOwningDocument(); michael@0: aCSSParseEnv.mSheetURI = sheet->GetSheetURI(); michael@0: aCSSParseEnv.mBaseURI = sheet->GetBaseURI(); michael@0: aCSSParseEnv.mPrincipal = cssSheet->Principal(); michael@0: aCSSParseEnv.mCSSLoader = document ? document->CSSLoader() : nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMCSSDeclaration::ParsePropertyValue(const nsCSSProperty aPropID, michael@0: const nsAString& aPropValue, michael@0: bool aIsImportant) michael@0: { michael@0: css::Declaration* olddecl = GetCSSDeclaration(true); michael@0: if (!olddecl) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: CSSParsingEnvironment env; michael@0: GetCSSParsingEnvironment(env); michael@0: if (!env.mPrincipal) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to michael@0: // Attribute setting code, which leads in turn to BeginUpdate. We michael@0: // need to start the update now so that the old rule doesn't get used michael@0: // between when we mutate the declaration and when we set the new michael@0: // rule (see stack in bug 209575). michael@0: mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true); michael@0: css::Declaration* decl = olddecl->EnsureMutable(); michael@0: michael@0: nsCSSParser cssParser(env.mCSSLoader); michael@0: bool changed; michael@0: nsresult result = cssParser.ParseProperty(aPropID, aPropValue, env.mSheetURI, michael@0: env.mBaseURI, env.mPrincipal, decl, michael@0: &changed, aIsImportant); michael@0: if (NS_FAILED(result) || !changed) { michael@0: if (decl != olddecl) { michael@0: delete decl; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: return SetCSSDeclaration(decl); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMCSSDeclaration::ParseCustomPropertyValue(const nsAString& aPropertyName, michael@0: const nsAString& aPropValue, michael@0: bool aIsImportant) michael@0: { michael@0: MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aPropertyName)); michael@0: michael@0: css::Declaration* olddecl = GetCSSDeclaration(true); michael@0: if (!olddecl) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: CSSParsingEnvironment env; michael@0: GetCSSParsingEnvironment(env); michael@0: if (!env.mPrincipal) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to michael@0: // Attribute setting code, which leads in turn to BeginUpdate. We michael@0: // need to start the update now so that the old rule doesn't get used michael@0: // between when we mutate the declaration and when we set the new michael@0: // rule (see stack in bug 209575). michael@0: mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true); michael@0: css::Declaration* decl = olddecl->EnsureMutable(); michael@0: michael@0: nsCSSParser cssParser(env.mCSSLoader); michael@0: bool changed; michael@0: nsresult result = michael@0: cssParser.ParseVariable(Substring(aPropertyName, michael@0: CSS_CUSTOM_NAME_PREFIX_LENGTH), michael@0: aPropValue, env.mSheetURI, michael@0: env.mBaseURI, env.mPrincipal, decl, michael@0: &changed, aIsImportant); michael@0: if (NS_FAILED(result) || !changed) { michael@0: if (decl != olddecl) { michael@0: delete decl; michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: return SetCSSDeclaration(decl); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMCSSDeclaration::RemoveProperty(const nsCSSProperty aPropID) michael@0: { michael@0: css::Declaration* decl = GetCSSDeclaration(false); michael@0: if (!decl) { michael@0: return NS_OK; // no decl, so nothing to remove michael@0: } michael@0: michael@0: // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to michael@0: // Attribute setting code, which leads in turn to BeginUpdate. We michael@0: // need to start the update now so that the old rule doesn't get used michael@0: // between when we mutate the declaration and when we set the new michael@0: // rule (see stack in bug 209575). michael@0: mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true); michael@0: michael@0: decl = decl->EnsureMutable(); michael@0: decl->RemoveProperty(aPropID); michael@0: return SetCSSDeclaration(decl); michael@0: } michael@0: michael@0: nsresult michael@0: nsDOMCSSDeclaration::RemoveCustomProperty(const nsAString& aPropertyName) michael@0: { michael@0: MOZ_ASSERT(Substring(aPropertyName, 0, michael@0: CSS_CUSTOM_NAME_PREFIX_LENGTH).EqualsLiteral("--")); michael@0: michael@0: css::Declaration* decl = GetCSSDeclaration(false); michael@0: if (!decl) { michael@0: return NS_OK; // no decl, so nothing to remove michael@0: } michael@0: michael@0: // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to michael@0: // Attribute setting code, which leads in turn to BeginUpdate. We michael@0: // need to start the update now so that the old rule doesn't get used michael@0: // between when we mutate the declaration and when we set the new michael@0: // rule (see stack in bug 209575). michael@0: mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true); michael@0: michael@0: decl = decl->EnsureMutable(); michael@0: decl->RemoveVariableDeclaration(Substring(aPropertyName, michael@0: CSS_CUSTOM_NAME_PREFIX_LENGTH)); michael@0: return SetCSSDeclaration(decl); michael@0: } michael@0: michael@0: bool IsCSSPropertyExposedToJS(nsCSSProperty aProperty, JSContext* cx, JSObject* obj) michael@0: { michael@0: nsCSSProps::EnabledState enabledState = nsCSSProps::eEnabledForAllContent; michael@0: michael@0: // Optimization: we skip checking properties of the JSContext michael@0: // in the majority case where the property does not have the michael@0: // CSS_PROPERTY_ALWAYS_ENABLED_IN_PRIVILEGED_CONTENT flag. michael@0: bool isEnabledInChromeOrCertifiedApp michael@0: = nsCSSProps::PropHasFlags(aProperty, michael@0: CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP); michael@0: michael@0: if (isEnabledInChromeOrCertifiedApp) { michael@0: if (dom::IsInCertifiedApp(cx, obj) || michael@0: nsContentUtils::ThreadsafeIsCallerChrome()) michael@0: { michael@0: enabledState |= nsCSSProps::eEnabledInChromeOrCertifiedApp; michael@0: } michael@0: } michael@0: return nsCSSProps::IsEnabled(aProperty, enabledState); michael@0: }